@smilintux/skcapstone 0.4.6 → 0.5.0

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 (77) hide show
  1. package/.github/workflows/publish.yml +8 -1
  2. package/docs/CUSTOM_AGENT.md +184 -0
  3. package/docs/GETTING_STARTED.md +3 -0
  4. package/launchd/com.skcapstone.daemon.plist +52 -0
  5. package/launchd/com.skcapstone.memory-compress.plist +45 -0
  6. package/launchd/com.skcapstone.skcomm-heartbeat.plist +33 -0
  7. package/launchd/com.skcapstone.skcomm-queue-drain.plist +34 -0
  8. package/launchd/install-launchd.sh +156 -0
  9. package/package.json +1 -1
  10. package/pyproject.toml +1 -1
  11. package/scripts/archive-sessions.sh +88 -0
  12. package/scripts/install.sh +39 -8
  13. package/scripts/notion-api.py +259 -0
  14. package/scripts/nvidia-proxy.mjs +878 -0
  15. package/scripts/proxy-monitor.sh +89 -0
  16. package/scripts/refresh-anthropic-token.sh +94 -0
  17. package/scripts/skgateway.mjs +856 -0
  18. package/scripts/telegram-catchup-all.sh +136 -0
  19. package/scripts/watch-anthropic-token.sh +117 -0
  20. package/src/skcapstone/__init__.py +1 -1
  21. package/src/skcapstone/_cli_monolith.py +4 -4
  22. package/src/skcapstone/api.py +36 -35
  23. package/src/skcapstone/auction.py +8 -8
  24. package/src/skcapstone/blueprint_registry.py +2 -2
  25. package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
  26. package/src/skcapstone/brain_first.py +238 -0
  27. package/src/skcapstone/chat.py +4 -4
  28. package/src/skcapstone/cli/__init__.py +2 -0
  29. package/src/skcapstone/cli/agents_spawner.py +5 -2
  30. package/src/skcapstone/cli/chat.py +5 -2
  31. package/src/skcapstone/cli/consciousness.py +5 -2
  32. package/src/skcapstone/cli/daemon.py +116 -41
  33. package/src/skcapstone/cli/itil.py +434 -0
  34. package/src/skcapstone/cli/memory.py +4 -4
  35. package/src/skcapstone/cli/skills_cmd.py +2 -2
  36. package/src/skcapstone/cli/soul.py +5 -2
  37. package/src/skcapstone/cli/status.py +11 -8
  38. package/src/skcapstone/cli/upgrade_cmd.py +7 -4
  39. package/src/skcapstone/cli/watch_cmd.py +9 -6
  40. package/src/skcapstone/config_validator.py +7 -4
  41. package/src/skcapstone/consciousness_config.py +27 -0
  42. package/src/skcapstone/consciousness_loop.py +20 -18
  43. package/src/skcapstone/coordination.py +6 -2
  44. package/src/skcapstone/daemon.py +51 -42
  45. package/src/skcapstone/dashboard.py +8 -8
  46. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +42 -0
  47. package/src/skcapstone/doctor.py +5 -2
  48. package/src/skcapstone/dreaming.py +1440 -0
  49. package/src/skcapstone/emotion_tracker.py +2 -2
  50. package/src/skcapstone/export.py +2 -2
  51. package/src/skcapstone/fuse_mount.py +21 -13
  52. package/src/skcapstone/heartbeat.py +33 -29
  53. package/src/skcapstone/itil.py +1104 -0
  54. package/src/skcapstone/launchd.py +426 -0
  55. package/src/skcapstone/mcp_server.py +306 -4
  56. package/src/skcapstone/mcp_tools/__init__.py +4 -0
  57. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  58. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  59. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  60. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  61. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  62. package/src/skcapstone/mcp_tools/did_tools.py +9 -6
  63. package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
  64. package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
  65. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  66. package/src/skcapstone/mcp_tools/soul_tools.py +6 -2
  67. package/src/skcapstone/mdns_discovery.py +2 -2
  68. package/src/skcapstone/metrics.py +8 -8
  69. package/src/skcapstone/migrate_memories.py +2 -2
  70. package/src/skcapstone/models.py +14 -0
  71. package/src/skcapstone/onboard.py +137 -14
  72. package/src/skcapstone/peer_directory.py +2 -2
  73. package/src/skcapstone/providers/docker.py +2 -2
  74. package/src/skcapstone/scheduled_tasks.py +107 -0
  75. package/src/skcapstone/service_health.py +83 -4
  76. package/src/skcapstone/sync_watcher.py +2 -2
  77. package/src/skcapstone/systemd.py +17 -0
@@ -0,0 +1,434 @@
1
+ """ITIL service management CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+ from ._common import AGENT_HOME, SHARED_ROOT, console
12
+
13
+
14
+ def register_itil_commands(main: click.Group) -> None:
15
+ """Register the itil command group."""
16
+
17
+ @main.group()
18
+ def itil():
19
+ """ITIL service management — incidents, problems, changes."""
20
+
21
+ # ── itil status ───────────────────────────────────────────────────
22
+
23
+ @itil.command("status")
24
+ def itil_status():
25
+ """Show ITIL dashboard: open incidents, active problems, pending changes."""
26
+ from ..itil import ITILManager
27
+
28
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
29
+ status = mgr.get_status()
30
+
31
+ inc = status["incidents"]
32
+ prb = status["problems"]
33
+ chg = status["changes"]
34
+ kedb = status["kedb"]
35
+
36
+ console.print(f"\n[bold]ITIL Dashboard[/bold]")
37
+ console.print(f" Incidents: [red]{inc['open']}[/red] open / {inc['total']} total")
38
+ for sev, count in inc.get("by_severity", {}).items():
39
+ if count:
40
+ console.print(f" {sev}: {count}")
41
+ console.print(f" Problems: [yellow]{prb['active']}[/yellow] active / {prb['total']} total")
42
+ console.print(f" Changes: [blue]{chg['pending']}[/blue] pending / {chg['total']} total")
43
+ console.print(f" KEDB: {kedb['total']} entries")
44
+
45
+ if inc["open_list"]:
46
+ console.print(f"\n[bold red]Open Incidents:[/bold red]")
47
+ for i in inc["open_list"]:
48
+ console.print(
49
+ f" [{i['id']}] {i['severity'].upper()} {i['title']} "
50
+ f"({i['status']}) @{i['managed_by']}"
51
+ )
52
+
53
+ if chg["pending_list"]:
54
+ console.print(f"\n[bold blue]Pending Changes:[/bold blue]")
55
+ for c in chg["pending_list"]:
56
+ console.print(
57
+ f" [{c['id']}] {c['title']} ({c['status']}, "
58
+ f"{c['change_type']}) @{c['managed_by']}"
59
+ )
60
+
61
+ console.print()
62
+
63
+ # ── itil incident ─────────────────────────────────────────────────
64
+
65
+ @itil.group()
66
+ def incident():
67
+ """Incident management."""
68
+
69
+ @incident.command("create")
70
+ @click.option("--title", "-t", required=True, help="Incident title")
71
+ @click.option(
72
+ "--severity", "-s", default="sev3",
73
+ type=click.Choice(["sev1", "sev2", "sev3", "sev4"]),
74
+ help="Severity level",
75
+ )
76
+ @click.option("--service", multiple=True, help="Affected service(s)")
77
+ @click.option("--impact", default="", help="Business impact")
78
+ @click.option("--by", "managed_by", default="human", help="Managing agent")
79
+ @click.option("--tag", multiple=True, help="Tags")
80
+ def incident_create(title, severity, service, impact, managed_by, tag):
81
+ """Create a new incident."""
82
+ from ..itil import ITILManager
83
+
84
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
85
+ inc = mgr.create_incident(
86
+ title=title,
87
+ severity=severity,
88
+ affected_services=list(service),
89
+ impact=impact,
90
+ managed_by=managed_by,
91
+ created_by=managed_by,
92
+ tags=list(tag),
93
+ )
94
+ console.print(
95
+ f"\n [green]Created:[/green] {inc.id} — {inc.title} "
96
+ f"({inc.severity.value}, {inc.status.value})"
97
+ )
98
+ if inc.gtd_item_ids:
99
+ console.print(f" [dim]GTD item(s): {', '.join(inc.gtd_item_ids)}[/dim]")
100
+ console.print()
101
+
102
+ @incident.command("list")
103
+ @click.option(
104
+ "--status", type=click.Choice([
105
+ "detected", "acknowledged", "investigating",
106
+ "escalated", "resolved", "closed",
107
+ ]),
108
+ help="Filter by status",
109
+ )
110
+ @click.option(
111
+ "--severity",
112
+ type=click.Choice(["sev1", "sev2", "sev3", "sev4"]),
113
+ help="Filter by severity",
114
+ )
115
+ @click.option("--service", help="Filter by affected service")
116
+ def incident_list(status, severity, service):
117
+ """List incidents."""
118
+ from ..itil import ITILManager
119
+
120
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
121
+ incidents = mgr.list_incidents(status=status, severity=severity, service=service)
122
+
123
+ if not incidents:
124
+ console.print("\n [dim]No incidents found[/dim]\n")
125
+ return
126
+
127
+ console.print(f"\n[bold]Incidents ({len(incidents)}):[/bold]")
128
+ for i in incidents:
129
+ sev = i.severity.value.upper()
130
+ console.print(
131
+ f" [{i.id}] {sev} {i.title} ({i.status.value}) @{i.managed_by}"
132
+ )
133
+ console.print()
134
+
135
+ @incident.command("update")
136
+ @click.argument("incident_id")
137
+ @click.option("--agent", default="human", help="Agent making the update")
138
+ @click.option(
139
+ "--status", "new_status",
140
+ type=click.Choice([
141
+ "acknowledged", "investigating", "escalated", "resolved", "closed",
142
+ ]),
143
+ help="New status",
144
+ )
145
+ @click.option(
146
+ "--severity",
147
+ type=click.Choice(["sev1", "sev2", "sev3", "sev4"]),
148
+ help="New severity",
149
+ )
150
+ @click.option("--note", default="", help="Timeline note")
151
+ @click.option("--resolution", default=None, help="Resolution summary")
152
+ def incident_update(incident_id, agent, new_status, severity, note, resolution):
153
+ """Update an incident status or metadata."""
154
+ from ..itil import ITILManager
155
+
156
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
157
+ try:
158
+ inc = mgr.update_incident(
159
+ incident_id=incident_id,
160
+ agent=agent,
161
+ new_status=new_status,
162
+ severity=severity,
163
+ note=note,
164
+ resolution_summary=resolution,
165
+ )
166
+ console.print(
167
+ f"\n [green]Updated:[/green] {inc.id} -> {inc.status.value} "
168
+ f"({inc.severity.value})\n"
169
+ )
170
+ except ValueError as exc:
171
+ console.print(f"\n [red]Error:[/red] {exc}\n")
172
+
173
+ # ── itil problem ──────────────────────────────────────────────────
174
+
175
+ @itil.group()
176
+ def problem():
177
+ """Problem management."""
178
+
179
+ @problem.command("create")
180
+ @click.option("--title", "-t", required=True, help="Problem title")
181
+ @click.option("--by", "managed_by", default="human", help="Managing agent")
182
+ @click.option("--incident", "incident_ids", multiple=True, help="Related incident ID(s)")
183
+ @click.option("--workaround", default="", help="Known workaround")
184
+ @click.option("--tag", multiple=True, help="Tags")
185
+ def problem_create(title, managed_by, incident_ids, workaround, tag):
186
+ """Create a new problem record."""
187
+ from ..itil import ITILManager
188
+
189
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
190
+ prb = mgr.create_problem(
191
+ title=title,
192
+ managed_by=managed_by,
193
+ created_by=managed_by,
194
+ related_incident_ids=list(incident_ids),
195
+ workaround=workaround,
196
+ tags=list(tag),
197
+ )
198
+ console.print(
199
+ f"\n [green]Created:[/green] {prb.id} — {prb.title} ({prb.status.value})\n"
200
+ )
201
+
202
+ @problem.command("list")
203
+ @click.option(
204
+ "--status",
205
+ type=click.Choice(["identified", "analyzing", "known_error", "resolved"]),
206
+ help="Filter by status",
207
+ )
208
+ def problem_list(status):
209
+ """List problems."""
210
+ from ..itil import ITILManager
211
+
212
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
213
+ problems = mgr.list_problems(status=status)
214
+
215
+ if not problems:
216
+ console.print("\n [dim]No problems found[/dim]\n")
217
+ return
218
+
219
+ console.print(f"\n[bold]Problems ({len(problems)}):[/bold]")
220
+ for p in problems:
221
+ console.print(
222
+ f" [{p.id}] {p.title} ({p.status.value}) @{p.managed_by}"
223
+ )
224
+ console.print()
225
+
226
+ @problem.command("update")
227
+ @click.argument("problem_id")
228
+ @click.option("--agent", default="human", help="Agent making the update")
229
+ @click.option(
230
+ "--status", "new_status",
231
+ type=click.Choice(["analyzing", "known_error", "resolved"]),
232
+ help="New status",
233
+ )
234
+ @click.option("--root-cause", default=None, help="Root cause description")
235
+ @click.option("--workaround", default=None, help="Workaround")
236
+ @click.option("--note", default="", help="Timeline note")
237
+ @click.option("--create-kedb", is_flag=True, help="Create KEDB entry")
238
+ def problem_update(problem_id, agent, new_status, root_cause, workaround, note, create_kedb):
239
+ """Update a problem record."""
240
+ from ..itil import ITILManager
241
+
242
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
243
+ try:
244
+ prb = mgr.update_problem(
245
+ problem_id=problem_id,
246
+ agent=agent,
247
+ new_status=new_status,
248
+ root_cause=root_cause,
249
+ workaround=workaround,
250
+ note=note,
251
+ create_kedb=create_kedb,
252
+ )
253
+ console.print(
254
+ f"\n [green]Updated:[/green] {prb.id} -> {prb.status.value}\n"
255
+ )
256
+ if prb.kedb_id:
257
+ console.print(f" [dim]KEDB entry: {prb.kedb_id}[/dim]\n")
258
+ except ValueError as exc:
259
+ console.print(f"\n [red]Error:[/red] {exc}\n")
260
+
261
+ # ── itil change ───────────────────────────────────────────────────
262
+
263
+ @itil.group()
264
+ def change():
265
+ """Change management (RFC)."""
266
+
267
+ @change.command("propose")
268
+ @click.option("--title", "-t", required=True, help="Change title")
269
+ @click.option(
270
+ "--type", "change_type", default="normal",
271
+ type=click.Choice(["standard", "normal", "emergency"]),
272
+ help="Change type",
273
+ )
274
+ @click.option(
275
+ "--risk", default="medium",
276
+ type=click.Choice(["low", "medium", "high"]),
277
+ help="Risk level",
278
+ )
279
+ @click.option("--rollback", default="", help="Rollback plan")
280
+ @click.option("--test-plan", default="", help="Test plan")
281
+ @click.option("--by", "managed_by", default="human", help="Managing agent")
282
+ @click.option("--implementer", default=None, help="Implementing agent")
283
+ @click.option("--problem", "related_problem_id", default=None, help="Related problem ID")
284
+ @click.option("--tag", multiple=True, help="Tags")
285
+ def change_propose(title, change_type, risk, rollback, test_plan,
286
+ managed_by, implementer, related_problem_id, tag):
287
+ """Propose a new change (RFC)."""
288
+ from ..itil import ITILManager
289
+
290
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
291
+ chg = mgr.propose_change(
292
+ title=title,
293
+ change_type=change_type,
294
+ risk=risk,
295
+ rollback_plan=rollback,
296
+ test_plan=test_plan,
297
+ managed_by=managed_by,
298
+ created_by=managed_by,
299
+ implementer=implementer,
300
+ related_problem_id=related_problem_id,
301
+ tags=list(tag),
302
+ )
303
+ console.print(
304
+ f"\n [green]Proposed:[/green] {chg.id} — {chg.title} "
305
+ f"({chg.change_type.value}, {chg.status.value})\n"
306
+ )
307
+
308
+ @change.command("list")
309
+ @click.option(
310
+ "--status",
311
+ type=click.Choice([
312
+ "proposed", "reviewing", "approved", "rejected",
313
+ "implementing", "deployed", "verified", "failed", "closed",
314
+ ]),
315
+ help="Filter by status",
316
+ )
317
+ def change_list(status):
318
+ """List changes."""
319
+ from ..itil import ITILManager
320
+
321
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
322
+ changes = mgr.list_changes(status=status)
323
+
324
+ if not changes:
325
+ console.print("\n [dim]No changes found[/dim]\n")
326
+ return
327
+
328
+ console.print(f"\n[bold]Changes ({len(changes)}):[/bold]")
329
+ for c in changes:
330
+ console.print(
331
+ f" [{c.id}] {c.title} ({c.status.value}, "
332
+ f"{c.change_type.value}) @{c.managed_by}"
333
+ )
334
+ console.print()
335
+
336
+ @change.command("update")
337
+ @click.argument("change_id")
338
+ @click.option("--agent", default="human", help="Agent making the update")
339
+ @click.option(
340
+ "--status", "new_status",
341
+ type=click.Choice([
342
+ "reviewing", "approved", "rejected", "implementing",
343
+ "deployed", "verified", "failed", "closed",
344
+ ]),
345
+ help="New status",
346
+ )
347
+ @click.option("--note", default="", help="Timeline note")
348
+ def change_update(change_id, agent, new_status, note):
349
+ """Update a change status."""
350
+ from ..itil import ITILManager
351
+
352
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
353
+ try:
354
+ chg = mgr.update_change(
355
+ change_id=change_id,
356
+ agent=agent,
357
+ new_status=new_status,
358
+ note=note,
359
+ )
360
+ console.print(
361
+ f"\n [green]Updated:[/green] {chg.id} -> {chg.status.value}\n"
362
+ )
363
+ except ValueError as exc:
364
+ console.print(f"\n [red]Error:[/red] {exc}\n")
365
+
366
+ # ── itil cab ──────────────────────────────────────────────────────
367
+
368
+ @itil.group()
369
+ def cab():
370
+ """Change Advisory Board voting."""
371
+
372
+ @cab.command("vote")
373
+ @click.argument("change_id")
374
+ @click.option("--agent", default="human", help="Voting agent")
375
+ @click.option(
376
+ "--decision", default="approved",
377
+ type=click.Choice(["approved", "rejected", "abstain"]),
378
+ help="Vote decision",
379
+ )
380
+ @click.option("--conditions", default="", help="Approval conditions")
381
+ def cab_vote(change_id, agent, decision, conditions):
382
+ """Submit a CAB vote for a change."""
383
+ from ..itil import ITILManager
384
+
385
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
386
+ vote = mgr.submit_cab_vote(
387
+ change_id=change_id,
388
+ agent=agent,
389
+ decision=decision,
390
+ conditions=conditions,
391
+ )
392
+ console.print(
393
+ f"\n [green]Voted:[/green] {vote.agent} -> {vote.decision.value} "
394
+ f"on {vote.change_id}\n"
395
+ )
396
+
397
+ # ── itil kedb ─────────────────────────────────────────────────────
398
+
399
+ @itil.group()
400
+ def kedb():
401
+ """Known Error Database."""
402
+
403
+ @kedb.command("search")
404
+ @click.argument("query")
405
+ def kedb_search(query):
406
+ """Search the Known Error Database."""
407
+ from ..itil import ITILManager
408
+
409
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
410
+ results = mgr.search_kedb(query)
411
+
412
+ if not results:
413
+ console.print(f"\n [dim]No KEDB entries matching '{query}'[/dim]\n")
414
+ return
415
+
416
+ console.print(f"\n[bold]KEDB Results ({len(results)}):[/bold]")
417
+ for e in results:
418
+ console.print(f" [{e.id}] {e.title}")
419
+ if e.workaround:
420
+ console.print(f" [dim]Workaround: {e.workaround[:100]}[/dim]")
421
+ if e.root_cause:
422
+ console.print(f" [dim]Root cause: {e.root_cause[:100]}[/dim]")
423
+ console.print()
424
+
425
+ # ── itil board ────────────────────────────────────────────────────
426
+
427
+ @itil.command("board")
428
+ def itil_board():
429
+ """Generate ITIL-BOARD.md overview."""
430
+ from ..itil import ITILManager
431
+
432
+ mgr = ITILManager(Path(SHARED_ROOT).expanduser())
433
+ path = mgr.write_board_md()
434
+ console.print(f"\n [green]Generated:[/green] {path}\n")
@@ -463,8 +463,8 @@ def register_memory_commands(main: click.Group) -> None:
463
463
  try:
464
464
  from ..memory_adapter import get_unified, entry_to_memory
465
465
  unified = get_unified()
466
- except Exception:
467
- pass
466
+ except Exception as exc:
467
+ logger.warning("Memory adapter unavailable, falling back to file-only mode: %s", exc)
468
468
 
469
469
  for layer in MemoryLayer:
470
470
  layer_dir = mem_dir / layer.value
@@ -481,8 +481,8 @@ def register_memory_commands(main: click.Group) -> None:
481
481
  if existing:
482
482
  skipped += 1
483
483
  continue
484
- except Exception:
485
- pass
484
+ except Exception as exc:
485
+ logger.debug("Failed to check existing memory %s: %s", mem_id, exc)
486
486
 
487
487
  if unified:
488
488
  from ..models import MemoryEntry
@@ -114,8 +114,8 @@ def register_skills_commands(main: click.Group) -> None:
114
114
  try:
115
115
  skill_entries = client.search(query) if query else client.list_skills()
116
116
  source = "remote"
117
- except Exception:
118
- pass
117
+ except Exception as exc:
118
+ logger.warning("Registry client query failed, falling back: %s", exc)
119
119
 
120
120
  # 2. Try GitHub raw catalog (always fresh, no server needed)
121
121
  if skill_entries is None and not offline:
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import logging
6
7
  import shutil
7
8
  import sys
8
9
  from datetime import datetime, timezone
@@ -18,6 +19,8 @@ from .. import SKCAPSTONE_AGENT
18
19
  from rich.panel import Panel
19
20
  from rich.table import Table
20
21
 
22
+ logger = logging.getLogger(__name__)
23
+
21
24
  # Path to the soul-blueprints repository (community blueprints)
22
25
  _BLUEPRINTS_REPO = Path.home() / "clawd" / "soul-blueprints" / "blueprints"
23
26
 
@@ -332,8 +335,8 @@ def register_soul_commands(main: click.Group) -> None:
332
335
  import json
333
336
  base_data = json.loads(base_path.read_text(encoding="utf-8"))
334
337
  vibe = base_data.get("vibe", "")
335
- except Exception:
336
- pass
338
+ except Exception as exc:
339
+ logger.warning("Failed to read vibe from soul base.json: %s", exc)
337
340
 
338
341
  lines = [
339
342
  f"Agent: [bold magenta]{agent}[/]",
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import logging
6
7
  import os
7
8
  import shutil
8
9
  import sys
@@ -19,6 +20,8 @@ from ..runtime import get_runtime
19
20
  from rich.panel import Panel
20
21
  from rich.table import Table
21
22
 
23
+ logger = logging.getLogger(__name__)
24
+
22
25
 
23
26
  def _probe_llm_backends() -> dict[str, bool]:
24
27
  """Probe LLM backend availability.
@@ -43,8 +46,8 @@ def _probe_llm_backends() -> dict[str, bool]:
43
46
  urllib.request.Request(f"{host}/api/tags"), timeout=2
44
47
  ):
45
48
  backends["ollama"] = True
46
- except Exception:
47
- pass
49
+ except Exception as exc:
50
+ logger.debug("Ollama probe failed (not available): %s", exc)
48
51
  return backends
49
52
 
50
53
 
@@ -129,8 +132,8 @@ def _read_local_heartbeat(home: Path) -> Optional[dict]:
129
132
  hb_path = home / "heartbeats" / f"{agent_name}.json"
130
133
  if hb_path.exists():
131
134
  return json.loads(hb_path.read_text())
132
- except Exception:
133
- pass
135
+ except Exception as exc:
136
+ logger.warning("Failed to load heartbeat data: %s", exc)
134
137
  return None
135
138
 
136
139
 
@@ -332,8 +335,8 @@ def register_status_commands(main: click.Group) -> None:
332
335
  f"\n [bold yellow]WARNING:[/] [yellow]Low disk space: "
333
336
  f"{free_gb:.1f} GB free[/]"
334
337
  )
335
- except Exception:
336
- pass
338
+ except Exception as exc:
339
+ logger.debug("Failed to check disk space: %s", exc)
337
340
 
338
341
  console.print()
339
342
  console.print(f" [dim]Home: {m.home}[/]")
@@ -718,8 +721,8 @@ def register_status_commands(main: click.Group) -> None:
718
721
  import webbrowser
719
722
  try:
720
723
  webbrowser.open(url)
721
- except Exception:
722
- pass
724
+ except Exception as exc:
725
+ logger.warning("Failed to open browser for dashboard: %s", exc)
723
726
 
724
727
  server = start_dashboard(home_path, port=port)
725
728
  try:
@@ -11,6 +11,7 @@ Commands:
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ import logging
14
15
  import subprocess
15
16
  import sys
16
17
  from pathlib import Path
@@ -18,6 +19,8 @@ from typing import Optional
18
19
 
19
20
  import click
20
21
 
22
+ logger = logging.getLogger(__name__)
23
+
21
24
  from ._common import AGENT_HOME, console
22
25
 
23
26
  # ── Package definitions ───────────────────────────────────────────────────────
@@ -62,8 +65,8 @@ def _get_installed_version(package: str) -> Optional[str]:
62
65
  for line in result.stdout.splitlines():
63
66
  if line.startswith("Version:"):
64
67
  return line.split(":", 1)[1].strip()
65
- except Exception:
66
- pass
68
+ except Exception as exc:
69
+ logger.debug("Failed to get installed version for %s: %s", package, exc)
67
70
  return None
68
71
 
69
72
 
@@ -91,8 +94,8 @@ def _get_latest_version(package: str) -> Optional[str]:
91
94
  versions = [v.strip() for v in parts[-1].split(",")]
92
95
  if versions:
93
96
  return versions[0]
94
- except Exception:
95
- pass
97
+ except Exception as exc:
98
+ logger.debug("Failed to get latest PyPI version for %s: %s", package, exc)
96
99
  return None
97
100
 
98
101
 
@@ -14,6 +14,7 @@ Usage:
14
14
  from __future__ import annotations
15
15
 
16
16
  import json
17
+ import logging
17
18
  import time
18
19
  import urllib.request
19
20
  from datetime import datetime, timezone
@@ -29,6 +30,8 @@ from rich.text import Text
29
30
 
30
31
  from ._common import AGENT_HOME, console
31
32
 
33
+ logger = logging.getLogger(__name__)
34
+
32
35
 
33
36
  def _fetch_consciousness(port: int = 7777) -> dict:
34
37
  """Fetch consciousness loop status from the running daemon.
@@ -87,8 +90,8 @@ def _build_renderable(home: Path, daemon_port: int = 7777) -> Group:
87
90
  memory_short = m.memory.short_term
88
91
  memory_mid = m.memory.mid_term
89
92
  memory_long = m.memory.long_term
90
- except Exception:
91
- pass
93
+ except Exception as exc:
94
+ logger.warning("Failed to read runtime manifest for watch display: %s", exc)
92
95
 
93
96
  # ── Consciousness data ────────────────────────────────────────────────
94
97
  cdata = _fetch_consciousness(daemon_port)
@@ -99,8 +102,8 @@ def _build_renderable(home: Path, daemon_port: int = 7777) -> Group:
99
102
  from ..memory_engine import list_memories
100
103
 
101
104
  recent_memories = list_memories(home, limit=5)
102
- except Exception:
103
- pass
105
+ except Exception as exc:
106
+ logger.warning("Failed to load recent memories for watch display: %s", exc)
104
107
 
105
108
  # ── Coordination board ────────────────────────────────────────────────
106
109
  board_tasks: list = []
@@ -118,8 +121,8 @@ def _build_renderable(home: Path, daemon_port: int = 7777) -> Group:
118
121
  board_tasks = [
119
122
  v for v in views if v.status.value in ("open", "in_progress")
120
123
  ][:8]
121
- except Exception:
122
- pass
124
+ except Exception as exc:
125
+ logger.warning("Failed to load coordination board for watch display: %s", exc)
123
126
 
124
127
  # ── Header ────────────────────────────────────────────────────────────
125
128
  con_color = {
@@ -16,6 +16,7 @@ Usage:
16
16
  from __future__ import annotations
17
17
 
18
18
  import json
19
+ import logging
19
20
  import re
20
21
  from dataclasses import dataclass, field
21
22
  from pathlib import Path
@@ -23,6 +24,8 @@ from typing import Any, Optional
23
24
 
24
25
  import yaml
25
26
 
27
+ logger = logging.getLogger(__name__)
28
+
26
29
 
27
30
  # ---------------------------------------------------------------------------
28
31
  # Result models
@@ -113,8 +116,8 @@ def _yaml_key_line(text: str, key: str) -> Optional[int]:
113
116
  for key_node, _ in node.value:
114
117
  if isinstance(key_node, yaml.ScalarNode) and key_node.value == key:
115
118
  return key_node.start_mark.line + 1
116
- except Exception:
117
- pass
119
+ except Exception as exc:
120
+ logger.debug("Failed to locate YAML key '%s' for line number: %s", key, exc)
118
121
  return None
119
122
 
120
123
 
@@ -147,8 +150,8 @@ def _yaml_seq_item_line(text: str, seq_key: str, idx: int, subkey: str) -> Optio
147
150
  if kk.value == subkey:
148
151
  return kk.start_mark.line + 1
149
152
  return item.start_mark.line + 1
150
- except Exception:
151
- pass
153
+ except Exception as exc:
154
+ logger.debug("Failed to locate YAML seq item line for '%s[%d].%s': %s", seq_key, idx, subkey, exc)
152
155
  return None
153
156
 
154
157