agent-devkit 0.0.4 → 0.1.5
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/README.md +42 -2
- package/package.json +1 -1
- package/runtime/README.md +50 -2
- package/runtime/agent +29 -13
- package/runtime/agents/README.md +3 -0
- package/runtime/agents/github-pr-reviewer/AGENTS.md +10 -0
- package/runtime/agents/github-pr-reviewer/README.md +7 -0
- package/runtime/agents/github-pr-reviewer/agent.yaml +33 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/decision-rules.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/workflow.md +9 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/decision-rules.md +7 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/workflow.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/decision-rules.md +7 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/workflow.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/decision-rules.md +9 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/workflow.md +10 -0
- package/runtime/agents/github-pr-reviewer/infra/README.md +7 -0
- package/runtime/agents/github-pr-reviewer/knowledge/context.md +7 -0
- package/runtime/agents/github-pr-reviewer/knowledge/system.md +17 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-automation-output.md +10 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-inspection-output.md +9 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-list-output.md +9 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-review-output.md +17 -0
- package/runtime/cli/README.md +37 -1
- package/runtime/cli/aikit/__init__.py +1 -1
- package/runtime/cli/aikit/aliases.py +196 -0
- package/runtime/cli/aikit/app_home.py +78 -0
- package/runtime/cli/aikit/audit.py +344 -0
- package/runtime/cli/aikit/calendar.py +163 -0
- package/runtime/cli/aikit/configuration_orchestrator.py +291 -0
- package/runtime/cli/aikit/decision_store.py +158 -0
- package/runtime/cli/aikit/diagnostics.py +9 -0
- package/runtime/cli/aikit/fallback.py +48 -2
- package/runtime/cli/aikit/github_pr.py +254 -0
- package/runtime/cli/aikit/identity.py +75 -0
- package/runtime/cli/aikit/llm.py +249 -48
- package/runtime/cli/aikit/main.py +1240 -32
- package/runtime/cli/aikit/memory.py +148 -8
- package/runtime/cli/aikit/model_router.py +70 -0
- package/runtime/cli/aikit/notifications.py +9 -0
- package/runtime/cli/aikit/ollama.py +237 -0
- package/runtime/cli/aikit/permissions.py +345 -0
- package/runtime/cli/aikit/personality.py +123 -0
- package/runtime/cli/aikit/provider_wizard.py +19 -0
- package/runtime/cli/aikit/providers.py +4 -5
- package/runtime/cli/aikit/review_gate.py +40 -0
- package/runtime/cli/aikit/scheduler.py +18 -0
- package/runtime/cli/aikit/sessions.py +396 -0
- package/runtime/cli/aikit/setup_wizard.py +12 -0
- package/runtime/cli/aikit/tasks.py +351 -0
- package/runtime/cli/aikit/toolchain.py +274 -0
- package/runtime/providers/calendar.yaml +28 -0
- package/runtime/providers/github.yaml +42 -0
- package/runtime/providers/local-notification.yaml +19 -0
- package/runtime/providers/local-scheduler.yaml +24 -0
- package/runtime/vendor/skills/napkin/napkin.md +13 -3
|
@@ -12,19 +12,54 @@ import sys
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
|
+
from cli.aikit.aliases import add_alias, list_aliases, remove_alias, sync_aliases
|
|
15
16
|
from cli.aikit import __version__
|
|
17
|
+
from cli.aikit.audit import export_audit, list_audits, record_audit, show_audit
|
|
18
|
+
from cli.aikit.calendar import calendar_list, calendar_summary, calendar_today, calendar_tomorrow, configure_calendar
|
|
19
|
+
from cli.aikit.configuration_orchestrator import provider_wizard_from_requirement
|
|
20
|
+
from cli.aikit.decision_store import list_decisions, reset_decisions, set_decision
|
|
16
21
|
from cli.aikit.diagnostics import build_diagnostics
|
|
17
22
|
from cli.aikit.fallback import evaluate_provider_requirements
|
|
23
|
+
from cli.aikit.github_pr import pr_create_automation, pr_inspect, pr_list_review_requests, pr_review
|
|
18
24
|
from cli.aikit.guardrails import evaluate_execution_guardrails
|
|
25
|
+
from cli.aikit.identity import enforce_identity_response, is_identity_question, local_identity_response, public_name
|
|
19
26
|
from cli.aikit.llm import (
|
|
20
27
|
configure_backend,
|
|
21
28
|
doctor_backends,
|
|
22
29
|
invoke_agent_prompt,
|
|
23
30
|
list_backends,
|
|
24
|
-
|
|
31
|
+
llm_preference,
|
|
25
32
|
set_default_backend,
|
|
33
|
+
set_llm_preference,
|
|
26
34
|
)
|
|
27
|
-
from cli.aikit.memory import record_usage, reset_memory, show_memory
|
|
35
|
+
from cli.aikit.memory import memory_path_payload, napkin_context, record_usage, reset_memory, show_memory
|
|
36
|
+
from cli.aikit.model_router import build_model_plan
|
|
37
|
+
from cli.aikit.ollama import ollama_models, ollama_pull, ollama_status, ollama_update
|
|
38
|
+
from cli.aikit.personality import load_personality, reset_personality, setup_personality, update_personality
|
|
39
|
+
from cli.aikit.permissions import grant_permission, revoke_permission, show_permissions
|
|
40
|
+
from cli.aikit.provider_wizard import missing_source_wizard
|
|
41
|
+
from cli.aikit.review_gate import build_review_gate, mark_reviewed
|
|
42
|
+
from cli.aikit.sessions import (
|
|
43
|
+
build_contextual_prompt,
|
|
44
|
+
get_or_create_session,
|
|
45
|
+
list_sessions,
|
|
46
|
+
record_exchange,
|
|
47
|
+
resume_session,
|
|
48
|
+
show_session,
|
|
49
|
+
)
|
|
50
|
+
from cli.aikit.setup_wizard import setup_wizard
|
|
51
|
+
from cli.aikit.scheduler import run_scheduler_once, scheduler_daemon_plan
|
|
52
|
+
from cli.aikit.tasks import (
|
|
53
|
+
create_task,
|
|
54
|
+
delete_task,
|
|
55
|
+
list_tasks,
|
|
56
|
+
run_task,
|
|
57
|
+
show_task,
|
|
58
|
+
task_history,
|
|
59
|
+
update_task_schedule,
|
|
60
|
+
update_task_status,
|
|
61
|
+
)
|
|
62
|
+
from cli.aikit.toolchain import doctor_toolchain, install_toolchain, list_toolchain
|
|
28
63
|
from cli.aikit.install import InstallError, install_runtime
|
|
29
64
|
from cli.aikit.lock import lock_status, parse_profiles
|
|
30
65
|
from cli.aikit.output import run_payload
|
|
@@ -69,6 +104,23 @@ DETERMINISTIC_COMMANDS = (
|
|
|
69
104
|
"credential",
|
|
70
105
|
"source",
|
|
71
106
|
"memory",
|
|
107
|
+
"personality",
|
|
108
|
+
"setup",
|
|
109
|
+
"alias",
|
|
110
|
+
"session",
|
|
111
|
+
"toolchain",
|
|
112
|
+
"task",
|
|
113
|
+
"scheduler",
|
|
114
|
+
"calendar",
|
|
115
|
+
"pr",
|
|
116
|
+
"permissions",
|
|
117
|
+
"audit",
|
|
118
|
+
"config",
|
|
119
|
+
"tools",
|
|
120
|
+
"integrations",
|
|
121
|
+
"skills",
|
|
122
|
+
"decisions",
|
|
123
|
+
"ollama",
|
|
72
124
|
"install",
|
|
73
125
|
)
|
|
74
126
|
LLM_COMMANDS = ("agent",)
|
|
@@ -86,11 +138,13 @@ def main(argv: list[str] | None = None, *, prog: str | None = None) -> int:
|
|
|
86
138
|
try:
|
|
87
139
|
result = dispatch(args)
|
|
88
140
|
except DevKitError as exc:
|
|
141
|
+
maybe_record_cli_audit(args, result=None, error=str(exc))
|
|
89
142
|
print(f"error: {exc}", file=sys.stderr)
|
|
90
143
|
return 1
|
|
91
144
|
|
|
92
145
|
if result is None:
|
|
93
146
|
return 0
|
|
147
|
+
maybe_record_cli_audit(args, result=result, error=None)
|
|
94
148
|
|
|
95
149
|
if getattr(args, "json", False):
|
|
96
150
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
@@ -109,7 +163,15 @@ def build_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
109
163
|
description="AI DevKit CLI",
|
|
110
164
|
)
|
|
111
165
|
parser.add_argument("--json", action="store_true", help="print machine-readable JSON")
|
|
166
|
+
parser.add_argument("--dry-run", dest="global_dry_run", action="store_true", help="show execution plan without external write effects")
|
|
112
167
|
parser.add_argument("-v", "--version", action="store_true", help="print CLI version and exit")
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"-s",
|
|
170
|
+
"--sessions",
|
|
171
|
+
dest="sessions_shortcut",
|
|
172
|
+
action="store_true",
|
|
173
|
+
help="list local conversation sessions and exit",
|
|
174
|
+
)
|
|
113
175
|
|
|
114
176
|
subparsers = parser.add_subparsers(dest="command")
|
|
115
177
|
|
|
@@ -119,6 +181,10 @@ def build_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
119
181
|
)
|
|
120
182
|
agent_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
121
183
|
agent_parser.add_argument("--llm", help="LLM backend id to use")
|
|
184
|
+
agent_parser.add_argument("--dry-run", action="store_true", help="show execution plan without invoking LLM or external writes")
|
|
185
|
+
agent_parser.add_argument("--no-llm-fallback", action="store_true", help="disable automatic fallback to secondary LLM backends")
|
|
186
|
+
agent_parser.add_argument("--session", dest="session_id", help="resume a local conversation session")
|
|
187
|
+
agent_parser.add_argument("--new-session", action="store_true", help="start a new local conversation session")
|
|
122
188
|
agent_parser.add_argument("prompt", nargs=argparse.REMAINDER)
|
|
123
189
|
|
|
124
190
|
commands_parser = subparsers.add_parser("commands", help="list CLI command modes")
|
|
@@ -159,10 +225,125 @@ def build_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
159
225
|
|
|
160
226
|
memory_parser = subparsers.add_parser("memory", help="inspect or reset local AI DevKit memory")
|
|
161
227
|
memory_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
162
|
-
memory_parser.add_argument("action", nargs="?", default="show", choices=["show", "reset"])
|
|
228
|
+
memory_parser.add_argument("action", nargs="?", default="show", choices=["show", "path", "reset"])
|
|
163
229
|
memory_parser.add_argument("--agent", dest="agent_id")
|
|
164
230
|
memory_parser.add_argument("--source", dest="source_id")
|
|
165
231
|
memory_parser.add_argument("--all", action="store_true", help="reset all local memory")
|
|
232
|
+
memory_parser.add_argument("--sessions", action="store_true", help="reset local conversation sessions")
|
|
233
|
+
memory_parser.add_argument("--tasks", action="store_true", help="reset local task schedules")
|
|
234
|
+
memory_parser.add_argument("--cache", action="store_true", help="reset local cache")
|
|
235
|
+
|
|
236
|
+
personality_parser = subparsers.add_parser("personality", help="inspect or update local Agent DevKit personality")
|
|
237
|
+
personality_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
238
|
+
personality_parser.add_argument("action", nargs="?", default="show", choices=["show", "edit", "reset"])
|
|
239
|
+
personality_parser.add_argument("--name", dest="agent_name", help="public agent name")
|
|
240
|
+
personality_parser.add_argument("--user-name", help="user name")
|
|
241
|
+
personality_parser.add_argument("--language", help="default response language")
|
|
242
|
+
personality_parser.add_argument("--tone", help="response tone")
|
|
243
|
+
personality_parser.add_argument("--detail-level", help="response detail level")
|
|
244
|
+
|
|
245
|
+
setup_parser = subparsers.add_parser("setup", help="run setup helpers")
|
|
246
|
+
setup_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
247
|
+
setup_parser.add_argument("--dry-run", action="store_true", help="show setup plan without installing external tools")
|
|
248
|
+
setup_parser.add_argument("--yes", action="store_true", help="confirm setup actions")
|
|
249
|
+
setup_parser.add_argument("action", nargs="?", default="plan", choices=["plan", "personality"])
|
|
250
|
+
|
|
251
|
+
alias_parser = subparsers.add_parser("alias", help="manage local command aliases for agent")
|
|
252
|
+
alias_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
253
|
+
alias_parser.add_argument("action", nargs="?", default="list", choices=["add", "list", "remove", "sync"])
|
|
254
|
+
alias_parser.add_argument("name", nargs="?")
|
|
255
|
+
alias_parser.add_argument("--force", action="store_true", help="allow replacing an existing local alias file")
|
|
256
|
+
|
|
257
|
+
session_parser = subparsers.add_parser("session", help="manage local conversation sessions")
|
|
258
|
+
session_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
259
|
+
session_parser.add_argument("action", nargs="?", default="list", choices=["list", "show", "resume"])
|
|
260
|
+
session_parser.add_argument("session_id", nargs="?")
|
|
261
|
+
|
|
262
|
+
toolchain_parser = subparsers.add_parser("toolchain", help="inspect or install external tools")
|
|
263
|
+
toolchain_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
264
|
+
toolchain_parser.add_argument("action", nargs="?", default="list", choices=["list", "doctor", "install"])
|
|
265
|
+
toolchain_parser.add_argument("tool", nargs="?", default="all")
|
|
266
|
+
toolchain_parser.add_argument("--dry-run", action="store_true", help="show install plan without executing it")
|
|
267
|
+
toolchain_parser.add_argument("--yes", action="store_true", help="confirm external tool installation")
|
|
268
|
+
|
|
269
|
+
task_parser = subparsers.add_parser("task", help="manage local scheduled tasks")
|
|
270
|
+
task_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
271
|
+
task_parser.add_argument("action", nargs="?", default="list", choices=["create", "list", "show", "history", "run", "pause", "resume", "update", "enable", "disable", "delete"])
|
|
272
|
+
task_parser.add_argument("task_id", nargs="?")
|
|
273
|
+
task_parser.add_argument("--title")
|
|
274
|
+
task_parser.add_argument("--prompt")
|
|
275
|
+
task_parser.add_argument("--every")
|
|
276
|
+
task_parser.add_argument("--cron")
|
|
277
|
+
task_parser.add_argument("--dry-run", action="store_true")
|
|
278
|
+
task_parser.add_argument("--yes", action="store_true")
|
|
279
|
+
|
|
280
|
+
scheduler_parser = subparsers.add_parser("scheduler", help="run local scheduler")
|
|
281
|
+
scheduler_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
282
|
+
scheduler_parser.add_argument("action", nargs="?", default="run-once", choices=["run-once", "daemon"])
|
|
283
|
+
scheduler_parser.add_argument("--dry-run", action="store_true")
|
|
284
|
+
|
|
285
|
+
calendar_parser = subparsers.add_parser("calendar", help="inspect configured calendar")
|
|
286
|
+
calendar_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
287
|
+
calendar_parser.add_argument("action", nargs="?", default="today", choices=["today", "tomorrow", "list", "configure"])
|
|
288
|
+
calendar_parser.add_argument("--from", dest="date_from")
|
|
289
|
+
calendar_parser.add_argument("--to", dest="date_to")
|
|
290
|
+
calendar_parser.add_argument("--ics")
|
|
291
|
+
calendar_parser.add_argument("--timezone")
|
|
292
|
+
|
|
293
|
+
pr_parser = subparsers.add_parser("pr", help="review GitHub pull requests")
|
|
294
|
+
pr_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
295
|
+
pr_parser.add_argument("action", nargs="?", default="list-review-requests", choices=["list-review-requests", "inspect", "review", "automation"])
|
|
296
|
+
pr_parser.add_argument("pr_ref", nargs="?")
|
|
297
|
+
pr_parser.add_argument("automation_action", nargs="?")
|
|
298
|
+
pr_parser.add_argument("--approve", action="store_true")
|
|
299
|
+
pr_parser.add_argument("--request-changes", action="store_true")
|
|
300
|
+
pr_parser.add_argument("--comment")
|
|
301
|
+
pr_parser.add_argument("--allow-write", action="store_true")
|
|
302
|
+
pr_parser.add_argument("--dry-run", action="store_true")
|
|
303
|
+
pr_parser.add_argument("--time", default="09:00")
|
|
304
|
+
|
|
305
|
+
permissions_parser = subparsers.add_parser("permissions", help="manage local permission policies")
|
|
306
|
+
permissions_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
307
|
+
permissions_parser.add_argument("action", nargs="?", default="show", choices=["show", "grant", "revoke"])
|
|
308
|
+
permissions_parser.add_argument("agent", nargs="?")
|
|
309
|
+
permissions_parser.add_argument("provider", nargs="?")
|
|
310
|
+
permissions_parser.add_argument("level", nargs="?")
|
|
311
|
+
permissions_parser.add_argument("--project")
|
|
312
|
+
permissions_parser.add_argument("--task", dest="task_id")
|
|
313
|
+
|
|
314
|
+
audit_parser = subparsers.add_parser("audit", help="inspect local execution audit trail")
|
|
315
|
+
audit_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
316
|
+
audit_parser.add_argument("action", nargs="?", default="list", choices=["list", "show", "export"])
|
|
317
|
+
audit_parser.add_argument("execution_id", nargs="?")
|
|
318
|
+
audit_parser.add_argument("--limit", type=int, default=20)
|
|
319
|
+
audit_parser.add_argument("--format", default="md", choices=["md", "json"])
|
|
320
|
+
|
|
321
|
+
config_parser = subparsers.add_parser("config", help="inspect local Agent DevKit configuration")
|
|
322
|
+
config_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
323
|
+
config_parser.add_argument("action", nargs="?", default="show", choices=["show", "path"])
|
|
324
|
+
|
|
325
|
+
for command_name, help_text in (
|
|
326
|
+
("tools", "manage enabled local tools"),
|
|
327
|
+
("integrations", "manage provider integration decisions"),
|
|
328
|
+
("skills", "manage local skill decisions"),
|
|
329
|
+
):
|
|
330
|
+
control_parser = subparsers.add_parser(command_name, help=help_text)
|
|
331
|
+
control_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
332
|
+
control_parser.add_argument("action", nargs="?", default="list", choices=["list", "enable", "disable"])
|
|
333
|
+
control_parser.add_argument("item_id", nargs="?")
|
|
334
|
+
|
|
335
|
+
decisions_parser = subparsers.add_parser("decisions", help="inspect or reset local opt-in and opt-out decisions")
|
|
336
|
+
decisions_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
337
|
+
decisions_parser.add_argument("action", nargs="?", default="list", choices=["list", "forget", "reset"])
|
|
338
|
+
decisions_parser.add_argument("item_id", nargs="?")
|
|
339
|
+
decisions_parser.add_argument("--category", choices=["tools", "integrations", "skills", "llms"])
|
|
340
|
+
|
|
341
|
+
ollama_parser = subparsers.add_parser("ollama", help="inspect and manage local Ollama models")
|
|
342
|
+
ollama_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
343
|
+
ollama_parser.add_argument("action", nargs="?", default="status", choices=["status", "models", "pull", "update"])
|
|
344
|
+
ollama_parser.add_argument("model", nargs="?")
|
|
345
|
+
ollama_parser.add_argument("--yes", action="store_true", help="confirm Ollama model or update operation")
|
|
346
|
+
ollama_parser.add_argument("--dry-run", action="store_true", help="show Ollama operation without executing it")
|
|
166
347
|
|
|
167
348
|
llm_parser = subparsers.add_parser("llm", help="manage LLM backends")
|
|
168
349
|
llm_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
@@ -170,14 +351,17 @@ def build_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
170
351
|
"action",
|
|
171
352
|
nargs="?",
|
|
172
353
|
default="list",
|
|
173
|
-
choices=["list", "doctor", "configure", "set-default"],
|
|
354
|
+
choices=["list", "doctor", "configure", "set-default", "default", "enable", "disable", "preference"],
|
|
174
355
|
)
|
|
175
356
|
llm_parser.add_argument("backend", nargs="?")
|
|
357
|
+
llm_parser.add_argument("preference_value", nargs="?")
|
|
176
358
|
llm_parser.add_argument("--api-key-env", help="environment variable that stores the API key")
|
|
177
359
|
llm_parser.add_argument("--base-url", help="OpenAI-compatible base URL")
|
|
178
360
|
llm_parser.add_argument("--model", help="default model id")
|
|
179
361
|
llm_parser.add_argument("--command", dest="host_command", help="host CLI command name or path")
|
|
180
362
|
llm_parser.add_argument("--set-default", action="store_true", help="set backend as the default LLM")
|
|
363
|
+
llm_parser.add_argument("--primary", help="primary backend for LLM preference")
|
|
364
|
+
llm_parser.add_argument("--order", help="comma-separated fallback order")
|
|
181
365
|
|
|
182
366
|
install_parser = subparsers.add_parser("install", help="install AI DevKit host artifacts")
|
|
183
367
|
install_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
|
@@ -237,6 +421,8 @@ def build_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
237
421
|
def dispatch(args: argparse.Namespace) -> dict[str, Any] | None:
|
|
238
422
|
if args.version:
|
|
239
423
|
return {"kind": "version", "program": getattr(args, "prog_name", "aikit"), "version": __version__}
|
|
424
|
+
if args.sessions_shortcut:
|
|
425
|
+
return list_sessions()
|
|
240
426
|
|
|
241
427
|
if not args.command:
|
|
242
428
|
raise DevKitError("missing command. Use --help for usage.")
|
|
@@ -256,6 +442,36 @@ def dispatch(args: argparse.Namespace) -> dict[str, Any] | None:
|
|
|
256
442
|
return dispatch_source(args)
|
|
257
443
|
if command == "memory":
|
|
258
444
|
return dispatch_memory(args)
|
|
445
|
+
if command == "personality":
|
|
446
|
+
return dispatch_personality(args)
|
|
447
|
+
if command == "setup":
|
|
448
|
+
return dispatch_setup(args)
|
|
449
|
+
if command == "alias":
|
|
450
|
+
return dispatch_alias(args)
|
|
451
|
+
if command == "session":
|
|
452
|
+
return dispatch_session(args)
|
|
453
|
+
if command == "toolchain":
|
|
454
|
+
return dispatch_toolchain(args)
|
|
455
|
+
if command == "task":
|
|
456
|
+
return dispatch_task(args)
|
|
457
|
+
if command == "scheduler":
|
|
458
|
+
return dispatch_scheduler(args)
|
|
459
|
+
if command == "calendar":
|
|
460
|
+
return dispatch_calendar(args)
|
|
461
|
+
if command == "pr":
|
|
462
|
+
return dispatch_pr(args)
|
|
463
|
+
if command == "permissions":
|
|
464
|
+
return dispatch_permissions(args)
|
|
465
|
+
if command == "audit":
|
|
466
|
+
return dispatch_audit(args)
|
|
467
|
+
if command == "config":
|
|
468
|
+
return dispatch_config(args)
|
|
469
|
+
if command in {"tools", "integrations", "skills"}:
|
|
470
|
+
return dispatch_control_category(command, args)
|
|
471
|
+
if command == "decisions":
|
|
472
|
+
return dispatch_decisions(args)
|
|
473
|
+
if command == "ollama":
|
|
474
|
+
return dispatch_ollama(args)
|
|
259
475
|
if command == "llm":
|
|
260
476
|
return dispatch_llm(args)
|
|
261
477
|
if command == "install":
|
|
@@ -327,8 +543,22 @@ def list_command_modes() -> dict[str, Any]:
|
|
|
327
543
|
}
|
|
328
544
|
|
|
329
545
|
|
|
546
|
+
def maybe_record_cli_audit(args: argparse.Namespace, *, result: dict[str, Any] | None, error: str | None) -> None:
|
|
547
|
+
command = canonical_command(getattr(args, "command", None) or "")
|
|
548
|
+
if command in {"", "audit"} or getattr(args, "version", False):
|
|
549
|
+
return
|
|
550
|
+
try:
|
|
551
|
+
audit = record_audit(command=command, args=vars(args), result=result, error=error)
|
|
552
|
+
except Exception:
|
|
553
|
+
return
|
|
554
|
+
if result is not None:
|
|
555
|
+
result["audit"] = audit
|
|
556
|
+
|
|
557
|
+
|
|
330
558
|
def dispatch_llm(args: argparse.Namespace) -> dict[str, Any]:
|
|
331
559
|
try:
|
|
560
|
+
if args.action != "preference" and args.preference_value:
|
|
561
|
+
raise DevKitError(f"llm {args.action} received an unexpected argument: {args.preference_value}")
|
|
332
562
|
if args.action == "list":
|
|
333
563
|
if args.backend:
|
|
334
564
|
raise DevKitError("llm list does not accept a backend argument")
|
|
@@ -346,15 +576,112 @@ def dispatch_llm(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
346
576
|
command=args.host_command,
|
|
347
577
|
set_default=args.set_default,
|
|
348
578
|
)
|
|
349
|
-
if args.action
|
|
579
|
+
if args.action in {"set-default", "default"}:
|
|
350
580
|
if not args.backend:
|
|
351
|
-
raise DevKitError("llm
|
|
581
|
+
raise DevKitError(f"llm {args.action} requires a backend")
|
|
352
582
|
return set_default_backend(args.backend)
|
|
583
|
+
if args.action in {"enable", "disable"}:
|
|
584
|
+
if not args.backend:
|
|
585
|
+
raise DevKitError(f"llm {args.action} requires a backend")
|
|
586
|
+
state = "enabled" if args.action == "enable" else "disabled_by_user"
|
|
587
|
+
return set_decision("llms", args.backend, state, reason=f"llm {args.action} command")
|
|
588
|
+
if args.action == "preference":
|
|
589
|
+
if args.backend in {None, "show"}:
|
|
590
|
+
return llm_preference()
|
|
591
|
+
if args.backend == "set":
|
|
592
|
+
if not args.primary and not args.order:
|
|
593
|
+
raise DevKitError("llm preference set requires --primary or --order")
|
|
594
|
+
return set_llm_preference(primary=args.primary, order=args.order)
|
|
595
|
+
if args.backend == "reorder":
|
|
596
|
+
order = args.preference_value or args.order
|
|
597
|
+
if not order:
|
|
598
|
+
raise DevKitError("llm preference reorder requires an order value or --order")
|
|
599
|
+
return set_llm_preference(order=order)
|
|
600
|
+
raise DevKitError("llm preference action must be show, set or reorder")
|
|
353
601
|
except ValueError as exc:
|
|
354
602
|
raise DevKitError(str(exc)) from exc
|
|
355
603
|
raise DevKitError(f"unsupported llm action: {args.action}")
|
|
356
604
|
|
|
357
605
|
|
|
606
|
+
def dispatch_config(args: argparse.Namespace) -> dict[str, Any]:
|
|
607
|
+
from cli.aikit.llm import config_path
|
|
608
|
+
|
|
609
|
+
if args.action == "path":
|
|
610
|
+
return {"kind": "config", "status": "ok", "path": str(config_path())}
|
|
611
|
+
if args.action == "show":
|
|
612
|
+
return {
|
|
613
|
+
"kind": "config",
|
|
614
|
+
"status": "ok",
|
|
615
|
+
"path": str(config_path()),
|
|
616
|
+
"decisions": list_decisions(),
|
|
617
|
+
"llm": llm_preference(),
|
|
618
|
+
"ollama": ollama_status(),
|
|
619
|
+
}
|
|
620
|
+
raise DevKitError(f"unsupported config action: {args.action}")
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def dispatch_control_category(command: str, args: argparse.Namespace) -> dict[str, Any]:
|
|
624
|
+
try:
|
|
625
|
+
category = command
|
|
626
|
+
if args.action == "list":
|
|
627
|
+
if args.item_id:
|
|
628
|
+
raise DevKitError(f"{command} list does not accept an item id")
|
|
629
|
+
payload = list_decisions(category)
|
|
630
|
+
payload["kind"] = command
|
|
631
|
+
return payload
|
|
632
|
+
if args.action in {"enable", "disable"}:
|
|
633
|
+
if not args.item_id:
|
|
634
|
+
raise DevKitError(f"{command} {args.action} requires an item id")
|
|
635
|
+
state = "enabled" if args.action == "enable" else "disabled_by_user"
|
|
636
|
+
payload = set_decision(category, args.item_id, state, reason=f"{command} {args.action} command")
|
|
637
|
+
payload["kind"] = command[:-1] if command.endswith("s") else command
|
|
638
|
+
return payload
|
|
639
|
+
except ValueError as exc:
|
|
640
|
+
raise DevKitError(str(exc)) from exc
|
|
641
|
+
raise DevKitError(f"unsupported {command} action: {args.action}")
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def dispatch_decisions(args: argparse.Namespace) -> dict[str, Any]:
|
|
645
|
+
try:
|
|
646
|
+
if args.action == "list":
|
|
647
|
+
if args.item_id:
|
|
648
|
+
raise DevKitError("decisions list does not accept an item id")
|
|
649
|
+
return list_decisions(args.category)
|
|
650
|
+
if args.action == "reset":
|
|
651
|
+
if args.item_id:
|
|
652
|
+
raise DevKitError("decisions reset does not accept an item id")
|
|
653
|
+
return reset_decisions(args.category)
|
|
654
|
+
if args.action == "forget":
|
|
655
|
+
if not args.item_id:
|
|
656
|
+
raise DevKitError("decisions forget requires an item id")
|
|
657
|
+
category = args.category or "tools"
|
|
658
|
+
return set_decision(category, args.item_id, "available", reason="decision forgotten")
|
|
659
|
+
except ValueError as exc:
|
|
660
|
+
raise DevKitError(str(exc)) from exc
|
|
661
|
+
raise DevKitError(f"unsupported decisions action: {args.action}")
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def dispatch_ollama(args: argparse.Namespace) -> dict[str, Any]:
|
|
665
|
+
try:
|
|
666
|
+
if args.action == "status":
|
|
667
|
+
if args.model:
|
|
668
|
+
raise DevKitError("ollama status does not accept a model")
|
|
669
|
+
return ollama_status()
|
|
670
|
+
if args.action == "models":
|
|
671
|
+
if args.model:
|
|
672
|
+
raise DevKitError("ollama models does not accept a model")
|
|
673
|
+
return ollama_models()
|
|
674
|
+
if args.action == "pull":
|
|
675
|
+
return ollama_pull(args.model, yes=args.yes, dry_run=effective_dry_run(args))
|
|
676
|
+
if args.action == "update":
|
|
677
|
+
if args.model:
|
|
678
|
+
raise DevKitError("ollama update does not accept a model")
|
|
679
|
+
return ollama_update(yes=args.yes, dry_run=effective_dry_run(args))
|
|
680
|
+
except ValueError as exc:
|
|
681
|
+
raise DevKitError(str(exc)) from exc
|
|
682
|
+
raise DevKitError(f"unsupported ollama action: {args.action}")
|
|
683
|
+
|
|
684
|
+
|
|
358
685
|
def dispatch_install(args: argparse.Namespace) -> dict[str, Any]:
|
|
359
686
|
try:
|
|
360
687
|
return install_runtime(
|
|
@@ -363,7 +690,7 @@ def dispatch_install(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
363
690
|
host=args.host,
|
|
364
691
|
target=Path(args.target) if args.target else None,
|
|
365
692
|
home=Path(args.home) if args.home else None,
|
|
366
|
-
dry_run=args
|
|
693
|
+
dry_run=effective_dry_run(args),
|
|
367
694
|
profiles=parse_profiles(args.profiles),
|
|
368
695
|
)
|
|
369
696
|
except InstallError as exc:
|
|
@@ -452,40 +779,549 @@ def dispatch_source(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
452
779
|
def dispatch_memory(args: argparse.Namespace) -> dict[str, Any]:
|
|
453
780
|
if args.action == "show":
|
|
454
781
|
return show_memory(ROOT, agent_id=args.agent_id, source_id=args.source_id)
|
|
782
|
+
if args.action == "path":
|
|
783
|
+
return memory_path_payload()
|
|
455
784
|
if args.action == "reset":
|
|
456
|
-
return reset_memory(
|
|
785
|
+
return reset_memory(
|
|
786
|
+
all_memory=args.all,
|
|
787
|
+
agent_id=args.agent_id,
|
|
788
|
+
source_id=args.source_id,
|
|
789
|
+
reset_sessions=args.sessions,
|
|
790
|
+
reset_tasks=args.tasks,
|
|
791
|
+
reset_cache=args.cache,
|
|
792
|
+
)
|
|
457
793
|
raise DevKitError(f"unsupported memory action: {args.action}")
|
|
458
794
|
|
|
459
795
|
|
|
796
|
+
def dispatch_personality(args: argparse.Namespace) -> dict[str, Any]:
|
|
797
|
+
if args.action == "show":
|
|
798
|
+
return load_personality()
|
|
799
|
+
if args.action == "edit":
|
|
800
|
+
if not any([args.agent_name, args.user_name, args.language, args.tone, args.detail_level]):
|
|
801
|
+
payload = load_personality()
|
|
802
|
+
payload["status"] = "needs-input"
|
|
803
|
+
payload["message"] = "Use --name, --user-name, --language, --tone or --detail-level to edit non-interactively."
|
|
804
|
+
return payload
|
|
805
|
+
return update_personality(
|
|
806
|
+
agent_name=args.agent_name,
|
|
807
|
+
user_name=args.user_name,
|
|
808
|
+
language=args.language,
|
|
809
|
+
tone=args.tone,
|
|
810
|
+
detail_level=args.detail_level,
|
|
811
|
+
)
|
|
812
|
+
if args.action == "reset":
|
|
813
|
+
return reset_personality()
|
|
814
|
+
raise DevKitError(f"unsupported personality action: {args.action}")
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def dispatch_setup(args: argparse.Namespace) -> dict[str, Any]:
|
|
818
|
+
if args.action == "personality":
|
|
819
|
+
return setup_personality()
|
|
820
|
+
if args.action == "plan":
|
|
821
|
+
return setup_wizard(ROOT, dry_run=effective_dry_run(args), yes=args.yes)
|
|
822
|
+
raise DevKitError(f"unsupported setup action: {args.action}")
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def dispatch_toolchain(args: argparse.Namespace) -> dict[str, Any]:
|
|
826
|
+
try:
|
|
827
|
+
if args.action == "list":
|
|
828
|
+
if args.tool != "all":
|
|
829
|
+
raise DevKitError("toolchain list does not accept a tool argument")
|
|
830
|
+
return list_toolchain(ROOT)
|
|
831
|
+
if args.action == "doctor":
|
|
832
|
+
return doctor_toolchain(ROOT, None if args.tool == "all" else args.tool)
|
|
833
|
+
if args.action == "install":
|
|
834
|
+
return install_toolchain(ROOT, None if args.tool == "all" else args.tool, dry_run=effective_dry_run(args), yes=args.yes)
|
|
835
|
+
except ValueError as exc:
|
|
836
|
+
raise DevKitError(str(exc)) from exc
|
|
837
|
+
raise DevKitError(f"unsupported toolchain action: {args.action}")
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
def dispatch_task(args: argparse.Namespace) -> dict[str, Any]:
|
|
841
|
+
try:
|
|
842
|
+
if args.action == "list":
|
|
843
|
+
return list_tasks()
|
|
844
|
+
if args.action == "create":
|
|
845
|
+
schedule = {"type": "manual"}
|
|
846
|
+
if args.every:
|
|
847
|
+
schedule = {"type": "interval", "every": args.every}
|
|
848
|
+
if args.cron:
|
|
849
|
+
schedule = {"type": "cron", "cron": args.cron}
|
|
850
|
+
return create_task(task_id=args.task_id, title=args.title, prompt=args.prompt, schedule=schedule)
|
|
851
|
+
if args.action == "show":
|
|
852
|
+
require_id(args.task_id, "task show")
|
|
853
|
+
return show_task(args.task_id)
|
|
854
|
+
if args.action == "history":
|
|
855
|
+
require_id(args.task_id, "task history")
|
|
856
|
+
return task_history(args.task_id)
|
|
857
|
+
if args.action == "run":
|
|
858
|
+
require_id(args.task_id, "task run")
|
|
859
|
+
return run_task(args.task_id, dry_run=effective_dry_run(args))
|
|
860
|
+
if args.action in {"pause", "disable"}:
|
|
861
|
+
require_id(args.task_id, f"task {args.action}")
|
|
862
|
+
return update_task_status(args.task_id, "paused" if args.action == "pause" else "disabled")
|
|
863
|
+
if args.action in {"resume", "enable"}:
|
|
864
|
+
require_id(args.task_id, f"task {args.action}")
|
|
865
|
+
return update_task_status(args.task_id, "enabled")
|
|
866
|
+
if args.action == "update":
|
|
867
|
+
require_id(args.task_id, "task update")
|
|
868
|
+
return update_task_schedule(args.task_id, every=args.every, cron=args.cron)
|
|
869
|
+
if args.action == "delete":
|
|
870
|
+
require_id(args.task_id, "task delete")
|
|
871
|
+
return delete_task(args.task_id, yes=args.yes)
|
|
872
|
+
except ValueError as exc:
|
|
873
|
+
raise DevKitError(str(exc)) from exc
|
|
874
|
+
raise DevKitError(f"unsupported task action: {args.action}")
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def dispatch_scheduler(args: argparse.Namespace) -> dict[str, Any]:
|
|
878
|
+
if args.action == "run-once":
|
|
879
|
+
return run_scheduler_once(dry_run=effective_dry_run(args))
|
|
880
|
+
if args.action == "daemon":
|
|
881
|
+
return scheduler_daemon_plan()
|
|
882
|
+
raise DevKitError(f"unsupported scheduler action: {args.action}")
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def dispatch_calendar(args: argparse.Namespace) -> dict[str, Any]:
|
|
886
|
+
try:
|
|
887
|
+
if args.action == "configure":
|
|
888
|
+
return configure_calendar(ics_path=args.ics, timezone=args.timezone)
|
|
889
|
+
if args.action == "today":
|
|
890
|
+
return calendar_today()
|
|
891
|
+
if args.action == "tomorrow":
|
|
892
|
+
return calendar_tomorrow()
|
|
893
|
+
if args.action == "list":
|
|
894
|
+
return calendar_list(args.date_from, args.date_to)
|
|
895
|
+
except ValueError as exc:
|
|
896
|
+
raise DevKitError(str(exc)) from exc
|
|
897
|
+
raise DevKitError(f"unsupported calendar action: {args.action}")
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def dispatch_pr(args: argparse.Namespace) -> dict[str, Any]:
|
|
901
|
+
if args.action == "list-review-requests":
|
|
902
|
+
if effective_dry_run(args):
|
|
903
|
+
return pr_read_dry_run("list-review-requests")
|
|
904
|
+
return pr_list_review_requests()
|
|
905
|
+
if args.action == "inspect":
|
|
906
|
+
require_id(args.pr_ref, "pr inspect")
|
|
907
|
+
if effective_dry_run(args):
|
|
908
|
+
return pr_read_dry_run("inspect", pr_ref=args.pr_ref)
|
|
909
|
+
return pr_inspect(args.pr_ref)
|
|
910
|
+
if args.action == "review":
|
|
911
|
+
require_id(args.pr_ref, "pr review")
|
|
912
|
+
return pr_review(
|
|
913
|
+
args.pr_ref,
|
|
914
|
+
approve=args.approve,
|
|
915
|
+
request_changes=args.request_changes,
|
|
916
|
+
comment=args.comment,
|
|
917
|
+
allow_write=args.allow_write,
|
|
918
|
+
dry_run=effective_dry_run(args),
|
|
919
|
+
)
|
|
920
|
+
if args.action == "automation":
|
|
921
|
+
if args.automation_action not in {None, "create"}:
|
|
922
|
+
raise DevKitError("pr automation action must be create")
|
|
923
|
+
if effective_dry_run(args):
|
|
924
|
+
return pr_automation_dry_run(time=args.time)
|
|
925
|
+
return pr_create_automation(time=args.time)
|
|
926
|
+
raise DevKitError(f"unsupported pr action: {args.action}")
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def dispatch_permissions(args: argparse.Namespace) -> dict[str, Any]:
|
|
930
|
+
try:
|
|
931
|
+
if args.action == "show":
|
|
932
|
+
return show_permissions()
|
|
933
|
+
if args.action == "grant":
|
|
934
|
+
if not args.agent or not args.provider or not args.level:
|
|
935
|
+
raise DevKitError("permissions grant requires agent, provider and level")
|
|
936
|
+
return grant_permission(args.agent, args.provider, args.level, project=args.project, task_id=args.task_id)
|
|
937
|
+
if args.action == "revoke":
|
|
938
|
+
if not args.agent or not args.provider:
|
|
939
|
+
raise DevKitError("permissions revoke requires agent and provider")
|
|
940
|
+
return revoke_permission(args.agent, args.provider, args.level, project=args.project, task_id=args.task_id)
|
|
941
|
+
except ValueError as exc:
|
|
942
|
+
raise DevKitError(str(exc)) from exc
|
|
943
|
+
raise DevKitError(f"unsupported permissions action: {args.action}")
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def dispatch_audit(args: argparse.Namespace) -> dict[str, Any]:
|
|
947
|
+
try:
|
|
948
|
+
if args.action == "list":
|
|
949
|
+
if args.execution_id:
|
|
950
|
+
raise DevKitError("audit list does not accept an execution id")
|
|
951
|
+
return list_audits(limit=max(1, int(args.limit or 20)))
|
|
952
|
+
if args.action == "show":
|
|
953
|
+
require_id(args.execution_id, "audit show")
|
|
954
|
+
return show_audit(args.execution_id)
|
|
955
|
+
if args.action == "export":
|
|
956
|
+
require_id(args.execution_id, "audit export")
|
|
957
|
+
return export_audit(args.execution_id, fmt=args.format)
|
|
958
|
+
except ValueError as exc:
|
|
959
|
+
raise DevKitError(str(exc)) from exc
|
|
960
|
+
raise DevKitError(f"unsupported audit action: {args.action}")
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def pr_read_dry_run(action: str, *, pr_ref: str | None = None) -> dict[str, Any]:
|
|
964
|
+
return {
|
|
965
|
+
"kind": "pr",
|
|
966
|
+
"status": "planned",
|
|
967
|
+
"ok": True,
|
|
968
|
+
"dry_run": True,
|
|
969
|
+
"mode": "report-only",
|
|
970
|
+
"provider": "github",
|
|
971
|
+
"action": action,
|
|
972
|
+
"pr_ref": pr_ref,
|
|
973
|
+
"commands": planned_pr_commands(action, pr_ref=pr_ref),
|
|
974
|
+
"summary": "Dry-run only. GitHub would be read through gh; no PR write action would be submitted.",
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def pr_automation_dry_run(*, time: str) -> dict[str, Any]:
|
|
979
|
+
return {
|
|
980
|
+
"kind": "pr-automation",
|
|
981
|
+
"status": "planned",
|
|
982
|
+
"ok": True,
|
|
983
|
+
"dry_run": True,
|
|
984
|
+
"mode": "report-only",
|
|
985
|
+
"provider": "github",
|
|
986
|
+
"task": {
|
|
987
|
+
"id": "daily-pr-review",
|
|
988
|
+
"title": "Revisar PRs pendentes diariamente",
|
|
989
|
+
"schedule": {"type": "daily", "time": time},
|
|
990
|
+
"action": {
|
|
991
|
+
"type": "capability",
|
|
992
|
+
"agent": "github-pr-reviewer",
|
|
993
|
+
"capability": "list-review-requests",
|
|
994
|
+
"external_writes": False,
|
|
995
|
+
},
|
|
996
|
+
"permissions": {"mode": "report-only", "comment": False, "approve": False, "request_changes": False},
|
|
997
|
+
},
|
|
998
|
+
"summary": "Dry-run only. A local report-only PR review task would be created.",
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
def planned_pr_commands(action: str, *, pr_ref: str | None = None) -> list[list[str]]:
|
|
1003
|
+
if action == "list-review-requests":
|
|
1004
|
+
return [["gh", "pr", "list", "--review-requested", "@me", "--json", "number,title,url,author,headRefName,baseRefName,isDraft"]]
|
|
1005
|
+
if action == "inspect":
|
|
1006
|
+
return [["gh", "pr", "view", str(pr_ref), "--json", "number,title,url,author,body,headRefName,baseRefName,state,isDraft,reviewDecision,mergeable"]]
|
|
1007
|
+
return []
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def require_id(value: str | None, command: str) -> None:
|
|
1011
|
+
if not value:
|
|
1012
|
+
raise DevKitError(f"{command} requires an id")
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def effective_dry_run(args: argparse.Namespace) -> bool:
|
|
1016
|
+
return bool(getattr(args, "dry_run", False) or getattr(args, "global_dry_run", False))
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def dispatch_alias(args: argparse.Namespace) -> dict[str, Any]:
|
|
1020
|
+
try:
|
|
1021
|
+
if args.action == "list":
|
|
1022
|
+
if args.name:
|
|
1023
|
+
raise DevKitError("alias list does not accept a name")
|
|
1024
|
+
return list_aliases()
|
|
1025
|
+
if args.action == "add":
|
|
1026
|
+
if not args.name:
|
|
1027
|
+
raise DevKitError("alias add requires a name")
|
|
1028
|
+
return add_alias(args.name, force=args.force)
|
|
1029
|
+
if args.action == "remove":
|
|
1030
|
+
if not args.name:
|
|
1031
|
+
raise DevKitError("alias remove requires a name")
|
|
1032
|
+
return remove_alias(args.name)
|
|
1033
|
+
if args.action == "sync":
|
|
1034
|
+
if args.name:
|
|
1035
|
+
raise DevKitError("alias sync does not accept a name")
|
|
1036
|
+
return sync_aliases()
|
|
1037
|
+
except ValueError as exc:
|
|
1038
|
+
raise DevKitError(str(exc)) from exc
|
|
1039
|
+
raise DevKitError(f"unsupported alias action: {args.action}")
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
def dispatch_session(args: argparse.Namespace) -> dict[str, Any]:
|
|
1043
|
+
try:
|
|
1044
|
+
if args.action == "list":
|
|
1045
|
+
if args.session_id:
|
|
1046
|
+
raise DevKitError("session list does not accept a session id")
|
|
1047
|
+
return list_sessions()
|
|
1048
|
+
if args.action == "show":
|
|
1049
|
+
if not args.session_id:
|
|
1050
|
+
raise DevKitError("session show requires a session id")
|
|
1051
|
+
return show_session(args.session_id)
|
|
1052
|
+
if args.action == "resume":
|
|
1053
|
+
if not args.session_id:
|
|
1054
|
+
raise DevKitError("session resume requires a session id")
|
|
1055
|
+
return resume_session(args.session_id)
|
|
1056
|
+
except ValueError as exc:
|
|
1057
|
+
raise DevKitError(str(exc)) from exc
|
|
1058
|
+
raise DevKitError(f"unsupported session action: {args.action}")
|
|
1059
|
+
|
|
1060
|
+
|
|
460
1061
|
def agent_requires_llm(args: argparse.Namespace) -> dict[str, Any]:
|
|
461
1062
|
prompt = " ".join(args.prompt).strip()
|
|
462
1063
|
if not prompt:
|
|
463
1064
|
raise DevKitError("agent requires a natural-language prompt")
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
return invoke_deterministic_route(prompt, route)
|
|
1065
|
+
if effective_dry_run(args):
|
|
1066
|
+
return build_agent_dry_run_plan(prompt, args)
|
|
467
1067
|
try:
|
|
468
|
-
|
|
1068
|
+
session = get_or_create_session(
|
|
1069
|
+
session_id=args.session_id,
|
|
1070
|
+
force_new=args.new_session,
|
|
1071
|
+
prompt=prompt,
|
|
1072
|
+
project=str(Path.cwd()),
|
|
1073
|
+
backend=args.llm,
|
|
1074
|
+
)
|
|
469
1075
|
except ValueError as exc:
|
|
470
1076
|
raise DevKitError(str(exc)) from exc
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
1077
|
+
personality = load_personality()
|
|
1078
|
+
name = public_name(personality=personality, invoked_as=getattr(args, "prog_name", "agent"))
|
|
1079
|
+
if is_identity_question(prompt):
|
|
1080
|
+
result = {
|
|
1081
|
+
"kind": "agent",
|
|
1082
|
+
"status": "ok",
|
|
1083
|
+
"ok": True,
|
|
1084
|
+
"requires_llm": False,
|
|
1085
|
+
"prompt_received": True,
|
|
1086
|
+
"prompt_length": len(prompt),
|
|
1087
|
+
"identity": {"name": name, "source": "local"},
|
|
1088
|
+
"response": local_identity_response(prompt, name=name),
|
|
1089
|
+
}
|
|
1090
|
+
return finalize_agent_session(result, session, prompt, backend=args.llm)
|
|
1091
|
+
natural_result = dispatch_natural_operational_prompt(prompt)
|
|
1092
|
+
if natural_result:
|
|
1093
|
+
return finalize_agent_session(natural_result, session, prompt, backend=args.llm)
|
|
1094
|
+
route = route_prompt(prompt)
|
|
1095
|
+
if route:
|
|
1096
|
+
result = invoke_deterministic_route(prompt, route)
|
|
1097
|
+
return finalize_agent_session(result, session, prompt, backend=args.llm)
|
|
1098
|
+
contextual_prompt = build_contextual_prompt(str(session["id"]), prompt)
|
|
1099
|
+
model_plan = build_model_plan(prompt)
|
|
1100
|
+
result = invoke_agent_prompt(
|
|
1101
|
+
contextual_prompt,
|
|
1102
|
+
args.llm,
|
|
1103
|
+
public_name=name,
|
|
1104
|
+
allow_fallback=not args.no_llm_fallback,
|
|
1105
|
+
)
|
|
1106
|
+
review_gate = build_review_gate(prompt, model_plan=model_plan)
|
|
1107
|
+
if result.get("ok"):
|
|
1108
|
+
review_gate = mark_reviewed(review_gate, reviewer=str(result.get("llm_backend") or "coordinator"))
|
|
1109
|
+
result["model_plan"] = model_plan
|
|
1110
|
+
result["review_gate"] = review_gate
|
|
1111
|
+
result["prompt_length"] = len(prompt)
|
|
1112
|
+
result["session_context_applied"] = contextual_prompt != prompt
|
|
1113
|
+
if result.get("response"):
|
|
1114
|
+
result["response"] = enforce_identity_response(str(result["response"]), prompt, name=name)
|
|
1115
|
+
result["identity"] = {"name": name, "source": "local"}
|
|
1116
|
+
return finalize_agent_session(result, session, prompt, backend=result.get("llm_backend") or args.llm)
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def dispatch_natural_operational_prompt(prompt: str) -> dict[str, Any] | None:
|
|
1120
|
+
normalized = " ".join(prompt.lower().split())
|
|
1121
|
+
control_result = dispatch_natural_control_prompt(normalized)
|
|
1122
|
+
if control_result:
|
|
1123
|
+
control_result["prompt_received"] = True
|
|
1124
|
+
control_result["prompt_length"] = len(prompt)
|
|
1125
|
+
return control_result
|
|
1126
|
+
if "agenda" in normalized:
|
|
1127
|
+
if "amanha" in normalized or "amanhã" in normalized:
|
|
1128
|
+
payload = calendar_tomorrow()
|
|
1129
|
+
else:
|
|
1130
|
+
payload = calendar_today()
|
|
1131
|
+
payload = dict(payload)
|
|
1132
|
+
payload["kind"] = "agent"
|
|
1133
|
+
payload["mode"] = "calendar-route"
|
|
1134
|
+
payload["requires_llm"] = False
|
|
1135
|
+
payload["prompt_received"] = True
|
|
1136
|
+
payload["prompt_length"] = len(prompt)
|
|
1137
|
+
payload["response"] = calendar_summary(payload)
|
|
1138
|
+
if payload.get("status") == "needs-input":
|
|
1139
|
+
payload["ok"] = False
|
|
1140
|
+
else:
|
|
1141
|
+
payload["ok"] = True
|
|
1142
|
+
return payload
|
|
1143
|
+
if has_pr_intent(normalized):
|
|
1144
|
+
if any(marker in normalized for marker in ("diariamente", "todo dia", "diaria", "diária", "recorrente")):
|
|
1145
|
+
payload = pr_create_automation()
|
|
1146
|
+
return {
|
|
1147
|
+
"kind": "agent",
|
|
1148
|
+
"status": payload.get("status"),
|
|
1149
|
+
"ok": True,
|
|
1150
|
+
"mode": "pr-automation-route",
|
|
1151
|
+
"requires_llm": False,
|
|
1152
|
+
"prompt_received": True,
|
|
1153
|
+
"prompt_length": len(prompt),
|
|
1154
|
+
"response": "Automacao diaria de revisao de PRs criada em modo report-only.",
|
|
1155
|
+
"result": payload,
|
|
1156
|
+
}
|
|
1157
|
+
payload = pr_list_review_requests()
|
|
1158
|
+
return {
|
|
1159
|
+
"kind": "agent",
|
|
1160
|
+
"status": payload.get("status"),
|
|
1161
|
+
"ok": payload.get("status") == "ok",
|
|
1162
|
+
"mode": "pr-route",
|
|
1163
|
+
"requires_llm": False,
|
|
1164
|
+
"prompt_received": True,
|
|
1165
|
+
"prompt_length": len(prompt),
|
|
1166
|
+
"response": summarize_pr_list(payload),
|
|
1167
|
+
"result": payload,
|
|
1168
|
+
"exit_code": payload.get("exit_code", 0 if payload.get("status") == "ok" else 2),
|
|
1169
|
+
}
|
|
1170
|
+
return None
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
def dispatch_natural_control_prompt(normalized_prompt: str) -> dict[str, Any] | None:
|
|
1174
|
+
if "decis" in normalized_prompt and any(marker in normalized_prompt for marker in ("mostre", "liste", "ver", "mostrar", "listar")):
|
|
1175
|
+
return {
|
|
1176
|
+
"kind": "agent",
|
|
1177
|
+
"status": "ok",
|
|
1178
|
+
"ok": True,
|
|
1179
|
+
"mode": "control-center-route",
|
|
1180
|
+
"requires_llm": False,
|
|
1181
|
+
"response": "Estas sao as decisoes locais registradas.",
|
|
1182
|
+
"result": list_decisions(),
|
|
1183
|
+
}
|
|
1184
|
+
control_targets = {
|
|
1185
|
+
"ollama": ("tools", "ollama"),
|
|
1186
|
+
"azure devops": ("tools", "azure-devops"),
|
|
1187
|
+
"azure": ("tools", "azure-devops"),
|
|
1188
|
+
"github": ("integrations", "github"),
|
|
1189
|
+
"figma": ("tools", "figma"),
|
|
1190
|
+
}
|
|
1191
|
+
action: str | None = None
|
|
1192
|
+
if any(marker in normalized_prompt for marker in ("desative", "desabilite", "disable", "bloqueie")):
|
|
1193
|
+
action = "disable"
|
|
1194
|
+
if any(marker in normalized_prompt for marker in ("reative", "ative", "habilite", "enable")):
|
|
1195
|
+
action = "enable"
|
|
1196
|
+
if not action:
|
|
1197
|
+
return None
|
|
1198
|
+
for marker, (category, item_id) in control_targets.items():
|
|
1199
|
+
if marker in normalized_prompt:
|
|
1200
|
+
state = "enabled" if action == "enable" else "disabled_by_user"
|
|
1201
|
+
result = set_decision(category, item_id, state, reason=f"natural prompt: {action}")
|
|
1202
|
+
return {
|
|
1203
|
+
"kind": "agent",
|
|
1204
|
+
"status": "ok",
|
|
1205
|
+
"ok": True,
|
|
1206
|
+
"mode": "control-center-route",
|
|
1207
|
+
"requires_llm": False,
|
|
1208
|
+
"response": f"{item_id} foi {'ativado' if state == 'enabled' else 'desativado'} para {category}.",
|
|
1209
|
+
"result": {
|
|
1210
|
+
"category": category,
|
|
1211
|
+
"id": item_id,
|
|
1212
|
+
"state": state,
|
|
1213
|
+
"decision": result.get("item"),
|
|
1214
|
+
},
|
|
1215
|
+
}
|
|
1216
|
+
if any(marker in normalized_prompt for marker in ("ferramentas", "tools")) and any(
|
|
1217
|
+
marker in normalized_prompt for marker in ("mostre", "liste", "ver", "mostrar", "listar")
|
|
1218
|
+
):
|
|
1219
|
+
return {
|
|
1220
|
+
"kind": "agent",
|
|
1221
|
+
"status": "ok",
|
|
1222
|
+
"ok": True,
|
|
1223
|
+
"mode": "control-center-route",
|
|
1224
|
+
"requires_llm": False,
|
|
1225
|
+
"response": "Estas sao as ferramentas com decisoes locais registradas.",
|
|
1226
|
+
"result": list_decisions("tools"),
|
|
1227
|
+
}
|
|
1228
|
+
return None
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
def build_agent_dry_run_plan(prompt: str, args: argparse.Namespace) -> dict[str, Any]:
|
|
1232
|
+
normalized = " ".join(prompt.lower().split())
|
|
1233
|
+
route = route_prompt(prompt)
|
|
1234
|
+
model_plan = build_model_plan(prompt, route=route)
|
|
1235
|
+
review_gate = build_review_gate(prompt, route=route, model_plan=model_plan)
|
|
1236
|
+
plan: dict[str, Any] = {
|
|
474
1237
|
"kind": "agent",
|
|
475
|
-
"status": "
|
|
476
|
-
"ok":
|
|
477
|
-
"
|
|
478
|
-
"
|
|
1238
|
+
"status": "planned",
|
|
1239
|
+
"ok": True,
|
|
1240
|
+
"dry_run": True,
|
|
1241
|
+
"requires_llm": False,
|
|
479
1242
|
"prompt_received": True,
|
|
480
1243
|
"prompt_length": len(prompt),
|
|
481
|
-
"
|
|
482
|
-
"
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
],
|
|
487
|
-
"
|
|
1244
|
+
"mode": "dry-run",
|
|
1245
|
+
"intent": "llm" if not route else route.get("intent"),
|
|
1246
|
+
"route": route,
|
|
1247
|
+
"llm_backend": getattr(args, "llm", None),
|
|
1248
|
+
"external_writes": False,
|
|
1249
|
+
"providers": {"used": [], "missing": [], "skipped": []},
|
|
1250
|
+
"commands": [],
|
|
1251
|
+
"permissions": [],
|
|
1252
|
+
"model_plan": model_plan,
|
|
1253
|
+
"review_gate": review_gate,
|
|
1254
|
+
"response": "Dry-run: nenhuma chamada LLM ou escrita externa foi executada.",
|
|
488
1255
|
}
|
|
1256
|
+
if "agenda" in normalized:
|
|
1257
|
+
plan.update(
|
|
1258
|
+
{
|
|
1259
|
+
"intent": "calendar",
|
|
1260
|
+
"mode": "calendar-dry-run",
|
|
1261
|
+
"providers": {"used": ["calendar"], "missing": [], "skipped": []},
|
|
1262
|
+
"data_reads": ["configured calendar provider, if present"],
|
|
1263
|
+
"response": "Dry-run: o calendario seria consultado localmente se configurado.",
|
|
1264
|
+
}
|
|
1265
|
+
)
|
|
1266
|
+
return plan
|
|
1267
|
+
if has_pr_intent(normalized):
|
|
1268
|
+
recurring = any(marker in normalized for marker in ("diariamente", "todo dia", "diaria", "diária", "recorrente"))
|
|
1269
|
+
plan.update(
|
|
1270
|
+
{
|
|
1271
|
+
"intent": "github-pr-review",
|
|
1272
|
+
"mode": "pr-dry-run",
|
|
1273
|
+
"providers": {"used": ["github"], "missing": [], "skipped": []},
|
|
1274
|
+
"commands": planned_pr_commands("list-review-requests"),
|
|
1275
|
+
"external_writes": False,
|
|
1276
|
+
"permissions": [{"agent": "github-pr-reviewer", "provider": "github", "required_level": "read-only"}],
|
|
1277
|
+
"response": (
|
|
1278
|
+
"Dry-run: a automacao diaria de PR seria planejada em modo report-only."
|
|
1279
|
+
if recurring
|
|
1280
|
+
else "Dry-run: PRs aguardando revisao seriam listadas via gh em modo report-only."
|
|
1281
|
+
),
|
|
1282
|
+
}
|
|
1283
|
+
)
|
|
1284
|
+
return plan
|
|
1285
|
+
if route:
|
|
1286
|
+
plan["providers"] = {"used": [route.get("provider")], "missing": [], "skipped": []}
|
|
1287
|
+
plan["response"] = "Dry-run: a capability roteada seria executada somente apos validar source/provider."
|
|
1288
|
+
return plan
|
|
1289
|
+
plan["response"] = "Dry-run: o prompt exigiria LLM configurada; nenhuma chamada foi feita."
|
|
1290
|
+
return plan
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
def has_pr_intent(normalized_prompt: str) -> bool:
|
|
1294
|
+
tokens = {token.strip(".,;:!?()[]{}\"'") for token in normalized_prompt.split()}
|
|
1295
|
+
return bool({"pr", "prs"} & tokens) or "pull request" in normalized_prompt or "pull requests" in normalized_prompt
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
def summarize_pr_list(payload: dict[str, Any]) -> str:
|
|
1299
|
+
if payload.get("status") != "ok":
|
|
1300
|
+
return str(payload.get("message") or "Nao foi possivel listar PRs.")
|
|
1301
|
+
items = payload.get("items") or []
|
|
1302
|
+
if not items:
|
|
1303
|
+
return "Nenhuma PR aguardando sua revisao foi encontrada."
|
|
1304
|
+
lines = []
|
|
1305
|
+
for item in items:
|
|
1306
|
+
number = item.get("number")
|
|
1307
|
+
title = item.get("title") or "-"
|
|
1308
|
+
url = item.get("url") or ""
|
|
1309
|
+
lines.append(f"- #{number} {title} {url}".strip())
|
|
1310
|
+
return "\n".join(lines)
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
def finalize_agent_session(
|
|
1314
|
+
result: dict[str, Any],
|
|
1315
|
+
session: dict[str, Any],
|
|
1316
|
+
prompt: str,
|
|
1317
|
+
*,
|
|
1318
|
+
backend: str | None = None,
|
|
1319
|
+
) -> dict[str, Any]:
|
|
1320
|
+
try:
|
|
1321
|
+
result["session"] = record_exchange(str(session["id"]), prompt=prompt, result=result, backend=backend)
|
|
1322
|
+
except ValueError as exc:
|
|
1323
|
+
raise DevKitError(str(exc)) from exc
|
|
1324
|
+
return result
|
|
489
1325
|
|
|
490
1326
|
|
|
491
1327
|
def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -499,21 +1335,25 @@ def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str,
|
|
|
499
1335
|
raise DevKitError(str(exc)) from exc
|
|
500
1336
|
|
|
501
1337
|
if not source:
|
|
1338
|
+
wizard = missing_source_wizard(prompt, route, root=ROOT)
|
|
502
1339
|
return {
|
|
503
1340
|
"kind": "agent",
|
|
504
1341
|
"status": "needs-input",
|
|
505
1342
|
"ok": False,
|
|
506
1343
|
"requires_source": True,
|
|
1344
|
+
"provider": route.get("provider"),
|
|
507
1345
|
"source_provider": route.get("provider"),
|
|
508
1346
|
"prompt_received": True,
|
|
509
1347
|
"prompt_length": len(prompt),
|
|
510
1348
|
"route": route,
|
|
511
1349
|
"napkin": napkin_context(ROOT, agent_id=route.get("agent_id")),
|
|
512
|
-
"
|
|
1350
|
+
"setup_wizard": wizard,
|
|
1351
|
+
"next_question": wizard.get("next_question"),
|
|
1352
|
+
"message": wizard.get("message"),
|
|
513
1353
|
"next_steps": [
|
|
514
|
-
"
|
|
515
|
-
"
|
|
516
|
-
"
|
|
1354
|
+
"Responda a pergunta do wizard para autorizar ou negar a configuracao desta fonte.",
|
|
1355
|
+
"Se preferir teste local, configure uma source com fixture sem armazenar segredos.",
|
|
1356
|
+
"O prompt original sera retomado apos a fonte reutilizavel ser configurada.",
|
|
517
1357
|
],
|
|
518
1358
|
"exit_code": 2,
|
|
519
1359
|
}
|
|
@@ -523,6 +1363,10 @@ def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str,
|
|
|
523
1363
|
result = run_capability(agent, str(route["capability_id"]), capability_args, capture_output=True)
|
|
524
1364
|
response = result.get("stdout") or result.get("error") or ""
|
|
525
1365
|
record_usage(prompt, route=route, source_id=str(source["id"]))
|
|
1366
|
+
model_plan = build_model_plan(prompt, route=route)
|
|
1367
|
+
review_gate = build_review_gate(prompt, route=route, model_plan=model_plan)
|
|
1368
|
+
if result.get("ok"):
|
|
1369
|
+
review_gate = mark_reviewed(review_gate, reviewer="deterministic-coordinator")
|
|
526
1370
|
return {
|
|
527
1371
|
"kind": "agent",
|
|
528
1372
|
"status": result.get("status"),
|
|
@@ -533,6 +1377,8 @@ def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str,
|
|
|
533
1377
|
"route": route,
|
|
534
1378
|
"source": public_source(source),
|
|
535
1379
|
"napkin": napkin_context(ROOT, agent_id=route.get("agent_id"), source_id=str(source["id"])),
|
|
1380
|
+
"model_plan": model_plan,
|
|
1381
|
+
"review_gate": review_gate,
|
|
536
1382
|
"response": response,
|
|
537
1383
|
"result": result,
|
|
538
1384
|
"exit_code": result.get("exit_code", 0 if result.get("ok") else 1),
|
|
@@ -729,9 +1575,9 @@ def run_capability(
|
|
|
729
1575
|
next_steps=guardrail["next_steps"],
|
|
730
1576
|
exit_code=2,
|
|
731
1577
|
)
|
|
732
|
-
readiness = evaluate_provider_requirements(ROOT, data)
|
|
1578
|
+
readiness = evaluate_provider_requirements(ROOT, data, capability_args)
|
|
733
1579
|
if not readiness["ready"]:
|
|
734
|
-
|
|
1580
|
+
payload = run_payload(
|
|
735
1581
|
status=readiness["status"],
|
|
736
1582
|
agent=summarize_agent(agent),
|
|
737
1583
|
capability=data.get("id", capability_id),
|
|
@@ -745,6 +1591,17 @@ def run_capability(
|
|
|
745
1591
|
artifacts=readiness["artifacts"],
|
|
746
1592
|
exit_code=readiness.get("exit_code"),
|
|
747
1593
|
)
|
|
1594
|
+
wizard = setup_wizard_from_readiness(readiness, agent=summarize_agent(agent), capability_id=str(data.get("id", capability_id)))
|
|
1595
|
+
if wizard:
|
|
1596
|
+
payload["setup_wizard"] = wizard
|
|
1597
|
+
payload["next_question"] = wizard.get("next_question")
|
|
1598
|
+
payload["configuration_agent"] = wizard.get("owner_agent")
|
|
1599
|
+
payload["next_steps"] = [
|
|
1600
|
+
"Responda a pergunta do wizard para autorizar ou negar a configuracao deste provider.",
|
|
1601
|
+
"Informe uma referencia segura de credencial por variavel de ambiente, arquivo ou cadeia nativa quando solicitado.",
|
|
1602
|
+
"Reexecute ou retome a mesma capability depois que a configuracao estiver salva.",
|
|
1603
|
+
]
|
|
1604
|
+
return payload
|
|
748
1605
|
|
|
749
1606
|
runner_ref = (data.get("entrypoint", {}) or {}).get("runner")
|
|
750
1607
|
if not runner_ref:
|
|
@@ -863,6 +1720,26 @@ def supports_runtime_source(agent_id: str, capability_id: str) -> bool:
|
|
|
863
1720
|
return agent_id == "azure-devops-orchestrator" and capability_id == "read-card"
|
|
864
1721
|
|
|
865
1722
|
|
|
1723
|
+
def setup_wizard_from_readiness(readiness: dict[str, Any], *, agent: dict[str, Any], capability_id: str) -> dict[str, Any] | None:
|
|
1724
|
+
providers = readiness.get("providers") if isinstance(readiness.get("providers"), dict) else {}
|
|
1725
|
+
missing = providers.get("missing") or []
|
|
1726
|
+
if not missing:
|
|
1727
|
+
return None
|
|
1728
|
+
provider_id = str(missing[0])
|
|
1729
|
+
details = providers.get("details") or []
|
|
1730
|
+
detail = next((item for item in details if isinstance(item, dict) and item.get("id") == provider_id), {})
|
|
1731
|
+
try:
|
|
1732
|
+
return provider_wizard_from_requirement(
|
|
1733
|
+
ROOT,
|
|
1734
|
+
provider_id,
|
|
1735
|
+
agent_id=str(agent.get("id") or ""),
|
|
1736
|
+
capability_id=capability_id,
|
|
1737
|
+
reason=str(detail.get("purpose") or "Provider is required but not configured."),
|
|
1738
|
+
)
|
|
1739
|
+
except Exception:
|
|
1740
|
+
return None
|
|
1741
|
+
|
|
1742
|
+
|
|
866
1743
|
def doctor(project: str | None = None, home: str | None = None, scope: str = "auto") -> dict[str, Any]:
|
|
867
1744
|
agents = list_agents()
|
|
868
1745
|
capabilities = list_all_capabilities()
|
|
@@ -1050,6 +1927,8 @@ def print_human(result: dict[str, Any]) -> None:
|
|
|
1050
1927
|
print_llm_configure(result)
|
|
1051
1928
|
elif kind == "llm-default":
|
|
1052
1929
|
print_llm_default(result)
|
|
1930
|
+
elif kind == "llm-preference":
|
|
1931
|
+
print_llm_preference(result)
|
|
1053
1932
|
elif kind == "providers":
|
|
1054
1933
|
print_providers(result)
|
|
1055
1934
|
elif kind == "provider-status":
|
|
@@ -1072,8 +1951,54 @@ def print_human(result: dict[str, Any]) -> None:
|
|
|
1072
1951
|
print_source_remove(result)
|
|
1073
1952
|
elif kind == "memory":
|
|
1074
1953
|
print_memory(result)
|
|
1954
|
+
elif kind == "memory-path":
|
|
1955
|
+
print_memory_path(result)
|
|
1075
1956
|
elif kind == "memory-reset":
|
|
1076
1957
|
print_memory_reset(result)
|
|
1958
|
+
elif kind == "personality":
|
|
1959
|
+
print_personality(result)
|
|
1960
|
+
elif kind == "aliases":
|
|
1961
|
+
print_aliases(result)
|
|
1962
|
+
elif kind == "alias":
|
|
1963
|
+
print_alias(result)
|
|
1964
|
+
elif kind == "sessions":
|
|
1965
|
+
print_sessions(result)
|
|
1966
|
+
elif kind == "session":
|
|
1967
|
+
print_session(result)
|
|
1968
|
+
elif kind == "setup":
|
|
1969
|
+
print_setup(result)
|
|
1970
|
+
elif kind == "toolchain":
|
|
1971
|
+
print_toolchain(result)
|
|
1972
|
+
elif kind == "toolchain-doctor":
|
|
1973
|
+
print_toolchain_doctor(result)
|
|
1974
|
+
elif kind == "toolchain-install":
|
|
1975
|
+
print_toolchain_install(result)
|
|
1976
|
+
elif kind == "tasks":
|
|
1977
|
+
print_tasks(result)
|
|
1978
|
+
elif kind == "task":
|
|
1979
|
+
print_task(result)
|
|
1980
|
+
elif kind == "task-history":
|
|
1981
|
+
print_task_history(result)
|
|
1982
|
+
elif kind == "task-run":
|
|
1983
|
+
print_task_run(result)
|
|
1984
|
+
elif kind == "scheduler":
|
|
1985
|
+
print_scheduler(result)
|
|
1986
|
+
elif kind == "calendar":
|
|
1987
|
+
print_calendar(result)
|
|
1988
|
+
elif kind == "calendar-configure":
|
|
1989
|
+
print_calendar_configure(result)
|
|
1990
|
+
elif kind in {"pr", "pr-review", "pr-automation"}:
|
|
1991
|
+
print_pr(result)
|
|
1992
|
+
elif kind == "permissions":
|
|
1993
|
+
print_permissions(result)
|
|
1994
|
+
elif kind in {"audit", "audit-entry", "audit-export"}:
|
|
1995
|
+
print_audit(result)
|
|
1996
|
+
elif kind == "config":
|
|
1997
|
+
print_config(result)
|
|
1998
|
+
elif kind in {"tools", "tool", "integrations", "integration", "skills", "skill", "decisions", "decision", "decisions-reset"}:
|
|
1999
|
+
print_control(result)
|
|
2000
|
+
elif kind in {"ollama-status", "ollama-models", "ollama-pull", "ollama-update"}:
|
|
2001
|
+
print_ollama(result)
|
|
1077
2002
|
elif kind == "install":
|
|
1078
2003
|
print_install(result)
|
|
1079
2004
|
else:
|
|
@@ -1201,6 +2126,11 @@ def print_agent_response(result: dict[str, Any]) -> None:
|
|
|
1201
2126
|
print(result.get("response", ""))
|
|
1202
2127
|
return
|
|
1203
2128
|
print(result.get("message") or result.get("response") or "Agent execution did not complete.")
|
|
2129
|
+
question = result.get("next_question") or ((result.get("setup_wizard") or {}).get("next_question") if isinstance(result.get("setup_wizard"), dict) else None)
|
|
2130
|
+
if isinstance(question, dict) and question.get("text"):
|
|
2131
|
+
print(f"\nPergunta: {question['text']}")
|
|
2132
|
+
if question.get("type") == "confirm":
|
|
2133
|
+
print("[s/N]")
|
|
1204
2134
|
if result.get("llm_backend"):
|
|
1205
2135
|
print(f"Requested backend: {result['llm_backend']}")
|
|
1206
2136
|
if result.get("next_steps"):
|
|
@@ -1242,6 +2172,10 @@ def print_source_remove(result: dict[str, Any]) -> None:
|
|
|
1242
2172
|
|
|
1243
2173
|
def print_memory(result: dict[str, Any]) -> None:
|
|
1244
2174
|
print(f"Memory home: {result['memory_home']}")
|
|
2175
|
+
if result.get("files"):
|
|
2176
|
+
print("\nFiles:")
|
|
2177
|
+
for item in result["files"]:
|
|
2178
|
+
print(f"- {item['name']}: {item['path']}")
|
|
1245
2179
|
for bucket in ("prompts", "routes", "sources"):
|
|
1246
2180
|
print(f"\n{bucket.title()}:")
|
|
1247
2181
|
items = result["usage"].get(bucket) or []
|
|
@@ -1257,6 +2191,218 @@ def print_memory_reset(result: dict[str, Any]) -> None:
|
|
|
1257
2191
|
print(f"Config: {result['config_path']}")
|
|
1258
2192
|
|
|
1259
2193
|
|
|
2194
|
+
def print_memory_path(result: dict[str, Any]) -> None:
|
|
2195
|
+
print(f"Memory home: {result['home']}")
|
|
2196
|
+
if result.get("created"):
|
|
2197
|
+
print("Created:")
|
|
2198
|
+
for path in result["created"]:
|
|
2199
|
+
print(f"- {path}")
|
|
2200
|
+
print("Files:")
|
|
2201
|
+
for item in result["files"]:
|
|
2202
|
+
print(f"- {item['name']}: {item['path']}")
|
|
2203
|
+
|
|
2204
|
+
|
|
2205
|
+
def print_personality(result: dict[str, Any]) -> None:
|
|
2206
|
+
print(f"Personality: {result.get('status', 'ok')}")
|
|
2207
|
+
print(f"Path: {result['path']}")
|
|
2208
|
+
print(f"Agent name: {result.get('agent_name') or '-'}")
|
|
2209
|
+
print(f"User name: {result.get('user_name') or '-'}")
|
|
2210
|
+
print(f"Language: {result.get('language') or '-'}")
|
|
2211
|
+
print(f"Tone: {result.get('tone') or '-'}")
|
|
2212
|
+
print(f"Detail level: {result.get('detail_level') or '-'}")
|
|
2213
|
+
if result.get("message"):
|
|
2214
|
+
print(result["message"])
|
|
2215
|
+
if result.get("questions"):
|
|
2216
|
+
print("Setup questions:")
|
|
2217
|
+
for question in result["questions"]:
|
|
2218
|
+
print(f"- {question}")
|
|
2219
|
+
|
|
2220
|
+
|
|
2221
|
+
def print_aliases(result: dict[str, Any]) -> None:
|
|
2222
|
+
print(f"Aliases config: {result['config_path']}")
|
|
2223
|
+
if not result["items"]:
|
|
2224
|
+
print("No aliases configured.")
|
|
2225
|
+
return
|
|
2226
|
+
for item in result["items"]:
|
|
2227
|
+
print(f"- {item['name']}: {item['path']}")
|
|
2228
|
+
|
|
2229
|
+
|
|
2230
|
+
def print_alias(result: dict[str, Any]) -> None:
|
|
2231
|
+
print(f"Alias {result['status']}: {result['name']}")
|
|
2232
|
+
if result.get("path"):
|
|
2233
|
+
print(f"Path: {result['path']}")
|
|
2234
|
+
if result.get("removed_paths"):
|
|
2235
|
+
print("Removed:")
|
|
2236
|
+
for path in result["removed_paths"]:
|
|
2237
|
+
print(f"- {path}")
|
|
2238
|
+
print(f"Config: {result['config_path']}")
|
|
2239
|
+
|
|
2240
|
+
|
|
2241
|
+
def print_sessions(result: dict[str, Any]) -> None:
|
|
2242
|
+
print(f"Sessions home: {result['home']}")
|
|
2243
|
+
if result.get("active_session_id"):
|
|
2244
|
+
print(f"Active: {result['active_session_id']}")
|
|
2245
|
+
if not result["items"]:
|
|
2246
|
+
print("No sessions found.")
|
|
2247
|
+
return
|
|
2248
|
+
for item in result["items"]:
|
|
2249
|
+
marker = " active" if item.get("active") else ""
|
|
2250
|
+
print(
|
|
2251
|
+
f"- {item['id']}{marker} {item.get('title') or '-'} "
|
|
2252
|
+
f"{item.get('exchange_count', 0)} exchanges ~{item.get('token_estimate', 0)} tokens"
|
|
2253
|
+
)
|
|
2254
|
+
if item.get("project"):
|
|
2255
|
+
print(f" Project: {item['project']}")
|
|
2256
|
+
|
|
2257
|
+
|
|
2258
|
+
def print_session(result: dict[str, Any]) -> None:
|
|
2259
|
+
session = result["session"]
|
|
2260
|
+
print(f"Session {result['status']}: {session['id']}")
|
|
2261
|
+
print(f"Title: {session.get('title') or '-'}")
|
|
2262
|
+
print(f"Path: {session.get('path') or '-'}")
|
|
2263
|
+
print(f"Project: {session.get('project') or '-'}")
|
|
2264
|
+
print(f"Exchanges: {session.get('exchange_count', 0)}")
|
|
2265
|
+
print(f"Token estimate: {session.get('token_estimate', 0)}")
|
|
2266
|
+
|
|
2267
|
+
|
|
2268
|
+
def print_setup(result: dict[str, Any]) -> None:
|
|
2269
|
+
print(f"Setup: {result['status']}")
|
|
2270
|
+
print(f"Dry-run: {result.get('dry_run', False)}")
|
|
2271
|
+
toolchain = result.get("toolchain") or {}
|
|
2272
|
+
print(f"Toolchain: {toolchain.get('status', '-')}")
|
|
2273
|
+
missing = (toolchain.get("required_missing") or []) + (toolchain.get("optional_missing") or [])
|
|
2274
|
+
if missing:
|
|
2275
|
+
print(f"Missing: {', '.join(missing)}")
|
|
2276
|
+
if result.get("next_steps"):
|
|
2277
|
+
print("Next steps:")
|
|
2278
|
+
for step in result["next_steps"]:
|
|
2279
|
+
print(f"- {step}")
|
|
2280
|
+
|
|
2281
|
+
|
|
2282
|
+
def print_toolchain(result: dict[str, Any]) -> None:
|
|
2283
|
+
print(f"Toolchain: {result['status']}")
|
|
2284
|
+
print(f"Platform: {result['platform']}")
|
|
2285
|
+
for item in result["items"]:
|
|
2286
|
+
print(f"- {item['id']} {item['command']} required={item['required']}")
|
|
2287
|
+
|
|
2288
|
+
|
|
2289
|
+
def print_toolchain_doctor(result: dict[str, Any]) -> None:
|
|
2290
|
+
print(f"Toolchain doctor: {result['status']}")
|
|
2291
|
+
print(f"Platform: {result['platform']}")
|
|
2292
|
+
for item in result["items"]:
|
|
2293
|
+
print(f"- {item['id']}: {item['status']}")
|
|
2294
|
+
if item.get("binary"):
|
|
2295
|
+
print(f" {item['binary']}")
|
|
2296
|
+
if item.get("install"):
|
|
2297
|
+
print(f" Install: {item['install']}")
|
|
2298
|
+
|
|
2299
|
+
|
|
2300
|
+
def print_toolchain_install(result: dict[str, Any]) -> None:
|
|
2301
|
+
print(f"Toolchain install: {result['status']}")
|
|
2302
|
+
if result.get("message"):
|
|
2303
|
+
print(result["message"])
|
|
2304
|
+
for plan in result["plans"]:
|
|
2305
|
+
print(f"- {plan['id']}: {plan.get('command') or '-'}")
|
|
2306
|
+
|
|
2307
|
+
|
|
2308
|
+
def print_tasks(result: dict[str, Any]) -> None:
|
|
2309
|
+
print(f"Tasks: {len(result['items'])}")
|
|
2310
|
+
for item in result["items"]:
|
|
2311
|
+
print(f"- {item['id']} {item['status']} {item.get('title') or '-'}")
|
|
2312
|
+
|
|
2313
|
+
|
|
2314
|
+
def print_task(result: dict[str, Any]) -> None:
|
|
2315
|
+
if result.get("message"):
|
|
2316
|
+
print(result["message"])
|
|
2317
|
+
task = result.get("task") or {}
|
|
2318
|
+
if task:
|
|
2319
|
+
print(f"Task {result['status']}: {task.get('id')}")
|
|
2320
|
+
print(f"Title: {task.get('title') or '-'}")
|
|
2321
|
+
print(f"Status: {task.get('status') or '-'}")
|
|
2322
|
+
|
|
2323
|
+
|
|
2324
|
+
def print_task_history(result: dict[str, Any]) -> None:
|
|
2325
|
+
print(result.get("history") or "No history.")
|
|
2326
|
+
|
|
2327
|
+
|
|
2328
|
+
def print_task_run(result: dict[str, Any]) -> None:
|
|
2329
|
+
print(f"Task run: {result['status']}")
|
|
2330
|
+
if result.get("message"):
|
|
2331
|
+
print(result["message"])
|
|
2332
|
+
|
|
2333
|
+
|
|
2334
|
+
def print_scheduler(result: dict[str, Any]) -> None:
|
|
2335
|
+
print(f"Scheduler: {result['status']}")
|
|
2336
|
+
if result.get("message"):
|
|
2337
|
+
print(result["message"])
|
|
2338
|
+
if "due_count" in result:
|
|
2339
|
+
print(f"Due tasks: {result['due_count']}")
|
|
2340
|
+
|
|
2341
|
+
|
|
2342
|
+
def print_calendar(result: dict[str, Any]) -> None:
|
|
2343
|
+
if result.get("status") != "ok":
|
|
2344
|
+
print(result.get("message") or "Calendar is not available.")
|
|
2345
|
+
for step in result.get("next_steps") or []:
|
|
2346
|
+
print(f"- {step}")
|
|
2347
|
+
return
|
|
2348
|
+
print(calendar_summary(result))
|
|
2349
|
+
|
|
2350
|
+
|
|
2351
|
+
def print_calendar_configure(result: dict[str, Any]) -> None:
|
|
2352
|
+
print(f"Calendar configured: {result['provider']}")
|
|
2353
|
+
print(f"Source: {result['source_ref']}")
|
|
2354
|
+
print("Stored secret: no")
|
|
2355
|
+
|
|
2356
|
+
|
|
2357
|
+
def print_pr(result: dict[str, Any]) -> None:
|
|
2358
|
+
if result.get("message"):
|
|
2359
|
+
print(result["message"])
|
|
2360
|
+
if result.get("items") is not None:
|
|
2361
|
+
print(summarize_pr_list(result))
|
|
2362
|
+
elif result.get("summary"):
|
|
2363
|
+
print(result["summary"])
|
|
2364
|
+
elif result.get("task"):
|
|
2365
|
+
task = result["task"]
|
|
2366
|
+
print(f"PR automation {result['status']}: {task.get('id')}")
|
|
2367
|
+
|
|
2368
|
+
|
|
2369
|
+
def print_permissions(result: dict[str, Any]) -> None:
|
|
2370
|
+
print(f"Permissions: {result['status']}")
|
|
2371
|
+
if result.get("default_level"):
|
|
2372
|
+
print(f"Default: {result['default_level']}")
|
|
2373
|
+
if result.get("grant"):
|
|
2374
|
+
grant = result["grant"]
|
|
2375
|
+
print(f"Grant: {grant.get('agent')} / {grant.get('provider')} -> {grant.get('level')}")
|
|
2376
|
+
if result.get("removed") is not None:
|
|
2377
|
+
print(f"Removed: {len(result.get('removed') or [])}")
|
|
2378
|
+
grants = result.get("grants")
|
|
2379
|
+
if grants is not None:
|
|
2380
|
+
if not grants:
|
|
2381
|
+
print("No explicit grants.")
|
|
2382
|
+
for grant in grants:
|
|
2383
|
+
print(f"- {grant.get('agent')} / {grant.get('provider')} -> {grant.get('level')}")
|
|
2384
|
+
if result.get("json_path"):
|
|
2385
|
+
print(f"Policy: {result['json_path']}")
|
|
2386
|
+
|
|
2387
|
+
|
|
2388
|
+
def print_audit(result: dict[str, Any]) -> None:
|
|
2389
|
+
if result["kind"] == "audit":
|
|
2390
|
+
print(f"Audit home: {result['home']}")
|
|
2391
|
+
for item in result.get("items") or []:
|
|
2392
|
+
print(f"- {item.get('id')} {item.get('created_at')} {item.get('command')} {item.get('status')}")
|
|
2393
|
+
return
|
|
2394
|
+
if result["kind"] == "audit-entry":
|
|
2395
|
+
entry = result.get("entry") or {}
|
|
2396
|
+
print(f"Audit: {entry.get('id')}")
|
|
2397
|
+
print(f"Created: {entry.get('created_at')}")
|
|
2398
|
+
print(f"Command: {entry.get('command')}")
|
|
2399
|
+
print(f"Status: {(entry.get('result') or {}).get('status')}")
|
|
2400
|
+
print(f"JSON: {result.get('json_path')}")
|
|
2401
|
+
print(f"Markdown: {result.get('markdown_path')}")
|
|
2402
|
+
return
|
|
2403
|
+
print(result.get("content") or "")
|
|
2404
|
+
|
|
2405
|
+
|
|
1260
2406
|
def print_llm_backends(result: dict[str, Any]) -> None:
|
|
1261
2407
|
print(f"LLM config: {result['config_path']}")
|
|
1262
2408
|
print(f"Default: {result.get('default') or '-'}")
|
|
@@ -1294,6 +2440,16 @@ def print_llm_default(result: dict[str, Any]) -> None:
|
|
|
1294
2440
|
print(f"Config: {result['config_path']}")
|
|
1295
2441
|
|
|
1296
2442
|
|
|
2443
|
+
def print_llm_preference(result: dict[str, Any]) -> None:
|
|
2444
|
+
print(f"LLM preference: {result.get('status', 'ok')}")
|
|
2445
|
+
print(f"Primary: {result.get('primary') or '-'}")
|
|
2446
|
+
print(f"Fallback enabled: {result.get('fallback_enabled', True)}")
|
|
2447
|
+
print("Order:")
|
|
2448
|
+
for backend_id in result.get("order") or []:
|
|
2449
|
+
print(f"- {backend_id}")
|
|
2450
|
+
print(f"Config: {result['config_path']}")
|
|
2451
|
+
|
|
2452
|
+
|
|
1297
2453
|
def print_providers(result: dict[str, Any]) -> None:
|
|
1298
2454
|
if not result["items"]:
|
|
1299
2455
|
print("No providers found.")
|
|
@@ -1368,6 +2524,58 @@ def print_credential_backends(result: dict[str, Any]) -> None:
|
|
|
1368
2524
|
print(f"- {item}")
|
|
1369
2525
|
|
|
1370
2526
|
|
|
2527
|
+
def print_config(result: dict[str, Any]) -> None:
|
|
2528
|
+
print(f"Config: {result.get('path')}")
|
|
2529
|
+
if result.get("llm"):
|
|
2530
|
+
print(f"Primary LLM: {(result['llm'] or {}).get('primary') or '-'}")
|
|
2531
|
+
if result.get("ollama"):
|
|
2532
|
+
print(f"Ollama: {(result['ollama'] or {}).get('status')}")
|
|
2533
|
+
|
|
2534
|
+
|
|
2535
|
+
def print_control(result: dict[str, Any]) -> None:
|
|
2536
|
+
kind = result.get("kind")
|
|
2537
|
+
if kind == "decisions-reset":
|
|
2538
|
+
print(f"Decisions reset: {result.get('category') or 'all'}")
|
|
2539
|
+
print(f"Path: {result.get('path')}")
|
|
2540
|
+
return
|
|
2541
|
+
if "items" in result:
|
|
2542
|
+
print(f"{kind}:")
|
|
2543
|
+
for item in result.get("items") or []:
|
|
2544
|
+
print(f"- {item.get('category')}:{item.get('id')} {item.get('state')}")
|
|
2545
|
+
if not result.get("items"):
|
|
2546
|
+
print("- none")
|
|
2547
|
+
return
|
|
2548
|
+
item = result.get("item") or result
|
|
2549
|
+
print(f"{item.get('category') or result.get('category')}:{item.get('id') or result.get('id')} {item.get('state') or result.get('state')}")
|
|
2550
|
+
|
|
2551
|
+
|
|
2552
|
+
def print_ollama(result: dict[str, Any]) -> None:
|
|
2553
|
+
kind = result.get("kind")
|
|
2554
|
+
if kind == "ollama-status":
|
|
2555
|
+
print(f"Ollama: {result.get('status')}")
|
|
2556
|
+
print(f"Binary: {result.get('binary') or '-'}")
|
|
2557
|
+
print(f"Version: {result.get('version') or '-'}")
|
|
2558
|
+
daemon = result.get("daemon") or {}
|
|
2559
|
+
print(f"Daemon: {daemon.get('status') or '-'}")
|
|
2560
|
+
print(f"Models: {result.get('model_count', 0)}")
|
|
2561
|
+
if result.get("install_plan"):
|
|
2562
|
+
print(f"Install: {(result['install_plan'] or {}).get('command')}")
|
|
2563
|
+
return
|
|
2564
|
+
if kind == "ollama-models":
|
|
2565
|
+
print(f"Ollama models: {result.get('status')}")
|
|
2566
|
+
for item in result.get("items") or []:
|
|
2567
|
+
print(f"- {item.get('name')} {item.get('size') or '-'}")
|
|
2568
|
+
if not result.get("items"):
|
|
2569
|
+
print("- none")
|
|
2570
|
+
return
|
|
2571
|
+
print(f"{kind}: {result.get('status')}")
|
|
2572
|
+
if result.get("command"):
|
|
2573
|
+
command = result["command"]
|
|
2574
|
+
print("Command: " + (" ".join(command) if isinstance(command, list) else str(command)))
|
|
2575
|
+
if result.get("message"):
|
|
2576
|
+
print(result["message"])
|
|
2577
|
+
|
|
2578
|
+
|
|
1371
2579
|
def print_install(result: dict[str, Any]) -> None:
|
|
1372
2580
|
print(f"AI DevKit install: {result['status']}")
|
|
1373
2581
|
print(f"Scope: {result['scope']}")
|