agent-devkit 0.2.0 → 0.3.1
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 +66 -13
- package/bin/agent.mjs +133 -7
- package/package.json +1 -1
- package/runtime/README.md +205 -13
- package/runtime/agent +31 -5
- package/runtime/agents/README.md +18 -0
- package/runtime/agents/contribution-reviewer/AGENTS.md +8 -0
- package/runtime/agents/contribution-reviewer/README.md +8 -0
- package/runtime/agents/contribution-reviewer/agent.yaml +40 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/capability.yaml +27 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/workflow.md +6 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/capability.yaml +25 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/workflow.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/capability.yaml +26 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/workflow.md +6 -0
- package/runtime/agents/contribution-reviewer/infra/README.md +6 -0
- package/runtime/agents/contribution-reviewer/knowledge/context.md +8 -0
- package/runtime/agents/contribution-reviewer/knowledge/system.md +8 -0
- package/runtime/agents/contribution-reviewer/templates/README.md +3 -0
- package/runtime/agents/knowledge-author/AGENTS.md +7 -0
- package/runtime/agents/knowledge-author/README.md +7 -0
- package/runtime/agents/knowledge-author/agent.yaml +37 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/capability.yaml +30 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-author/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-author/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-author/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-author/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-curator/AGENTS.md +7 -0
- package/runtime/agents/knowledge-curator/README.md +6 -0
- package/runtime/agents/knowledge-curator/agent.yaml +37 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/capability.yaml +29 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/decision-rules.md +6 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/workflow.md +7 -0
- package/runtime/agents/knowledge-curator/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-curator/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-curator/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-curator/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-infra-builder/AGENTS.md +8 -0
- package/runtime/agents/knowledge-infra-builder/README.md +8 -0
- package/runtime/agents/knowledge-infra-builder/agent.yaml +38 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/capability.yaml +30 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/decision-rules.md +6 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/workflow.md +7 -0
- package/runtime/agents/knowledge-infra-builder/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-infra-builder/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-infra-builder/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-infra-builder/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-owner/AGENTS.md +7 -0
- package/runtime/agents/knowledge-owner/README.md +6 -0
- package/runtime/agents/knowledge-owner/agent.yaml +37 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/capability.yaml +28 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-owner/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-owner/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-owner/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-owner/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-reviewer/AGENTS.md +7 -0
- package/runtime/agents/knowledge-reviewer/README.md +7 -0
- package/runtime/agents/knowledge-reviewer/agent.yaml +36 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/capability.yaml +26 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-reviewer/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-reviewer/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-reviewer/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-reviewer/templates/.gitkeep +1 -0
- package/runtime/agents/local-memory-manager/AGENTS.md +5 -0
- package/runtime/agents/local-memory-manager/README.md +7 -0
- package/runtime/agents/local-memory-manager/agent.yaml +38 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/capability.yaml +19 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/decision-rules.md +5 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/workflow.md +6 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/capability.yaml +19 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/decision-rules.md +5 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/workflow.md +5 -0
- package/runtime/agents/local-memory-manager/infra/.gitkeep +1 -0
- package/runtime/agents/local-memory-manager/knowledge/context.md +4 -0
- package/runtime/agents/local-memory-manager/knowledge/system.md +4 -0
- package/runtime/agents/local-memory-manager/templates/.gitkeep +1 -0
- package/runtime/agents/memory-sync-manager/AGENTS.md +7 -0
- package/runtime/agents/memory-sync-manager/README.md +7 -0
- package/runtime/agents/memory-sync-manager/agent.yaml +37 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/capability.yaml +29 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/decision-rules.md +6 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/workflow.md +7 -0
- package/runtime/agents/memory-sync-manager/infra/.gitkeep +1 -0
- package/runtime/agents/memory-sync-manager/knowledge/context.md +4 -0
- package/runtime/agents/memory-sync-manager/knowledge/system.md +4 -0
- package/runtime/agents/memory-sync-manager/templates/.gitkeep +1 -0
- package/runtime/agents/shared-memory-curator/AGENTS.md +5 -0
- package/runtime/agents/shared-memory-curator/README.md +6 -0
- package/runtime/agents/shared-memory-curator/agent.yaml +38 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/infra/.gitkeep +1 -0
- package/runtime/agents/shared-memory-curator/knowledge/context.md +5 -0
- package/runtime/agents/shared-memory-curator/knowledge/system.md +4 -0
- package/runtime/agents/shared-memory-curator/templates/.gitkeep +1 -0
- package/runtime/cli/README.md +47 -8
- package/runtime/cli/aikit/__init__.py +1 -1
- package/runtime/cli/aikit/agent_registry.py +4 -2
- package/runtime/cli/aikit/agentic_commands.py +158 -0
- package/runtime/cli/aikit/app_home.py +2 -0
- package/runtime/cli/aikit/audit.py +16 -6
- package/runtime/cli/aikit/catalog.py +278 -8
- package/runtime/cli/aikit/cli_dispatch.py +489 -13
- package/runtime/cli/aikit/cli_parser.py +146 -8
- package/runtime/cli/aikit/contribution.py +132 -2
- package/runtime/cli/aikit/doctor_runtime.py +85 -0
- package/runtime/cli/aikit/embedded_mini_brain.py +351 -0
- package/runtime/cli/aikit/eval.py +356 -10
- package/runtime/cli/aikit/human_output.py +310 -4
- package/runtime/cli/aikit/interactive_wizard.py +146 -0
- package/runtime/cli/aikit/knowledge_base.py +1067 -0
- package/runtime/cli/aikit/llm.py +40 -6
- package/runtime/cli/aikit/local_artifacts.py +444 -0
- package/runtime/cli/aikit/local_llm.py +176 -0
- package/runtime/cli/aikit/local_llm_operator.py +15 -5
- package/runtime/cli/aikit/main.py +15 -0
- package/runtime/cli/aikit/mcp_manifest.py +798 -0
- package/runtime/cli/aikit/mcp_tools.py +643 -5
- package/runtime/cli/aikit/memory.py +405 -0
- package/runtime/cli/aikit/mini_brain.py +56 -25
- package/runtime/cli/aikit/model_router.py +42 -9
- package/runtime/cli/aikit/natural_prompt_runtime.py +194 -2
- package/runtime/cli/aikit/ollama.py +64 -15
- package/runtime/cli/aikit/onboarding.py +551 -0
- package/runtime/cli/aikit/output.py +67 -0
- package/runtime/cli/aikit/prompt_injection.py +12 -1
- package/runtime/cli/aikit/review_gate.py +14 -2
- package/runtime/cli/aikit/roadmap_cli.py +1 -1
- package/runtime/cli/aikit/secrets.py +3 -2
- package/runtime/cli/aikit/setup_wizard_payload.py +3 -0
- package/runtime/cli/aikit/shared_memory.py +415 -0
- package/runtime/cli/aikit/specialist_readiness.py +152 -0
- package/runtime/cli/aikit/tasks.py +104 -1
- package/runtime/cli/aikit/team.py +380 -0
- package/runtime/cli/aikit/toolchain.py +7 -2
- package/runtime/cli/aikit/workflows.py +115 -14
- package/runtime/models/qwen2.5-0.5b-instruct/manifest.json +30 -0
- package/runtime/providers/knowledge-github.yaml +40 -0
- package/runtime/providers/knowledge-google-drive.yaml +32 -0
- package/runtime/providers/knowledge-local.yaml +26 -0
- package/runtime/providers/knowledge-notion.yaml +32 -0
- package/runtime/providers/knowledge-obsidian.yaml +24 -0
- package/runtime/providers/knowledge-onedrive.yaml +36 -0
- package/runtime/providers/knowledge-s3.yaml +45 -0
- package/runtime/providers/knowledge-sharepoint.yaml +39 -0
- package/runtime/providers/knowledge-supabase.yaml +43 -0
- package/runtime/providers/knowledge-vector.yaml +39 -0
- package/runtime/requirements.txt +6 -0
- package/runtime/scripts/docker-cli-qa.sh +453 -0
- package/runtime/scripts/release-catalog-snapshot.json +55 -4
- package/runtime/scripts/release-gate.py +54 -13
- package/runtime/tooling/toolchain.yaml +92 -0
- package/runtime/vendor/skills/napkin/napkin.md +21 -7
- package/runtime/workflows/azure-card-analysis/README.md +3 -0
- package/runtime/workflows/azure-card-analysis/workflow.yaml +30 -0
- package/runtime/workflows/daily-pr-review/README.md +3 -0
- package/runtime/workflows/daily-pr-review/workflow.yaml +31 -0
- package/runtime/workflows/incident-analysis/README.md +3 -0
- package/runtime/workflows/incident-analysis/workflow.yaml +33 -0
- package/runtime/workflows/release-prep/README.md +3 -0
- package/runtime/workflows/release-prep/workflow.yaml +30 -0
package/runtime/cli/aikit/llm.py
CHANGED
|
@@ -14,6 +14,14 @@ from pathlib import Path
|
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
16
|
from cli.aikit.app_home import app_home, config_path as app_config_path, ensure_app_home
|
|
17
|
+
from cli.aikit.embedded_mini_brain import (
|
|
18
|
+
EMBEDDED_BACKEND_ID,
|
|
19
|
+
EMBEDDED_MODEL_ID,
|
|
20
|
+
EmbeddedMiniBrainError,
|
|
21
|
+
embedded_backend_config,
|
|
22
|
+
embedded_backend_doctor,
|
|
23
|
+
invoke_embedded_mini_brain,
|
|
24
|
+
)
|
|
17
25
|
from cli.aikit.identity import host_cli_prompt, identity_system_prompt
|
|
18
26
|
|
|
19
27
|
|
|
@@ -33,6 +41,14 @@ class LlmBackend:
|
|
|
33
41
|
|
|
34
42
|
|
|
35
43
|
BACKENDS: dict[str, LlmBackend] = {
|
|
44
|
+
EMBEDDED_BACKEND_ID: LlmBackend(
|
|
45
|
+
id=EMBEDDED_BACKEND_ID,
|
|
46
|
+
display_name="Embedded mini-brain",
|
|
47
|
+
kind="embedded-local",
|
|
48
|
+
auth="none",
|
|
49
|
+
default_model=EMBEDDED_MODEL_ID,
|
|
50
|
+
notes="Uses the Agent DevKit embedded mini-brain for setup, onboarding and low-risk conversation.",
|
|
51
|
+
),
|
|
36
52
|
"openai": LlmBackend(
|
|
37
53
|
id="openai",
|
|
38
54
|
display_name="OpenAI API",
|
|
@@ -77,7 +93,7 @@ BACKENDS: dict[str, LlmBackend] = {
|
|
|
77
93
|
base_url_env="OLLAMA_BASE_URL",
|
|
78
94
|
model_env="OLLAMA_MODEL",
|
|
79
95
|
default_base_url="http://localhost:11434/v1",
|
|
80
|
-
default_model="
|
|
96
|
+
default_model="qwen3:0.6b",
|
|
81
97
|
notes="Uses a local Ollama server through an OpenAI-compatible endpoint.",
|
|
82
98
|
),
|
|
83
99
|
"codex-cli": LlmBackend(
|
|
@@ -100,7 +116,7 @@ BACKENDS: dict[str, LlmBackend] = {
|
|
|
100
116
|
|
|
101
117
|
ENV_VAR_NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
102
118
|
DEFAULT_AGENT_TIMEOUT_SECONDS = 120
|
|
103
|
-
DEFAULT_FALLBACK_ORDER = ("claude-code", "codex-cli", "openai", "anthropic", "openrouter", "ollama")
|
|
119
|
+
DEFAULT_FALLBACK_ORDER = ("claude-code", "codex-cli", "openai", "anthropic", "openrouter", "ollama", EMBEDDED_BACKEND_ID)
|
|
104
120
|
|
|
105
121
|
|
|
106
122
|
def config_home() -> Path:
|
|
@@ -323,6 +339,8 @@ def normalize_backend_order(order: str | list[str] | tuple[str, ...]) -> list[st
|
|
|
323
339
|
|
|
324
340
|
|
|
325
341
|
def default_backend_config(backend: LlmBackend) -> dict[str, Any]:
|
|
342
|
+
if backend.id == EMBEDDED_BACKEND_ID:
|
|
343
|
+
return embedded_backend_config()
|
|
326
344
|
entry: dict[str, Any] = {"kind": backend.kind, "auth": backend.auth}
|
|
327
345
|
if backend.auth == "api-key-env":
|
|
328
346
|
entry["api_key_ref"] = f"env:{backend.api_key_env}"
|
|
@@ -346,7 +364,8 @@ def doctor_backends(backend_id: str | None = None) -> dict[str, Any]:
|
|
|
346
364
|
|
|
347
365
|
checks = [doctor_backend(BACKENDS[item], config) for item in ids]
|
|
348
366
|
status = "ok"
|
|
349
|
-
|
|
367
|
+
missing_statuses = {"missing", "not-installed", "dependency-missing", "invalid-model"}
|
|
368
|
+
if any(item["status"] in missing_statuses for item in checks):
|
|
350
369
|
status = "partial" if not backend_id else "missing"
|
|
351
370
|
if any(item["status"] == "error" for item in checks):
|
|
352
371
|
status = "error"
|
|
@@ -361,6 +380,8 @@ def doctor_backends(backend_id: str | None = None) -> dict[str, Any]:
|
|
|
361
380
|
|
|
362
381
|
|
|
363
382
|
def doctor_backend(backend: LlmBackend, config: dict[str, Any]) -> dict[str, Any]:
|
|
383
|
+
if backend.id == EMBEDDED_BACKEND_ID:
|
|
384
|
+
return embedded_backend_doctor()
|
|
364
385
|
configured = config.get("llm", {}).get("backends", {}).get(backend.id, {})
|
|
365
386
|
if not isinstance(configured, dict):
|
|
366
387
|
configured = {}
|
|
@@ -387,18 +408,26 @@ def doctor_backend(backend: LlmBackend, config: dict[str, Any]) -> dict[str, Any
|
|
|
387
408
|
}
|
|
388
409
|
|
|
389
410
|
if backend.auth == "none":
|
|
390
|
-
|
|
411
|
+
env_base_url = env_value(backend.base_url_env)
|
|
412
|
+
base_url = configured.get("base_url") or env_base_url or backend.default_base_url
|
|
391
413
|
model = configured.get("model") or env_value(backend.model_env) or backend.default_model
|
|
414
|
+
binary = shutil.which(backend.id) if backend.id == "ollama" else None
|
|
415
|
+
local_available = bool(configured or env_base_url or binary)
|
|
392
416
|
return {
|
|
393
417
|
"id": backend.id,
|
|
394
418
|
"display_name": backend.display_name,
|
|
395
419
|
"kind": backend.kind,
|
|
396
|
-
"status": "ok" if
|
|
420
|
+
"status": "ok" if local_available else "missing",
|
|
397
421
|
"configured": bool(configured),
|
|
398
422
|
"base_url": base_url,
|
|
399
423
|
"model": model,
|
|
424
|
+
"binary": binary,
|
|
400
425
|
"health": "unchecked",
|
|
401
|
-
"message":
|
|
426
|
+
"message": (
|
|
427
|
+
"Local backend configured; daemon health is not probed by default."
|
|
428
|
+
if local_available
|
|
429
|
+
else "Local backend is not configured and no local binary was found in PATH."
|
|
430
|
+
),
|
|
402
431
|
}
|
|
403
432
|
|
|
404
433
|
api_key_env = configured.get("api_key_env") or backend.api_key_env
|
|
@@ -608,6 +637,11 @@ class LlmPolicyError(LlmInvocationError):
|
|
|
608
637
|
def invoke_resolved_backend(backend: dict[str, Any], prompt: str, *, public_name: str = "Agent DevKit") -> str:
|
|
609
638
|
kind = backend.get("kind")
|
|
610
639
|
backend_id = backend.get("id")
|
|
640
|
+
if kind == "embedded-local" and backend_id == EMBEDDED_BACKEND_ID:
|
|
641
|
+
try:
|
|
642
|
+
return invoke_embedded_mini_brain(prompt, public_name=public_name)
|
|
643
|
+
except EmbeddedMiniBrainError as exc:
|
|
644
|
+
raise LlmInvocationError(str(exc)) from exc
|
|
611
645
|
if kind == "openai-compatible":
|
|
612
646
|
return invoke_openai_compatible(backend, prompt, public_name=public_name)
|
|
613
647
|
if kind == "anthropic":
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""Local user-created skills, scripts and agent scaffolds."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from cli.aikit.app_home import app_home, ensure_app_home
|
|
14
|
+
from cli.aikit.errors import DevKitError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
LOCAL_ARTIFACT_SCHEMA_VERSION = "agent-devkit.local-artifacts/v1"
|
|
18
|
+
ID_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]*$")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def skill_create(skill_id: str | None, *, description: str | None = None, force: bool = False) -> dict[str, Any]:
|
|
22
|
+
item_id = require_id(skill_id, "skill id")
|
|
23
|
+
path = local_home("skills") / item_id
|
|
24
|
+
if path.exists() and not force:
|
|
25
|
+
raise DevKitError(f"local skill already exists: {item_id}")
|
|
26
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
skill_path = path / "SKILL.md"
|
|
28
|
+
if force or not skill_path.exists():
|
|
29
|
+
skill_path.write_text(render_skill(item_id, description), encoding="utf-8")
|
|
30
|
+
return local_artifact_payload("local-skill", "created", item_id, path, write_policy="local_config_write")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def skill_list() -> dict[str, Any]:
|
|
34
|
+
return local_artifact_list("local-skills", "skills", marker="SKILL.md")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def skill_show(skill_id: str | None) -> dict[str, Any]:
|
|
38
|
+
return local_artifact_show("local-skill", "skills", require_id(skill_id, "skill id"), marker="SKILL.md")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def skill_update(skill_id: str | None, *, description: str | None = None) -> dict[str, Any]:
|
|
42
|
+
item_id = require_id(skill_id, "skill id")
|
|
43
|
+
path = local_home("skills") / item_id
|
|
44
|
+
if not path.exists():
|
|
45
|
+
raise DevKitError(f"local skill not found: {item_id}")
|
|
46
|
+
(path / "SKILL.md").write_text(render_skill(item_id, description), encoding="utf-8")
|
|
47
|
+
return local_artifact_payload("local-skill", "updated", item_id, path, write_policy="local_config_write")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def skill_delete(skill_id: str | None, *, yes: bool = False) -> dict[str, Any]:
|
|
51
|
+
item_id = require_id(skill_id, "skill id")
|
|
52
|
+
path = local_home("skills") / item_id
|
|
53
|
+
if not yes:
|
|
54
|
+
payload = local_artifact_payload("local-skill", "needs-confirmation", item_id, path, write_policy="local_config_write")
|
|
55
|
+
payload["ok"] = False
|
|
56
|
+
payload["exit_code"] = 2
|
|
57
|
+
return payload
|
|
58
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
59
|
+
return local_artifact_payload("local-skill", "deleted", item_id, path, write_policy="local_config_write")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def script_create(script_id: str | None, *, command: str | None = None, force: bool = False) -> dict[str, Any]:
|
|
63
|
+
item_id = require_id(script_id, "script id")
|
|
64
|
+
path = local_home("scripts") / f"{item_id}.sh"
|
|
65
|
+
if path.exists() and not force:
|
|
66
|
+
raise DevKitError(f"local script already exists: {item_id}")
|
|
67
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
path.write_text(render_script(command), encoding="utf-8")
|
|
69
|
+
path.chmod(0o755)
|
|
70
|
+
return local_artifact_payload("local-script", "created", item_id, path, write_policy="local_write")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def script_list() -> dict[str, Any]:
|
|
74
|
+
home = local_home("scripts")
|
|
75
|
+
items = [
|
|
76
|
+
local_artifact_item(path.stem, path, "local-script", enabled=True)
|
|
77
|
+
for path in sorted(home.glob("*.sh"))
|
|
78
|
+
]
|
|
79
|
+
return {"kind": "local-scripts", "schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION, "status": "ok", "home": str(home), "items": items}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def script_run(script_id: str | None, *, dry_run: bool = False, yes: bool = False) -> dict[str, Any]:
|
|
83
|
+
item_id = require_id(script_id, "script id")
|
|
84
|
+
path = local_home("scripts") / f"{item_id}.sh"
|
|
85
|
+
if not path.exists():
|
|
86
|
+
raise DevKitError(f"local script not found: {item_id}")
|
|
87
|
+
if dry_run or not yes:
|
|
88
|
+
return {
|
|
89
|
+
"kind": "local-script-run",
|
|
90
|
+
"schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION,
|
|
91
|
+
"status": "planned" if dry_run else "needs-confirmation",
|
|
92
|
+
"ok": bool(dry_run),
|
|
93
|
+
"id": item_id,
|
|
94
|
+
"path": str(path),
|
|
95
|
+
"command": [str(path)],
|
|
96
|
+
"write_policy": "local_write",
|
|
97
|
+
"message": "Use --yes to run the local script." if not dry_run and not yes else "Dry-run only.",
|
|
98
|
+
**({} if dry_run else {"exit_code": 2}),
|
|
99
|
+
}
|
|
100
|
+
process = subprocess.run([str(path)], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=300)
|
|
101
|
+
return {
|
|
102
|
+
"kind": "local-script-run",
|
|
103
|
+
"schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION,
|
|
104
|
+
"status": "ok" if process.returncode == 0 else "failed",
|
|
105
|
+
"ok": process.returncode == 0,
|
|
106
|
+
"id": item_id,
|
|
107
|
+
"path": str(path),
|
|
108
|
+
"exit_code": process.returncode,
|
|
109
|
+
"stdout": process.stdout[-4000:],
|
|
110
|
+
"stderr": process.stderr[-4000:],
|
|
111
|
+
"write_policy": "local_write",
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def local_agent_create(agent_id: str | None, *, description: str | None = None, force: bool = False) -> dict[str, Any]:
|
|
116
|
+
item_id = require_id(agent_id, "agent id")
|
|
117
|
+
path = local_home("agents") / item_id
|
|
118
|
+
if path.exists() and not force:
|
|
119
|
+
raise DevKitError(f"local agent already exists: {item_id}")
|
|
120
|
+
(path / "capabilities").mkdir(parents=True, exist_ok=True)
|
|
121
|
+
(path / "knowledge").mkdir(parents=True, exist_ok=True)
|
|
122
|
+
(path / "templates").mkdir(parents=True, exist_ok=True)
|
|
123
|
+
(path / "infra").mkdir(parents=True, exist_ok=True)
|
|
124
|
+
(path / "agent.yaml").write_text(render_agent_yaml(item_id, description), encoding="utf-8")
|
|
125
|
+
(path / "README.md").write_text(f"# {item_id}\n\n{description or 'Local Agent DevKit agent.'}\n", encoding="utf-8")
|
|
126
|
+
return local_artifact_payload("local-agent", "created", item_id, path, write_policy="local_config_write")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def local_agent_validate(agent_id: str | None) -> dict[str, Any]:
|
|
130
|
+
item_id = require_id(agent_id, "agent id")
|
|
131
|
+
path = local_home("agents") / item_id
|
|
132
|
+
checks = [
|
|
133
|
+
{"id": "path-exists", "status": "passed" if path.exists() else "failed"},
|
|
134
|
+
{"id": "agent-yaml", "status": "passed" if (path / "agent.yaml").exists() else "failed"},
|
|
135
|
+
{"id": "capabilities-dir", "status": "passed" if (path / "capabilities").is_dir() else "failed"},
|
|
136
|
+
{"id": "knowledge-dir", "status": "passed" if (path / "knowledge").is_dir() else "failed"},
|
|
137
|
+
]
|
|
138
|
+
return {
|
|
139
|
+
"kind": "local-agent-validation",
|
|
140
|
+
"schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION,
|
|
141
|
+
"status": "passed" if all(check["status"] == "passed" for check in checks) else "failed",
|
|
142
|
+
"id": item_id,
|
|
143
|
+
"path": str(path),
|
|
144
|
+
"checks": checks,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def local_agent_show(agent_id: str | None) -> dict[str, Any]:
|
|
149
|
+
item_id = require_id(agent_id, "agent id")
|
|
150
|
+
path = local_home("agents") / item_id
|
|
151
|
+
if not (path / "agent.yaml").exists():
|
|
152
|
+
raise DevKitError(f"local agent not found: {item_id}")
|
|
153
|
+
payload = local_artifact_payload("local-agent", "ok", item_id, path, write_policy="read_only")
|
|
154
|
+
payload["manifest"] = (path / "agent.yaml").read_text(encoding="utf-8")
|
|
155
|
+
readme = path / "README.md"
|
|
156
|
+
if readme.exists():
|
|
157
|
+
payload["readme"] = readme.read_text(encoding="utf-8")
|
|
158
|
+
return payload
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def local_agent_list() -> dict[str, Any]:
|
|
162
|
+
return local_artifact_list("local-agents", "agents", marker="agent.yaml")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def local_automation_create(
|
|
166
|
+
automation_id: str | None,
|
|
167
|
+
*,
|
|
168
|
+
title: str | None = None,
|
|
169
|
+
prompt: str | None = None,
|
|
170
|
+
command: str | None = None,
|
|
171
|
+
every: str | None = None,
|
|
172
|
+
cron: str | None = None,
|
|
173
|
+
force: bool = False,
|
|
174
|
+
) -> dict[str, Any]:
|
|
175
|
+
item_id = generated_id(automation_id, title, prefix="automation")
|
|
176
|
+
path = local_home("automations") / item_id
|
|
177
|
+
manifest = path / "automation.json"
|
|
178
|
+
if manifest.exists() and not force:
|
|
179
|
+
raise DevKitError(f"local automation already exists: {item_id}")
|
|
180
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
payload = automation_manifest(item_id, title=title, prompt=prompt, command=command, every=every, cron=cron, enabled=True)
|
|
182
|
+
write_json(manifest, payload)
|
|
183
|
+
if command:
|
|
184
|
+
script_path = path / "run.sh"
|
|
185
|
+
script_path.write_text(render_script(command), encoding="utf-8")
|
|
186
|
+
script_path.chmod(0o755)
|
|
187
|
+
return local_automation_payload("created", item_id, path, payload)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def local_automation_list() -> dict[str, Any]:
|
|
191
|
+
home = local_home("automations")
|
|
192
|
+
items = []
|
|
193
|
+
for manifest in sorted(home.glob("*/automation.json")):
|
|
194
|
+
payload = read_json(manifest)
|
|
195
|
+
item_id = str(payload.get("id") or manifest.parent.name)
|
|
196
|
+
items.append(
|
|
197
|
+
{
|
|
198
|
+
"id": item_id,
|
|
199
|
+
"kind": "local-automation",
|
|
200
|
+
"path": str(manifest.parent),
|
|
201
|
+
"enabled": payload.get("enabled") is True,
|
|
202
|
+
"title": payload.get("title"),
|
|
203
|
+
"schedule": payload.get("schedule") or {},
|
|
204
|
+
"write_policy": payload.get("write_policy") or "local_write",
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
return {"kind": "local-automations", "schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION, "status": "ok", "home": str(home), "items": items}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def local_automation_show(automation_id: str | None) -> dict[str, Any]:
|
|
211
|
+
item_id = require_id(automation_id, "automation id")
|
|
212
|
+
path = local_home("automations") / item_id
|
|
213
|
+
payload = read_required_automation(path, item_id)
|
|
214
|
+
return local_automation_payload("ok", item_id, path, payload)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def local_automation_update(
|
|
218
|
+
automation_id: str | None,
|
|
219
|
+
*,
|
|
220
|
+
title: str | None = None,
|
|
221
|
+
prompt: str | None = None,
|
|
222
|
+
command: str | None = None,
|
|
223
|
+
every: str | None = None,
|
|
224
|
+
cron: str | None = None,
|
|
225
|
+
) -> dict[str, Any]:
|
|
226
|
+
item_id = require_id(automation_id, "automation id")
|
|
227
|
+
path = local_home("automations") / item_id
|
|
228
|
+
current = read_required_automation(path, item_id)
|
|
229
|
+
current_schedule = current.get("schedule") if isinstance(current.get("schedule"), dict) else {"type": "manual"}
|
|
230
|
+
updated = automation_manifest(
|
|
231
|
+
item_id,
|
|
232
|
+
title=title if title is not None else str(current.get("title") or ""),
|
|
233
|
+
prompt=prompt if prompt is not None else current.get("prompt"),
|
|
234
|
+
command=command if command is not None else current.get("command"),
|
|
235
|
+
every=every,
|
|
236
|
+
cron=cron,
|
|
237
|
+
enabled=current.get("enabled") is not False,
|
|
238
|
+
created_at=str(current.get("created_at") or now_iso()),
|
|
239
|
+
)
|
|
240
|
+
if every is None and cron is None:
|
|
241
|
+
updated["schedule"] = current_schedule
|
|
242
|
+
write_json(path / "automation.json", updated)
|
|
243
|
+
return local_automation_payload("updated", item_id, path, updated)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def local_automation_enable(automation_id: str | None, enabled: bool) -> dict[str, Any]:
|
|
247
|
+
item_id = require_id(automation_id, "automation id")
|
|
248
|
+
path = local_home("automations") / item_id
|
|
249
|
+
payload = read_required_automation(path, item_id)
|
|
250
|
+
payload["enabled"] = enabled
|
|
251
|
+
payload["updated_at"] = now_iso()
|
|
252
|
+
write_json(path / "automation.json", payload)
|
|
253
|
+
return local_automation_payload("enabled" if enabled else "disabled", item_id, path, payload)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def local_automation_remove(automation_id: str | None, *, yes: bool = False) -> dict[str, Any]:
|
|
257
|
+
item_id = require_id(automation_id, "automation id")
|
|
258
|
+
path = local_home("automations") / item_id
|
|
259
|
+
if not yes:
|
|
260
|
+
payload = local_artifact_payload("local-automation", "needs-confirmation", item_id, path, write_policy="local_config_write")
|
|
261
|
+
payload["ok"] = False
|
|
262
|
+
payload["exit_code"] = 2
|
|
263
|
+
return payload
|
|
264
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
265
|
+
return local_artifact_payload("local-automation", "removed", item_id, path, write_policy="local_config_write")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def local_automation_validate(automation_id: str | None) -> dict[str, Any]:
|
|
269
|
+
item_id = require_id(automation_id, "automation id")
|
|
270
|
+
path = local_home("automations") / item_id
|
|
271
|
+
payload = read_json(path / "automation.json") if (path / "automation.json").exists() else {}
|
|
272
|
+
checks = [
|
|
273
|
+
{"id": "path-exists", "status": "passed" if path.exists() else "failed"},
|
|
274
|
+
{"id": "manifest-exists", "status": "passed" if (path / "automation.json").exists() else "failed"},
|
|
275
|
+
{"id": "has-action", "status": "passed" if payload.get("prompt") or payload.get("command") else "failed"},
|
|
276
|
+
{"id": "no-stored-secret", "status": "passed" if not contains_secret_like_text(json.dumps(payload, ensure_ascii=False)) else "failed"},
|
|
277
|
+
]
|
|
278
|
+
return {
|
|
279
|
+
"kind": "local-automation-validation",
|
|
280
|
+
"schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION,
|
|
281
|
+
"status": "passed" if all(check["status"] == "passed" for check in checks) else "failed",
|
|
282
|
+
"id": item_id,
|
|
283
|
+
"path": str(path),
|
|
284
|
+
"checks": checks,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def local_home(*parts: str) -> Path:
|
|
289
|
+
ensure_app_home()
|
|
290
|
+
path = app_home() / "local" / Path(*parts)
|
|
291
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
return path
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def local_artifact_list(kind: str, folder: str, *, marker: str) -> dict[str, Any]:
|
|
296
|
+
home = local_home(folder)
|
|
297
|
+
items = [
|
|
298
|
+
local_artifact_item(path.parent.name, path.parent, kind.removesuffix("s"), enabled=True)
|
|
299
|
+
for path in sorted(home.glob(f"*/{marker}"))
|
|
300
|
+
]
|
|
301
|
+
return {"kind": kind, "schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION, "status": "ok", "home": str(home), "items": items}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def local_artifact_show(kind: str, folder: str, item_id: str, *, marker: str) -> dict[str, Any]:
|
|
305
|
+
path = local_home(folder) / item_id
|
|
306
|
+
marker_path = path / marker
|
|
307
|
+
if not marker_path.exists():
|
|
308
|
+
raise DevKitError(f"{kind} not found: {item_id}")
|
|
309
|
+
payload = local_artifact_payload(kind, "ok", item_id, path, write_policy="read_only")
|
|
310
|
+
payload["content"] = marker_path.read_text(encoding="utf-8")
|
|
311
|
+
return payload
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def local_artifact_payload(kind: str, status: str, item_id: str, path: Path, *, write_policy: str) -> dict[str, Any]:
|
|
315
|
+
return {
|
|
316
|
+
"kind": kind,
|
|
317
|
+
"schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION,
|
|
318
|
+
"status": status,
|
|
319
|
+
"id": item_id,
|
|
320
|
+
"path": str(path),
|
|
321
|
+
"write_policy": write_policy,
|
|
322
|
+
"stored_secret": False,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def local_artifact_item(item_id: str, path: Path, kind: str, *, enabled: bool) -> dict[str, Any]:
|
|
327
|
+
return {"id": item_id, "kind": kind, "path": str(path), "enabled": enabled}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def require_id(value: str | None, label: str) -> str:
|
|
331
|
+
item_id = (value or "").strip()
|
|
332
|
+
if not ID_PATTERN.fullmatch(item_id):
|
|
333
|
+
raise DevKitError(f"{label} must use lowercase letters, numbers, dots, dashes or underscores")
|
|
334
|
+
return item_id
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def generated_id(value: str | None, title: str | None, *, prefix: str) -> str:
|
|
338
|
+
if value:
|
|
339
|
+
return require_id(value, f"{prefix} id")
|
|
340
|
+
raw = title or f"{prefix}-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"
|
|
341
|
+
slug = re.sub(r"[^a-z0-9._-]+", "-", raw.strip().lower()).strip("-._")
|
|
342
|
+
return require_id(slug or prefix, f"{prefix} id")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def automation_manifest(
|
|
346
|
+
item_id: str,
|
|
347
|
+
*,
|
|
348
|
+
title: str | None,
|
|
349
|
+
prompt: Any,
|
|
350
|
+
command: Any,
|
|
351
|
+
every: str | None,
|
|
352
|
+
cron: str | None,
|
|
353
|
+
enabled: bool,
|
|
354
|
+
created_at: str | None = None,
|
|
355
|
+
) -> dict[str, Any]:
|
|
356
|
+
schedule: dict[str, Any] = {"type": "manual"}
|
|
357
|
+
if every:
|
|
358
|
+
schedule = {"type": "interval", "every": every}
|
|
359
|
+
if cron:
|
|
360
|
+
schedule = {"type": "cron", "cron": cron}
|
|
361
|
+
now = now_iso()
|
|
362
|
+
return {
|
|
363
|
+
"schema_version": LOCAL_ARTIFACT_SCHEMA_VERSION,
|
|
364
|
+
"id": item_id,
|
|
365
|
+
"kind": "local-automation",
|
|
366
|
+
"title": title or item_id,
|
|
367
|
+
"prompt": prompt,
|
|
368
|
+
"command": command,
|
|
369
|
+
"schedule": schedule,
|
|
370
|
+
"enabled": enabled,
|
|
371
|
+
"write_policy": "local_write" if command else "local_config_write",
|
|
372
|
+
"external_writes": False,
|
|
373
|
+
"stored_secret": False,
|
|
374
|
+
"created_at": created_at or now,
|
|
375
|
+
"updated_at": now,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def local_automation_payload(status: str, item_id: str, path: Path, manifest: dict[str, Any]) -> dict[str, Any]:
|
|
380
|
+
payload = local_artifact_payload("local-automation", status, item_id, path, write_policy=str(manifest.get("write_policy") or "local_config_write"))
|
|
381
|
+
payload["automation"] = manifest
|
|
382
|
+
return payload
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def read_required_automation(path: Path, item_id: str) -> dict[str, Any]:
|
|
386
|
+
manifest = path / "automation.json"
|
|
387
|
+
if not manifest.exists():
|
|
388
|
+
raise DevKitError(f"local automation not found: {item_id}")
|
|
389
|
+
return read_json(manifest)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def read_json(path: Path) -> dict[str, Any]:
|
|
393
|
+
try:
|
|
394
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
395
|
+
except (OSError, json.JSONDecodeError):
|
|
396
|
+
return {}
|
|
397
|
+
return payload if isinstance(payload, dict) else {}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
401
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
402
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def contains_secret_like_text(text: str) -> bool:
|
|
406
|
+
return bool(re.search(r"(?i)(api[_-]?key|token|secret|password|senha|pat)\s*[:=]", text))
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def now_iso() -> str:
|
|
410
|
+
return datetime.now(timezone.utc).isoformat()
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def render_skill(skill_id: str, description: str | None) -> str:
|
|
414
|
+
return f"""---
|
|
415
|
+
name: {skill_id}
|
|
416
|
+
description: {description or 'Local Agent DevKit skill.'}
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
# {skill_id}
|
|
420
|
+
|
|
421
|
+
{description or 'Local skill created by Agent DevKit.'}
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def render_script(command: str | None) -> str:
|
|
426
|
+
body = command or "echo 'local script placeholder'"
|
|
427
|
+
return f"#!/usr/bin/env sh\nset -eu\n{body}\n"
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def render_agent_yaml(agent_id: str, description: str | None) -> str:
|
|
431
|
+
return json.dumps(
|
|
432
|
+
{
|
|
433
|
+
"id": agent_id,
|
|
434
|
+
"kind": "agent",
|
|
435
|
+
"name": agent_id,
|
|
436
|
+
"version": "0.1.0",
|
|
437
|
+
"status": "draft",
|
|
438
|
+
"purpose": description or "Local Agent DevKit agent.",
|
|
439
|
+
"default_context": ["knowledge/context.md"],
|
|
440
|
+
"capabilities": [],
|
|
441
|
+
},
|
|
442
|
+
ensure_ascii=False,
|
|
443
|
+
indent=2,
|
|
444
|
+
) + "\n"
|