agentship-cli 0.0.1__py3-none-any.whl
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.
- agentship_cli/main.py +1023 -0
- agentship_cli/migrations.py +82 -0
- agentship_cli/py.typed +0 -0
- agentship_cli/scaffold.py +271 -0
- agentship_cli/verify.py +391 -0
- agentship_cli-0.0.1.dist-info/METADATA +25 -0
- agentship_cli-0.0.1.dist-info/RECORD +9 -0
- agentship_cli-0.0.1.dist-info/WHEEL +4 -0
- agentship_cli-0.0.1.dist-info/entry_points.txt +2 -0
agentship_cli/verify.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""The engine behind ``agentship verify`` — AgentShip's "verifiable agents" proof.
|
|
2
|
+
|
|
3
|
+
``verify`` is the one themed report that proves two honesty claims at once:
|
|
4
|
+
|
|
5
|
+
1. **No over-claims.** Every installed engine genuinely honours the capabilities it
|
|
6
|
+
declares (and fails fast when asked for one it does not) — the engine×capability
|
|
7
|
+
conformance grid from :mod:`agentship.conformance`.
|
|
8
|
+
2. **The wired contracts hold.** A project's agents load, validate against their
|
|
9
|
+
engines, and the framework's cross-cutting promises (observability span tree,
|
|
10
|
+
service auth/tenant isolation, A2A card honesty) still stand.
|
|
11
|
+
|
|
12
|
+
Each check is one :class:`Section`. The cardinal rule is *declare, don't fake*
|
|
13
|
+
(DESIGN §11): a section whose optional dependency is not installed, or which has no
|
|
14
|
+
target to check, reports **SKIPPED with a reason** — never a fake green. Only a real
|
|
15
|
+
failure (an over-claim, an invalid spec, a broken contract) fails the run.
|
|
16
|
+
|
|
17
|
+
This module owns no Click surface; :func:`run_verification` returns a
|
|
18
|
+
:class:`VerifyReport` that the ``verify`` command in :mod:`agentship_cli.main`
|
|
19
|
+
renders and turns into an exit code.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from collections.abc import Callable
|
|
25
|
+
from contextlib import AbstractContextManager
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from agentship.conformance import CellResult, run_capability_grid
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Section:
|
|
34
|
+
"""One line of the verify report: a named check and how it fared.
|
|
35
|
+
|
|
36
|
+
``name`` is the human label shown in the report. ``passed``/``total`` count the
|
|
37
|
+
sub-checks that held out of those attempted (``0/0`` for a skipped section).
|
|
38
|
+
``skipped_reason`` is ``None`` when the section actually ran; when set, the
|
|
39
|
+
section did not run (a missing optional dependency or no target to check) and the
|
|
40
|
+
reason is shown verbatim — a skip never fails the overall run. ``details`` carries
|
|
41
|
+
extra lines to surface under the section (e.g. each named over-claim).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
name: str
|
|
45
|
+
passed: int
|
|
46
|
+
total: int
|
|
47
|
+
skipped_reason: str | None
|
|
48
|
+
details: tuple[str, ...] = ()
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def skipped(self) -> bool:
|
|
52
|
+
"""Whether this section was skipped (declared, not faked) rather than run."""
|
|
53
|
+
return self.skipped_reason is not None
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def failed(self) -> bool:
|
|
57
|
+
"""Whether this section ran and had a real failure (some sub-check did not hold).
|
|
58
|
+
|
|
59
|
+
A skipped section is never failed — that is the honesty rule: a missing code
|
|
60
|
+
path or dependency is reported as SKIPPED, never counted against the run.
|
|
61
|
+
"""
|
|
62
|
+
return not self.skipped and self.passed < self.total
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class VerifyReport:
|
|
67
|
+
"""The whole ``agentship verify`` result: every section plus the over-claim roll-up.
|
|
68
|
+
|
|
69
|
+
``over_claims`` names each conformance ``prove`` cell that failed — an engine that
|
|
70
|
+
declared a capability it does not actually honour, the headline dishonesty
|
|
71
|
+
``verify`` exists to catch. ``ok`` is true when no section had a *real* failure
|
|
72
|
+
(skips do not count), which the CLI turns into the process exit code.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
sections: tuple[Section, ...]
|
|
76
|
+
over_claims: tuple[str, ...]
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def ok(self) -> bool:
|
|
80
|
+
"""True when no section really failed — skipped sections never fail the run."""
|
|
81
|
+
return not any(section.failed for section in self.sections)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _langgraph_offline() -> Callable[[str], AbstractContextManager] | None:
|
|
85
|
+
"""Return the langgraph offline model provider, or ``None`` if the extra is absent.
|
|
86
|
+
|
|
87
|
+
The grid's positive cells for a model-backed engine must run without a real
|
|
88
|
+
provider key; :func:`agentship_langgraph.testing.offline` supplies the fake-model
|
|
89
|
+
seam. It lives in an engine extra, so we import it lazily and degrade to ``None``
|
|
90
|
+
(grid runs unpatched — fine for the model-free ``echo`` engine) when it is not
|
|
91
|
+
installed, rather than making langgraph a hard dependency of ``verify``.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
from agentship_langgraph.testing import offline
|
|
95
|
+
except ImportError:
|
|
96
|
+
return None
|
|
97
|
+
return offline
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def _section_capability_grid() -> tuple[Section, tuple[str, ...]]:
|
|
101
|
+
"""Run the engine×capability conformance grid and summarise it as a section.
|
|
102
|
+
|
|
103
|
+
Every cell (positive ``prove`` and negative ``reject``) counts toward the section
|
|
104
|
+
total; a failed ``prove`` cell is additionally recorded as a named over-claim
|
|
105
|
+
(``engine.capability``) — the exact dishonesty ``verify`` is built to surface. The
|
|
106
|
+
grid never raises: each failure is a failing :class:`CellResult`, so this always
|
|
107
|
+
returns a complete section.
|
|
108
|
+
"""
|
|
109
|
+
results: list[CellResult] = await run_capability_grid(offline=_langgraph_offline())
|
|
110
|
+
passed = sum(1 for cell in results if cell.passed)
|
|
111
|
+
over_claims = tuple(
|
|
112
|
+
f"{cell.engine}.{cell.capability}"
|
|
113
|
+
for cell in results
|
|
114
|
+
if cell.kind == "prove" and not cell.passed
|
|
115
|
+
)
|
|
116
|
+
details = tuple(f"over-claim: {name} declared but not honoured" for name in over_claims)
|
|
117
|
+
section = Section("engine×capability grid", passed, len(results), None, details)
|
|
118
|
+
return section, over_claims
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _section_spec_validation(agents_dir: Path | None) -> Section:
|
|
122
|
+
"""Validate every spec in ``agents_dir`` with the same checks ``doctor`` runs.
|
|
123
|
+
|
|
124
|
+
Skipped (honestly, with a reason) when no ``--agents-dir`` was given — there is
|
|
125
|
+
nothing to validate. Otherwise each ``*.yaml`` is loaded and capability-gated via
|
|
126
|
+
the shared :func:`agentship_cli.main._check_agent` helper (no duplication of
|
|
127
|
+
doctor's logic): a spec passes iff that returns ``None``. Every invalid spec is
|
|
128
|
+
listed with its reason and the section fails.
|
|
129
|
+
"""
|
|
130
|
+
if agents_dir is None:
|
|
131
|
+
return Section("spec validation", 0, 0, "no --agents-dir given (nothing to validate)")
|
|
132
|
+
|
|
133
|
+
# Imported here (not at module load) to avoid a circular import with main.py.
|
|
134
|
+
from agentship.errors import AgentShipError
|
|
135
|
+
|
|
136
|
+
from .main import _agent_files, _check_agent
|
|
137
|
+
|
|
138
|
+
files = _agent_files(agents_dir)
|
|
139
|
+
if not files:
|
|
140
|
+
return Section("spec validation", 0, 0, f"no *.yaml specs in {agents_dir}")
|
|
141
|
+
|
|
142
|
+
passed = 0
|
|
143
|
+
details: list[str] = []
|
|
144
|
+
for path in files:
|
|
145
|
+
try:
|
|
146
|
+
reason = _check_agent(path)
|
|
147
|
+
except AgentShipError as exc:
|
|
148
|
+
reason = str(exc)
|
|
149
|
+
if reason is None:
|
|
150
|
+
passed += 1
|
|
151
|
+
else:
|
|
152
|
+
details.append(f"invalid spec {path.name}: {reason}")
|
|
153
|
+
return Section("spec validation", passed, len(files), None, tuple(details))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
async def _section_observability() -> Section:
|
|
157
|
+
"""Assert one real agent run emits the frozen root ``agent`` span (CONF-OBS-1).
|
|
158
|
+
|
|
159
|
+
Skipped (with a reason) when the ``agentship-observability`` extra is not
|
|
160
|
+
installed. Otherwise it builds an ``echo`` agent — no model, no key — attaches a
|
|
161
|
+
:class:`RecordingObserver`, runs one turn, and asserts the recorded tree is
|
|
162
|
+
exactly one root whose name/kind are the frozen ``agent`` span from SEMCONV. The
|
|
163
|
+
model-free echo engine emits no child spans, so the contract checked here is the
|
|
164
|
+
root-span shape the span tree hangs off; richer child structure is proven in the
|
|
165
|
+
per-engine observability suite.
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
from agentship.observability import RecordingObserver, SpanKind, semconv
|
|
169
|
+
except ImportError:
|
|
170
|
+
return Section(
|
|
171
|
+
"observability span-tree",
|
|
172
|
+
0,
|
|
173
|
+
0,
|
|
174
|
+
"agentship-observability not installed — pip install 'agentship-sdk[observability]'",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
from agentship.runtime import build_agent
|
|
178
|
+
from agentship.spec import AgentSpec
|
|
179
|
+
|
|
180
|
+
checks: list[tuple[str, bool]] = []
|
|
181
|
+
obs = RecordingObserver()
|
|
182
|
+
agent = build_agent(AgentSpec(name="verify-obs", engine="echo"), observer=obs)
|
|
183
|
+
await agent.run("ping", user_id="verify")
|
|
184
|
+
|
|
185
|
+
checks.append(("exactly one root span", len(obs.roots) == 1))
|
|
186
|
+
if obs.roots:
|
|
187
|
+
root = obs.roots[0]
|
|
188
|
+
# The root span is named "agent <name>" so a backend's trace list identifies the agent;
|
|
189
|
+
# the check is the prefix, since the suffix is the agent's own name.
|
|
190
|
+
checks.append(
|
|
191
|
+
("root span is named 'agent <name>'", root.name.startswith(semconv.SPAN_AGENT))
|
|
192
|
+
)
|
|
193
|
+
checks.append(("root span kind is AGENT", root.kind is SpanKind.AGENT))
|
|
194
|
+
# The trace view read-port surfaces the recorded tree the eval/audit phases read.
|
|
195
|
+
view = obs.trace_view()
|
|
196
|
+
span_names = [s.name for s in view.spans()]
|
|
197
|
+
checks.append(("root visible via trace_view()", root.name in span_names))
|
|
198
|
+
|
|
199
|
+
passed = sum(1 for _, ok in checks if ok)
|
|
200
|
+
details = tuple(f"failed: {label}" for label, ok in checks if not ok)
|
|
201
|
+
return Section("observability span-tree", passed, len(checks), None, details)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _section_service_contracts(agents_dir: Path | None) -> Section:
|
|
205
|
+
"""Assert the service enforces auth + tenant isolation (mirrors P04 conformance).
|
|
206
|
+
|
|
207
|
+
Skipped (with a reason) when the ``agentship-service`` extra is not installed or no
|
|
208
|
+
``--agents-dir`` was given (the section is about serving a project's agents). When
|
|
209
|
+
it runs it builds the real app via ``create_app`` over an in-memory ``echo`` agent
|
|
210
|
+
and a two-tenant API-key table, then drives a FastAPI ``TestClient`` to assert the
|
|
211
|
+
same promises as ``test_phase04_service_security.py``:
|
|
212
|
+
|
|
213
|
+
- an unauthenticated invoke is 401 (no open ingress);
|
|
214
|
+
- a request without the agent's invoke scope is 403;
|
|
215
|
+
- tenant B cannot read (404) or cancel (403) a task tenant A created, while A can;
|
|
216
|
+
- the ``/healthz`` liveness probe is the sole public path.
|
|
217
|
+
"""
|
|
218
|
+
if agents_dir is None:
|
|
219
|
+
return Section("service contracts", 0, 0, "no --agents-dir given (nothing to serve)")
|
|
220
|
+
try:
|
|
221
|
+
from agentship_service import AgentRegistry, create_app
|
|
222
|
+
from fastapi.testclient import TestClient
|
|
223
|
+
except ImportError:
|
|
224
|
+
return Section(
|
|
225
|
+
"service contracts",
|
|
226
|
+
0,
|
|
227
|
+
0,
|
|
228
|
+
"agentship-service not installed — pip install 'agentship-sdk[service]'",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
import json
|
|
232
|
+
|
|
233
|
+
from agentship.auth import ApiKeyAuthProvider, EnvApiKeyStore
|
|
234
|
+
from agentship.runtime import build_agent
|
|
235
|
+
from agentship.spec import AgentSpec
|
|
236
|
+
|
|
237
|
+
keys = json.dumps(
|
|
238
|
+
[
|
|
239
|
+
{"key": "acme", "user": "u1", "tenant": "acme", "scopes": ["*"]},
|
|
240
|
+
{"key": "beta", "user": "u2", "tenant": "beta", "scopes": ["*"]},
|
|
241
|
+
{"key": "narrow", "user": "u3", "tenant": "acme", "scopes": ["agent:other:invoke"]},
|
|
242
|
+
]
|
|
243
|
+
)
|
|
244
|
+
agents = AgentRegistry([build_agent(AgentSpec(name="support", engine="echo"))])
|
|
245
|
+
auth = ApiKeyAuthProvider(EnvApiKeyStore(raw=keys))
|
|
246
|
+
client = TestClient(create_app(auth=auth, agents=agents))
|
|
247
|
+
|
|
248
|
+
checks: list[tuple[str, bool]] = []
|
|
249
|
+
invoke = "/v1/agents/support:invoke"
|
|
250
|
+
|
|
251
|
+
def _invoke(key: str | None) -> int:
|
|
252
|
+
"""POST an invoke with the given API key (or none) and return the status code."""
|
|
253
|
+
headers = {"x-api-key": key} if key is not None else {}
|
|
254
|
+
return client.post(invoke, headers=headers, json={"input": "hi"}).status_code
|
|
255
|
+
|
|
256
|
+
# (a) No unauthenticated ingress: a missing or unknown key is rejected.
|
|
257
|
+
checks.append(("unauthenticated invoke rejected (401)", _invoke(None) == 401))
|
|
258
|
+
checks.append(("unknown key rejected (401)", _invoke("nope") == 401))
|
|
259
|
+
# A valid key with the right scope authenticates; one missing the scope is 403.
|
|
260
|
+
checks.append(("valid key + scope allowed (200)", _invoke("acme") == 200))
|
|
261
|
+
checks.append(("missing scope denied (403)", _invoke("narrow") == 403))
|
|
262
|
+
|
|
263
|
+
# (b) Tenant isolation: B cannot read/cancel A's task; A can.
|
|
264
|
+
created = client.post(
|
|
265
|
+
"/v1/tasks", headers={"x-api-key": "acme"}, json={"agent": "support", "input": "do it"}
|
|
266
|
+
)
|
|
267
|
+
checks.append(("task created by tenant A (202)", created.status_code == 202))
|
|
268
|
+
if created.status_code == 202:
|
|
269
|
+
task_id = created.json()["id"]
|
|
270
|
+
checks.append(
|
|
271
|
+
(
|
|
272
|
+
"tenant B cannot read A's task (404)",
|
|
273
|
+
client.get(f"/v1/tasks/{task_id}", headers={"x-api-key": "beta"}).status_code
|
|
274
|
+
== 404,
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
checks.append(
|
|
278
|
+
(
|
|
279
|
+
"tenant B cannot cancel A's task (403)",
|
|
280
|
+
client.post(
|
|
281
|
+
f"/v1/tasks/{task_id}:cancel", headers={"x-api-key": "beta"}
|
|
282
|
+
).status_code
|
|
283
|
+
== 403,
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
checks.append(
|
|
287
|
+
(
|
|
288
|
+
"owner A reads its own task (200)",
|
|
289
|
+
client.get(f"/v1/tasks/{task_id}", headers={"x-api-key": "acme"}).status_code
|
|
290
|
+
== 200,
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# (c) The liveness probe is the only public path.
|
|
295
|
+
checks.append(("/healthz is public (200)", client.get("/healthz").status_code == 200))
|
|
296
|
+
|
|
297
|
+
passed = sum(1 for _, ok in checks if ok)
|
|
298
|
+
details = tuple(f"failed: {label}" for label, ok in checks if not ok)
|
|
299
|
+
return Section("service contracts", passed, len(checks), None, details)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _section_a2a_interop(agents_dir: Path | None) -> Section:
|
|
303
|
+
"""Assert every A2A-exposed spec renders a schema-valid, honest Agent Card.
|
|
304
|
+
|
|
305
|
+
Skipped (with a reason) when no ``--agents-dir`` was given or no loaded spec sets
|
|
306
|
+
``a2a.expose: true`` — the interop layer is optional (Layer 0 agents never reach
|
|
307
|
+
the wire), so having nothing exposed is a legitimate skip, not a pass. For each
|
|
308
|
+
exposed agent it builds the card from the spec + the engine's real capabilities
|
|
309
|
+
(never hand-written) and asserts the required card fields are present, its
|
|
310
|
+
``streaming`` claim matches the engine, and every declared security scheme is
|
|
311
|
+
advertised in ``securitySchemes`` — the "declare, don't fake" card honesty.
|
|
312
|
+
"""
|
|
313
|
+
if agents_dir is None:
|
|
314
|
+
return Section("A2A interop", 0, 0, "no --agents-dir given (no agents to expose)")
|
|
315
|
+
|
|
316
|
+
from agentship.a2a.card import build_agent_card
|
|
317
|
+
from agentship.engines.base import ENGINES
|
|
318
|
+
from agentship.errors import AgentShipError
|
|
319
|
+
from agentship.spec import load_spec
|
|
320
|
+
|
|
321
|
+
from .main import _agent_files
|
|
322
|
+
|
|
323
|
+
exposed: list = []
|
|
324
|
+
for path in _agent_files(agents_dir):
|
|
325
|
+
try:
|
|
326
|
+
spec = load_spec(path)
|
|
327
|
+
except AgentShipError:
|
|
328
|
+
continue # invalid specs are the spec-validation section's job, not this one
|
|
329
|
+
a2a = getattr(spec, "a2a", None)
|
|
330
|
+
if a2a is not None and getattr(a2a, "expose", False):
|
|
331
|
+
exposed.append(spec)
|
|
332
|
+
|
|
333
|
+
if not exposed:
|
|
334
|
+
return Section("A2A interop", 0, 0, "no spec exposes a2a (interop layer optional)")
|
|
335
|
+
|
|
336
|
+
checks: list[tuple[str, bool]] = []
|
|
337
|
+
required = {"name", "url", "capabilities", "securitySchemes"}
|
|
338
|
+
for spec in exposed:
|
|
339
|
+
engine_cls = ENGINES.get(spec.engine)
|
|
340
|
+
if engine_cls is None:
|
|
341
|
+
checks.append((f"{spec.name}: engine {spec.engine!r} installed", False))
|
|
342
|
+
continue
|
|
343
|
+
caps = engine_cls.capabilities
|
|
344
|
+
card = build_agent_card(
|
|
345
|
+
spec,
|
|
346
|
+
caps,
|
|
347
|
+
base_url="https://verify.local",
|
|
348
|
+
security=spec.a2a.security,
|
|
349
|
+
oauth2=spec.a2a.oauth2,
|
|
350
|
+
)
|
|
351
|
+
body = card.model_dump(mode="json", by_alias=True)
|
|
352
|
+
checks.append((f"{spec.name}: card has required fields", required <= set(body)))
|
|
353
|
+
checks.append(
|
|
354
|
+
(
|
|
355
|
+
f"{spec.name}: card streaming matches engine",
|
|
356
|
+
body["capabilities"].get("streaming") == caps.streaming,
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
checks.append(
|
|
360
|
+
(
|
|
361
|
+
f"{spec.name}: advertises its security schemes",
|
|
362
|
+
all(scheme in body["securitySchemes"] for scheme in spec.a2a.security),
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
passed = sum(1 for _, ok in checks if ok)
|
|
367
|
+
details = tuple(f"failed: {label}" for label, ok in checks if not ok)
|
|
368
|
+
return Section("A2A interop", passed, len(checks), None, details)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
async def run_verification(agents_dir: Path | None, *, live: bool) -> VerifyReport:
|
|
372
|
+
"""Run every verify section and assemble the themed :class:`VerifyReport`.
|
|
373
|
+
|
|
374
|
+
``agents_dir`` (from ``--agents-dir``) scopes the project-level sections (spec
|
|
375
|
+
validation, service contracts, A2A interop); when ``None`` those sections skip
|
|
376
|
+
honestly. ``live`` is threaded for future live-provider checks — today every
|
|
377
|
+
section is fully offline (the grid uses the fake-model seam, all agents use the
|
|
378
|
+
model-free ``echo`` engine), so a run needs no provider keys. Sections whose
|
|
379
|
+
optional dependency is absent report SKIPPED rather than failing.
|
|
380
|
+
"""
|
|
381
|
+
_ = live # reserved: all current sections run offline; live checks land later.
|
|
382
|
+
|
|
383
|
+
grid_section, over_claims = await _section_capability_grid()
|
|
384
|
+
sections = (
|
|
385
|
+
grid_section,
|
|
386
|
+
_section_spec_validation(agents_dir),
|
|
387
|
+
await _section_observability(),
|
|
388
|
+
_section_service_contracts(agents_dir),
|
|
389
|
+
_section_a2a_interop(agents_dir),
|
|
390
|
+
)
|
|
391
|
+
return VerifyReport(sections, over_claims)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agentship-cli
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: AgentShip CLI — the `agentship` command that loads a YAML spec, builds the agent, and runs (or streams) one turn.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Requires-Dist: agentship-core==0.0.1
|
|
8
|
+
Requires-Dist: agentship-service==0.0.1
|
|
9
|
+
Requires-Dist: click>=8
|
|
10
|
+
Requires-Dist: python-dotenv>=1
|
|
11
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# agentship-cli
|
|
15
|
+
|
|
16
|
+
The `agentship` command-line interface. Owns the `agentship` console script; import as `agentship_cli`.
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
|
|
20
|
+
- `agentship init [DIR]` — scaffold a new single-tenant project (`agents/assistant.yaml`, `.env.example`, `README.md`) in `DIR` (default `.`). Refuses to overwrite existing files.
|
|
21
|
+
- `agentship new-agent NAME [--engine langgraph] [--agents-dir agents]` — write one starter agent spec at `<agents-dir>/NAME.yaml`. Refuses to overwrite.
|
|
22
|
+
- `agentship doctor [--agents-dir DIR | FILE]` — validate agent specs against their engines' declared capabilities. Prints a per-agent `OK`/`✗` line and exits `1` if any agent is invalid. An engine whose package is not installed yields an actionable `pip install` hint (no traceback).
|
|
23
|
+
- `agentship run FILE --input ... [--stream] [--env-file ...]` — load a YAML spec, build the agent, run (or stream) one turn, and print the output.
|
|
24
|
+
|
|
25
|
+
Harness errors print as a clean `Error:`/status line with a non-zero exit code rather than a traceback; pass `--debug` (on `run`/`doctor`) to re-raise for the full trace.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
agentship_cli/main.py,sha256=3bTEbeS1f4a0k_-ePoN1Rft4DRUsPR2EUK6_XV98gMY,42685
|
|
2
|
+
agentship_cli/migrations.py,sha256=GH71Jo_13EGeDdSoqdB2jG7bj8ApkTuKnwFzOFwUApk,3592
|
|
3
|
+
agentship_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
agentship_cli/scaffold.py,sha256=kYgia7cdcGNWhYbrzP71eoCv5TUsRmMXSZs99k3zATQ,10790
|
|
5
|
+
agentship_cli/verify.py,sha256=nyplEDuzXJ1lZE6PujjUTELlFFWrIqW8LTNZppl7vdY,16980
|
|
6
|
+
agentship_cli-0.0.1.dist-info/METADATA,sha256=W6ogNa1KorpL03HwNfuDCQyMY6F2nIquGquj6xf5m-M,1532
|
|
7
|
+
agentship_cli-0.0.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
8
|
+
agentship_cli-0.0.1.dist-info/entry_points.txt,sha256=JM6IhqoVtTkgi5ij5_cusBMEcZzZiwQNbHW_Yj4melU,54
|
|
9
|
+
agentship_cli-0.0.1.dist-info/RECORD,,
|