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 +1 -29
- intent_cli/__main__.py +2 -4
- intent_cli/cli.py +480 -145
- intent_cli/output.py +23 -0
- intent_cli/store.py +88 -100
- intent_cli_python-1.0.0.dist-info/METADATA +167 -0
- intent_cli_python-1.0.0.dist-info/RECORD +11 -0
- intent_cli/constants.py +0 -19
- intent_cli/core.py +0 -443
- intent_cli/errors.py +0 -33
- intent_cli/git.py +0 -83
- intent_cli/helpers.py +0 -25
- intent_cli_python-0.5.0.dist-info/METADATA +0 -155
- intent_cli_python-0.5.0.dist-info/RECORD +0 -15
- {intent_cli_python-0.5.0.dist-info → intent_cli_python-1.0.0.dist-info}/WHEEL +0 -0
- {intent_cli_python-0.5.0.dist-info → intent_cli_python-1.0.0.dist-info}/entry_points.txt +0 -0
- {intent_cli_python-0.5.0.dist-info → intent_cli_python-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {intent_cli_python-0.5.0.dist-info → intent_cli_python-1.0.0.dist-info}/top_level.txt +0 -0
intent_cli/__init__.py
CHANGED
|
@@ -1,29 +1 @@
|
|
|
1
|
-
"""Intent CLI
|
|
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
intent_cli/cli.py
CHANGED
|
@@ -1,162 +1,497 @@
|
|
|
1
|
-
|
|
1
|
+
"""Intent CLI — entry point and command handlers."""
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import json
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from . import
|
|
9
|
-
from .
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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)
|