entelligence-cli 0.1.97__tar.gz → 0.1.99__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/PKG-INFO +1 -1
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/api_client.py +14 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/insights.py +145 -23
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/discovery.py +224 -4
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/models.py +10 -1
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/runner.py +5 -3
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli.egg-info/PKG-INFO +1 -1
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli.egg-info/SOURCES.txt +3 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/pyproject.toml +1 -1
- entelligence_cli-0.1.99/tests/test_insights_kiro_prompts.py +106 -0
- entelligence_cli-0.1.99/tests/test_insights_structured_payload.py +148 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_upload_home_backfill.py +10 -2
- entelligence_cli-0.1.99/tests/test_upload_kiro.py +375 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/README.md +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/__init__.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/ascii-art.txt +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/browser_login.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/cli.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/__init__.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/auth.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/budget.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/config.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/incident.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/review.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/router.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/security.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/commands/update.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/config.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/exceptions.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/git_operations.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/__init__.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/bedrock.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/catalog.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/claude_settings.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/codex_auth.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/codex_settings.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/compress.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/daemon.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/engine.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/gateway.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/gemini_translate.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/openai_translate.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/proxy.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/router/state.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/scheduling.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/textual_ui.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/ui/__init__.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/ui/interactive.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/ui/loaders.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/ui/output.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/ui/styles.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/update_checker.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/__init__.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/conductor.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/config.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/cursor_usage.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/manifest.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/pii.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/secrets.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/upload/stores.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli/wrapped_game.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli.egg-info/dependency_links.txt +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli.egg-info/entry_points.txt +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli.egg-info/requires.txt +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/entelligence_cli.egg-info/top_level.txt +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/setup.cfg +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_attach_fix_smoke.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_attach_ticket_smoke.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_budget_smoke.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_codex_auth.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_codex_settings.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_conductor.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_cursor_usage.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_discovery_head_unicode.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_discovery_opencode.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_incident_smoke.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_insights_me_smoke.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_pii.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_presigned_skip_index.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_router_commands.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_security_commands.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_update_command.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_upload_cursor_store.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_upload_repo_filter.py +0 -0
- {entelligence_cli-0.1.97 → entelligence_cli-0.1.99}/tests/test_upload_repo_roots.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: entelligence-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.99
|
|
4
4
|
Summary: AI-powered code review assistant for your terminal - Get instant, intelligent feedback on your code changes
|
|
5
5
|
Author-email: EntelligenceAI <info@entelligence.ai>
|
|
6
6
|
Project-URL: Homepage, https://entelligence.ai
|
|
@@ -759,6 +759,20 @@ class APIClient:
|
|
|
759
759
|
"""
|
|
760
760
|
return self._eku_post("/agentInsightsIngest/", payload, org_uuid=org_uuid)
|
|
761
761
|
|
|
762
|
+
def ingest_agent_insights_structured(self, org_uuid: str, payload: dict) -> dict:
|
|
763
|
+
"""`POST /agent-insights/ingest-structured/` — push locally-computed
|
|
764
|
+
session+task data to the coding-agent graph (ENT-3631/ENT-3634).
|
|
765
|
+
|
|
766
|
+
`payload` carries {orgUUID, clientVersion?, extractionVersion,
|
|
767
|
+
sessions:[...]}; org/user identity is derived server-side from the
|
|
768
|
+
token, same as `ingest_agent_insights`. Returns the parsed response
|
|
769
|
+
dict ({sessions_stored, sessions_skipped, tasks_written,
|
|
770
|
+
tasks_deferred, ...}), or `{"Error": "<message>"}` on failure.
|
|
771
|
+
"""
|
|
772
|
+
return self._eku_post(
|
|
773
|
+
"/agent-insights/ingest-structured/", payload, org_uuid=org_uuid, timeout=120
|
|
774
|
+
)
|
|
775
|
+
|
|
762
776
|
def fetch_organization_repos(self, org_uuid: str) -> dict:
|
|
763
777
|
"""`POST /fetchOrganizationRepos/` — list every repo connected to the org.
|
|
764
778
|
|
|
@@ -135,6 +135,73 @@ _PUSH_COLUMNS = (
|
|
|
135
135
|
"effort_level",
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
+
# ENT-3634 — the structured-ingest payload contract (ENT-3633). Bump if the
|
|
139
|
+
# shape changes in a way older backends can't accept; must stay >= the
|
|
140
|
+
# backend's own MIN_SUPPORTED_EXTRACTION_VERSION (agent_insights/controller.py).
|
|
141
|
+
_STRUCTURED_EXTRACTION_VERSION = 1
|
|
142
|
+
|
|
143
|
+
# cinsights' local column names -> the structured-ingest endpoint's field
|
|
144
|
+
# names. Built by reading cinsights' actual `db/models.py`, not assumed —
|
|
145
|
+
# several names differ (`agent_type` vs `agent`, `prompt_tokens` vs
|
|
146
|
+
# `input_tokens`, the three *_waste columns vs *_waste_tokens, etc.). Fields
|
|
147
|
+
# genuinely absent from cinsights today (repo_name, branch, is_automated,
|
|
148
|
+
# per-task category/files_touched/counters) are simply not in either map and
|
|
149
|
+
# stay unset on the wire — the backend already defaults them (ENT-3739 tracks
|
|
150
|
+
# adding the missing per-task computation to cinsights).
|
|
151
|
+
_SESSION_FIELD_MAP = {
|
|
152
|
+
"agent_type": "agent",
|
|
153
|
+
"model": "model",
|
|
154
|
+
"start_time": "start_time",
|
|
155
|
+
"end_time": "end_time",
|
|
156
|
+
"prompt_tokens": "input_tokens",
|
|
157
|
+
"completion_tokens": "output_tokens",
|
|
158
|
+
"turn_count": "turn_count",
|
|
159
|
+
"span_count": "span_count",
|
|
160
|
+
"interrupted_turn_waste": "interrupt_waste_tokens",
|
|
161
|
+
"repeated_edit_waste": "repeat_waste_tokens",
|
|
162
|
+
"failed_retry_waste": "retry_waste_tokens",
|
|
163
|
+
"floor_drift_score": "floor_drift",
|
|
164
|
+
"agent_version": "agent_version",
|
|
165
|
+
"effort_level": "effort_level",
|
|
166
|
+
"adaptive_thinking_disabled": "adaptive_thinking_disabled",
|
|
167
|
+
}
|
|
168
|
+
_TASK_FIELD_MAP = {
|
|
169
|
+
"task_number": "task_number",
|
|
170
|
+
"name": "name",
|
|
171
|
+
"description": "description",
|
|
172
|
+
"start_turn": "start_turn",
|
|
173
|
+
"end_turn": "end_turn",
|
|
174
|
+
"turn_count": "turn_count",
|
|
175
|
+
"prompt_tokens_total": "prompt_tokens_total",
|
|
176
|
+
"completion_tokens_total": "completion_tokens_total",
|
|
177
|
+
"context_at_start": "context_at_start",
|
|
178
|
+
"estimated_waste_tokens": "estimated_waste_tokens",
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _structured_session_payload(row: dict, tasks: list[dict] | None) -> dict:
|
|
183
|
+
"""Map one `coding_session` row (+ its `task` rows) to the structured-ingest
|
|
184
|
+
endpoint's session shape (ENT-3634 / ENT-3633).
|
|
185
|
+
|
|
186
|
+
Field renames only — no new computation. `cached_tokens` is the one
|
|
187
|
+
exception, summing cinsights' two separate cache columns into the single
|
|
188
|
+
field the endpoint expects.
|
|
189
|
+
"""
|
|
190
|
+
session: dict = {
|
|
191
|
+
wire: row[local] for local, wire in _SESSION_FIELD_MAP.items() if row.get(local) is not None
|
|
192
|
+
}
|
|
193
|
+
session["session_id"] = row.get("id")
|
|
194
|
+
cache_read = row.get("cache_read_tokens") or 0
|
|
195
|
+
cache_write = row.get("cache_write_tokens") or 0
|
|
196
|
+
if cache_read or cache_write:
|
|
197
|
+
session["cached_tokens"] = cache_read + cache_write
|
|
198
|
+
if tasks:
|
|
199
|
+
session["tasks"] = [
|
|
200
|
+
{wire: t[local] for local, wire in _TASK_FIELD_MAP.items() if t.get(local) is not None}
|
|
201
|
+
for t in tasks
|
|
202
|
+
]
|
|
203
|
+
return session
|
|
204
|
+
|
|
138
205
|
|
|
139
206
|
def _local_db_path() -> str | None:
|
|
140
207
|
"""Filesystem path of the local cinsights SQLite DB (where `index` writes)."""
|
|
@@ -222,15 +289,19 @@ def _sync_cmd(
|
|
|
222
289
|
secrets_mode,
|
|
223
290
|
limit,
|
|
224
291
|
):
|
|
225
|
-
"""Push your locally-computed coding-agent
|
|
292
|
+
"""Push your locally-computed coding-agent sessions to Entelligence (Path A).
|
|
226
293
|
|
|
227
|
-
Refreshes the local index, then uploads
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
294
|
+
Refreshes the local index, then uploads session + task data — the same
|
|
295
|
+
data the dashboard shows, and (ENT-3634) the same data that now lands on
|
|
296
|
+
the coding-agent graph, not just RDS metrics, via the structured-ingest
|
|
297
|
+
endpoint. Raw transcript text never leaves the machine. Identity (org +
|
|
298
|
+
you) is derived server-side from your CLI token, and re-runs upsert, so
|
|
299
|
+
it's safe to cron daily.
|
|
231
300
|
|
|
232
301
|
With --transcripts, uploads the raw (redacted) transcript files to the store
|
|
233
|
-
for the ingestion worker instead of pushing
|
|
302
|
+
for the ingestion worker instead of pushing structured data — Path B.
|
|
303
|
+
Mutually exclusive with the default push for now (ENT-3732 tracks making
|
|
304
|
+
the two independently schedulable).
|
|
234
305
|
"""
|
|
235
306
|
obj = ctx.obj or {}
|
|
236
307
|
api_client = obj.get("api_client")
|
|
@@ -314,12 +385,16 @@ def _sync_cmd(
|
|
|
314
385
|
if name:
|
|
315
386
|
bucket = skill_dist.setdefault(sr["session_id"], {})
|
|
316
387
|
bucket[name] = bucket.get(name, 0) + 1
|
|
317
|
-
# Per-session task segmentation
|
|
318
|
-
#
|
|
388
|
+
# Per-session task segmentation. Every column cinsights' `task` table
|
|
389
|
+
# actually has (ENT-3634) — description/turn_count/completion_tokens_total/
|
|
390
|
+
# context_at_start were dropped by the old SELECT, which only needed
|
|
391
|
+
# name/turn-range/waste for the context-growth chart; the structured-ingest
|
|
392
|
+
# endpoint (ENT-3631) needs the rest too.
|
|
319
393
|
task_map: dict[str, list] = {}
|
|
320
394
|
for tr in conn.execute(
|
|
321
|
-
"SELECT session_id, task_number, name, start_turn, end_turn, "
|
|
322
|
-
"prompt_tokens_total,
|
|
395
|
+
"SELECT session_id, task_number, name, description, start_turn, end_turn, "
|
|
396
|
+
"turn_count, prompt_tokens_total, completion_tokens_total, context_at_start, "
|
|
397
|
+
"estimated_waste_tokens FROM task ORDER BY task_number"
|
|
323
398
|
):
|
|
324
399
|
task_map.setdefault(tr["session_id"], []).append(dict(tr))
|
|
325
400
|
# LLM-generated per-session insights (category/title/content/severity).
|
|
@@ -331,8 +406,9 @@ def _sync_cmd(
|
|
|
331
406
|
finally:
|
|
332
407
|
conn.close()
|
|
333
408
|
|
|
334
|
-
# 3. Keep sessions newer than the high-water mark (unless --all),
|
|
335
|
-
#
|
|
409
|
+
# 3. Keep sessions newer than the high-water mark (unless --all), map to the
|
|
410
|
+
# structured-ingest endpoint's field names (ENT-3634), and dedup on the
|
|
411
|
+
# engine's stable session key.
|
|
336
412
|
hwm = None if push_all else _read_high_water_mark()
|
|
337
413
|
sessions: list[dict] = []
|
|
338
414
|
max_seen: datetime | None = None
|
|
@@ -343,21 +419,20 @@ def _sync_cmd(
|
|
|
343
419
|
# re-upload every run). The first sync (no HWM) still includes them once.
|
|
344
420
|
if not push_all and hwm is not None and (last is None or last <= hwm):
|
|
345
421
|
continue
|
|
346
|
-
session =
|
|
347
|
-
|
|
422
|
+
session = _structured_session_payload(row, task_map.get(row.get("id")))
|
|
423
|
+
# Extras the structured-ingest endpoint doesn't read yet (ENT-3631 accepts
|
|
424
|
+
# and ignores them) — sent anyway so nothing here needs to change once the
|
|
425
|
+
# backend catches up (findings/coaching routing, Decision 1; RDS column
|
|
426
|
+
# parity for tool/skill distribution).
|
|
348
427
|
td = tool_dist.get(row.get("id"))
|
|
349
428
|
if td:
|
|
350
429
|
session["tool_distribution"] = td
|
|
351
430
|
sd = skill_dist.get(row.get("id"))
|
|
352
431
|
if sd:
|
|
353
432
|
session["skill_distribution"] = sd
|
|
354
|
-
# Per-session drill-down material: context growth (per-turn), tasks, insights.
|
|
355
433
|
cg = row.get("context_growth_json")
|
|
356
434
|
if cg:
|
|
357
435
|
session["context_growth"] = cg # already a JSON string in the engine DB
|
|
358
|
-
tasks = task_map.get(row.get("id"))
|
|
359
|
-
if tasks:
|
|
360
|
-
session["tasks"] = tasks
|
|
361
436
|
insights = insight_map.get(row.get("id"))
|
|
362
437
|
if insights:
|
|
363
438
|
session["insights"] = insights
|
|
@@ -376,24 +451,35 @@ def _sync_cmd(
|
|
|
376
451
|
return
|
|
377
452
|
|
|
378
453
|
pushed = 0
|
|
379
|
-
|
|
454
|
+
sessions_stored_total = 0
|
|
455
|
+
tasks_written_total = 0
|
|
380
456
|
client_version = _client_version()
|
|
381
457
|
for i in range(0, len(sessions), batch_size):
|
|
382
458
|
batch = sessions[i : i + batch_size]
|
|
383
459
|
# orgUUID is only for the server's @require_org_access check; the backend
|
|
384
460
|
# derives the real identity (org + dev) from the auth token, not the body.
|
|
385
|
-
payload = {
|
|
386
|
-
|
|
461
|
+
payload = {
|
|
462
|
+
"orgUUID": org_uuid,
|
|
463
|
+
"clientVersion": client_version,
|
|
464
|
+
"extractionVersion": _STRUCTURED_EXTRACTION_VERSION,
|
|
465
|
+
"sessions": batch,
|
|
466
|
+
}
|
|
467
|
+
resp = api_client.ingest_agent_insights_structured(org_uuid, payload)
|
|
387
468
|
if isinstance(resp, dict) and resp.get("Error"):
|
|
388
469
|
raise click.ClickException(f"Sync failed after {pushed} session(s): {resp['Error']}")
|
|
389
|
-
|
|
470
|
+
if isinstance(resp, dict):
|
|
471
|
+
sessions_stored_total += int(resp.get("sessions_stored") or 0)
|
|
472
|
+
tasks_written_total += int(resp.get("tasks_written") or 0)
|
|
390
473
|
pushed += len(batch)
|
|
391
474
|
click.echo(f" pushed {pushed}/{len(sessions)}")
|
|
392
475
|
|
|
393
476
|
if max_seen is not None:
|
|
394
477
|
_write_high_water_mark(max_seen)
|
|
395
478
|
|
|
396
|
-
click.echo(
|
|
479
|
+
click.echo(
|
|
480
|
+
f"Synced {pushed} session(s) ({sessions_stored_total} stored, "
|
|
481
|
+
f"{tasks_written_total} tasks) to Entelligence."
|
|
482
|
+
)
|
|
397
483
|
|
|
398
484
|
|
|
399
485
|
# ----- auto-push: fire `sync` in the background, throttled to once/day -----
|
|
@@ -601,6 +687,17 @@ _RE_IDE_CONTEXT_TAG = re.compile(r"<ide_[^>]*>.*?</ide_[^>]*>", re.IGNORECASE |
|
|
|
601
687
|
_RE_USER_QUERY_TAG = re.compile(r"</?user_query>", re.IGNORECASE)
|
|
602
688
|
|
|
603
689
|
|
|
690
|
+
def _kiro_homes() -> list[str]:
|
|
691
|
+
"""Kiro home dirs to read session event logs from (~/.kiro).
|
|
692
|
+
|
|
693
|
+
Unlike the others there's no cinsights config to consult — the insights engine
|
|
694
|
+
has no Kiro source, so metrics for Kiro sessions come from the uploaded
|
|
695
|
+
transcript (``sync --transcripts``), not the local index. Prompts for the wrap
|
|
696
|
+
are read straight off disk here.
|
|
697
|
+
"""
|
|
698
|
+
return [os.path.expanduser("~/.kiro")]
|
|
699
|
+
|
|
700
|
+
|
|
604
701
|
def _strip_prompt_tags(text: str) -> str:
|
|
605
702
|
"""Drop `<ide_*>` blocks and unwrap Cursor's `<user_query>` tags."""
|
|
606
703
|
text = _RE_IDE_CONTEXT_TAG.sub("", text)
|
|
@@ -686,6 +783,23 @@ def _user_prompt_text(obj: dict) -> str | None:
|
|
|
686
783
|
txt = _codex_user_text((payload.get("message") or "").strip())
|
|
687
784
|
if txt is None:
|
|
688
785
|
return None
|
|
786
|
+
# --- Kiro -----------------------------------------------------------------
|
|
787
|
+
# Events are enveloped: {"id", "timestamp", "payload": {"type": ..., ...}} with
|
|
788
|
+
# no top-level "type" — that absence is what tells them apart from Codex, whose
|
|
789
|
+
# branches above match on their own line_type first.
|
|
790
|
+
elif not line_type and isinstance(obj.get("payload"), dict):
|
|
791
|
+
payload = obj["payload"]
|
|
792
|
+
ptype = payload.get("type")
|
|
793
|
+
if ptype == "user":
|
|
794
|
+
content = payload.get("content")
|
|
795
|
+
txt = content.strip() if isinstance(content, str) else None
|
|
796
|
+
elif ptype == "interaction_resolved":
|
|
797
|
+
# The developer answering a Kiro question — their words, so a real prompt.
|
|
798
|
+
selected = payload.get("selectedOption")
|
|
799
|
+
if payload.get("outcome") == "answered" and isinstance(selected, str):
|
|
800
|
+
txt = selected.strip()
|
|
801
|
+
if txt is None:
|
|
802
|
+
return None
|
|
689
803
|
# --- Claude Code / Cursor -------------------------------------------------
|
|
690
804
|
else:
|
|
691
805
|
if line_type != "user" and obj.get("role") != "user":
|
|
@@ -737,6 +851,14 @@ def _extract_user_prompts(days: int, cap: int = _WRAP_MAX_PROMPTS) -> list[str]:
|
|
|
737
851
|
for home in _codex_homes():
|
|
738
852
|
files += glob.glob(os.path.join(home, "sessions", "**", "*.jsonl"), recursive=True)
|
|
739
853
|
files += glob.glob(os.path.join(home, "archived_sessions", "**", "*.jsonl"), recursive=True)
|
|
854
|
+
# Kiro: one event log per session dir, <home>/sessions/<ws-hash>/<session-id>/
|
|
855
|
+
# messages.jsonl. Enveloped lines ({"payload": {"type": "user", ...}}) — handled
|
|
856
|
+
# in _user_prompt_text. Sub-executions are sub-agent turns, not the developer's,
|
|
857
|
+
# so they're deliberately not globbed here.
|
|
858
|
+
for home in _kiro_homes():
|
|
859
|
+
files += glob.glob(
|
|
860
|
+
os.path.join(home, "sessions", "*", "*", "messages.jsonl"),
|
|
861
|
+
)
|
|
740
862
|
for f in files:
|
|
741
863
|
try:
|
|
742
864
|
if os.path.getmtime(f) < cutoff:
|
|
@@ -1127,6 +1127,217 @@ def _scan_sessions_in_parallel(sessions: list[Session], *, workers: int) -> None
|
|
|
1127
1127
|
progress.advance(task)
|
|
1128
1128
|
|
|
1129
1129
|
|
|
1130
|
+
# ----- Discovery: Kiro (session directories) ---------------------------------
|
|
1131
|
+
#
|
|
1132
|
+
# A Kiro session is a DIRECTORY, not a file: sessions/<ws-hash>/<session-id>/ holds
|
|
1133
|
+
# session.json (metadata) + messages.jsonl (event log), and sub-agent runs land in
|
|
1134
|
+
# sub-executions/<execution-id>.jsonl. We synthesize one NDJSON per session — a
|
|
1135
|
+
# `kiro_session` header line then the raw events, merged by timestamp — mirroring
|
|
1136
|
+
# the cursor_store/opencode pattern so scan, redaction, upload, and dedup are all
|
|
1137
|
+
# unchanged downstream. The backend's `detect_agent` sniffs the header type.
|
|
1138
|
+
|
|
1139
|
+
KIRO_CACHE_ROOT = Path.home() / ".entelligence" / "cache" / "kiro"
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
def _count_kiro_sessions(home: Path) -> int:
|
|
1143
|
+
"""Count session dirs under <home>/sessions/<ws-hash>/<session-id>/ (0 if none)."""
|
|
1144
|
+
sessions_root = home / "sessions"
|
|
1145
|
+
if not sessions_root.is_dir():
|
|
1146
|
+
return 0
|
|
1147
|
+
n = 0
|
|
1148
|
+
try:
|
|
1149
|
+
for ws_hash_dir in sessions_root.iterdir():
|
|
1150
|
+
if not ws_hash_dir.is_dir():
|
|
1151
|
+
continue
|
|
1152
|
+
for session_dir in ws_hash_dir.iterdir():
|
|
1153
|
+
if session_dir.is_dir() and (session_dir / "session.json").is_file():
|
|
1154
|
+
n += 1
|
|
1155
|
+
except OSError:
|
|
1156
|
+
return n
|
|
1157
|
+
return n
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def _kiro_cwd(session_meta: dict) -> Path | None:
|
|
1161
|
+
"""The session's primary workspace root (``workspacePaths[0]``), else None.
|
|
1162
|
+
|
|
1163
|
+
None means the repo pre-filter can't place the session by cwd and falls back to
|
|
1164
|
+
transcript-based recovery — see ``_partition_by_connected_repos``.
|
|
1165
|
+
"""
|
|
1166
|
+
paths = session_meta.get("workspacePaths")
|
|
1167
|
+
if isinstance(paths, list) and paths:
|
|
1168
|
+
first = paths[0]
|
|
1169
|
+
if isinstance(first, str) and first.strip():
|
|
1170
|
+
return Path(first)
|
|
1171
|
+
return None
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
def _kiro_read_events(path: Path) -> list[dict]:
|
|
1175
|
+
"""Parse one Kiro JSONL event log; unparseable/non-dict lines are dropped."""
|
|
1176
|
+
events: list[dict] = []
|
|
1177
|
+
try:
|
|
1178
|
+
data = path.read_bytes()
|
|
1179
|
+
except OSError:
|
|
1180
|
+
return events
|
|
1181
|
+
for raw_line in data.split(b"\n"):
|
|
1182
|
+
raw_line = raw_line.strip()
|
|
1183
|
+
if not raw_line:
|
|
1184
|
+
continue
|
|
1185
|
+
try:
|
|
1186
|
+
obj = json.loads(raw_line)
|
|
1187
|
+
except (json.JSONDecodeError, UnicodeDecodeError, ValueError):
|
|
1188
|
+
continue
|
|
1189
|
+
if isinstance(obj, dict):
|
|
1190
|
+
events.append(obj)
|
|
1191
|
+
return events
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
def _kiro_source_mtime(messages_mtime: float, session_dir: Path) -> float:
|
|
1195
|
+
"""Latest mtime across every file `_kiro_synthesize` reads for this session.
|
|
1196
|
+
|
|
1197
|
+
That is ``messages.jsonl`` plus any ``sub-executions/*.jsonl``. The cache
|
|
1198
|
+
freshness guard compares against this so a sub-agent file updated after
|
|
1199
|
+
``messages.jsonl`` still triggers re-synthesis — comparing against
|
|
1200
|
+
``messages.jsonl`` alone would serve a cache missing those sub-agent events.
|
|
1201
|
+
A missing/unreadable ``sub-executions/`` dir falls back to ``messages_mtime``.
|
|
1202
|
+
"""
|
|
1203
|
+
latest = messages_mtime
|
|
1204
|
+
sub_exec_dir = session_dir / "sub-executions"
|
|
1205
|
+
if sub_exec_dir.is_dir():
|
|
1206
|
+
try:
|
|
1207
|
+
for sub_file in sub_exec_dir.iterdir():
|
|
1208
|
+
if sub_file.suffix != ".jsonl":
|
|
1209
|
+
continue
|
|
1210
|
+
with contextlib.suppress(OSError):
|
|
1211
|
+
latest = max(latest, sub_file.stat().st_mtime)
|
|
1212
|
+
except OSError:
|
|
1213
|
+
pass
|
|
1214
|
+
return latest
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
def _kiro_synthesize(session_meta: dict, messages_path: Path, session_dir: Path) -> bytes | None:
|
|
1218
|
+
"""Build one session's NDJSON: `kiro_session` header + events, merged by timestamp.
|
|
1219
|
+
|
|
1220
|
+
Sub-execution (sub-agent) events are merged inline so the whole conversation —
|
|
1221
|
+
including sub-agent tool calls — travels as a single uploadable file.
|
|
1222
|
+
|
|
1223
|
+
Returns None when the session has no ``turn_end`` event: an empty or
|
|
1224
|
+
metadata-only session has nothing worth uploading.
|
|
1225
|
+
"""
|
|
1226
|
+
events = _kiro_read_events(messages_path)
|
|
1227
|
+
sub_exec_dir = session_dir / "sub-executions"
|
|
1228
|
+
if sub_exec_dir.is_dir():
|
|
1229
|
+
try:
|
|
1230
|
+
sub_files = sorted(p for p in sub_exec_dir.iterdir() if p.suffix == ".jsonl")
|
|
1231
|
+
except OSError:
|
|
1232
|
+
sub_files = []
|
|
1233
|
+
for sub_file in sub_files:
|
|
1234
|
+
events.extend(_kiro_read_events(sub_file))
|
|
1235
|
+
|
|
1236
|
+
# Stable sort on timestamp: equal/absent timestamps keep file order (main
|
|
1237
|
+
# stream first, then each sub-execution in name order).
|
|
1238
|
+
events.sort(key=lambda e: str(e.get("timestamp") or ""))
|
|
1239
|
+
|
|
1240
|
+
has_turns = any(
|
|
1241
|
+
isinstance(e.get("payload"), dict) and e["payload"].get("type") == "turn_end"
|
|
1242
|
+
for e in events
|
|
1243
|
+
)
|
|
1244
|
+
if not has_turns:
|
|
1245
|
+
return None
|
|
1246
|
+
|
|
1247
|
+
# Marker key LAST so it always wins: if a future session.json carries its own
|
|
1248
|
+
# `type`, spreading it after would clobber the `kiro_session` marker and the
|
|
1249
|
+
# backend's detect_agent would fail to recognise the transcript (dropping it).
|
|
1250
|
+
header = {**session_meta, "type": "kiro_session"}
|
|
1251
|
+
lines = [json.dumps(header, separators=(",", ":"))]
|
|
1252
|
+
lines.extend(json.dumps(e, separators=(",", ":")) for e in events)
|
|
1253
|
+
return ("\n".join(lines) + "\n").encode()
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
def find_kiro_candidates(home: HomeSpec, *, cache_root: Path | None = None) -> Iterator[Session]:
|
|
1257
|
+
"""Yield a Session per Kiro session, synthesizing an NDJSON transcript.
|
|
1258
|
+
|
|
1259
|
+
``home.path`` is the Kiro home (holds ``sessions/``). Each session is materialized
|
|
1260
|
+
to ``<cache_root>/<ws-hash>/<session-id>.jsonl`` and that file becomes the Session's
|
|
1261
|
+
``abs_path``, mirroring ``find_cursor_store_candidates``.
|
|
1262
|
+
|
|
1263
|
+
The cache is only rewritten when ``messages.jsonl`` is newer than it, so a re-run
|
|
1264
|
+
over unchanged sessions costs a stat() rather than a full re-synthesis.
|
|
1265
|
+
"""
|
|
1266
|
+
sessions_root = home.path / "sessions"
|
|
1267
|
+
if not sessions_root.is_dir():
|
|
1268
|
+
logger.debug("kiro: no sessions dir under %s", home.path)
|
|
1269
|
+
return
|
|
1270
|
+
root = cache_root or KIRO_CACHE_ROOT
|
|
1271
|
+
try:
|
|
1272
|
+
ws_hash_dirs = sorted(p for p in sessions_root.iterdir() if p.is_dir())
|
|
1273
|
+
except OSError as exc:
|
|
1274
|
+
logger.debug("kiro: cannot list %s: %s", sessions_root, exc)
|
|
1275
|
+
return
|
|
1276
|
+
|
|
1277
|
+
for ws_hash_dir in ws_hash_dirs:
|
|
1278
|
+
ws_hash = _safe_component(ws_hash_dir.name, "unknown")
|
|
1279
|
+
try:
|
|
1280
|
+
session_dirs = sorted(p for p in ws_hash_dir.iterdir() if p.is_dir())
|
|
1281
|
+
except OSError:
|
|
1282
|
+
continue
|
|
1283
|
+
for session_dir in session_dirs:
|
|
1284
|
+
session_json_path = session_dir / "session.json"
|
|
1285
|
+
messages_path = session_dir / "messages.jsonl"
|
|
1286
|
+
if not session_json_path.is_file() or not messages_path.is_file():
|
|
1287
|
+
continue
|
|
1288
|
+
try:
|
|
1289
|
+
session_meta = json.loads(session_json_path.read_bytes())
|
|
1290
|
+
except (OSError, json.JSONDecodeError, ValueError):
|
|
1291
|
+
logger.debug("kiro: bad session.json in %s", session_dir)
|
|
1292
|
+
continue
|
|
1293
|
+
if not isinstance(session_meta, dict):
|
|
1294
|
+
continue
|
|
1295
|
+
try:
|
|
1296
|
+
messages_mtime = messages_path.stat().st_mtime
|
|
1297
|
+
except OSError:
|
|
1298
|
+
continue
|
|
1299
|
+
# Freshness is judged against every file the synthesis reads — not
|
|
1300
|
+
# just messages.jsonl. Sub-executions are merged in too, and a
|
|
1301
|
+
# sub-agent file updated after messages.jsonl would otherwise leave
|
|
1302
|
+
# the cache "fresh" while missing those events.
|
|
1303
|
+
source_mtime = _kiro_source_mtime(messages_mtime, session_dir)
|
|
1304
|
+
|
|
1305
|
+
session_id = _safe_component(session_meta.get("id") or session_dir.name, "session")
|
|
1306
|
+
rel = Path(ws_hash) / f"{session_id}.jsonl"
|
|
1307
|
+
abs_path = root / rel
|
|
1308
|
+
cwd = _kiro_cwd(session_meta)
|
|
1309
|
+
|
|
1310
|
+
fresh = False
|
|
1311
|
+
with contextlib.suppress(OSError):
|
|
1312
|
+
fresh = abs_path.stat().st_mtime >= source_mtime
|
|
1313
|
+
if not fresh:
|
|
1314
|
+
data = _kiro_synthesize(session_meta, messages_path, session_dir)
|
|
1315
|
+
if data is None:
|
|
1316
|
+
continue
|
|
1317
|
+
try:
|
|
1318
|
+
abs_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1319
|
+
abs_path.write_bytes(data)
|
|
1320
|
+
except OSError as exc:
|
|
1321
|
+
logger.debug("kiro materialize failed %s: %s", abs_path, exc)
|
|
1322
|
+
continue
|
|
1323
|
+
try:
|
|
1324
|
+
st = abs_path.stat()
|
|
1325
|
+
except OSError:
|
|
1326
|
+
continue
|
|
1327
|
+
yield Session(
|
|
1328
|
+
agent=AgentKind.KIRO,
|
|
1329
|
+
home_label=home.label,
|
|
1330
|
+
abs_path=abs_path,
|
|
1331
|
+
rel_path=rel,
|
|
1332
|
+
size_bytes=st.st_size,
|
|
1333
|
+
# messages.jsonl mtime, not the cache file's: the cache is rewritten
|
|
1334
|
+
# on re-synthesis and would otherwise report every session as "just now".
|
|
1335
|
+
mtime=datetime.fromtimestamp(messages_mtime, tz=UTC),
|
|
1336
|
+
cwd=cwd,
|
|
1337
|
+
secret_findings=[],
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1340
|
+
|
|
1130
1341
|
def find_all_candidates(homes: Iterable[HomeSpec]) -> list[Session]:
|
|
1131
1342
|
"""Cheap pass: find every candidate session, no secret scan. Newest-first."""
|
|
1132
1343
|
found: list[Session] = []
|
|
@@ -1141,6 +1352,8 @@ def find_all_candidates(homes: Iterable[HomeSpec]) -> list[Session]:
|
|
|
1141
1352
|
elif home.kind is AgentKind.CURSOR:
|
|
1142
1353
|
found.extend(find_cursor_store_candidates(home))
|
|
1143
1354
|
cursor_homes_seen = True
|
|
1355
|
+
elif home.kind is AgentKind.KIRO:
|
|
1356
|
+
found.extend(find_kiro_candidates(home))
|
|
1144
1357
|
# IDE composers live in state.vscdb (not under ~/.cursor). Discover once per
|
|
1145
1358
|
# run when any Cursor home is configured — usage API joins on composerId.
|
|
1146
1359
|
if cursor_homes_seen:
|
|
@@ -1325,14 +1538,15 @@ def _detect_candidate_homes(prefix: str, counter: Callable[[Path], int]) -> dict
|
|
|
1325
1538
|
return out
|
|
1326
1539
|
|
|
1327
1540
|
|
|
1328
|
-
def auto_detect_homes() -> tuple[list[Path], list[Path], list[Path], list[Path]]:
|
|
1329
|
-
"""Return (claude_homes, codex_homes, opencode_homes, cursor_homes)
|
|
1330
|
-
that hold transcripts."""
|
|
1541
|
+
def auto_detect_homes() -> tuple[list[Path], list[Path], list[Path], list[Path], list[Path]]:
|
|
1542
|
+
"""Return (claude_homes, codex_homes, opencode_homes, cursor_homes, kiro_homes)
|
|
1543
|
+
auto-detected that hold transcripts."""
|
|
1331
1544
|
claude = sorted(_detect_candidate_homes(".claude", _count_cc_sessions).keys())
|
|
1332
1545
|
codex = sorted(_detect_candidate_homes(".codex", _count_codex_sessions).keys())
|
|
1333
1546
|
opencode = sorted(_detect_opencode_homes().keys())
|
|
1334
1547
|
cursor = sorted(_detect_candidate_homes(".cursor", _count_cursor_sessions).keys())
|
|
1335
|
-
|
|
1548
|
+
kiro = sorted(_detect_candidate_homes(".kiro", _count_kiro_sessions).keys())
|
|
1549
|
+
return claude, codex, opencode, cursor, kiro
|
|
1336
1550
|
|
|
1337
1551
|
|
|
1338
1552
|
def homes_from_config(cfg: UploadConfig) -> list[HomeSpec]:
|
|
@@ -1362,4 +1576,10 @@ def homes_from_config(cfg: UploadConfig) -> list[HomeSpec]:
|
|
|
1362
1576
|
console.print(f"[yellow]skip:[/yellow] cursor-home {p} no longer exists")
|
|
1363
1577
|
continue
|
|
1364
1578
|
homes.append(HomeSpec(kind=AgentKind.CURSOR, path=p.resolve()))
|
|
1579
|
+
for raw in cfg.kiro_homes:
|
|
1580
|
+
p = Path(raw).expanduser()
|
|
1581
|
+
if not p.is_dir():
|
|
1582
|
+
console.print(f"[yellow]skip:[/yellow] kiro-home {p} no longer exists")
|
|
1583
|
+
continue
|
|
1584
|
+
homes.append(HomeSpec(kind=AgentKind.KIRO, path=p.resolve()))
|
|
1365
1585
|
return homes
|
|
@@ -22,6 +22,7 @@ class AgentKind(StrEnum):
|
|
|
22
22
|
CODEX = "codex"
|
|
23
23
|
OPENCODE = "opencode"
|
|
24
24
|
CURSOR = "cursor"
|
|
25
|
+
KIRO = "kiro"
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class HomeSpec(BaseModel):
|
|
@@ -127,11 +128,19 @@ class UploadConfig(BaseModel):
|
|
|
127
128
|
# chats/<ws-hash>/<uuid>/ or acp-sessions/<uuid>/, so discovery synthesizes an NDJSON
|
|
128
129
|
# transcript per session (see discovery.py), same as opencode.
|
|
129
130
|
cursor_homes: list[str] = Field(default_factory=list)
|
|
131
|
+
# Kiro homes (e.g. ~/.kiro). A session is a DIRECTORY — sessions/<ws-hash>/<session-id>/
|
|
132
|
+
# holding session.json + messages.jsonl (+ sub-executions/*.jsonl) — so discovery
|
|
133
|
+
# synthesizes one NDJSON transcript per session (see discovery.py), same as cursor/opencode.
|
|
134
|
+
kiro_homes: list[str] = Field(default_factory=list)
|
|
130
135
|
secrets_mode: Literal["redact", "allow", "skip"] = Field(default="redact")
|
|
131
136
|
skip_prefixes: list[str] = Field(default_factory=list)
|
|
132
137
|
entelligence: EntelligenceIdentity | None = None
|
|
133
138
|
|
|
134
139
|
def needs_setup(self) -> bool:
|
|
135
140
|
return not (
|
|
136
|
-
self.claude_homes
|
|
141
|
+
self.claude_homes
|
|
142
|
+
or self.codex_homes
|
|
143
|
+
or self.opencode_homes
|
|
144
|
+
or self.cursor_homes
|
|
145
|
+
or self.kiro_homes
|
|
137
146
|
)
|
|
@@ -372,13 +372,14 @@ def _backfill_missing_home_kinds(cfg: UploadConfig) -> bool:
|
|
|
372
372
|
Returns:
|
|
373
373
|
True if any kind was backfilled (caller should persist), else False.
|
|
374
374
|
"""
|
|
375
|
-
cc, cx, oc, cu = auto_detect_homes()
|
|
375
|
+
cc, cx, oc, cu, ki = auto_detect_homes()
|
|
376
376
|
added = False
|
|
377
377
|
for detected, attr in (
|
|
378
378
|
(cc, "claude_homes"),
|
|
379
379
|
(cx, "codex_homes"),
|
|
380
380
|
(oc, "opencode_homes"),
|
|
381
381
|
(cu, "cursor_homes"),
|
|
382
|
+
(ki, "kiro_homes"),
|
|
382
383
|
):
|
|
383
384
|
if not getattr(cfg, attr) and detected:
|
|
384
385
|
setattr(cfg, attr, [str(p) for p in detected])
|
|
@@ -413,12 +414,13 @@ def run_transcript_upload(
|
|
|
413
414
|
if setup or (
|
|
414
415
|
cfg.needs_setup() and not (claude_home or codex_home or opencode_home or cursor_home)
|
|
415
416
|
):
|
|
416
|
-
cc, cx, oc, cu = auto_detect_homes()
|
|
417
|
+
cc, cx, oc, cu, ki = auto_detect_homes()
|
|
417
418
|
cfg.claude_homes = [str(p) for p in cc]
|
|
418
419
|
cfg.codex_homes = [str(p) for p in cx]
|
|
419
420
|
cfg.opencode_homes = [str(p) for p in oc]
|
|
420
421
|
cfg.cursor_homes = [str(p) for p in cu]
|
|
421
|
-
|
|
422
|
+
cfg.kiro_homes = [str(p) for p in ki]
|
|
423
|
+
if cc or cx or oc or cu or ki:
|
|
422
424
|
save_config(cfg)
|
|
423
425
|
elif not (claude_home or codex_home or opencode_home or cursor_home) and (
|
|
424
426
|
_backfill_missing_home_kinds(cfg)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: entelligence-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.99
|
|
4
4
|
Summary: AI-powered code review assistant for your terminal - Get instant, intelligent feedback on your code changes
|
|
5
5
|
Author-email: EntelligenceAI <info@entelligence.ai>
|
|
6
6
|
Project-URL: Homepage, https://entelligence.ai
|
|
@@ -68,7 +68,9 @@ tests/test_cursor_usage.py
|
|
|
68
68
|
tests/test_discovery_head_unicode.py
|
|
69
69
|
tests/test_discovery_opencode.py
|
|
70
70
|
tests/test_incident_smoke.py
|
|
71
|
+
tests/test_insights_kiro_prompts.py
|
|
71
72
|
tests/test_insights_me_smoke.py
|
|
73
|
+
tests/test_insights_structured_payload.py
|
|
72
74
|
tests/test_pii.py
|
|
73
75
|
tests/test_presigned_skip_index.py
|
|
74
76
|
tests/test_router_commands.py
|
|
@@ -76,5 +78,6 @@ tests/test_security_commands.py
|
|
|
76
78
|
tests/test_update_command.py
|
|
77
79
|
tests/test_upload_cursor_store.py
|
|
78
80
|
tests/test_upload_home_backfill.py
|
|
81
|
+
tests/test_upload_kiro.py
|
|
79
82
|
tests/test_upload_repo_filter.py
|
|
80
83
|
tests/test_upload_repo_roots.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "entelligence-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.99"
|
|
8
8
|
description = "AI-powered code review assistant for your terminal - Get instant, intelligent feedback on your code changes"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|