agentdir-cli 0.7.5__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.
agentdir/cli.py ADDED
@@ -0,0 +1,1900 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from dataclasses import asdict
7
+ from pathlib import Path
8
+
9
+ from . import __version__
10
+ from .actors import create_actor, send_message
11
+ from .artifacts import add_artifact
12
+ from .audit import (
13
+ audit_claims,
14
+ audit_session,
15
+ format_claims_audit,
16
+ format_session_audit,
17
+ strict_claims_exit_code,
18
+ strict_session_exit_code,
19
+ )
20
+ from .context import (
21
+ CONSUMPTION_PURPOSES,
22
+ audit_context_pack,
23
+ build_context_pack,
24
+ cite_context_sources,
25
+ consume_context_sources,
26
+ emit_context_pack,
27
+ format_context_audit,
28
+ format_context_pack,
29
+ write_context_pack,
30
+ )
31
+ from .capture import DEFAULT_MAX_CAPTURE_BYTES, run_tool
32
+ from .control import (
33
+ adopt_repo,
34
+ build_final_report,
35
+ build_status,
36
+ finish_work,
37
+ format_final_report,
38
+ format_status,
39
+ format_work_start,
40
+ start_work,
41
+ )
42
+ from .daemon import format_memory_daemon_status, memory_daemon_status
43
+ from .daemon import run_memory_daemon, start_memory_daemon, stop_memory_daemon
44
+ from .doctor import run_doctor
45
+ from .events import emit_event
46
+ from .federation import VISIBILITY_CHOICES, add_root_to_group, create_root_group
47
+ from .federation import doctor_registered_roots, format_federated_hits, format_registered_roots
48
+ from .federation import format_root_diagnostics, format_root_groups, format_root_suggestions
49
+ from .federation import list_registered_roots, list_root_groups, rebuild_registered_roots, register_root, remove_registered_root
50
+ from .federation import remove_root_from_group, suggest_roots
51
+ from .federation import search_federated_memory
52
+ from .git import git_output
53
+ from .gitignore import GITIGNORE_CHOICES, ensure_agentdir_ignored, gitignore_plan
54
+ from .hooks import DEFAULT_HOOKS, MANAGED_MARKER, hook_status, install_hooks, record_hook_event, uninstall_hooks
55
+ from .index import rebuild_index, update_index
56
+ from .memory import DEFAULT_MIN_SCORE, RETRIEVAL_MODES, explain_memory_match, memory_backend_status
57
+ from .memory import configure_embeddings, configure_team_backend, configure_vector_backend
58
+ from .memory import format_memory_explanation, format_memory_hits, memory_stats, search_memory
59
+ from .query import query_messages
60
+ from .replay import replay_session
61
+ from .retention import (
62
+ archive_sessions,
63
+ format_retention_result,
64
+ prune_sessions,
65
+ )
66
+ from .review import (
67
+ EVIDENCE_FAMILIES,
68
+ evidence_brief,
69
+ evidence_rows,
70
+ filter_evidence,
71
+ format_evidence,
72
+ format_evidence_brief,
73
+ format_summary,
74
+ format_timeline,
75
+ summarize_session,
76
+ timeline_rows,
77
+ )
78
+ from .sessions import end_session, ensure_session, read_current_session, start_session
79
+ from .rendering import rich_doctor
80
+ from .secrets import (
81
+ format_secret_findings,
82
+ format_secret_redaction,
83
+ redact_secret_records,
84
+ scan_secret_records,
85
+ )
86
+ from .skills import (
87
+ BROAD_PROJECT_INTEGRATIONS,
88
+ INTEGRATION_NAMES,
89
+ codex_skill_path_no_create,
90
+ generic_guidance_path_no_create,
91
+ install_codex_skill,
92
+ install_generic_guidance,
93
+ install_integrations,
94
+ integration_doctor,
95
+ integration_plan,
96
+ uninstall_integrations,
97
+ )
98
+ from .store import AgentDirError, init_root, resolve_root
99
+ from .upgrade import UpgradeOptions, format_upgrade_result, upgrade_agentdir, upgrade_exit_code
100
+
101
+
102
+ def read_body(path: str | None) -> str:
103
+ if not path or path == "-":
104
+ return sys.stdin.read()
105
+ return Path(path).expanduser().read_text(encoding="utf-8")
106
+
107
+
108
+ def read_body_or_literal(value: str | None) -> str | None:
109
+ if not value:
110
+ return None
111
+ if value == "-":
112
+ return sys.stdin.read()
113
+ path = Path(value).expanduser()
114
+ if path.is_file():
115
+ return path.read_text(encoding="utf-8")
116
+ return value
117
+
118
+
119
+ def print_json(data: object) -> None:
120
+ print(json.dumps(data, indent=2, sort_keys=True))
121
+
122
+
123
+ def setup_plan(args: argparse.Namespace, *, mode: str) -> dict[str, object]:
124
+ root = command_root(args, create=False)
125
+ skill_target = getattr(args, "codex_skill", getattr(args, "install_skill", "none"))
126
+ codex_skill = None
127
+ if skill_target != "none":
128
+ path = codex_skill_path_no_create(root, target=skill_target)
129
+ codex_skill = {
130
+ "target": skill_target,
131
+ "path": str(path),
132
+ "action": "update" if path.exists() else "create",
133
+ "exists": path.exists(),
134
+ "would_write": True,
135
+ }
136
+ generic = None
137
+ if should_install_legacy_generic(args):
138
+ path = generic_guidance_path_no_create(root, target=args.install_generic)
139
+ generic = {
140
+ "target": args.install_generic,
141
+ "path": str(path),
142
+ "action": "update" if path.exists() else "create",
143
+ "exists": path.exists(),
144
+ "would_write": True,
145
+ }
146
+ names = setup_integration_names(args)
147
+ return {
148
+ "mode": mode,
149
+ "dry_run": True,
150
+ "root": str(root),
151
+ "would_create_root": not (root / "VERSION").is_file(),
152
+ "hooks": [] if args.no_hooks else hooks_install_plan(force=args.force),
153
+ "codex_skill": codex_skill,
154
+ "generic_guidance": generic,
155
+ "integrations": integration_plan(
156
+ root,
157
+ names,
158
+ target=args.integration_target,
159
+ force=args.force,
160
+ ) if names else [],
161
+ "gitignore": gitignore_plan(target=getattr(args, "gitignore", "none"), cwd=Path.cwd()),
162
+ }
163
+
164
+
165
+ def print_setup_plan(plan: dict[str, object]) -> None:
166
+ print(f"mode={plan['mode']}")
167
+ print(f"root={plan['root']}")
168
+ print(f"dry_run={str(plan['dry_run']).lower()}")
169
+ print(f"would_create_root={str(plan['would_create_root']).lower()}")
170
+ print(f"hooks={len(plan['hooks'])}") # type: ignore[arg-type]
171
+ integrations = plan.get("integrations") or []
172
+ print(f"integrations={len(integrations)}")
173
+ gitignore = plan.get("gitignore") or {}
174
+ if isinstance(gitignore, dict):
175
+ print(f"gitignore={gitignore.get('target')}:{gitignore.get('action')}")
176
+
177
+
178
+ def setup_integration_names(args: argparse.Namespace) -> list[str]:
179
+ if getattr(args, "install_integrations", "all") == "none":
180
+ return []
181
+ names = list(BROAD_PROJECT_INTEGRATIONS)
182
+ install_generic = getattr(args, "install_generic", "project")
183
+ integration_target = getattr(args, "integration_target", "project")
184
+ if install_generic != integration_target and "generic" in names:
185
+ names.remove("generic")
186
+ return names
187
+
188
+
189
+ def should_install_legacy_generic(args: argparse.Namespace) -> bool:
190
+ install_generic = getattr(args, "install_generic", "none")
191
+ if install_generic == "none":
192
+ return False
193
+ return not (
194
+ install_generic == getattr(args, "integration_target", "project")
195
+ and "generic" in setup_integration_names(args)
196
+ )
197
+
198
+
199
+ def selected_gitignore_target(args: argparse.Namespace) -> str:
200
+ target = getattr(args, "gitignore", "none")
201
+ if target != "ask":
202
+ return target
203
+ if getattr(args, "json", False) or not sys.stdin.isatty() or not sys.stderr.isatty():
204
+ return "none"
205
+
206
+ sys.stderr.write(
207
+ "Ignore .agentdir? [P]roject .gitignore / [U]ser Git ignore / [N]one "
208
+ "(default: project): "
209
+ )
210
+ sys.stderr.flush()
211
+ answer = sys.stdin.readline().strip().lower()
212
+ if answer in ("", "p", "project"):
213
+ return "project"
214
+ if answer in ("u", "user"):
215
+ return "user"
216
+ if answer in ("n", "none", "no"):
217
+ return "none"
218
+ raise AgentDirError("Invalid gitignore choice; use project, user, or none")
219
+
220
+
221
+ def format_gitignore_result(result: dict[str, object] | None) -> str:
222
+ if not result:
223
+ return "none"
224
+ target = result.get("target")
225
+ action = result.get("action")
226
+ path = result.get("path")
227
+ changed = result.get("changed")
228
+ suffix = f":{path}" if path else ""
229
+ changed_text = "changed" if changed else "unchanged"
230
+ return f"{target}:{action}:{changed_text}{suffix}"
231
+
232
+
233
+ def hooks_install_plan(*, force: bool = False, hooks: list[str] | None = None) -> list[dict[str, object]]:
234
+ hooks_dir = git_hooks_dir_no_create()
235
+ plan: list[dict[str, object]] = []
236
+ for name in hooks or list(DEFAULT_HOOKS):
237
+ path = hooks_dir / name
238
+ original = hooks_dir / f"{name}.agentdir-original"
239
+ existing = path.read_text(encoding="utf-8", errors="ignore") if path.is_file() else ""
240
+ managed = MANAGED_MARKER in existing
241
+ if not path.exists():
242
+ action = "create"
243
+ elif managed:
244
+ action = "update"
245
+ elif original.exists() and not force:
246
+ action = "refuse"
247
+ else:
248
+ action = "backup-and-install"
249
+ plan.append(
250
+ {
251
+ "hook": name,
252
+ "path": str(path),
253
+ "action": action,
254
+ "installed": path.exists(),
255
+ "managed": managed,
256
+ "original": str(original) if original.exists() else None,
257
+ "would_write": action != "refuse",
258
+ "would_refuse": action == "refuse",
259
+ }
260
+ )
261
+ return plan
262
+
263
+
264
+ def hooks_uninstall_plan(hooks: list[str] | None = None) -> list[dict[str, object]]:
265
+ hooks_dir = git_hooks_dir_no_create()
266
+ plan: list[dict[str, object]] = []
267
+ for name in hooks or list(DEFAULT_HOOKS):
268
+ path = hooks_dir / name
269
+ original = hooks_dir / f"{name}.agentdir-original"
270
+ existing = path.read_text(encoding="utf-8", errors="ignore") if path.is_file() else ""
271
+ managed = MANAGED_MARKER in existing
272
+ if managed and original.exists():
273
+ action = "restore-original"
274
+ elif managed:
275
+ action = "remove"
276
+ elif path.exists():
277
+ action = "preserve-unmanaged"
278
+ else:
279
+ action = "none"
280
+ plan.append(
281
+ {
282
+ "hook": name,
283
+ "path": str(path),
284
+ "action": action,
285
+ "installed": path.exists(),
286
+ "managed": managed,
287
+ "original": str(original) if original.exists() else None,
288
+ "would_write": action in {"restore-original", "remove"},
289
+ }
290
+ )
291
+ return plan
292
+
293
+
294
+ def git_hooks_dir_no_create() -> Path:
295
+ git_dir = git_output(["rev-parse", "--git-dir"])
296
+ if not git_dir:
297
+ raise AgentDirError("AgentDir hooks require a git repository")
298
+ path = Path(git_dir)
299
+ if not path.is_absolute():
300
+ root = git_output(["rev-parse", "--show-toplevel"])
301
+ base = Path(root) if root else Path.cwd()
302
+ path = base / path
303
+ return path.resolve() / "hooks"
304
+
305
+
306
+ def cmd_init(args: argparse.Namespace) -> int:
307
+ root_arg = args.root_option or args.root
308
+ print(init_root(resolve_root(root_arg, args.scope)).root)
309
+ return 0
310
+
311
+
312
+ def cmd_root(args: argparse.Namespace) -> int:
313
+ root = resolve_root(args.root_option, args.scope)
314
+ if args.json:
315
+ print_json({"root": str(root), "scope": args.scope or "default"})
316
+ else:
317
+ print(root)
318
+ return 0
319
+
320
+
321
+ def command_root(args: argparse.Namespace, *, create: bool = False) -> Path:
322
+ root_arg = getattr(args, "root_option", None) or getattr(args, "root", None)
323
+ root = resolve_root(root_arg, getattr(args, "scope", None))
324
+ if create:
325
+ init_root(root)
326
+ return root
327
+
328
+
329
+ def cmd_emit(args: argparse.Namespace) -> int:
330
+ root = command_root(args, create=True)
331
+ session = read_current_session(root) if not args.session else None
332
+ session_id = args.session or (session.session_id if session else None)
333
+ if not session_id:
334
+ session_id = start_session(root, title="AgentDir manual event").session_id
335
+ delivered = emit_event(
336
+ root,
337
+ session_id=session_id,
338
+ event_type=args.type,
339
+ body=read_body(args.body),
340
+ subject=args.subject,
341
+ from_actor=args.from_actor,
342
+ to_actor=args.to_actor,
343
+ task_id=args.task,
344
+ workspace=args.workspace,
345
+ git_head=args.git_head,
346
+ tool=args.tool,
347
+ tool_exit_code=args.tool_exit_code,
348
+ parent_message_id=args.parent,
349
+ artifact=args.artifact,
350
+ message_id=args.message_id,
351
+ )
352
+ print(delivered.path)
353
+ return 0
354
+
355
+
356
+ def cmd_actor_create(args: argparse.Namespace) -> int:
357
+ inbox, outbox = create_actor(command_root(args, create=True), args.actor_id)
358
+ print(f"inbox={inbox}")
359
+ print(f"outbox={outbox}")
360
+ return 0
361
+
362
+
363
+ def cmd_send(args: argparse.Namespace) -> int:
364
+ root = command_root(args, create=True)
365
+ current = read_current_session(root) if not args.session else None
366
+ inbox, outbox = send_message(
367
+ root=root,
368
+ from_actor=args.from_actor,
369
+ to_actor=args.to_actor,
370
+ event_type=args.type,
371
+ body=read_body(args.body),
372
+ subject=args.subject,
373
+ session_id=args.session or (current.session_id if current else None),
374
+ task_id=args.task,
375
+ message_id=args.message_id,
376
+ )
377
+ print(f"inbox={inbox}")
378
+ print(f"outbox={outbox}")
379
+ return 0
380
+
381
+
382
+ def cmd_artifact_add(args: argparse.Namespace) -> int:
383
+ artifact = add_artifact(command_root(args, create=True), args.path)
384
+ print_json(
385
+ {
386
+ "sha256": artifact.sha256,
387
+ "path": str(artifact.path),
388
+ "bytes": artifact.bytes,
389
+ "mime_type": artifact.mime_type,
390
+ }
391
+ )
392
+ return 0
393
+
394
+
395
+ def cmd_index_rebuild(args: argparse.Namespace) -> int:
396
+ result = rebuild_index(command_root(args, create=True))
397
+ print_json({"indexed": result.indexed, "malformed": result.malformed, "duplicates": result.duplicates})
398
+ return 0
399
+
400
+
401
+ def cmd_index_update(args: argparse.Namespace) -> int:
402
+ result = update_index(command_root(args, create=True))
403
+ print_json({"indexed": result.indexed, "malformed": result.malformed, "duplicates": result.duplicates})
404
+ return 0
405
+
406
+
407
+ def cmd_roots_register(args: argparse.Namespace) -> int:
408
+ entry = register_root(command_root(args, create=True), args.path, name=args.name, visibility=args.visibility)
409
+ if args.json:
410
+ print_json(entry)
411
+ else:
412
+ print(f"{entry['root_id']} {entry['name']} {entry['root_path']}")
413
+ return 0
414
+
415
+
416
+ def cmd_roots_list(args: argparse.Namespace) -> int:
417
+ roots = list_registered_roots(command_root(args, create=True))
418
+ if args.json:
419
+ print_json(roots)
420
+ else:
421
+ print(format_registered_roots(roots), end="")
422
+ return 0
423
+
424
+
425
+ def cmd_roots_remove(args: argparse.Namespace) -> int:
426
+ removed = remove_registered_root(command_root(args, create=True), args.identifier)
427
+ if args.json:
428
+ print_json(removed)
429
+ else:
430
+ print(f"removed={removed['root_id']}")
431
+ return 0
432
+
433
+
434
+ def cmd_roots_rebuild(args: argparse.Namespace) -> int:
435
+ results = rebuild_registered_roots(
436
+ command_root(args, create=True),
437
+ group=args.group,
438
+ stale_only=args.stale,
439
+ )
440
+ if args.json:
441
+ print_json(results)
442
+ else:
443
+ for result in results:
444
+ if result.get("skipped"):
445
+ status = f"skipped={result.get('reason')}"
446
+ else:
447
+ status = f"indexed={result['indexed']} malformed={result['malformed']}" if result.get("ok") else f"error={result.get('error')}"
448
+ print(f"{result['root_id']} {status}")
449
+ return 0
450
+
451
+
452
+ def cmd_roots_suggest(args: argparse.Namespace) -> int:
453
+ suggestions = suggest_roots(command_root(args, create=True), near=args.near)
454
+ if args.json:
455
+ print_json(suggestions)
456
+ else:
457
+ print(format_root_suggestions(suggestions), end="")
458
+ return 0
459
+
460
+
461
+ def cmd_roots_doctor(args: argparse.Namespace) -> int:
462
+ diagnostics = doctor_registered_roots(command_root(args, create=True), group=args.group)
463
+ if args.json:
464
+ print_json(diagnostics)
465
+ else:
466
+ print(format_root_diagnostics(diagnostics), end="")
467
+ return 0
468
+
469
+
470
+ def cmd_roots_group_create(args: argparse.Namespace) -> int:
471
+ group = create_root_group(command_root(args, create=True), args.name, args.members)
472
+ if args.json:
473
+ print_json(group)
474
+ else:
475
+ print(f"{group['name']} roots={len(group['root_ids'])}")
476
+ return 0
477
+
478
+
479
+ def cmd_roots_group_list(args: argparse.Namespace) -> int:
480
+ groups = list_root_groups(command_root(args, create=True))
481
+ if args.json:
482
+ print_json(groups)
483
+ else:
484
+ print(format_root_groups(groups), end="")
485
+ return 0
486
+
487
+
488
+ def cmd_roots_group_add(args: argparse.Namespace) -> int:
489
+ group = add_root_to_group(command_root(args, create=True), args.name, args.root_id)
490
+ if args.json:
491
+ print_json(group)
492
+ else:
493
+ print(f"{group['name']} roots={len(group['root_ids'])}")
494
+ return 0
495
+
496
+
497
+ def cmd_roots_group_remove(args: argparse.Namespace) -> int:
498
+ group = remove_root_from_group(command_root(args, create=True), args.name, args.root_id)
499
+ if args.json:
500
+ print_json(group)
501
+ else:
502
+ print(f"{group['name']} roots={len(group['root_ids'])}")
503
+ return 0
504
+
505
+
506
+ def cmd_archive(args: argparse.Namespace) -> int:
507
+ result = archive_sessions(
508
+ command_root(args, create=True),
509
+ sessions=args.sessions,
510
+ older_than_days=args.older_than_days,
511
+ keep_recent=args.keep_recent,
512
+ apply=args.apply,
513
+ )
514
+ if args.json:
515
+ print_json(result.as_dict())
516
+ else:
517
+ print(format_retention_result(result))
518
+ return 0
519
+
520
+
521
+ def cmd_prune(args: argparse.Namespace) -> int:
522
+ result = prune_sessions(
523
+ command_root(args, create=True),
524
+ sessions=args.sessions,
525
+ older_than_days=args.older_than_days,
526
+ keep_recent=args.keep_recent,
527
+ include_live_sessions=args.include_live_sessions,
528
+ apply=args.apply,
529
+ )
530
+ if args.json:
531
+ print_json(result.as_dict())
532
+ else:
533
+ print(format_retention_result(result))
534
+ return 0
535
+
536
+
537
+ def cmd_query(args: argparse.Namespace) -> int:
538
+ root = command_root(args)
539
+ if args.semantic and args.text:
540
+ raise AgentDirError("Use either --text for literal search or --semantic for vector memory search")
541
+ if args.semantic:
542
+ rebuild_index(root)
543
+ search = search_federated_memory if args.federated else search_memory
544
+ rows = search(
545
+ root,
546
+ args.semantic,
547
+ session_id=args.session,
548
+ event_type=args.type,
549
+ actor=args.actor,
550
+ task_id=args.task,
551
+ tool=args.tool,
552
+ git_head=args.git_head,
553
+ workspace=args.workspace,
554
+ since=args.since,
555
+ until=args.until,
556
+ limit=args.limit,
557
+ min_score=args.min_score,
558
+ retrieval_mode=args.retrieval,
559
+ )
560
+ else:
561
+ rows = query_messages(
562
+ root,
563
+ session_id=args.session,
564
+ event_type=args.type,
565
+ actor=args.actor,
566
+ task_id=args.task,
567
+ tool=args.tool,
568
+ git_head=args.git_head,
569
+ workspace=args.workspace,
570
+ text=args.text,
571
+ since=args.since,
572
+ until=args.until,
573
+ limit=args.limit,
574
+ )
575
+ if args.json:
576
+ print_json(rows)
577
+ elif args.semantic:
578
+ print(format_federated_hits(rows) if args.federated else format_memory_hits(rows))
579
+ else:
580
+ for row in rows:
581
+ body = (row.get("body_text") or "").strip().replace("\n", "\\n")
582
+ print(
583
+ f"{row.get('date_header') or row.get('indexed_at')} "
584
+ f"{row.get('event_type') or 'unknown'} "
585
+ f"{row.get('subject') or ''} "
586
+ f"{body} "
587
+ f"{row.get('file_path')}"
588
+ )
589
+ return 0
590
+
591
+
592
+ def cmd_replay(args: argparse.Namespace) -> int:
593
+ for line in replay_session(command_root(args), args.session):
594
+ print(line)
595
+ return 0
596
+
597
+
598
+ def cmd_doctor(args: argparse.Namespace) -> int:
599
+ report = run_doctor(command_root(args))
600
+ if args.json:
601
+ print_json(report.as_dict())
602
+ else:
603
+ rendered = rich_doctor(report.as_dict())
604
+ if rendered is not None:
605
+ print(rendered, end="")
606
+ else:
607
+ print(f"ok={str(report.ok).lower()}")
608
+ for warning in report.warnings:
609
+ print(f"warning: {warning}")
610
+ for error in report.errors:
611
+ print(f"error: {error}")
612
+ return 0 if report.ok else 1
613
+
614
+
615
+ def cmd_secrets_scan(args: argparse.Namespace) -> int:
616
+ findings = scan_secret_records(command_root(args))
617
+ if args.json:
618
+ print_json([asdict(finding) for finding in findings])
619
+ else:
620
+ print(format_secret_findings(findings))
621
+ return 1 if findings else 0
622
+
623
+
624
+ def cmd_secrets_redact(args: argparse.Namespace) -> int:
625
+ result = redact_secret_records(command_root(args), apply=args.apply)
626
+ if args.json:
627
+ print_json(result)
628
+ else:
629
+ print(format_secret_redaction(result))
630
+ return 0
631
+
632
+
633
+ def cmd_upgrade(args: argparse.Namespace) -> int:
634
+ result = upgrade_agentdir(
635
+ UpgradeOptions(
636
+ repo=args.upgrade_repo,
637
+ version=args.upgrade_version,
638
+ adopt=not args.upgrade_no_adopt,
639
+ install_skill=args.upgrade_install_skill,
640
+ hooks=not args.upgrade_no_hooks,
641
+ dry_run=args.upgrade_dry_run,
642
+ )
643
+ )
644
+ if args.upgrade_json:
645
+ print_json(result)
646
+ else:
647
+ print(format_upgrade_result(result))
648
+ return upgrade_exit_code(result)
649
+
650
+
651
+ def cmd_session_start(args: argparse.Namespace) -> int:
652
+ state = start_session(
653
+ command_root(args, create=True),
654
+ session_id=args.session_id,
655
+ title=args.title,
656
+ actor=args.actor,
657
+ note=read_body(args.note) if args.note else None,
658
+ )
659
+ if args.json:
660
+ print_json(asdict(state))
661
+ else:
662
+ print(state.session_id)
663
+ return 0
664
+
665
+
666
+ def cmd_session_current(args: argparse.Namespace) -> int:
667
+ state = read_current_session(command_root(args))
668
+ if state is None:
669
+ raise AgentDirError("No active AgentDir session")
670
+ if args.json:
671
+ print_json(asdict(state))
672
+ else:
673
+ print(state.session_id)
674
+ return 0
675
+
676
+
677
+ def cmd_session_ensure(args: argparse.Namespace) -> int:
678
+ state = ensure_session(
679
+ command_root(args, create=True),
680
+ session_id=args.session_id,
681
+ title=args.title,
682
+ actor=args.actor,
683
+ )
684
+ if args.json:
685
+ print_json(asdict(state))
686
+ else:
687
+ print(state.session_id)
688
+ return 0
689
+
690
+
691
+ def cmd_session_end(args: argparse.Namespace) -> int:
692
+ state = end_session(
693
+ command_root(args),
694
+ status=args.status,
695
+ summary=read_body_or_literal(args.summary),
696
+ actor=args.actor,
697
+ )
698
+ if args.json:
699
+ print_json(asdict(state))
700
+ else:
701
+ print(state.session_id)
702
+ return 0
703
+
704
+
705
+ def cmd_run(args: argparse.Namespace) -> int:
706
+ command = list(args.command or [])
707
+ if command and command[0] == "--":
708
+ command = command[1:]
709
+ if not command:
710
+ raise AgentDirError("Usage: agentdir run -- <command> [args...]")
711
+ return run_tool(
712
+ command_root(args, create=True),
713
+ argv=command,
714
+ session_id=args.session,
715
+ tool_name=args.name,
716
+ cwd=args.cwd,
717
+ max_capture_bytes=args.max_capture_bytes,
718
+ redact=not args.no_redact,
719
+ )
720
+
721
+
722
+ def cmd_hooks_install(args: argparse.Namespace) -> int:
723
+ infos = install_hooks(
724
+ command_root(args, create=True),
725
+ hooks=args.hooks,
726
+ force=args.force,
727
+ )
728
+ if args.json:
729
+ print_json([asdict(info) for info in infos])
730
+ else:
731
+ for info in infos:
732
+ print(f"{info.hook}: installed {info.path}")
733
+ return 0
734
+
735
+
736
+ def cmd_hooks_status(args: argparse.Namespace) -> int:
737
+ infos = hook_status(hooks=args.hooks)
738
+ if args.json:
739
+ print_json([asdict(info) for info in infos])
740
+ else:
741
+ for info in infos:
742
+ state = "managed" if info.managed else "unmanaged" if info.installed else "missing"
743
+ print(f"{info.hook}: {state} {info.path}")
744
+ return 0
745
+
746
+
747
+ def cmd_hooks_uninstall(args: argparse.Namespace) -> int:
748
+ infos = uninstall_hooks(hooks=args.hooks)
749
+ if args.json:
750
+ print_json([asdict(info) for info in infos])
751
+ else:
752
+ for info in infos:
753
+ print(f"{info.hook}: {'installed' if info.installed else 'removed'} {info.path}")
754
+ return 0
755
+
756
+
757
+ def cmd_hooks_record(args: argparse.Namespace) -> int:
758
+ hook_args = list(args.hook_args or [])
759
+ if hook_args and hook_args[0] == "--":
760
+ hook_args = hook_args[1:]
761
+ record_hook_event(
762
+ command_root(args, create=True),
763
+ hook=args.hook,
764
+ original_exit_code=args.original_exit_code,
765
+ stdin_file=args.stdin_file,
766
+ hook_args=hook_args,
767
+ )
768
+ return 0
769
+
770
+
771
+ def cmd_skills_install_codex(args: argparse.Namespace) -> int:
772
+ installed = install_codex_skill(
773
+ command_root(args, create=True),
774
+ target=args.target,
775
+ force=args.force,
776
+ )
777
+ if args.json:
778
+ print_json({"target": installed.target, "path": str(installed.path)})
779
+ else:
780
+ print(installed.path)
781
+ return 0
782
+
783
+
784
+ def cmd_skills_install_generic(args: argparse.Namespace) -> int:
785
+ installed = install_generic_guidance(
786
+ command_root(args, create=True),
787
+ target=args.target,
788
+ force=args.force,
789
+ )
790
+ if args.json:
791
+ print_json({"target": installed.target, "path": str(installed.path)})
792
+ else:
793
+ print(installed.path)
794
+ return 0
795
+
796
+
797
+ def cmd_setup(args: argparse.Namespace) -> int:
798
+ if args.dry_run:
799
+ result = setup_plan(args, mode="setup")
800
+ if args.json:
801
+ print_json(result)
802
+ else:
803
+ print_setup_plan(result)
804
+ return 0
805
+ root = command_root(args, create=True)
806
+ hooks = [] if args.no_hooks else install_hooks(root, force=args.force)
807
+ skill = None
808
+ if args.codex_skill != "none":
809
+ skill = install_codex_skill(root, target=args.codex_skill, force=args.force)
810
+ generic = None
811
+ if should_install_legacy_generic(args):
812
+ generic = install_generic_guidance(root, target=args.install_generic, force=args.force)
813
+ integrations = []
814
+ names = setup_integration_names(args)
815
+ if names:
816
+ integrations = install_integrations(root, names, target=args.integration_target, force=args.force)
817
+ gitignore = ensure_agentdir_ignored(target=selected_gitignore_target(args), cwd=Path.cwd())
818
+ result = {
819
+ "root": str(root),
820
+ "hooks": [asdict(info) for info in hooks],
821
+ "codex_skill": str(skill.path) if skill else None,
822
+ "generic_guidance": str(generic.path) if generic else None,
823
+ "integrations": integrations,
824
+ "gitignore": gitignore,
825
+ }
826
+ if args.json:
827
+ print_json(result)
828
+ else:
829
+ print(f"root={root}")
830
+ if hooks:
831
+ print(f"hooks={len(hooks)}")
832
+ if skill:
833
+ print(f"codex_skill={skill.path}")
834
+ if generic:
835
+ print(f"generic_guidance={generic.path}")
836
+ if integrations:
837
+ print(f"integrations={len(integrations)}")
838
+ print(f"gitignore={format_gitignore_result(gitignore)}")
839
+ return 0
840
+
841
+
842
+ def cmd_adopt(args: argparse.Namespace) -> int:
843
+ if args.dry_run:
844
+ result = setup_plan(args, mode="adopt")
845
+ if args.json:
846
+ print_json(result)
847
+ else:
848
+ print_setup_plan(result)
849
+ return 0
850
+ root = command_root(args, create=True)
851
+ hooks = [] if args.no_hooks else install_hooks(root, force=args.force)
852
+ skill = None
853
+ if args.install_skill != "none":
854
+ skill = install_codex_skill(root, target=args.install_skill, force=args.force)
855
+ generic = None
856
+ if should_install_legacy_generic(args):
857
+ generic = install_generic_guidance(root, target=args.install_generic, force=args.force)
858
+ integrations = []
859
+ names = setup_integration_names(args)
860
+ if names:
861
+ integrations = install_integrations(root, names, target=args.integration_target, force=args.force)
862
+ gitignore = ensure_agentdir_ignored(target=selected_gitignore_target(args), cwd=Path.cwd())
863
+ result = adopt_repo(
864
+ root,
865
+ install_hooks_result=[asdict(info) for info in hooks],
866
+ codex_skill_path=str(skill.path) if skill else None,
867
+ generic_guidance_path=str(generic.path) if generic else None,
868
+ integrations=integrations,
869
+ gitignore=gitignore,
870
+ )
871
+ if args.json:
872
+ print_json(result)
873
+ else:
874
+ print(f"root={result['root']}")
875
+ print(f"doctor_ok={str(result['doctor']['ok']).lower()}")
876
+ if hooks:
877
+ print(f"hooks={len(hooks)}")
878
+ if skill:
879
+ print(f"codex_skill={skill.path}")
880
+ if generic:
881
+ print(f"generic_guidance={generic.path}")
882
+ if integrations:
883
+ print(f"integrations={len(integrations)}")
884
+ print(f"gitignore={format_gitignore_result(gitignore)}")
885
+ print(f"next={result['next']}")
886
+ return 0
887
+
888
+
889
+ def cmd_unadopt(args: argparse.Namespace) -> int:
890
+ root = command_root(args, create=False)
891
+ hooks = [] if args.no_hooks else uninstall_hooks() if args.apply else hooks_uninstall_plan()
892
+ project_integrations = uninstall_integrations(root, ["all"], target="project", apply=args.apply)
893
+ store_integrations = uninstall_integrations(root, ["all"], target="store", apply=args.apply)
894
+ result = {
895
+ "root": str(root),
896
+ "applied": args.apply,
897
+ "hooks": [asdict(info) if hasattr(info, "__dataclass_fields__") else info for info in hooks],
898
+ "integrations": {
899
+ "project": project_integrations,
900
+ "store": store_integrations,
901
+ },
902
+ "preserved": [str(root)],
903
+ }
904
+ if args.json:
905
+ print_json(result)
906
+ else:
907
+ print(f"root={root}")
908
+ print(f"applied={str(args.apply).lower()}")
909
+ print(f"hooks={len(result['hooks'])}")
910
+ print(f"project_integrations={len(project_integrations)}")
911
+ print(f"store_integrations={len(store_integrations)}")
912
+ print(f"preserved={root}")
913
+ return 0
914
+
915
+
916
+ def cmd_status(args: argparse.Namespace) -> int:
917
+ status = build_status(command_root(args), scope=args.scope, rebuild=not args.no_rebuild)
918
+ if args.json:
919
+ print_json(status)
920
+ else:
921
+ print(format_status(status), end="")
922
+ return 0 if status["health"]["ok"] else 1
923
+
924
+
925
+ def cmd_work_start(args: argparse.Namespace) -> int:
926
+ result = start_work(
927
+ command_root(args, create=True),
928
+ args.task,
929
+ actor=args.actor,
930
+ emit_context=args.emit_context,
931
+ federated=args.federated,
932
+ federation_group=args.group,
933
+ memory_limit=args.memory_limit,
934
+ evidence_limit=args.evidence_limit,
935
+ recent_limit=args.recent_limit,
936
+ min_score=args.min_score,
937
+ retrieval_mode=args.retrieval,
938
+ )
939
+ if args.json:
940
+ print_json(result)
941
+ else:
942
+ print(format_work_start(result), end="")
943
+ return 0
944
+
945
+
946
+ def cmd_work_finish(args: argparse.Namespace) -> int:
947
+ claims_text = read_body(args.claims) if args.claims else None
948
+ result = finish_work(
949
+ command_root(args),
950
+ session_id=args.session,
951
+ actor=args.actor,
952
+ run_health_check=not args.no_doctor,
953
+ end=not args.keep_session,
954
+ claims_text=claims_text,
955
+ )
956
+ if args.json:
957
+ print_json({key: value for key, value in result.items() if key != "rendered"})
958
+ else:
959
+ print(result["rendered"], end="")
960
+ return 0
961
+
962
+
963
+ def cmd_report_final(args: argparse.Namespace) -> int:
964
+ claims_text = read_body(args.claims) if args.claims else None
965
+ report = build_final_report(
966
+ command_root(args),
967
+ session_id=args.session,
968
+ run_health_check=not args.no_doctor,
969
+ claims_text=claims_text,
970
+ )
971
+ if args.format == "json":
972
+ print_json(report)
973
+ else:
974
+ print(format_final_report(report), end="")
975
+ return 0
976
+
977
+
978
+ def cmd_summarize(args: argparse.Namespace) -> int:
979
+ summary = summarize_session(command_root(args), args.session)
980
+ if args.json:
981
+ print_json(summary)
982
+ else:
983
+ print(format_summary(summary))
984
+ return 0
985
+
986
+
987
+ def cmd_evidence(args: argparse.Namespace) -> int:
988
+ rows = evidence_rows(command_root(args), args.session)
989
+ if args.family or args.failed:
990
+ rows = filter_evidence(rows, family=args.family, failed=args.failed)
991
+ if args.brief:
992
+ brief = evidence_brief(rows, family=None, failed=False)
993
+ if args.json:
994
+ print_json(brief)
995
+ else:
996
+ print(format_evidence_brief(brief))
997
+ return 0
998
+ if args.json:
999
+ print_json(rows)
1000
+ else:
1001
+ print(format_evidence(rows))
1002
+ return 0
1003
+
1004
+
1005
+ def cmd_timeline(args: argparse.Namespace) -> int:
1006
+ rows = timeline_rows(command_root(args), args.session, limit=args.limit)
1007
+ if args.json:
1008
+ print_json(rows)
1009
+ else:
1010
+ print(format_timeline(rows))
1011
+ return 0
1012
+
1013
+
1014
+ def cmd_integrations_install(args: argparse.Namespace) -> int:
1015
+ root = command_root(args, create=args.target == "store")
1016
+ result = install_integrations(root, [args.name], target=args.target, force=args.force)
1017
+ if args.json:
1018
+ print_json(result)
1019
+ else:
1020
+ for item in result:
1021
+ print(f"{item['name']}: {item['path']}")
1022
+ return 0
1023
+
1024
+
1025
+ def cmd_integrations_doctor(args: argparse.Namespace) -> int:
1026
+ result = integration_doctor(command_root(args, create=False), ["all"], target=args.target)
1027
+ if args.json:
1028
+ print_json(result)
1029
+ else:
1030
+ print(f"ok={str(result['ok']).lower()}")
1031
+ for check in result["checks"]:
1032
+ print(f"{check['name']}: {check['state']} {check['path']}")
1033
+ return 0
1034
+
1035
+
1036
+ def cmd_memory_search(args: argparse.Namespace) -> int:
1037
+ root = command_root(args)
1038
+ if not args.no_rebuild:
1039
+ rebuild_index(root)
1040
+ search_kwargs = {
1041
+ "session_id": args.session,
1042
+ "event_type": args.type,
1043
+ "actor": args.actor,
1044
+ "task_id": args.task,
1045
+ "tool": args.tool,
1046
+ "git_head": args.git_head,
1047
+ "workspace": args.workspace,
1048
+ "since": args.since,
1049
+ "until": args.until,
1050
+ "limit": args.limit,
1051
+ "min_score": args.min_score,
1052
+ "retrieval_mode": args.retrieval,
1053
+ }
1054
+ rows = search_federated_memory(
1055
+ root,
1056
+ args.query,
1057
+ rebuild=not args.no_rebuild,
1058
+ group=args.group,
1059
+ **search_kwargs,
1060
+ ) if args.federated or args.group else search_memory(root, args.query, **search_kwargs)
1061
+ if args.json:
1062
+ print_json(rows)
1063
+ else:
1064
+ print(format_federated_hits(rows) if args.federated or args.group else format_memory_hits(rows))
1065
+ return 0
1066
+
1067
+
1068
+ def cmd_memory_explain(args: argparse.Namespace) -> int:
1069
+ root = command_root(args)
1070
+ if not args.no_rebuild:
1071
+ rebuild_index(root)
1072
+ explanation = explain_memory_match(
1073
+ root,
1074
+ args.query,
1075
+ source_id=args.source,
1076
+ min_score=args.min_score,
1077
+ )
1078
+ if args.json:
1079
+ print_json(explanation)
1080
+ else:
1081
+ print(format_memory_explanation(explanation))
1082
+ return 0
1083
+
1084
+
1085
+ def cmd_memory_stats(args: argparse.Namespace) -> int:
1086
+ root = command_root(args)
1087
+ if not args.no_rebuild:
1088
+ rebuild_index(root)
1089
+ stats = memory_stats(root)
1090
+ if args.json:
1091
+ print_json(stats)
1092
+ else:
1093
+ print(f"messages={stats['messages']}")
1094
+ print(f"memory_documents={stats['memory_documents']}")
1095
+ print(f"message_documents={stats['message_documents']}")
1096
+ print(f"session_summary_documents={stats['session_summary_documents']}")
1097
+ print(f"coverage={stats['coverage']:.3f}")
1098
+ print(f"vector_dim={stats['vector_dim']}")
1099
+ print(f"passages={stats['passages']}")
1100
+ print(f"terms={stats['terms']}")
1101
+ print(f"retrieval_backend={stats['retrieval_backend']}")
1102
+ return 0
1103
+
1104
+
1105
+ def cmd_memory_backend_list(args: argparse.Namespace) -> int:
1106
+ root = command_root(args)
1107
+ if not args.no_rebuild:
1108
+ rebuild_index(root)
1109
+ status = memory_backend_status(root)
1110
+ if args.json:
1111
+ print_json(status)
1112
+ else:
1113
+ print(f"active={status['active']}")
1114
+ print(f"source_of_truth={status['source_of_truth']}")
1115
+ for backend in status["backends"]:
1116
+ print(
1117
+ f"{backend['name']} enabled={str(backend['enabled']).lower()} "
1118
+ f"kind={backend['kind']}"
1119
+ )
1120
+ return 0
1121
+
1122
+
1123
+ def cmd_memory_backend_configure(args: argparse.Namespace) -> int:
1124
+ root = command_root(args, create=True)
1125
+ rebuild_index(root)
1126
+ status = configure_vector_backend(root, args.backend)
1127
+ if args.json:
1128
+ print_json(status)
1129
+ else:
1130
+ print(f"vector_backend={status['config'].get('vector_backend') or 'none'}")
1131
+ print(f"active={status['active']}")
1132
+ return 0
1133
+
1134
+
1135
+ def cmd_memory_embeddings_configure(args: argparse.Namespace) -> int:
1136
+ root = command_root(args, create=True)
1137
+ rebuild_index(root)
1138
+ status = configure_embeddings(root, args.provider, model=args.model)
1139
+ if args.json:
1140
+ print_json(status)
1141
+ else:
1142
+ embeddings = status["config"].get("embeddings") or {}
1143
+ print(f"embedding_provider={embeddings.get('provider') or 'none'}")
1144
+ print(f"embedding_model={embeddings.get('model') or ''}")
1145
+ print(f"active={status['active']}")
1146
+ return 0
1147
+
1148
+
1149
+ def cmd_memory_team_configure(args: argparse.Namespace) -> int:
1150
+ root = command_root(args, create=True)
1151
+ rebuild_index(root)
1152
+ status = configure_team_backend(root, args.backend)
1153
+ if args.json:
1154
+ print_json(status)
1155
+ else:
1156
+ print(f"team_backend={status['config'].get('team_backend') or 'none'}")
1157
+ print(f"active={status['active']}")
1158
+ return 0
1159
+
1160
+
1161
+ def cmd_memory_daemon_start(args: argparse.Namespace) -> int:
1162
+ status = start_memory_daemon(
1163
+ command_root(args, create=True),
1164
+ interval=args.interval,
1165
+ group=args.group,
1166
+ force=args.force,
1167
+ )
1168
+ if args.json:
1169
+ print_json(status)
1170
+ else:
1171
+ print(format_memory_daemon_status(status), end="")
1172
+ return 0
1173
+
1174
+
1175
+ def cmd_memory_daemon_status(args: argparse.Namespace) -> int:
1176
+ status = memory_daemon_status(command_root(args))
1177
+ if args.json:
1178
+ print_json(status)
1179
+ else:
1180
+ print(format_memory_daemon_status(status), end="")
1181
+ return 0
1182
+
1183
+
1184
+ def cmd_memory_daemon_stop(args: argparse.Namespace) -> int:
1185
+ status = stop_memory_daemon(command_root(args), timeout=args.timeout)
1186
+ if args.json:
1187
+ print_json(status)
1188
+ else:
1189
+ print(format_memory_daemon_status(status), end="")
1190
+ return 0
1191
+
1192
+
1193
+ def cmd_memory_daemon_run(args: argparse.Namespace) -> int:
1194
+ status = run_memory_daemon(
1195
+ command_root(args, create=True),
1196
+ interval=args.interval,
1197
+ group=args.group,
1198
+ once=args.once,
1199
+ )
1200
+ if args.json:
1201
+ print_json(status)
1202
+ else:
1203
+ print(format_memory_daemon_status(status), end="")
1204
+ return 0
1205
+
1206
+
1207
+ def cmd_context_build(args: argparse.Namespace) -> int:
1208
+ root = command_root(args, create=args.emit)
1209
+ if args.emit and not args.session and read_current_session(root) is None:
1210
+ ensure_session(root, title=f"Context pack: {args.task}")
1211
+ pack = build_context_pack(
1212
+ root,
1213
+ args.task,
1214
+ session_id=args.session,
1215
+ memory_limit=args.memory_limit,
1216
+ evidence_limit=args.evidence_limit,
1217
+ recent_limit=args.recent_limit,
1218
+ min_score=args.min_score,
1219
+ federated=args.federated,
1220
+ federation_group=args.group,
1221
+ retrieval_mode=args.retrieval,
1222
+ )
1223
+ emitted = None
1224
+ if args.emit:
1225
+ emitted = emit_context_pack(
1226
+ root,
1227
+ pack,
1228
+ selection_policy={
1229
+ "memory_limit": args.memory_limit,
1230
+ "evidence_limit": args.evidence_limit,
1231
+ "recent_limit": args.recent_limit,
1232
+ "min_score": args.min_score,
1233
+ "federated": args.federated,
1234
+ "federation_group": args.group,
1235
+ "retrieval_mode": args.retrieval,
1236
+ },
1237
+ scope=args.group or ("federated" if args.federated else args.scope or "project"),
1238
+ )
1239
+ if args.output:
1240
+ path = write_context_pack(args.output, pack, as_json=args.json)
1241
+ if emitted and args.json:
1242
+ print_json(
1243
+ {
1244
+ "context_output": str(path),
1245
+ "event_path": str(emitted.event_path),
1246
+ "artifact": {
1247
+ "sha256": emitted.artifact_sha256,
1248
+ "path": str(emitted.artifact_path),
1249
+ },
1250
+ "manifest": emitted.manifest,
1251
+ }
1252
+ )
1253
+ else:
1254
+ print(path)
1255
+ if emitted:
1256
+ print(f"context_pack={emitted.manifest['pack_id']}")
1257
+ elif args.json:
1258
+ if emitted:
1259
+ print_json(
1260
+ {
1261
+ "event_path": str(emitted.event_path),
1262
+ "artifact": {
1263
+ "sha256": emitted.artifact_sha256,
1264
+ "path": str(emitted.artifact_path),
1265
+ },
1266
+ "manifest": emitted.manifest,
1267
+ }
1268
+ )
1269
+ else:
1270
+ print_json(pack)
1271
+ else:
1272
+ print(format_context_pack(pack), end="")
1273
+ if emitted:
1274
+ print(f"\nContext pack: {emitted.manifest['pack_id']}")
1275
+ return 0
1276
+
1277
+
1278
+ def cmd_context_consume(args: argparse.Namespace) -> int:
1279
+ result = consume_context_sources(
1280
+ command_root(args),
1281
+ pack_id=args.pack,
1282
+ source_ids=args.sources,
1283
+ purpose=args.purpose,
1284
+ session_id=args.session,
1285
+ actor=args.actor,
1286
+ )
1287
+ if args.json:
1288
+ print_json(result)
1289
+ else:
1290
+ print(f"context_consumed={result['pack_id']}")
1291
+ print(f"purpose={result['purpose']}")
1292
+ print(f"sources={len(result['source_ids'])}")
1293
+ return 0
1294
+
1295
+
1296
+ def cmd_context_cite(args: argparse.Namespace) -> int:
1297
+ citation = cite_context_sources(
1298
+ command_root(args),
1299
+ pack_id=args.pack,
1300
+ source_ids=args.sources,
1301
+ output_format=args.format,
1302
+ session_id=args.session,
1303
+ actor=args.actor,
1304
+ )
1305
+ print(citation["rendered"], end="")
1306
+ return 0
1307
+
1308
+
1309
+ def cmd_audit_context(args: argparse.Namespace) -> int:
1310
+ audit = audit_context_pack(command_root(args), args.pack)
1311
+ if args.json:
1312
+ print_json(audit)
1313
+ else:
1314
+ print(format_context_audit(audit))
1315
+ return 0
1316
+
1317
+
1318
+ def cmd_audit_session(args: argparse.Namespace) -> int:
1319
+ audit = audit_session(command_root(args), args.session, strict=args.strict)
1320
+ if args.json:
1321
+ print_json(audit)
1322
+ else:
1323
+ print(format_session_audit(audit))
1324
+ return strict_session_exit_code(audit) if args.strict else 0
1325
+
1326
+
1327
+ def cmd_audit_claims(args: argparse.Namespace) -> int:
1328
+ audit = audit_claims(
1329
+ command_root(args),
1330
+ read_body(args.text),
1331
+ args.session,
1332
+ strict=args.strict,
1333
+ )
1334
+ if args.json:
1335
+ print_json(audit)
1336
+ else:
1337
+ print(format_claims_audit(audit))
1338
+ return strict_claims_exit_code(audit) if args.strict else 0
1339
+
1340
+
1341
+ def build_parser() -> argparse.ArgumentParser:
1342
+ parser = argparse.ArgumentParser(prog="agentdir")
1343
+ parser.add_argument("--version", action="version", version=f"agentdir {__version__}")
1344
+ parser.add_argument("--upgrade", action="store_true", help="reinstall latest AgentDir and re-adopt this repo")
1345
+ parser.add_argument("--upgrade-version", help="release tag to install instead of the latest release")
1346
+ parser.add_argument("--upgrade-repo", default="jstxn/agentdir", help="GitHub repo to install from")
1347
+ parser.add_argument(
1348
+ "--upgrade-install-skill",
1349
+ choices=("user", "project", "store", "none"),
1350
+ default="user",
1351
+ help="Codex skill target to use during re-adoption",
1352
+ )
1353
+ parser.add_argument("--upgrade-no-adopt", action="store_true", help="only reinstall, do not re-adopt repo")
1354
+ parser.add_argument("--upgrade-no-hooks", action="store_true", help="re-adopt without installing Git hooks")
1355
+ parser.add_argument("--upgrade-dry-run", action="store_true", help="show the upgrade plan without changing files")
1356
+ parser.add_argument("--upgrade-json", action="store_true", help="print upgrade result as JSON")
1357
+ sub = parser.add_subparsers(dest="command")
1358
+
1359
+ init = sub.add_parser("init")
1360
+ init.add_argument("root", nargs="?")
1361
+ add_scope_args(init)
1362
+ init.set_defaults(func=cmd_init)
1363
+
1364
+ root = sub.add_parser("root")
1365
+ add_scope_args(root)
1366
+ root.add_argument("--json", action="store_true")
1367
+ root.set_defaults(func=cmd_root)
1368
+
1369
+ status = sub.add_parser("status")
1370
+ add_scope_args(status)
1371
+ status.add_argument("--no-rebuild", action="store_true")
1372
+ status.add_argument("--json", action="store_true")
1373
+ status.set_defaults(func=cmd_status)
1374
+
1375
+ emit = sub.add_parser("emit")
1376
+ add_scope_args(emit)
1377
+ emit.add_argument("--session")
1378
+ emit.add_argument("--type", required=True)
1379
+ emit.add_argument("--body")
1380
+ emit.add_argument("--subject")
1381
+ emit.add_argument("--from", dest="from_actor", default="agent")
1382
+ emit.add_argument("--to", dest="to_actor")
1383
+ emit.add_argument("--task")
1384
+ emit.add_argument("--workspace")
1385
+ emit.add_argument("--git-head")
1386
+ emit.add_argument("--tool")
1387
+ emit.add_argument("--tool-exit-code", type=int)
1388
+ emit.add_argument("--parent")
1389
+ emit.add_argument("--artifact")
1390
+ emit.add_argument("--message-id")
1391
+ emit.set_defaults(func=cmd_emit)
1392
+
1393
+ session = sub.add_parser("session")
1394
+ session_sub = session.add_subparsers(dest="session_command", required=True)
1395
+ session_start = session_sub.add_parser("start")
1396
+ add_scope_args(session_start)
1397
+ session_start.add_argument("--id", dest="session_id")
1398
+ session_start.add_argument("--title")
1399
+ session_start.add_argument("--actor", default="agent")
1400
+ session_start.add_argument("--note")
1401
+ session_start.add_argument("--json", action="store_true")
1402
+ session_start.set_defaults(func=cmd_session_start)
1403
+ session_current = session_sub.add_parser("current")
1404
+ add_scope_args(session_current)
1405
+ session_current.add_argument("--json", action="store_true")
1406
+ session_current.set_defaults(func=cmd_session_current)
1407
+ session_ensure = session_sub.add_parser("ensure")
1408
+ add_scope_args(session_ensure)
1409
+ session_ensure.add_argument("--id", dest="session_id")
1410
+ session_ensure.add_argument("--title")
1411
+ session_ensure.add_argument("--actor", default="agent")
1412
+ session_ensure.add_argument("--json", action="store_true")
1413
+ session_ensure.set_defaults(func=cmd_session_ensure)
1414
+ session_end = session_sub.add_parser("end")
1415
+ add_scope_args(session_end)
1416
+ session_end.add_argument("--status", default="completed")
1417
+ session_end.add_argument("--summary")
1418
+ session_end.add_argument("--actor", default="agent")
1419
+ session_end.add_argument("--json", action="store_true")
1420
+ session_end.set_defaults(func=cmd_session_end)
1421
+
1422
+ actor = sub.add_parser("actor")
1423
+ actor_sub = actor.add_subparsers(dest="actor_command", required=True)
1424
+ actor_create = actor_sub.add_parser("create")
1425
+ add_scope_args(actor_create)
1426
+ actor_create.add_argument("actor_id")
1427
+ actor_create.set_defaults(func=cmd_actor_create)
1428
+
1429
+ send = sub.add_parser("send")
1430
+ add_scope_args(send)
1431
+ send.add_argument("--from", dest="from_actor", required=True)
1432
+ send.add_argument("--to", dest="to_actor", required=True)
1433
+ send.add_argument("--type", required=True)
1434
+ send.add_argument("--body")
1435
+ send.add_argument("--subject")
1436
+ send.add_argument("--session")
1437
+ send.add_argument("--task")
1438
+ send.add_argument("--message-id")
1439
+ send.set_defaults(func=cmd_send)
1440
+
1441
+ artifact = sub.add_parser("artifact")
1442
+ artifact_sub = artifact.add_subparsers(dest="artifact_command", required=True)
1443
+ artifact_add = artifact_sub.add_parser("add")
1444
+ add_scope_args(artifact_add)
1445
+ artifact_add.add_argument("path")
1446
+ artifact_add.set_defaults(func=cmd_artifact_add)
1447
+
1448
+ index = sub.add_parser("index")
1449
+ index_sub = index.add_subparsers(dest="index_command", required=True)
1450
+ rebuild = index_sub.add_parser("rebuild")
1451
+ add_scope_args(rebuild)
1452
+ rebuild.add_argument("--json", action="store_true")
1453
+ rebuild.set_defaults(func=cmd_index_rebuild)
1454
+ update = index_sub.add_parser("update")
1455
+ add_scope_args(update)
1456
+ update.add_argument("--json", action="store_true")
1457
+ update.set_defaults(func=cmd_index_update)
1458
+
1459
+ roots = sub.add_parser("roots")
1460
+ roots_sub = roots.add_subparsers(dest="roots_command", required=True)
1461
+ roots_register = roots_sub.add_parser("register")
1462
+ add_scope_args(roots_register)
1463
+ roots_register.add_argument("path")
1464
+ roots_register.add_argument("--name")
1465
+ roots_register.add_argument("--visibility", choices=VISIBILITY_CHOICES, default="private")
1466
+ roots_register.add_argument("--json", action="store_true")
1467
+ roots_register.set_defaults(func=cmd_roots_register)
1468
+ roots_list = roots_sub.add_parser("list")
1469
+ add_scope_args(roots_list)
1470
+ roots_list.add_argument("--json", action="store_true")
1471
+ roots_list.set_defaults(func=cmd_roots_list)
1472
+ roots_remove = roots_sub.add_parser("remove")
1473
+ add_scope_args(roots_remove)
1474
+ roots_remove.add_argument("identifier")
1475
+ roots_remove.add_argument("--json", action="store_true")
1476
+ roots_remove.set_defaults(func=cmd_roots_remove)
1477
+ roots_rebuild = roots_sub.add_parser("rebuild")
1478
+ add_scope_args(roots_rebuild)
1479
+ roots_rebuild.add_argument("--group")
1480
+ roots_rebuild.add_argument("--stale", action="store_true")
1481
+ roots_rebuild.add_argument("--json", action="store_true")
1482
+ roots_rebuild.set_defaults(func=cmd_roots_rebuild)
1483
+ roots_suggest = roots_sub.add_parser("suggest")
1484
+ add_scope_args(roots_suggest)
1485
+ roots_suggest.add_argument("--near")
1486
+ roots_suggest.add_argument("--json", action="store_true")
1487
+ roots_suggest.set_defaults(func=cmd_roots_suggest)
1488
+ roots_doctor = roots_sub.add_parser("doctor")
1489
+ add_scope_args(roots_doctor)
1490
+ roots_doctor.add_argument("--group")
1491
+ roots_doctor.add_argument("--json", action="store_true")
1492
+ roots_doctor.set_defaults(func=cmd_roots_doctor)
1493
+ roots_group = roots_sub.add_parser("group")
1494
+ roots_group_sub = roots_group.add_subparsers(dest="roots_group_command", required=True)
1495
+ roots_group_create = roots_group_sub.add_parser("create")
1496
+ add_scope_args(roots_group_create)
1497
+ roots_group_create.add_argument("name")
1498
+ roots_group_create.add_argument("--member", action="append", dest="members", required=True)
1499
+ roots_group_create.add_argument("--json", action="store_true")
1500
+ roots_group_create.set_defaults(func=cmd_roots_group_create)
1501
+ roots_group_list = roots_group_sub.add_parser("list")
1502
+ add_scope_args(roots_group_list)
1503
+ roots_group_list.add_argument("--json", action="store_true")
1504
+ roots_group_list.set_defaults(func=cmd_roots_group_list)
1505
+ roots_group_add = roots_group_sub.add_parser("add")
1506
+ add_scope_args(roots_group_add)
1507
+ roots_group_add.add_argument("name")
1508
+ roots_group_add.add_argument("root_id")
1509
+ roots_group_add.add_argument("--json", action="store_true")
1510
+ roots_group_add.set_defaults(func=cmd_roots_group_add)
1511
+ roots_group_remove = roots_group_sub.add_parser("remove")
1512
+ add_scope_args(roots_group_remove)
1513
+ roots_group_remove.add_argument("name")
1514
+ roots_group_remove.add_argument("root_id")
1515
+ roots_group_remove.add_argument("--json", action="store_true")
1516
+ roots_group_remove.set_defaults(func=cmd_roots_group_remove)
1517
+
1518
+ archive = sub.add_parser("archive")
1519
+ add_scope_args(archive)
1520
+ archive.add_argument("--session", action="append", dest="sessions")
1521
+ archive.add_argument("--older-than-days", type=int)
1522
+ archive.add_argument("--keep-recent", type=int)
1523
+ archive.add_argument("--apply", action="store_true")
1524
+ archive.add_argument("--json", action="store_true")
1525
+ archive.set_defaults(func=cmd_archive)
1526
+
1527
+ prune = sub.add_parser("prune")
1528
+ add_scope_args(prune)
1529
+ prune.add_argument("--session", action="append", dest="sessions")
1530
+ prune.add_argument("--older-than-days", type=int)
1531
+ prune.add_argument("--keep-recent", type=int)
1532
+ prune.add_argument("--include-live-sessions", action="store_true")
1533
+ prune.add_argument("--apply", action="store_true")
1534
+ prune.add_argument("--json", action="store_true")
1535
+ prune.set_defaults(func=cmd_prune)
1536
+
1537
+ query = sub.add_parser("query")
1538
+ add_scope_args(query)
1539
+ query.add_argument("--session")
1540
+ query.add_argument("--type")
1541
+ query.add_argument("--actor")
1542
+ query.add_argument("--task")
1543
+ query.add_argument("--tool")
1544
+ query.add_argument("--git-head")
1545
+ query.add_argument("--workspace")
1546
+ query.add_argument("--text")
1547
+ query.add_argument("--semantic")
1548
+ query.add_argument("--federated", action="store_true")
1549
+ query.add_argument("--retrieval", choices=RETRIEVAL_MODES, default="hybrid")
1550
+ query.add_argument("--min-score", type=float, default=DEFAULT_MIN_SCORE)
1551
+ query.add_argument("--since")
1552
+ query.add_argument("--until")
1553
+ query.add_argument("--limit", type=int, default=100)
1554
+ query.add_argument("--json", action="store_true")
1555
+ query.set_defaults(func=cmd_query)
1556
+
1557
+ replay = sub.add_parser("replay")
1558
+ add_scope_args(replay)
1559
+ replay.add_argument("--session", required=True)
1560
+ replay.set_defaults(func=cmd_replay)
1561
+
1562
+ doctor = sub.add_parser("doctor")
1563
+ add_scope_args(doctor)
1564
+ doctor.add_argument("--json", action="store_true")
1565
+ doctor.set_defaults(func=cmd_doctor)
1566
+
1567
+ secrets = sub.add_parser("secrets")
1568
+ secrets_sub = secrets.add_subparsers(dest="secrets_command", required=True)
1569
+ secrets_scan = secrets_sub.add_parser("scan")
1570
+ add_scope_args(secrets_scan)
1571
+ secrets_scan.add_argument("--json", action="store_true")
1572
+ secrets_scan.set_defaults(func=cmd_secrets_scan)
1573
+ secrets_redact = secrets_sub.add_parser("redact")
1574
+ add_scope_args(secrets_redact)
1575
+ secrets_redact.add_argument("--apply", action="store_true")
1576
+ secrets_redact.add_argument("--json", action="store_true")
1577
+ secrets_redact.set_defaults(func=cmd_secrets_redact)
1578
+
1579
+ run = sub.add_parser("run")
1580
+ add_scope_args(run)
1581
+ run.add_argument("--session")
1582
+ run.add_argument("--name")
1583
+ run.add_argument("--cwd")
1584
+ run.add_argument("--max-capture-bytes", type=int, default=DEFAULT_MAX_CAPTURE_BYTES)
1585
+ run.add_argument("--no-redact", action="store_true")
1586
+ run.add_argument("command", nargs=argparse.REMAINDER)
1587
+ run.set_defaults(func=cmd_run)
1588
+
1589
+ hooks = sub.add_parser("hooks")
1590
+ hooks_sub = hooks.add_subparsers(dest="hooks_command", required=True)
1591
+ hooks_install = hooks_sub.add_parser("install")
1592
+ add_scope_args(hooks_install)
1593
+ hooks_install.add_argument("--hook", action="append", dest="hooks")
1594
+ hooks_install.add_argument("--force", action="store_true")
1595
+ hooks_install.add_argument("--json", action="store_true")
1596
+ hooks_install.set_defaults(func=cmd_hooks_install)
1597
+ hooks_status = hooks_sub.add_parser("status")
1598
+ hooks_status.add_argument("--hook", action="append", dest="hooks")
1599
+ hooks_status.add_argument("--json", action="store_true")
1600
+ hooks_status.set_defaults(func=cmd_hooks_status)
1601
+ hooks_uninstall = hooks_sub.add_parser("uninstall")
1602
+ hooks_uninstall.add_argument("--hook", action="append", dest="hooks")
1603
+ hooks_uninstall.add_argument("--json", action="store_true")
1604
+ hooks_uninstall.set_defaults(func=cmd_hooks_uninstall)
1605
+ hooks_record = hooks_sub.add_parser("record")
1606
+ add_scope_args(hooks_record)
1607
+ hooks_record.add_argument("--hook", required=True)
1608
+ hooks_record.add_argument("--original-exit-code", type=int, required=True)
1609
+ hooks_record.add_argument("--stdin-file")
1610
+ hooks_record.add_argument("hook_args", nargs=argparse.REMAINDER)
1611
+ hooks_record.set_defaults(func=cmd_hooks_record)
1612
+
1613
+ skills = sub.add_parser("skills")
1614
+ skills_sub = skills.add_subparsers(dest="skills_command", required=True)
1615
+ skills_install = skills_sub.add_parser("install")
1616
+ skills_install_sub = skills_install.add_subparsers(dest="skill_name", required=True)
1617
+ codex_skill = skills_install_sub.add_parser("codex")
1618
+ add_scope_args(codex_skill)
1619
+ codex_skill.add_argument("--target", choices=("user", "project", "store"), default="user")
1620
+ codex_skill.add_argument("--force", action="store_true")
1621
+ codex_skill.add_argument("--json", action="store_true")
1622
+ codex_skill.set_defaults(func=cmd_skills_install_codex)
1623
+ generic_guidance = skills_install_sub.add_parser("generic")
1624
+ add_scope_args(generic_guidance)
1625
+ generic_guidance.add_argument("--target", choices=("project", "store"), default="store")
1626
+ generic_guidance.add_argument("--force", action="store_true")
1627
+ generic_guidance.add_argument("--json", action="store_true")
1628
+ generic_guidance.set_defaults(func=cmd_skills_install_generic)
1629
+
1630
+ setup = sub.add_parser("setup")
1631
+ add_scope_args(setup)
1632
+ setup.add_argument("--no-hooks", action="store_true")
1633
+ setup.add_argument("--codex-skill", choices=("user", "project", "store", "none"), default="user")
1634
+ setup.add_argument("--install-generic", choices=("project", "store", "none"), default="project")
1635
+ setup.add_argument("--install-integrations", choices=("all", "none"), default="all")
1636
+ setup.add_argument("--integration-target", choices=("project", "store"), default="project")
1637
+ setup.add_argument("--gitignore", choices=GITIGNORE_CHOICES, default="ask")
1638
+ setup.add_argument("--dry-run", action="store_true")
1639
+ setup.add_argument("--force", action="store_true")
1640
+ setup.add_argument("--json", action="store_true")
1641
+ setup.set_defaults(func=cmd_setup)
1642
+
1643
+ adopt = sub.add_parser("adopt")
1644
+ add_scope_args(adopt)
1645
+ adopt.add_argument("--install-skill", choices=("user", "project", "store", "none"), default="user")
1646
+ adopt.add_argument("--install-generic", choices=("project", "store", "none"), default="project")
1647
+ adopt.add_argument("--install-integrations", choices=("all", "none"), default="all")
1648
+ adopt.add_argument("--integration-target", choices=("project", "store"), default="project")
1649
+ adopt.add_argument("--gitignore", choices=GITIGNORE_CHOICES, default="ask")
1650
+ adopt.add_argument("--no-hooks", action="store_true")
1651
+ adopt.add_argument("--dry-run", action="store_true")
1652
+ adopt.add_argument("--force", action="store_true")
1653
+ adopt.add_argument("--json", action="store_true")
1654
+ adopt.set_defaults(func=cmd_adopt)
1655
+
1656
+ unadopt = sub.add_parser("unadopt")
1657
+ add_scope_args(unadopt)
1658
+ unadopt.add_argument("--apply", action="store_true")
1659
+ unadopt.add_argument("--no-hooks", action="store_true")
1660
+ unadopt.add_argument("--json", action="store_true")
1661
+ unadopt.set_defaults(func=cmd_unadopt)
1662
+
1663
+ integrations = sub.add_parser("integrations")
1664
+ integrations_sub = integrations.add_subparsers(dest="integrations_command", required=True)
1665
+ integrations_install = integrations_sub.add_parser("install")
1666
+ add_scope_args(integrations_install)
1667
+ integrations_install.add_argument("name", choices=(*INTEGRATION_NAMES, "all"))
1668
+ integrations_install.add_argument("--target", choices=("project", "store"), required=True)
1669
+ integrations_install.add_argument("--force", action="store_true")
1670
+ integrations_install.add_argument("--json", action="store_true")
1671
+ integrations_install.set_defaults(func=cmd_integrations_install)
1672
+ integrations_doctor = integrations_sub.add_parser("doctor")
1673
+ add_scope_args(integrations_doctor)
1674
+ integrations_doctor.add_argument("--target", choices=("project", "store"), default="project")
1675
+ integrations_doctor.add_argument("--json", action="store_true")
1676
+ integrations_doctor.set_defaults(func=cmd_integrations_doctor)
1677
+
1678
+ work = sub.add_parser("work")
1679
+ work_sub = work.add_subparsers(dest="work_command", required=True)
1680
+ work_start = work_sub.add_parser("start")
1681
+ add_scope_args(work_start)
1682
+ work_start.add_argument("task")
1683
+ work_start.add_argument("--actor", default="agent")
1684
+ work_start.add_argument("--emit-context", action="store_true")
1685
+ work_start.add_argument("--federated", action="store_true")
1686
+ work_start.add_argument("--group")
1687
+ work_start.add_argument("--memory-limit", type=int, default=8)
1688
+ work_start.add_argument("--evidence-limit", type=int, default=20)
1689
+ work_start.add_argument("--recent-limit", type=int, default=5)
1690
+ work_start.add_argument("--min-score", type=float, default=DEFAULT_MIN_SCORE)
1691
+ work_start.add_argument("--retrieval", choices=RETRIEVAL_MODES, default="hybrid")
1692
+ work_start.add_argument("--json", action="store_true")
1693
+ work_start.set_defaults(func=cmd_work_start)
1694
+ work_finish = work_sub.add_parser("finish")
1695
+ add_scope_args(work_finish)
1696
+ work_finish.add_argument("--session")
1697
+ work_finish.add_argument("--actor", default="agent")
1698
+ work_finish.add_argument("--keep-session", action="store_true")
1699
+ work_finish.add_argument("--no-doctor", action="store_true")
1700
+ work_finish.add_argument("--claims")
1701
+ work_finish.add_argument("--json", action="store_true")
1702
+ work_finish.set_defaults(func=cmd_work_finish)
1703
+
1704
+ report = sub.add_parser("report")
1705
+ report_sub = report.add_subparsers(dest="report_command", required=True)
1706
+ report_final = report_sub.add_parser("final")
1707
+ add_scope_args(report_final)
1708
+ report_final.add_argument("--session")
1709
+ report_final.add_argument("--format", choices=("md", "json"), default="md")
1710
+ report_final.add_argument("--no-doctor", action="store_true")
1711
+ report_final.add_argument("--claims")
1712
+ report_final.set_defaults(func=cmd_report_final)
1713
+
1714
+ summarize = sub.add_parser("summarize")
1715
+ add_scope_args(summarize)
1716
+ summarize.add_argument("--session")
1717
+ summarize.add_argument("--json", action="store_true")
1718
+ summarize.set_defaults(func=cmd_summarize)
1719
+
1720
+ evidence = sub.add_parser("evidence")
1721
+ add_scope_args(evidence)
1722
+ evidence.add_argument("--session")
1723
+ evidence.add_argument("--brief", action="store_true")
1724
+ evidence.add_argument("--family", choices=EVIDENCE_FAMILIES)
1725
+ evidence.add_argument("--failed", action="store_true")
1726
+ evidence.add_argument("--json", action="store_true")
1727
+ evidence.set_defaults(func=cmd_evidence)
1728
+
1729
+ timeline = sub.add_parser("timeline")
1730
+ add_scope_args(timeline)
1731
+ timeline.add_argument("--session")
1732
+ timeline.add_argument("--limit", type=int, default=100)
1733
+ timeline.add_argument("--json", action="store_true")
1734
+ timeline.set_defaults(func=cmd_timeline)
1735
+
1736
+ memory = sub.add_parser("memory")
1737
+ memory_sub = memory.add_subparsers(dest="memory_command", required=True)
1738
+ memory_search = memory_sub.add_parser("search")
1739
+ add_scope_args(memory_search)
1740
+ memory_search.add_argument("query")
1741
+ memory_search.add_argument("--session")
1742
+ memory_search.add_argument("--type")
1743
+ memory_search.add_argument("--actor")
1744
+ memory_search.add_argument("--task")
1745
+ memory_search.add_argument("--tool")
1746
+ memory_search.add_argument("--git-head")
1747
+ memory_search.add_argument("--workspace")
1748
+ memory_search.add_argument("--since")
1749
+ memory_search.add_argument("--until")
1750
+ memory_search.add_argument("--limit", type=int, default=10)
1751
+ memory_search.add_argument("--min-score", type=float, default=DEFAULT_MIN_SCORE)
1752
+ memory_search.add_argument("--retrieval", choices=RETRIEVAL_MODES, default="hybrid")
1753
+ memory_search.add_argument("--federated", action="store_true")
1754
+ memory_search.add_argument("--group")
1755
+ memory_search.add_argument("--no-rebuild", action="store_true")
1756
+ memory_search.add_argument("--json", action="store_true")
1757
+ memory_search.set_defaults(func=cmd_memory_search)
1758
+ memory_explain = memory_sub.add_parser("explain")
1759
+ add_scope_args(memory_explain)
1760
+ memory_explain.add_argument("query")
1761
+ memory_explain.add_argument("--source")
1762
+ memory_explain.add_argument("--min-score", type=float, default=0.0)
1763
+ memory_explain.add_argument("--no-rebuild", action="store_true")
1764
+ memory_explain.add_argument("--json", action="store_true")
1765
+ memory_explain.set_defaults(func=cmd_memory_explain)
1766
+ memory_stats_parser = memory_sub.add_parser("stats")
1767
+ add_scope_args(memory_stats_parser)
1768
+ memory_stats_parser.add_argument("--no-rebuild", action="store_true")
1769
+ memory_stats_parser.add_argument("--json", action="store_true")
1770
+ memory_stats_parser.set_defaults(func=cmd_memory_stats)
1771
+ memory_backend = memory_sub.add_parser("backend")
1772
+ memory_backend_sub = memory_backend.add_subparsers(dest="memory_backend_command", required=True)
1773
+ memory_backend_list = memory_backend_sub.add_parser("list")
1774
+ add_scope_args(memory_backend_list)
1775
+ memory_backend_list.add_argument("--no-rebuild", action="store_true")
1776
+ memory_backend_list.add_argument("--json", action="store_true")
1777
+ memory_backend_list.set_defaults(func=cmd_memory_backend_list)
1778
+ memory_backend_configure = memory_backend_sub.add_parser("configure")
1779
+ add_scope_args(memory_backend_configure)
1780
+ memory_backend_configure.add_argument("backend", choices=("sqlite-vec", "none"))
1781
+ memory_backend_configure.add_argument("--json", action="store_true")
1782
+ memory_backend_configure.set_defaults(func=cmd_memory_backend_configure)
1783
+ memory_embeddings = memory_sub.add_parser("embeddings")
1784
+ memory_embeddings_sub = memory_embeddings.add_subparsers(dest="memory_embeddings_command", required=True)
1785
+ memory_embeddings_configure = memory_embeddings_sub.add_parser("configure")
1786
+ add_scope_args(memory_embeddings_configure)
1787
+ memory_embeddings_configure.add_argument("provider", choices=("fastembed", "none"))
1788
+ memory_embeddings_configure.add_argument("--model")
1789
+ memory_embeddings_configure.add_argument("--json", action="store_true")
1790
+ memory_embeddings_configure.set_defaults(func=cmd_memory_embeddings_configure)
1791
+ memory_team = memory_sub.add_parser("team")
1792
+ memory_team_sub = memory_team.add_subparsers(dest="memory_team_command", required=True)
1793
+ memory_team_configure = memory_team_sub.add_parser("configure")
1794
+ add_scope_args(memory_team_configure)
1795
+ memory_team_configure.add_argument("backend", choices=("qdrant", "lancedb", "none"))
1796
+ memory_team_configure.add_argument("--json", action="store_true")
1797
+ memory_team_configure.set_defaults(func=cmd_memory_team_configure)
1798
+ memory_daemon = memory_sub.add_parser("daemon")
1799
+ memory_daemon_sub = memory_daemon.add_subparsers(dest="memory_daemon_command", required=True)
1800
+ memory_daemon_start = memory_daemon_sub.add_parser("start")
1801
+ add_scope_args(memory_daemon_start)
1802
+ memory_daemon_start.add_argument("--interval", type=float, default=2.0)
1803
+ memory_daemon_start.add_argument("--group")
1804
+ memory_daemon_start.add_argument("--force", action="store_true")
1805
+ memory_daemon_start.add_argument("--json", action="store_true")
1806
+ memory_daemon_start.set_defaults(func=cmd_memory_daemon_start)
1807
+ memory_daemon_status_parser = memory_daemon_sub.add_parser("status")
1808
+ add_scope_args(memory_daemon_status_parser)
1809
+ memory_daemon_status_parser.add_argument("--json", action="store_true")
1810
+ memory_daemon_status_parser.set_defaults(func=cmd_memory_daemon_status)
1811
+ memory_daemon_stop = memory_daemon_sub.add_parser("stop")
1812
+ add_scope_args(memory_daemon_stop)
1813
+ memory_daemon_stop.add_argument("--timeout", type=float, default=5.0)
1814
+ memory_daemon_stop.add_argument("--json", action="store_true")
1815
+ memory_daemon_stop.set_defaults(func=cmd_memory_daemon_stop)
1816
+ memory_daemon_run = memory_daemon_sub.add_parser("run")
1817
+ add_scope_args(memory_daemon_run)
1818
+ memory_daemon_run.add_argument("--interval", type=float, default=2.0)
1819
+ memory_daemon_run.add_argument("--group")
1820
+ memory_daemon_run.add_argument("--once", action="store_true")
1821
+ memory_daemon_run.add_argument("--json", action="store_true")
1822
+ memory_daemon_run.set_defaults(func=cmd_memory_daemon_run)
1823
+
1824
+ context = sub.add_parser("context")
1825
+ context_sub = context.add_subparsers(dest="context_command", required=True)
1826
+ context_build = context_sub.add_parser("build")
1827
+ add_scope_args(context_build)
1828
+ context_build.add_argument("task")
1829
+ context_build.add_argument("--session")
1830
+ context_build.add_argument("--memory-limit", type=int, default=8)
1831
+ context_build.add_argument("--evidence-limit", type=int, default=20)
1832
+ context_build.add_argument("--recent-limit", type=int, default=5)
1833
+ context_build.add_argument("--min-score", type=float, default=DEFAULT_MIN_SCORE)
1834
+ context_build.add_argument("--retrieval", choices=RETRIEVAL_MODES, default="hybrid")
1835
+ context_build.add_argument("--federated", action="store_true")
1836
+ context_build.add_argument("--group")
1837
+ context_build.add_argument("--output")
1838
+ context_build.add_argument("--emit", action="store_true")
1839
+ context_build.add_argument("--json", action="store_true")
1840
+ context_build.set_defaults(func=cmd_context_build)
1841
+ context_consume = context_sub.add_parser("consume")
1842
+ add_scope_args(context_consume)
1843
+ context_consume.add_argument("--pack", required=True)
1844
+ context_consume.add_argument("--source", action="append", dest="sources", required=True)
1845
+ context_consume.add_argument("--purpose", choices=CONSUMPTION_PURPOSES, required=True)
1846
+ context_consume.add_argument("--session")
1847
+ context_consume.add_argument("--actor", default="agent")
1848
+ context_consume.add_argument("--json", action="store_true")
1849
+ context_consume.set_defaults(func=cmd_context_consume)
1850
+ context_cite = context_sub.add_parser("cite")
1851
+ add_scope_args(context_cite)
1852
+ context_cite.add_argument("--pack", required=True)
1853
+ context_cite.add_argument("--source", action="append", dest="sources")
1854
+ context_cite.add_argument("--format", choices=("md", "json"), default="md")
1855
+ context_cite.add_argument("--session")
1856
+ context_cite.add_argument("--actor", default="agent")
1857
+ context_cite.set_defaults(func=cmd_context_cite)
1858
+
1859
+ audit = sub.add_parser("audit")
1860
+ audit_sub = audit.add_subparsers(dest="audit_command", required=True)
1861
+ audit_context = audit_sub.add_parser("context")
1862
+ add_scope_args(audit_context)
1863
+ audit_context.add_argument("--pack", required=True)
1864
+ audit_context.add_argument("--json", action="store_true")
1865
+ audit_context.set_defaults(func=cmd_audit_context)
1866
+ audit_session_parser = audit_sub.add_parser("session")
1867
+ add_scope_args(audit_session_parser)
1868
+ audit_session_parser.add_argument("--session")
1869
+ audit_session_parser.add_argument("--strict", action="store_true")
1870
+ audit_session_parser.add_argument("--json", action="store_true")
1871
+ audit_session_parser.set_defaults(func=cmd_audit_session)
1872
+ audit_claims_parser = audit_sub.add_parser("claims")
1873
+ add_scope_args(audit_claims_parser)
1874
+ audit_claims_parser.add_argument("--text", required=True)
1875
+ audit_claims_parser.add_argument("--session")
1876
+ audit_claims_parser.add_argument("--strict", action="store_true")
1877
+ audit_claims_parser.add_argument("--json", action="store_true")
1878
+ audit_claims_parser.set_defaults(func=cmd_audit_claims)
1879
+
1880
+ return parser
1881
+
1882
+
1883
+ def add_scope_args(parser: argparse.ArgumentParser) -> None:
1884
+ parser.add_argument("--root", dest="root_option")
1885
+ parser.add_argument("--scope", choices=("project", "user", "global", "machine"))
1886
+
1887
+
1888
+ def main(argv: list[str] | None = None) -> int:
1889
+ parser = build_parser()
1890
+ args = parser.parse_args(argv)
1891
+ try:
1892
+ if args.upgrade:
1893
+ return int(cmd_upgrade(args))
1894
+ if not hasattr(args, "func"):
1895
+ parser.print_help(sys.stderr)
1896
+ return 2
1897
+ return int(args.func(args))
1898
+ except AgentDirError as exc:
1899
+ print(f"agentdir: {exc}", file=sys.stderr)
1900
+ return 2