@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.
- package/.github/workflows/publish.yml +8 -1
- package/docs/CUSTOM_AGENT.md +184 -0
- package/docs/GETTING_STARTED.md +3 -0
- package/launchd/com.skcapstone.daemon.plist +52 -0
- package/launchd/com.skcapstone.memory-compress.plist +45 -0
- package/launchd/com.skcapstone.skcomm-heartbeat.plist +33 -0
- package/launchd/com.skcapstone.skcomm-queue-drain.plist +34 -0
- package/launchd/install-launchd.sh +156 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/archive-sessions.sh +88 -0
- package/scripts/install.sh +39 -8
- package/scripts/notion-api.py +259 -0
- package/scripts/nvidia-proxy.mjs +878 -0
- package/scripts/proxy-monitor.sh +89 -0
- package/scripts/refresh-anthropic-token.sh +94 -0
- package/scripts/skgateway.mjs +856 -0
- package/scripts/telegram-catchup-all.sh +136 -0
- package/scripts/watch-anthropic-token.sh +117 -0
- package/src/skcapstone/__init__.py +1 -1
- package/src/skcapstone/_cli_monolith.py +4 -4
- package/src/skcapstone/api.py +36 -35
- package/src/skcapstone/auction.py +8 -8
- package/src/skcapstone/blueprint_registry.py +2 -2
- package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/chat.py +4 -4
- package/src/skcapstone/cli/__init__.py +2 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/chat.py +5 -2
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/daemon.py +116 -41
- package/src/skcapstone/cli/itil.py +434 -0
- package/src/skcapstone/cli/memory.py +4 -4
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +5 -2
- package/src/skcapstone/cli/status.py +11 -8
- package/src/skcapstone/cli/upgrade_cmd.py +7 -4
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +27 -0
- package/src/skcapstone/consciousness_loop.py +20 -18
- package/src/skcapstone/coordination.py +6 -2
- package/src/skcapstone/daemon.py +51 -42
- package/src/skcapstone/dashboard.py +8 -8
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +42 -0
- package/src/skcapstone/doctor.py +5 -2
- package/src/skcapstone/dreaming.py +1440 -0
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +2 -2
- package/src/skcapstone/fuse_mount.py +21 -13
- package/src/skcapstone/heartbeat.py +33 -29
- package/src/skcapstone/itil.py +1104 -0
- package/src/skcapstone/launchd.py +426 -0
- package/src/skcapstone/mcp_server.py +306 -4
- package/src/skcapstone/mcp_tools/__init__.py +4 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +9 -6
- package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
- package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/soul_tools.py +6 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/metrics.py +8 -8
- package/src/skcapstone/migrate_memories.py +2 -2
- package/src/skcapstone/models.py +14 -0
- package/src/skcapstone/onboard.py +137 -14
- package/src/skcapstone/peer_directory.py +2 -2
- package/src/skcapstone/providers/docker.py +2 -2
- package/src/skcapstone/scheduled_tasks.py +107 -0
- package/src/skcapstone/service_health.py +83 -4
- package/src/skcapstone/sync_watcher.py +2 -2
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|