intent-cli-python 0.5.0__py3-none-any.whl → 1.0.0__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.
intent_cli/__init__.py CHANGED
@@ -1,29 +1 @@
1
- """Intent CLI package."""
2
-
3
- from importlib import metadata
4
- from pathlib import Path
5
- import re
6
- from typing import Optional
7
-
8
-
9
- PACKAGE_NAME = "intent-cli-python"
10
- REPO_ROOT = Path(__file__).resolve().parents[2]
11
- PYPROJECT_PATH = REPO_ROOT / "pyproject.toml"
12
- VERSION_PATTERN = re.compile(r'^version\s*=\s*"([^"]+)"\s*$', re.MULTILINE)
13
-
14
-
15
- def version_from_checkout() -> Optional[str]:
16
- if not PYPROJECT_PATH.exists():
17
- return None
18
- match = VERSION_PATTERN.search(PYPROJECT_PATH.read_text(encoding="utf-8"))
19
- if not match:
20
- return None
21
- return match.group(1)
22
-
23
-
24
- __version__ = version_from_checkout()
25
- if __version__ is None:
26
- try:
27
- __version__ = metadata.version(PACKAGE_NAME)
28
- except metadata.PackageNotFoundError:
29
- __version__ = "0.4.0"
1
+ """Intent CLI — semantic history for agent-driven development."""
intent_cli/__main__.py CHANGED
@@ -1,5 +1,3 @@
1
- from .cli import main
1
+ from intent_cli.cli import main
2
2
 
3
-
4
- if __name__ == "__main__":
5
- raise SystemExit(main())
3
+ main()
intent_cli/cli.py CHANGED
@@ -1,162 +1,497 @@
1
- from __future__ import annotations
1
+ """Intent CLI entry point and command handlers."""
2
2
 
3
3
  import argparse
4
4
  import json
5
- from pathlib import Path
6
- from typing import Any, Dict, Optional
7
-
8
- from . import __version__
9
- from .constants import EXIT_GENERAL_FAILURE, EXIT_SUCCESS
10
- from .core import IntentRepository
11
- from .errors import IntentError
12
-
5
+ import sys
6
+ from datetime import datetime, timezone
7
+
8
+ from intent_cli.output import success, error
9
+ from intent_cli.store import (
10
+ git_root, ensure_init, init_workspace,
11
+ next_id, read_object, write_object, list_objects, read_config,
12
+ )
13
+
14
+ VERSION = "1.0.0"
15
+
16
+
17
+ def _now():
18
+ return datetime.now(timezone.utc).isoformat()
19
+
20
+
21
+ def _require_init():
22
+ """Return .intent/ base path, or exit with error."""
23
+ base = ensure_init()
24
+ if base is not None:
25
+ return base
26
+ if git_root() is None:
27
+ error("GIT_STATE_INVALID", "Not inside a Git repository.",
28
+ suggested_fix="cd into a git repo and run: itt init")
29
+ error("NOT_INITIALIZED", ".intent/ directory not found.",
30
+ suggested_fix="itt init")
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Global commands
35
+ # ---------------------------------------------------------------------------
36
+
37
+ def cmd_version(_args):
38
+ success("version", {"version": VERSION})
39
+
40
+
41
+ def cmd_init(_args):
42
+ path, err = init_workspace()
43
+ if err == "GIT_STATE_INVALID":
44
+ error("GIT_STATE_INVALID", "Not inside a Git repository.",
45
+ suggested_fix="cd into a git repo and run: itt init")
46
+ if err == "ALREADY_EXISTS":
47
+ error("ALREADY_EXISTS", ".intent/ already exists.",
48
+ suggested_fix="Remove .intent/ first if you want to reinitialize.")
49
+ success("init", {"path": str(path)})
50
+
51
+
52
+ def cmd_inspect(_args):
53
+ base = _require_init()
54
+ config = read_config(base)
55
+
56
+ active_intents = []
57
+ suspend_intents = []
58
+ for obj in list_objects(base, "intent"):
59
+ entry = {
60
+ "id": obj["id"],
61
+ "title": obj["title"],
62
+ "status": obj["status"],
63
+ "decision_ids": obj.get("decision_ids", []),
64
+ "latest_snap_id": obj["snap_ids"][-1] if obj.get("snap_ids") else None,
65
+ }
66
+ if obj["status"] == "active":
67
+ active_intents.append(entry)
68
+ elif obj["status"] == "suspend":
69
+ suspend_intents.append(entry)
70
+
71
+ active_decisions = []
72
+ for obj in list_objects(base, "decision", status="active"):
73
+ active_decisions.append({
74
+ "id": obj["id"],
75
+ "title": obj["title"],
76
+ "status": obj["status"],
77
+ "intent_ids": obj.get("intent_ids", []),
78
+ })
13
79
 
14
- def emit(payload: Dict[str, Any]) -> None:
15
- print(json.dumps(payload, indent=2))
80
+ all_snaps = list_objects(base, "snap")
81
+ all_snaps.sort(key=lambda s: s.get("created_at", ""), reverse=True)
82
+ recent_snaps = []
83
+ for s in all_snaps[:10]:
84
+ recent_snaps.append({
85
+ "id": s["id"],
86
+ "title": s["title"],
87
+ "intent_id": s["intent_id"],
88
+ "status": s["status"],
89
+ "summary": s.get("summary", ""),
90
+ "feedback": s.get("feedback", ""),
91
+ })
92
+
93
+ warnings = []
94
+ intent_ids_on_disk = {o["id"] for o in list_objects(base, "intent")}
95
+ for s in all_snaps:
96
+ if s.get("intent_id") and s["intent_id"] not in intent_ids_on_disk:
97
+ warnings.append(f"Orphan snap {s['id']}: intent {s['intent_id']} not found")
98
+
99
+ print(json.dumps({
100
+ "ok": True,
101
+ "schema_version": config.get("schema_version", "1.0"),
102
+ "active_intents": active_intents,
103
+ "suspend_intents": suspend_intents,
104
+ "active_decisions": active_decisions,
105
+ "recent_snaps": recent_snaps,
106
+ "warnings": warnings,
107
+ }, indent=2, ensure_ascii=False))
108
+
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # Intent commands
112
+ # ---------------------------------------------------------------------------
113
+
114
+ def cmd_intent_create(args):
115
+ base = _require_init()
116
+ obj_id = next_id(base, "intent")
117
+
118
+ active_decisions = list_objects(base, "decision", status="active")
119
+ decision_ids = [d["id"] for d in active_decisions]
120
+
121
+ warnings = []
122
+ if not decision_ids:
123
+ warnings.append("No active decisions to attach.")
124
+
125
+ intent = {
126
+ "id": obj_id,
127
+ "object": "intent",
128
+ "created_at": _now(),
129
+ "title": args.title,
130
+ "status": "active",
131
+ "source_query": args.query,
132
+ "rationale": args.rationale,
133
+ "decision_ids": decision_ids,
134
+ "snap_ids": [],
135
+ }
136
+ write_object(base, "intent", obj_id, intent)
137
+
138
+ for d in active_decisions:
139
+ if obj_id not in d.get("intent_ids", []):
140
+ d.setdefault("intent_ids", []).append(obj_id)
141
+ write_object(base, "decision", d["id"], d)
142
+
143
+ success("intent.create", intent, warnings)
144
+
145
+
146
+ def cmd_intent_list(args):
147
+ base = _require_init()
148
+ success("intent.list", list_objects(base, "intent", status=args.status))
149
+
150
+
151
+ def cmd_intent_show(args):
152
+ base = _require_init()
153
+ obj = read_object(base, "intent", args.id)
154
+ if obj is None:
155
+ error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
156
+ success("intent.show", obj)
157
+
158
+
159
+ def cmd_intent_activate(args):
160
+ base = _require_init()
161
+ obj = read_object(base, "intent", args.id)
162
+ if obj is None:
163
+ error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
164
+ if obj["status"] != "suspend":
165
+ error("STATE_CONFLICT",
166
+ f"Cannot activate intent with status '{obj['status']}'. Only 'suspend' intents can be activated.",
167
+ suggested_fix=f"itt intent show {args.id}")
168
+
169
+ obj["status"] = "active"
170
+
171
+ active_decisions = list_objects(base, "decision", status="active")
172
+ for d in active_decisions:
173
+ if d["id"] not in obj["decision_ids"]:
174
+ obj["decision_ids"].append(d["id"])
175
+ if args.id not in d.get("intent_ids", []):
176
+ d.setdefault("intent_ids", []).append(args.id)
177
+ write_object(base, "decision", d["id"], d)
178
+
179
+ write_object(base, "intent", args.id, obj)
180
+ success("intent.activate", obj)
181
+
182
+
183
+ def cmd_intent_suspend(args):
184
+ base = _require_init()
185
+ obj = read_object(base, "intent", args.id)
186
+ if obj is None:
187
+ error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
188
+ if obj["status"] != "active":
189
+ error("STATE_CONFLICT",
190
+ f"Cannot suspend intent with status '{obj['status']}'. Only 'active' intents can be suspended.",
191
+ suggested_fix=f"itt intent show {args.id}")
192
+
193
+ obj["status"] = "suspend"
194
+ write_object(base, "intent", args.id, obj)
195
+ success("intent.suspend", obj)
196
+
197
+
198
+ def cmd_intent_done(args):
199
+ base = _require_init()
200
+ obj = read_object(base, "intent", args.id)
201
+ if obj is None:
202
+ error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
203
+ if obj["status"] != "active":
204
+ error("STATE_CONFLICT",
205
+ f"Cannot mark intent as done with status '{obj['status']}'. Only 'active' intents can be marked done.",
206
+ suggested_fix=f"itt intent show {args.id}")
207
+
208
+ obj["status"] = "done"
209
+ write_object(base, "intent", args.id, obj)
210
+ success("intent.done", obj)
211
+
212
+
213
+ # ---------------------------------------------------------------------------
214
+ # Snap commands
215
+ # ---------------------------------------------------------------------------
216
+
217
+ def cmd_snap_create(args):
218
+ base = _require_init()
219
+ intent_id = args.intent
220
+
221
+ intent = read_object(base, "intent", intent_id)
222
+ if intent is None:
223
+ error("OBJECT_NOT_FOUND", f"Intent {intent_id} not found.")
224
+ if intent["status"] != "active":
225
+ error("STATE_CONFLICT",
226
+ f"Cannot add snap to intent with status '{intent['status']}'. Only 'active' intents accept new snaps.",
227
+ suggested_fix=f"itt intent activate {intent_id}")
228
+
229
+ obj_id = next_id(base, "snap")
230
+ snap = {
231
+ "id": obj_id,
232
+ "object": "snap",
233
+ "created_at": _now(),
234
+ "title": args.title,
235
+ "status": "active",
236
+ "intent_id": intent_id,
237
+ "query": args.query,
238
+ "rationale": args.rationale,
239
+ "summary": args.summary,
240
+ "feedback": args.feedback,
241
+ }
242
+ write_object(base, "snap", obj_id, snap)
243
+
244
+ intent.setdefault("snap_ids", []).append(obj_id)
245
+ write_object(base, "intent", intent_id, intent)
246
+
247
+ success("snap.create", snap)
248
+
249
+
250
+ def cmd_snap_list(args):
251
+ base = _require_init()
252
+ objects = list_objects(base, "snap", status=args.status)
253
+ if args.intent:
254
+ objects = [s for s in objects if s.get("intent_id") == args.intent]
255
+ success("snap.list", objects)
256
+
257
+
258
+ def cmd_snap_show(args):
259
+ base = _require_init()
260
+ obj = read_object(base, "snap", args.id)
261
+ if obj is None:
262
+ error("OBJECT_NOT_FOUND", f"Snap {args.id} not found.")
263
+ success("snap.show", obj)
264
+
265
+
266
+ def cmd_snap_feedback(args):
267
+ base = _require_init()
268
+ obj = read_object(base, "snap", args.id)
269
+ if obj is None:
270
+ error("OBJECT_NOT_FOUND", f"Snap {args.id} not found.")
271
+ obj["feedback"] = args.feedback
272
+ write_object(base, "snap", args.id, obj)
273
+ success("snap.feedback", obj)
274
+
275
+
276
+ def cmd_snap_revert(args):
277
+ base = _require_init()
278
+ obj = read_object(base, "snap", args.id)
279
+ if obj is None:
280
+ error("OBJECT_NOT_FOUND", f"Snap {args.id} not found.")
281
+ if obj["status"] != "active":
282
+ error("STATE_CONFLICT",
283
+ f"Cannot revert snap with status '{obj['status']}'. Only 'active' snaps can be reverted.",
284
+ suggested_fix=f"itt snap show {args.id}")
285
+
286
+ obj["status"] = "reverted"
287
+ write_object(base, "snap", args.id, obj)
288
+ success("snap.revert", obj)
289
+
290
+
291
+ # ---------------------------------------------------------------------------
292
+ # Decision commands
293
+ # ---------------------------------------------------------------------------
294
+
295
+ def cmd_decision_create(args):
296
+ base = _require_init()
297
+ obj_id = next_id(base, "decision")
298
+
299
+ active_intents = list_objects(base, "intent", status="active")
300
+ intent_ids = [i["id"] for i in active_intents]
301
+
302
+ warnings = []
303
+ if not intent_ids:
304
+ warnings.append("No active intents to attach.")
305
+
306
+ decision = {
307
+ "id": obj_id,
308
+ "object": "decision",
309
+ "created_at": _now(),
310
+ "title": args.title,
311
+ "status": "active",
312
+ "rationale": args.rationale,
313
+ "intent_ids": intent_ids,
314
+ }
315
+ write_object(base, "decision", obj_id, decision)
316
+
317
+ for i in active_intents:
318
+ if obj_id not in i.get("decision_ids", []):
319
+ i.setdefault("decision_ids", []).append(obj_id)
320
+ write_object(base, "intent", i["id"], i)
321
+
322
+ success("decision.create", decision, warnings)
323
+
324
+
325
+ def cmd_decision_list(args):
326
+ base = _require_init()
327
+ success("decision.list", list_objects(base, "decision", status=args.status))
328
+
329
+
330
+ def cmd_decision_show(args):
331
+ base = _require_init()
332
+ obj = read_object(base, "decision", args.id)
333
+ if obj is None:
334
+ error("OBJECT_NOT_FOUND", f"Decision {args.id} not found.")
335
+ success("decision.show", obj)
16
336
 
17
337
 
18
- def ok(action: str, result: Any, **extra: Any) -> Dict[str, Any]:
19
- payload: Dict[str, Any] = {"ok": True, "action": action, "result": result}
20
- payload.update(extra)
21
- return payload
338
+ def cmd_decision_deprecate(args):
339
+ base = _require_init()
340
+ obj = read_object(base, "decision", args.id)
341
+ if obj is None:
342
+ error("OBJECT_NOT_FOUND", f"Decision {args.id} not found.")
343
+ if obj["status"] != "active":
344
+ error("STATE_CONFLICT",
345
+ f"Cannot deprecate decision with status '{obj['status']}'. Only 'active' decisions can be deprecated.",
346
+ suggested_fix=f"itt decision show {args.id}")
22
347
 
348
+ obj["status"] = "deprecated"
349
+ write_object(base, "decision", args.id, obj)
350
+ success("decision.deprecate", obj)
23
351
 
24
- def build_parser() -> argparse.ArgumentParser:
25
- parser = argparse.ArgumentParser(
26
- prog="itt",
27
- description="Intent CLI — semantic history for agents.",
28
- )
29
- parser.add_argument("--version", action="version", version=f"intent-cli {__version__}")
30
- sub = parser.add_subparsers(dest="command", required=True, title="commands")
31
352
 
32
- sub.add_parser("version", help="Show version")
353
+ def cmd_decision_attach(args):
354
+ base = _require_init()
355
+ decision = read_object(base, "decision", args.id)
356
+ if decision is None:
357
+ error("OBJECT_NOT_FOUND", f"Decision {args.id} not found.")
33
358
 
34
- sub.add_parser("init", help="Initialize Intent in the current Git repository")
359
+ intent_id = args.intent
360
+ intent = read_object(base, "intent", intent_id)
361
+ if intent is None:
362
+ error("OBJECT_NOT_FOUND", f"Intent {intent_id} not found.")
35
363
 
36
- start_p = sub.add_parser("start", help="Create and activate an intent")
37
- start_p.add_argument("title")
364
+ if intent_id not in decision.get("intent_ids", []):
365
+ decision.setdefault("intent_ids", []).append(intent_id)
366
+ write_object(base, "decision", args.id, decision)
38
367
 
39
- snap_p = sub.add_parser("snap", help="Record a snap (adopted by default)")
40
- snap_p.add_argument("title")
41
- snap_p.add_argument("-m", "--message", help="Rationale for this snap")
42
- snap_p.add_argument("--candidate", action="store_true", help="Record as candidate without adopting")
368
+ if args.id not in intent.get("decision_ids", []):
369
+ intent.setdefault("decision_ids", []).append(args.id)
370
+ write_object(base, "intent", intent_id, intent)
43
371
 
44
- adopt_p = sub.add_parser("adopt", help="Adopt a candidate snap")
45
- adopt_p.add_argument("snap_id", nargs="?")
46
- adopt_p.add_argument("-m", "--message", help="Rationale for adoption")
372
+ success("decision.attach", decision)
47
373
 
48
- revert_p = sub.add_parser("revert", help="Revert the latest adopted snap")
49
- revert_p.add_argument("-m", "--message", help="Rationale for revert")
50
374
 
51
- sub.add_parser("suspend", help="Suspend the active intent")
52
-
53
- resume_p = sub.add_parser("resume", help="Resume a suspended intent")
54
- resume_p.add_argument("intent_id", nargs="?")
55
-
56
- done_p = sub.add_parser("done", help="Close the active intent")
57
- done_p.add_argument("intent_id", nargs="?")
58
-
59
- sub.add_parser("inspect", help="Machine-readable workspace snapshot")
60
-
61
- list_p = sub.add_parser("list", help="List objects")
62
- list_p.add_argument("type", choices=["intent", "snap"])
63
- list_p.add_argument("--intent", dest="intent_id", help="Filter snaps by intent ID")
64
-
65
- show_p = sub.add_parser("show", help="Show a single object by ID")
66
- show_p.add_argument("id")
67
-
68
- return parser
69
-
70
-
71
- def main(argv: Optional[list[str]] = None) -> int:
72
- parser = build_parser()
73
- args = parser.parse_args(argv)
74
- repo = IntentRepository(Path.cwd())
75
-
76
- try:
77
- if args.command == "version":
78
- emit(ok("version", {"version": __version__}))
79
- return EXIT_SUCCESS
80
-
81
- if args.command == "init":
82
- repo.ensure_git()
83
- config, state = repo.init_workspace()
84
- emit(ok("init", {"config": config, "state": state}))
85
- return EXIT_SUCCESS
86
-
87
- if args.command == "start":
88
- intent, warnings = repo.create_intent(args.title)
89
- emit(ok("start", intent, warnings=warnings))
90
- return EXIT_SUCCESS
91
-
92
- if args.command == "snap":
93
- snap, warnings = repo.create_snap(
94
- args.title,
95
- rationale=args.message,
96
- candidate=args.candidate,
97
- )
98
- emit(ok("snap", snap, warnings=warnings))
99
- return EXIT_SUCCESS
100
-
101
- if args.command == "adopt":
102
- snap, warnings = repo.adopt_snap(
103
- snap_id=args.snap_id,
104
- rationale=args.message,
105
- )
106
- emit(ok("adopt", snap, warnings=warnings))
107
- return EXIT_SUCCESS
108
-
109
- if args.command == "revert":
110
- snap, warnings = repo.revert_snap(rationale=args.message)
111
- emit(ok("revert", snap, warnings=warnings))
112
- return EXIT_SUCCESS
113
-
114
- if args.command == "suspend":
115
- intent, warnings = repo.suspend_intent()
116
- emit(ok("suspend", intent, warnings=warnings))
117
- return EXIT_SUCCESS
118
-
119
- if args.command == "resume":
120
- intent, warnings = repo.resume_intent(intent_id=args.intent_id)
121
- emit(ok("resume", intent, warnings=warnings))
122
- return EXIT_SUCCESS
123
-
124
- if args.command == "done":
125
- intent, warnings = repo.close_intent(intent_id=args.intent_id)
126
- emit(ok("done", intent, warnings=warnings))
127
- return EXIT_SUCCESS
128
-
129
- if args.command == "inspect":
130
- emit(repo.inspect())
131
- return EXIT_SUCCESS
132
-
133
- if args.command == "list":
134
- items = repo.list_objects(args.type, intent_id=getattr(args, "intent_id", None))
135
- emit(ok("list", items, count=len(items)))
136
- return EXIT_SUCCESS
137
-
138
- if args.command == "show":
139
- obj = repo.show_object(args.id)
140
- emit(ok("show", obj))
141
- return EXIT_SUCCESS
142
-
143
- parser.error("unknown command")
144
- return 2
145
-
146
- except IntentError as error:
147
- emit(error.to_json())
148
- return error.exit_code
149
- except Exception as error:
150
- emit({
151
- "ok": False,
152
- "error": {
153
- "code": "INTERNAL_ERROR",
154
- "message": str(error),
155
- "details": {},
156
- },
157
- })
158
- return EXIT_GENERAL_FAILURE
375
+ # ---------------------------------------------------------------------------
376
+ # Argument parser
377
+ # ---------------------------------------------------------------------------
159
378
 
379
+ def main():
380
+ parser = argparse.ArgumentParser(prog="itt", description="Intent CLI")
381
+ sub = parser.add_subparsers(dest="command")
160
382
 
161
- if __name__ == "__main__":
162
- raise SystemExit(main())
383
+ # version / init / inspect
384
+ sub.add_parser("version")
385
+ sub.add_parser("init")
386
+ sub.add_parser("inspect")
387
+
388
+ # --- intent ---
389
+ p_intent = sub.add_parser("intent")
390
+ s_intent = p_intent.add_subparsers(dest="sub")
391
+
392
+ p = s_intent.add_parser("create")
393
+ p.add_argument("title")
394
+ p.add_argument("--query", default="")
395
+ p.add_argument("--rationale", default="")
396
+
397
+ p = s_intent.add_parser("list")
398
+ p.add_argument("--status", default=None)
399
+
400
+ p = s_intent.add_parser("show")
401
+ p.add_argument("id")
402
+
403
+ p = s_intent.add_parser("activate")
404
+ p.add_argument("id")
405
+
406
+ p = s_intent.add_parser("suspend")
407
+ p.add_argument("id")
408
+
409
+ p = s_intent.add_parser("done")
410
+ p.add_argument("id")
411
+
412
+ # --- snap ---
413
+ p_snap = sub.add_parser("snap")
414
+ s_snap = p_snap.add_subparsers(dest="sub")
415
+
416
+ p = s_snap.add_parser("create")
417
+ p.add_argument("title")
418
+ p.add_argument("--intent", required=True)
419
+ p.add_argument("--query", default="")
420
+ p.add_argument("--rationale", default="")
421
+ p.add_argument("--summary", default="")
422
+ p.add_argument("--feedback", default="")
423
+
424
+ p = s_snap.add_parser("list")
425
+ p.add_argument("--intent", default=None)
426
+ p.add_argument("--status", default=None)
427
+
428
+ p = s_snap.add_parser("show")
429
+ p.add_argument("id")
430
+
431
+ p = s_snap.add_parser("feedback")
432
+ p.add_argument("id")
433
+ p.add_argument("feedback")
434
+
435
+ p = s_snap.add_parser("revert")
436
+ p.add_argument("id")
437
+
438
+ # --- decision ---
439
+ p_decision = sub.add_parser("decision")
440
+ s_decision = p_decision.add_subparsers(dest="sub")
441
+
442
+ p = s_decision.add_parser("create")
443
+ p.add_argument("title")
444
+ p.add_argument("--rationale", default="")
445
+
446
+ p = s_decision.add_parser("list")
447
+ p.add_argument("--status", default=None)
448
+
449
+ p = s_decision.add_parser("show")
450
+ p.add_argument("id")
451
+
452
+ p = s_decision.add_parser("deprecate")
453
+ p.add_argument("id")
454
+
455
+ p = s_decision.add_parser("attach")
456
+ p.add_argument("id")
457
+ p.add_argument("--intent", required=True)
458
+
459
+ # --- dispatch ---
460
+ args = parser.parse_args()
461
+
462
+ if args.command is None:
463
+ parser.print_help()
464
+ sys.exit(1)
465
+
466
+ dispatch_global = {
467
+ "version": cmd_version,
468
+ "init": cmd_init,
469
+ "inspect": cmd_inspect,
470
+ }
471
+ if args.command in dispatch_global:
472
+ dispatch_global[args.command](args)
473
+ return
474
+
475
+ if not getattr(args, "sub", None):
476
+ {"intent": p_intent, "snap": p_snap, "decision": p_decision}[args.command].print_help()
477
+ sys.exit(1)
478
+
479
+ dispatch = {
480
+ ("intent", "create"): cmd_intent_create,
481
+ ("intent", "list"): cmd_intent_list,
482
+ ("intent", "show"): cmd_intent_show,
483
+ ("intent", "activate"): cmd_intent_activate,
484
+ ("intent", "suspend"): cmd_intent_suspend,
485
+ ("intent", "done"): cmd_intent_done,
486
+ ("snap", "create"): cmd_snap_create,
487
+ ("snap", "list"): cmd_snap_list,
488
+ ("snap", "show"): cmd_snap_show,
489
+ ("snap", "feedback"): cmd_snap_feedback,
490
+ ("snap", "revert"): cmd_snap_revert,
491
+ ("decision", "create"): cmd_decision_create,
492
+ ("decision", "list"): cmd_decision_list,
493
+ ("decision", "show"): cmd_decision_show,
494
+ ("decision", "deprecate"): cmd_decision_deprecate,
495
+ ("decision", "attach"): cmd_decision_attach,
496
+ }
497
+ dispatch[(args.command, args.sub)](args)