intent-cli-python 2.0.0__py3-none-any.whl → 2.1.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/cli.py +11 -57
- intent_cli/commands/core.py +164 -145
- intent_cli/commands/hub.py +9 -16
- intent_cli/hub/runtime.py +1 -1
- intent_cli/origin.py +57 -0
- intent_cli/store.py +1 -8
- intent_cli_python-2.1.0.dist-info/METADATA +136 -0
- intent_cli_python-2.1.0.dist-info/RECORD +20 -0
- intent_cli_python-2.0.0.dist-info/METADATA +0 -218
- intent_cli_python-2.0.0.dist-info/RECORD +0 -19
- {intent_cli_python-2.0.0.dist-info → intent_cli_python-2.1.0.dist-info}/WHEEL +0 -0
- {intent_cli_python-2.0.0.dist-info → intent_cli_python-2.1.0.dist-info}/entry_points.txt +0 -0
- {intent_cli_python-2.0.0.dist-info → intent_cli_python-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {intent_cli_python-2.0.0.dist-info → intent_cli_python-2.1.0.dist-info}/top_level.txt +0 -0
intent_cli/cli.py
CHANGED
|
@@ -4,28 +4,20 @@ import argparse
|
|
|
4
4
|
import sys
|
|
5
5
|
|
|
6
6
|
from intent_cli.commands.core import (
|
|
7
|
-
cmd_decision_attach,
|
|
8
7
|
cmd_decision_create,
|
|
9
8
|
cmd_decision_deprecate,
|
|
10
|
-
cmd_decision_list,
|
|
11
|
-
cmd_decision_show,
|
|
12
9
|
cmd_doctor,
|
|
13
10
|
cmd_init,
|
|
14
11
|
cmd_inspect,
|
|
15
12
|
cmd_intent_activate,
|
|
16
13
|
cmd_intent_create,
|
|
17
14
|
cmd_intent_done,
|
|
18
|
-
cmd_intent_list,
|
|
19
|
-
cmd_intent_show,
|
|
20
15
|
cmd_intent_suspend,
|
|
21
16
|
cmd_snap_create,
|
|
22
17
|
cmd_snap_feedback,
|
|
23
|
-
cmd_snap_list,
|
|
24
|
-
cmd_snap_revert,
|
|
25
|
-
cmd_snap_show,
|
|
26
18
|
cmd_version,
|
|
27
19
|
)
|
|
28
|
-
from intent_cli.commands.hub import cmd_hub_link,
|
|
20
|
+
from intent_cli.commands.hub import cmd_hub_link, cmd_hub_sync
|
|
29
21
|
|
|
30
22
|
|
|
31
23
|
def main():
|
|
@@ -42,10 +34,6 @@ def main():
|
|
|
42
34
|
p_hub = sub.add_parser("hub")
|
|
43
35
|
s_hub = p_hub.add_subparsers(dest="sub")
|
|
44
36
|
|
|
45
|
-
p = s_hub.add_parser("login")
|
|
46
|
-
p.add_argument("--api-base-url", default=None)
|
|
47
|
-
p.add_argument("--token", default=None)
|
|
48
|
-
|
|
49
37
|
p = s_hub.add_parser("link")
|
|
50
38
|
p.add_argument("--project-name", default=None)
|
|
51
39
|
p.add_argument("--api-base-url", default=None)
|
|
@@ -65,21 +53,14 @@ def main():
|
|
|
65
53
|
p.add_argument("--query", default="")
|
|
66
54
|
p.add_argument("--rationale", default="")
|
|
67
55
|
|
|
68
|
-
p = s_intent.add_parser("list")
|
|
69
|
-
p.add_argument("--status", default=None)
|
|
70
|
-
p.add_argument("--decision", default=None)
|
|
71
|
-
|
|
72
|
-
p = s_intent.add_parser("show")
|
|
73
|
-
p.add_argument("id")
|
|
74
|
-
|
|
75
56
|
p = s_intent.add_parser("activate")
|
|
76
|
-
p.add_argument("id")
|
|
57
|
+
p.add_argument("id", nargs="?")
|
|
77
58
|
|
|
78
59
|
p = s_intent.add_parser("suspend")
|
|
79
|
-
p.add_argument("id")
|
|
60
|
+
p.add_argument("id", nargs="?")
|
|
80
61
|
|
|
81
62
|
p = s_intent.add_parser("done")
|
|
82
|
-
p.add_argument("id")
|
|
63
|
+
p.add_argument("id", nargs="?")
|
|
83
64
|
|
|
84
65
|
# --- snap ---
|
|
85
66
|
p_snap = sub.add_parser("snap")
|
|
@@ -87,26 +68,19 @@ def main():
|
|
|
87
68
|
|
|
88
69
|
p = s_snap.add_parser("create")
|
|
89
70
|
p.add_argument("title")
|
|
90
|
-
p.add_argument("--intent", required=True)
|
|
91
|
-
p.add_argument("--query", default="")
|
|
92
|
-
p.add_argument("--rationale", default="")
|
|
93
|
-
p.add_argument("--summary", default="")
|
|
94
|
-
p.add_argument("--feedback", default="")
|
|
95
|
-
|
|
96
|
-
p = s_snap.add_parser("list")
|
|
97
71
|
p.add_argument("--intent", default=None)
|
|
98
|
-
p.add_argument(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
72
|
+
p.add_argument(
|
|
73
|
+
"--origin",
|
|
74
|
+
default=None,
|
|
75
|
+
metavar="LABEL",
|
|
76
|
+
help="Override auto-detected snap origin label (default: from env, see docs)",
|
|
77
|
+
)
|
|
78
|
+
p.add_argument("--summary", required=True)
|
|
102
79
|
|
|
103
80
|
p = s_snap.add_parser("feedback")
|
|
104
81
|
p.add_argument("id")
|
|
105
82
|
p.add_argument("feedback")
|
|
106
83
|
|
|
107
|
-
p = s_snap.add_parser("revert")
|
|
108
|
-
p.add_argument("id")
|
|
109
|
-
|
|
110
84
|
# --- decision ---
|
|
111
85
|
p_decision = sub.add_parser("decision")
|
|
112
86
|
s_decision = p_decision.add_subparsers(dest="sub")
|
|
@@ -115,20 +89,9 @@ def main():
|
|
|
115
89
|
p.add_argument("title")
|
|
116
90
|
p.add_argument("--rationale", default="")
|
|
117
91
|
|
|
118
|
-
p = s_decision.add_parser("list")
|
|
119
|
-
p.add_argument("--status", default=None)
|
|
120
|
-
p.add_argument("--intent", default=None)
|
|
121
|
-
|
|
122
|
-
p = s_decision.add_parser("show")
|
|
123
|
-
p.add_argument("id")
|
|
124
|
-
|
|
125
92
|
p = s_decision.add_parser("deprecate")
|
|
126
93
|
p.add_argument("id")
|
|
127
94
|
|
|
128
|
-
p = s_decision.add_parser("attach")
|
|
129
|
-
p.add_argument("id")
|
|
130
|
-
p.add_argument("--intent", required=True)
|
|
131
|
-
|
|
132
95
|
args = parser.parse_args()
|
|
133
96
|
|
|
134
97
|
if args.command is None:
|
|
@@ -155,24 +118,15 @@ def main():
|
|
|
155
118
|
sys.exit(1)
|
|
156
119
|
|
|
157
120
|
dispatch = {
|
|
158
|
-
("hub", "login"): cmd_hub_login,
|
|
159
121
|
("hub", "link"): cmd_hub_link,
|
|
160
122
|
("hub", "sync"): cmd_hub_sync,
|
|
161
123
|
("intent", "create"): cmd_intent_create,
|
|
162
|
-
("intent", "list"): cmd_intent_list,
|
|
163
|
-
("intent", "show"): cmd_intent_show,
|
|
164
124
|
("intent", "activate"): cmd_intent_activate,
|
|
165
125
|
("intent", "suspend"): cmd_intent_suspend,
|
|
166
126
|
("intent", "done"): cmd_intent_done,
|
|
167
127
|
("snap", "create"): cmd_snap_create,
|
|
168
|
-
("snap", "list"): cmd_snap_list,
|
|
169
|
-
("snap", "show"): cmd_snap_show,
|
|
170
128
|
("snap", "feedback"): cmd_snap_feedback,
|
|
171
|
-
("snap", "revert"): cmd_snap_revert,
|
|
172
129
|
("decision", "create"): cmd_decision_create,
|
|
173
|
-
("decision", "list"): cmd_decision_list,
|
|
174
|
-
("decision", "show"): cmd_decision_show,
|
|
175
130
|
("decision", "deprecate"): cmd_decision_deprecate,
|
|
176
|
-
("decision", "attach"): cmd_decision_attach,
|
|
177
131
|
}
|
|
178
132
|
dispatch[(args.command, args.sub)](args)
|
intent_cli/commands/core.py
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
import json
|
|
4
4
|
|
|
5
5
|
from intent_cli import __version__
|
|
6
|
-
from intent_cli.commands.common import now_utc, require_init
|
|
6
|
+
from intent_cli.commands.common import now_utc, require_init
|
|
7
7
|
from intent_cli.output import error, success
|
|
8
|
+
from intent_cli.origin import detect_origin
|
|
8
9
|
from intent_cli.store import (
|
|
9
10
|
VALID_STATUSES,
|
|
10
11
|
git_root,
|
|
11
12
|
init_workspace,
|
|
12
13
|
list_objects,
|
|
13
14
|
next_id,
|
|
14
|
-
read_config,
|
|
15
15
|
read_object,
|
|
16
16
|
validate_graph,
|
|
17
17
|
write_object,
|
|
@@ -41,43 +41,43 @@ def cmd_init(_args):
|
|
|
41
41
|
|
|
42
42
|
def cmd_inspect(_args):
|
|
43
43
|
base = require_init()
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
all_snaps = list_objects(base, "snap")
|
|
46
|
+
snap_by_id = {snap["id"]: snap for snap in all_snaps}
|
|
45
47
|
|
|
46
48
|
active_intents = []
|
|
47
|
-
|
|
49
|
+
suspended = []
|
|
48
50
|
for obj in list_objects(base, "intent"):
|
|
49
|
-
|
|
50
|
-
"id": obj["id"],
|
|
51
|
-
"title": obj["title"],
|
|
52
|
-
"status": obj["status"],
|
|
53
|
-
"decision_ids": obj.get("decision_ids", []),
|
|
54
|
-
"latest_snap_id": obj["snap_ids"][-1] if obj.get("snap_ids") else None,
|
|
55
|
-
}
|
|
51
|
+
latest_snap_id = obj["snap_ids"][-1] if obj.get("snap_ids") else None
|
|
56
52
|
if obj["status"] == "active":
|
|
57
|
-
|
|
53
|
+
latest_snap = None
|
|
54
|
+
if latest_snap_id:
|
|
55
|
+
snap = snap_by_id.get(latest_snap_id)
|
|
56
|
+
if snap is not None:
|
|
57
|
+
latest_snap = {
|
|
58
|
+
"id": snap["id"],
|
|
59
|
+
"title": snap["title"],
|
|
60
|
+
"summary": snap.get("summary", ""),
|
|
61
|
+
"feedback": snap.get("feedback", ""),
|
|
62
|
+
"origin": snap.get("origin", ""),
|
|
63
|
+
}
|
|
64
|
+
active_intents.append({
|
|
65
|
+
"id": obj["id"],
|
|
66
|
+
"title": obj["title"],
|
|
67
|
+
"latest_snap": latest_snap,
|
|
68
|
+
})
|
|
58
69
|
elif obj["status"] == "suspend":
|
|
59
|
-
|
|
70
|
+
suspended.append({
|
|
71
|
+
"id": obj["id"],
|
|
72
|
+
"title": obj["title"],
|
|
73
|
+
"latest_snap_id": latest_snap_id,
|
|
74
|
+
})
|
|
60
75
|
|
|
61
76
|
active_decisions = []
|
|
62
77
|
for obj in list_objects(base, "decision", status="active"):
|
|
63
78
|
active_decisions.append({
|
|
64
79
|
"id": obj["id"],
|
|
65
80
|
"title": obj["title"],
|
|
66
|
-
"status": obj["status"],
|
|
67
|
-
"intent_ids": obj.get("intent_ids", []),
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
all_snaps = list_objects(base, "snap")
|
|
71
|
-
all_snaps.sort(key=lambda s: s.get("created_at", ""), reverse=True)
|
|
72
|
-
recent_snaps = []
|
|
73
|
-
for snap in all_snaps[:10]:
|
|
74
|
-
recent_snaps.append({
|
|
75
|
-
"id": snap["id"],
|
|
76
|
-
"title": snap["title"],
|
|
77
|
-
"intent_id": snap["intent_id"],
|
|
78
|
-
"status": snap["status"],
|
|
79
|
-
"summary": snap.get("summary", ""),
|
|
80
|
-
"feedback": snap.get("feedback", ""),
|
|
81
81
|
})
|
|
82
82
|
|
|
83
83
|
warnings = []
|
|
@@ -88,11 +88,9 @@ def cmd_inspect(_args):
|
|
|
88
88
|
|
|
89
89
|
print(json.dumps({
|
|
90
90
|
"ok": True,
|
|
91
|
-
"schema_version": config.get("schema_version", "1.0"),
|
|
92
91
|
"active_intents": active_intents,
|
|
93
|
-
"suspend_intents": suspend_intents,
|
|
94
92
|
"active_decisions": active_decisions,
|
|
95
|
-
"
|
|
93
|
+
"suspended": suspended,
|
|
96
94
|
"warnings": warnings,
|
|
97
95
|
}, indent=2, ensure_ascii=False))
|
|
98
96
|
|
|
@@ -102,6 +100,45 @@ def cmd_doctor(_args):
|
|
|
102
100
|
success("doctor", validate_graph(base))
|
|
103
101
|
|
|
104
102
|
|
|
103
|
+
def _intent_result_for_json(intent):
|
|
104
|
+
result = dict(intent)
|
|
105
|
+
result.pop("decision_ids", None)
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _resolve_inferred_intent_id(
|
|
110
|
+
base,
|
|
111
|
+
explicit_id,
|
|
112
|
+
*,
|
|
113
|
+
status,
|
|
114
|
+
none_code,
|
|
115
|
+
none_message,
|
|
116
|
+
multi_code,
|
|
117
|
+
multi_message,
|
|
118
|
+
suggested_fix,
|
|
119
|
+
):
|
|
120
|
+
if explicit_id:
|
|
121
|
+
return explicit_id, False
|
|
122
|
+
|
|
123
|
+
candidates = sorted(
|
|
124
|
+
(
|
|
125
|
+
{"id": obj["id"], "title": obj["title"]}
|
|
126
|
+
for obj in list_objects(base, "intent", status=status)
|
|
127
|
+
),
|
|
128
|
+
key=lambda c: c["id"],
|
|
129
|
+
)
|
|
130
|
+
if not candidates:
|
|
131
|
+
error(none_code, none_message, suggested_fix=suggested_fix)
|
|
132
|
+
if len(candidates) > 1:
|
|
133
|
+
error(
|
|
134
|
+
multi_code,
|
|
135
|
+
multi_message,
|
|
136
|
+
details={"candidates": candidates},
|
|
137
|
+
suggested_fix=suggested_fix,
|
|
138
|
+
)
|
|
139
|
+
return candidates[0]["id"], True
|
|
140
|
+
|
|
141
|
+
|
|
105
142
|
def cmd_intent_create(args):
|
|
106
143
|
base = require_init()
|
|
107
144
|
obj_id = next_id(base, "intent")
|
|
@@ -131,36 +168,29 @@ def cmd_intent_create(args):
|
|
|
131
168
|
decision.setdefault("intent_ids", []).append(obj_id)
|
|
132
169
|
write_object(base, "decision", decision["id"], decision)
|
|
133
170
|
|
|
134
|
-
success("intent.create", intent, warnings)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def cmd_intent_list(args):
|
|
138
|
-
base = require_init()
|
|
139
|
-
validate_status_filter("intent", args.status)
|
|
140
|
-
objects = list_objects(base, "intent", status=args.status)
|
|
141
|
-
if args.decision:
|
|
142
|
-
objects = [obj for obj in objects if args.decision in obj.get("decision_ids", [])]
|
|
143
|
-
success("intent.list", objects)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def cmd_intent_show(args):
|
|
147
|
-
base = require_init()
|
|
148
|
-
obj = read_object(base, "intent", args.id)
|
|
149
|
-
if obj is None:
|
|
150
|
-
error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
|
|
151
|
-
success("intent.show", obj)
|
|
152
|
-
|
|
171
|
+
success("intent.create", _intent_result_for_json(intent), warnings)
|
|
153
172
|
|
|
154
173
|
def cmd_intent_activate(args):
|
|
155
174
|
base = require_init()
|
|
156
|
-
|
|
175
|
+
intent_id, inferred = _resolve_inferred_intent_id(
|
|
176
|
+
base,
|
|
177
|
+
args.id,
|
|
178
|
+
status="suspend",
|
|
179
|
+
none_code="NO_SUSPENDED_INTENT",
|
|
180
|
+
none_message="No suspended intent to activate.",
|
|
181
|
+
multi_code="MULTIPLE_SUSPENDED_INTENTS",
|
|
182
|
+
multi_message="Multiple suspended intents; specify which one with ID.",
|
|
183
|
+
suggested_fix="itt intent activate <id> or use: itt inspect",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
obj = read_object(base, "intent", intent_id)
|
|
157
187
|
if obj is None:
|
|
158
|
-
error("OBJECT_NOT_FOUND", f"Intent {
|
|
188
|
+
error("OBJECT_NOT_FOUND", f"Intent {intent_id} not found.")
|
|
159
189
|
if obj["status"] != "suspend":
|
|
160
190
|
error(
|
|
161
191
|
"STATE_CONFLICT",
|
|
162
192
|
f"Cannot activate intent with status '{obj['status']}'. Only 'suspend' intents can be activated.",
|
|
163
|
-
suggested_fix=
|
|
193
|
+
suggested_fix="Use: itt inspect",
|
|
164
194
|
)
|
|
165
195
|
|
|
166
196
|
obj["status"] = "active"
|
|
@@ -169,51 +199,105 @@ def cmd_intent_activate(args):
|
|
|
169
199
|
for decision in active_decisions:
|
|
170
200
|
if decision["id"] not in obj["decision_ids"]:
|
|
171
201
|
obj["decision_ids"].append(decision["id"])
|
|
172
|
-
if
|
|
173
|
-
decision.setdefault("intent_ids", []).append(
|
|
202
|
+
if intent_id not in decision.get("intent_ids", []):
|
|
203
|
+
decision.setdefault("intent_ids", []).append(intent_id)
|
|
174
204
|
write_object(base, "decision", decision["id"], decision)
|
|
175
205
|
|
|
176
|
-
write_object(base, "intent",
|
|
177
|
-
|
|
206
|
+
write_object(base, "intent", intent_id, obj)
|
|
207
|
+
warnings = []
|
|
208
|
+
if inferred:
|
|
209
|
+
warnings.append(f"Inferred intent {intent_id} (only suspended intent).")
|
|
210
|
+
success("intent.activate", _intent_result_for_json(obj), warnings)
|
|
178
211
|
|
|
179
212
|
|
|
180
213
|
def cmd_intent_suspend(args):
|
|
181
214
|
base = require_init()
|
|
182
|
-
|
|
215
|
+
intent_id, inferred = _resolve_inferred_intent_id(
|
|
216
|
+
base,
|
|
217
|
+
args.id,
|
|
218
|
+
status="active",
|
|
219
|
+
none_code="NO_ACTIVE_INTENT",
|
|
220
|
+
none_message="No active intent to suspend.",
|
|
221
|
+
multi_code="MULTIPLE_ACTIVE_INTENTS",
|
|
222
|
+
multi_message="Multiple active intents; specify which one with ID.",
|
|
223
|
+
suggested_fix="itt intent suspend <id> or use: itt inspect",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
obj = read_object(base, "intent", intent_id)
|
|
183
227
|
if obj is None:
|
|
184
|
-
error("OBJECT_NOT_FOUND", f"Intent {
|
|
228
|
+
error("OBJECT_NOT_FOUND", f"Intent {intent_id} not found.")
|
|
185
229
|
if obj["status"] != "active":
|
|
186
230
|
error(
|
|
187
231
|
"STATE_CONFLICT",
|
|
188
232
|
f"Cannot suspend intent with status '{obj['status']}'. Only 'active' intents can be suspended.",
|
|
189
|
-
suggested_fix=
|
|
233
|
+
suggested_fix="Use: itt inspect",
|
|
190
234
|
)
|
|
191
235
|
|
|
192
236
|
obj["status"] = "suspend"
|
|
193
|
-
write_object(base, "intent",
|
|
194
|
-
|
|
237
|
+
write_object(base, "intent", intent_id, obj)
|
|
238
|
+
warnings = []
|
|
239
|
+
if inferred:
|
|
240
|
+
warnings.append(f"Inferred intent {intent_id} (only active intent).")
|
|
241
|
+
success("intent.suspend", _intent_result_for_json(obj), warnings)
|
|
195
242
|
|
|
196
243
|
|
|
197
244
|
def cmd_intent_done(args):
|
|
198
245
|
base = require_init()
|
|
199
|
-
|
|
246
|
+
intent_id, inferred = _resolve_inferred_intent_id(
|
|
247
|
+
base,
|
|
248
|
+
args.id,
|
|
249
|
+
status="active",
|
|
250
|
+
none_code="NO_ACTIVE_INTENT",
|
|
251
|
+
none_message="No active intent to mark done.",
|
|
252
|
+
multi_code="MULTIPLE_ACTIVE_INTENTS",
|
|
253
|
+
multi_message="Multiple active intents; specify which one with ID.",
|
|
254
|
+
suggested_fix="itt intent done <id> or use: itt inspect",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
obj = read_object(base, "intent", intent_id)
|
|
200
258
|
if obj is None:
|
|
201
|
-
error("OBJECT_NOT_FOUND", f"Intent {
|
|
259
|
+
error("OBJECT_NOT_FOUND", f"Intent {intent_id} not found.")
|
|
202
260
|
if obj["status"] != "active":
|
|
203
261
|
error(
|
|
204
262
|
"STATE_CONFLICT",
|
|
205
263
|
f"Cannot mark intent as done with status '{obj['status']}'. Only 'active' intents can be marked done.",
|
|
206
|
-
suggested_fix=
|
|
264
|
+
suggested_fix="Use: itt inspect",
|
|
207
265
|
)
|
|
208
266
|
|
|
209
267
|
obj["status"] = "done"
|
|
210
|
-
write_object(base, "intent",
|
|
211
|
-
|
|
268
|
+
write_object(base, "intent", intent_id, obj)
|
|
269
|
+
warnings = []
|
|
270
|
+
if inferred:
|
|
271
|
+
warnings.append(f"Inferred intent {intent_id} (only active intent).")
|
|
272
|
+
success("intent.done", _intent_result_for_json(obj), warnings)
|
|
212
273
|
|
|
213
274
|
|
|
214
275
|
def cmd_snap_create(args):
|
|
215
276
|
base = require_init()
|
|
216
|
-
|
|
277
|
+
if args.intent:
|
|
278
|
+
intent_id = args.intent
|
|
279
|
+
inferred = False
|
|
280
|
+
else:
|
|
281
|
+
active = list_objects(base, "intent", status="active")
|
|
282
|
+
if not active:
|
|
283
|
+
error(
|
|
284
|
+
"NO_ACTIVE_INTENT",
|
|
285
|
+
"No active intent to attach the snap to.",
|
|
286
|
+
suggested_fix='Create or activate an intent first, e.g. itt intent create "TITLE" --query "..." or itt intent activate <id>',
|
|
287
|
+
)
|
|
288
|
+
if len(active) > 1:
|
|
289
|
+
candidates = sorted(
|
|
290
|
+
({"id": o["id"], "title": o["title"]} for o in active),
|
|
291
|
+
key=lambda c: c["id"],
|
|
292
|
+
)
|
|
293
|
+
error(
|
|
294
|
+
"MULTIPLE_ACTIVE_INTENTS",
|
|
295
|
+
"Multiple active intents; specify which one with --intent ID.",
|
|
296
|
+
details={"candidates": candidates},
|
|
297
|
+
suggested_fix="itt snap create TITLE --intent <id> --summary ...",
|
|
298
|
+
)
|
|
299
|
+
intent_id = active[0]["id"]
|
|
300
|
+
inferred = True
|
|
217
301
|
|
|
218
302
|
intent = read_object(base, "intent", intent_id)
|
|
219
303
|
if intent is None:
|
|
@@ -225,6 +309,11 @@ def cmd_snap_create(args):
|
|
|
225
309
|
suggested_fix=f"itt intent activate {intent_id}",
|
|
226
310
|
)
|
|
227
311
|
|
|
312
|
+
if args.origin is not None:
|
|
313
|
+
origin = (args.origin or "").strip()
|
|
314
|
+
else:
|
|
315
|
+
origin = detect_origin()
|
|
316
|
+
|
|
228
317
|
obj_id = next_id(base, "snap")
|
|
229
318
|
snap = {
|
|
230
319
|
"id": obj_id,
|
|
@@ -233,34 +322,19 @@ def cmd_snap_create(args):
|
|
|
233
322
|
"title": args.title,
|
|
234
323
|
"status": "active",
|
|
235
324
|
"intent_id": intent_id,
|
|
236
|
-
"query": args.query,
|
|
237
|
-
"rationale": args.rationale,
|
|
238
325
|
"summary": args.summary,
|
|
239
|
-
"feedback":
|
|
326
|
+
"feedback": "",
|
|
327
|
+
"origin": origin,
|
|
240
328
|
}
|
|
241
329
|
write_object(base, "snap", obj_id, snap)
|
|
242
330
|
|
|
243
331
|
intent.setdefault("snap_ids", []).append(obj_id)
|
|
244
332
|
write_object(base, "intent", intent_id, intent)
|
|
245
333
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
base = require_init()
|
|
251
|
-
validate_status_filter("snap", args.status)
|
|
252
|
-
objects = list_objects(base, "snap", status=args.status)
|
|
253
|
-
if args.intent:
|
|
254
|
-
objects = [snap for snap in objects if snap.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)
|
|
334
|
+
warnings = []
|
|
335
|
+
if inferred:
|
|
336
|
+
warnings.append(f"Inferred intent {intent_id} (only active intent).")
|
|
337
|
+
success("snap.create", snap, warnings)
|
|
264
338
|
|
|
265
339
|
|
|
266
340
|
def cmd_snap_feedback(args):
|
|
@@ -273,23 +347,6 @@ def cmd_snap_feedback(args):
|
|
|
273
347
|
success("snap.feedback", obj)
|
|
274
348
|
|
|
275
349
|
|
|
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(
|
|
283
|
-
"STATE_CONFLICT",
|
|
284
|
-
f"Cannot revert snap with status '{obj['status']}'. Only 'active' snaps can be reverted.",
|
|
285
|
-
suggested_fix=f"itt snap show {args.id}",
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
obj["status"] = "reverted"
|
|
289
|
-
write_object(base, "snap", args.id, obj)
|
|
290
|
-
success("snap.revert", obj)
|
|
291
|
-
|
|
292
|
-
|
|
293
350
|
def cmd_decision_create(args):
|
|
294
351
|
base = require_init()
|
|
295
352
|
obj_id = next_id(base, "decision")
|
|
@@ -320,23 +377,6 @@ def cmd_decision_create(args):
|
|
|
320
377
|
success("decision.create", decision, warnings)
|
|
321
378
|
|
|
322
379
|
|
|
323
|
-
def cmd_decision_list(args):
|
|
324
|
-
base = require_init()
|
|
325
|
-
validate_status_filter("decision", args.status)
|
|
326
|
-
objects = list_objects(base, "decision", status=args.status)
|
|
327
|
-
if args.intent:
|
|
328
|
-
objects = [obj for obj in objects if args.intent in obj.get("intent_ids", [])]
|
|
329
|
-
success("decision.list", objects)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
def cmd_decision_show(args):
|
|
333
|
-
base = require_init()
|
|
334
|
-
obj = read_object(base, "decision", args.id)
|
|
335
|
-
if obj is None:
|
|
336
|
-
error("OBJECT_NOT_FOUND", f"Decision {args.id} not found.")
|
|
337
|
-
success("decision.show", obj)
|
|
338
|
-
|
|
339
|
-
|
|
340
380
|
def cmd_decision_deprecate(args):
|
|
341
381
|
base = require_init()
|
|
342
382
|
obj = read_object(base, "decision", args.id)
|
|
@@ -346,31 +386,10 @@ def cmd_decision_deprecate(args):
|
|
|
346
386
|
error(
|
|
347
387
|
"STATE_CONFLICT",
|
|
348
388
|
f"Cannot deprecate decision with status '{obj['status']}'. Only 'active' decisions can be deprecated.",
|
|
349
|
-
suggested_fix=
|
|
389
|
+
suggested_fix="Use: itt inspect",
|
|
350
390
|
)
|
|
351
391
|
|
|
352
392
|
obj["status"] = "deprecated"
|
|
353
393
|
write_object(base, "decision", args.id, obj)
|
|
354
394
|
success("decision.deprecate", obj)
|
|
355
395
|
|
|
356
|
-
|
|
357
|
-
def cmd_decision_attach(args):
|
|
358
|
-
base = require_init()
|
|
359
|
-
decision = read_object(base, "decision", args.id)
|
|
360
|
-
if decision is None:
|
|
361
|
-
error("OBJECT_NOT_FOUND", f"Decision {args.id} not found.")
|
|
362
|
-
|
|
363
|
-
intent_id = args.intent
|
|
364
|
-
intent = read_object(base, "intent", intent_id)
|
|
365
|
-
if intent is None:
|
|
366
|
-
error("OBJECT_NOT_FOUND", f"Intent {intent_id} not found.")
|
|
367
|
-
|
|
368
|
-
if intent_id not in decision.get("intent_ids", []):
|
|
369
|
-
decision.setdefault("intent_ids", []).append(intent_id)
|
|
370
|
-
write_object(base, "decision", args.id, decision)
|
|
371
|
-
|
|
372
|
-
if args.id not in intent.get("decision_ids", []):
|
|
373
|
-
intent.setdefault("decision_ids", []).append(args.id)
|
|
374
|
-
write_object(base, "intent", intent_id, intent)
|
|
375
|
-
|
|
376
|
-
success("decision.attach", decision)
|
intent_cli/commands/hub.py
CHANGED
|
@@ -13,33 +13,26 @@ from intent_cli.output import error, success
|
|
|
13
13
|
from intent_cli.store import make_runtime_id, write_hub_config
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def
|
|
16
|
+
def cmd_hub_link(args):
|
|
17
17
|
base = require_init()
|
|
18
18
|
hub = load_hub(base)
|
|
19
|
+
repo = current_github_repo()
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
if args.api_base_url:
|
|
22
|
+
hub["api_base_url"] = args.api_base_url.rstrip("/")
|
|
23
|
+
api_base_url = hub.get("api_base_url")
|
|
21
24
|
if not api_base_url:
|
|
22
25
|
error(
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
suggested_fix="Run: itt hub
|
|
26
|
+
"HUB_NOT_CONFIGURED",
|
|
27
|
+
"IntHub API base URL is not configured.",
|
|
28
|
+
suggested_fix="Run: itt hub link --api-base-url http://127.0.0.1:8000",
|
|
26
29
|
)
|
|
27
30
|
|
|
28
|
-
hub["api_base_url"] = api_base_url.rstrip("/")
|
|
29
31
|
if args.token:
|
|
30
32
|
hub["auth_token"] = args.token
|
|
31
33
|
elif "auth_token" not in hub:
|
|
32
34
|
hub["auth_token"] = ""
|
|
33
35
|
|
|
34
|
-
write_hub_config(base, hub)
|
|
35
|
-
success("hub.login", sanitize_hub_config(hub))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def cmd_hub_link(args):
|
|
39
|
-
base = require_init()
|
|
40
|
-
hub = load_hub(base)
|
|
41
|
-
repo = current_github_repo()
|
|
42
|
-
api_base_url = hub_api_base(base, args)
|
|
43
36
|
token = hub_auth_token(base, args)
|
|
44
37
|
|
|
45
38
|
workspace_id = hub.get("workspace_id") or make_runtime_id("wks")
|
|
@@ -59,7 +52,7 @@ def cmd_hub_link(args):
|
|
|
59
52
|
|
|
60
53
|
updated = {
|
|
61
54
|
"api_base_url": api_base_url,
|
|
62
|
-
"auth_token":
|
|
55
|
+
"auth_token": hub.get("auth_token", ""),
|
|
63
56
|
"workspace_id": result["workspace_id"],
|
|
64
57
|
"project_id": result["project_id"],
|
|
65
58
|
"repo_binding": result["repo_binding"],
|
intent_cli/hub/runtime.py
CHANGED
|
@@ -24,7 +24,7 @@ def hub_api_base(base, args):
|
|
|
24
24
|
error(
|
|
25
25
|
"HUB_NOT_CONFIGURED",
|
|
26
26
|
"IntHub API base URL is not configured.",
|
|
27
|
-
suggested_fix="Run: itt hub
|
|
27
|
+
suggested_fix="Run: itt hub link --api-base-url http://127.0.0.1:8000",
|
|
28
28
|
)
|
|
29
29
|
return api_base_url.rstrip("/")
|
|
30
30
|
|
intent_cli/origin.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Infer snap origin (host / tool label) from the process environment.
|
|
2
|
+
|
|
3
|
+
The `itt` subprocess inherits env from its parent (IDE terminal, CI, etc.).
|
|
4
|
+
Override with ITT_ORIGIN or INTENT_ORIGIN.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _slugify_origin(value):
|
|
12
|
+
"""Normalize a host/tool label into a short stable slug."""
|
|
13
|
+
text = re.sub(r"[^a-z0-9]+", "-", value.strip().lower())
|
|
14
|
+
return text.strip("-")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def detect_origin(environ=None):
|
|
18
|
+
"""Return a short stable label, or \"\" when unknown.
|
|
19
|
+
|
|
20
|
+
Precedence:
|
|
21
|
+
1. ITT_ORIGIN, then INTENT_ORIGIN (trimmed, non-empty)
|
|
22
|
+
2. Built-in heuristics for common host environments
|
|
23
|
+
"""
|
|
24
|
+
env = environ if environ is not None else os.environ
|
|
25
|
+
|
|
26
|
+
for key in ("ITT_ORIGIN", "INTENT_ORIGIN"):
|
|
27
|
+
raw = (env.get(key) or "").strip()
|
|
28
|
+
if raw:
|
|
29
|
+
return raw
|
|
30
|
+
|
|
31
|
+
if env.get("CLAUDECODE"):
|
|
32
|
+
return "claude-code"
|
|
33
|
+
|
|
34
|
+
if env.get("CURSOR_TRACE_ID"):
|
|
35
|
+
return "cursor"
|
|
36
|
+
|
|
37
|
+
codex_originator = (env.get("CODEX_INTERNAL_ORIGINATOR_OVERRIDE") or "").strip()
|
|
38
|
+
if codex_originator:
|
|
39
|
+
return _slugify_origin(codex_originator)
|
|
40
|
+
|
|
41
|
+
if env.get("CODEX_THREAD_ID") or env.get("CODEX_SHELL") or env.get("CODEX_CI"):
|
|
42
|
+
return "codex"
|
|
43
|
+
|
|
44
|
+
term = (env.get("TERM_PROGRAM") or "").lower()
|
|
45
|
+
if term == "vscode":
|
|
46
|
+
return "vscode"
|
|
47
|
+
|
|
48
|
+
if env.get("CODESPACES") == "true":
|
|
49
|
+
return "codespaces"
|
|
50
|
+
|
|
51
|
+
if env.get("GITHUB_ACTIONS") == "true":
|
|
52
|
+
return "github-actions"
|
|
53
|
+
|
|
54
|
+
if env.get("GITPOD_WORKSPACE_ID"):
|
|
55
|
+
return "gitpod"
|
|
56
|
+
|
|
57
|
+
return ""
|
intent_cli/store.py
CHANGED
|
@@ -10,7 +10,7 @@ SUBDIRS = {"intent": "intents", "snap": "snaps", "decision": "decisions"}
|
|
|
10
10
|
HUB_CONFIG = "hub.json"
|
|
11
11
|
VALID_STATUSES = {
|
|
12
12
|
"intent": {"active", "suspend", "done"},
|
|
13
|
-
"snap": {"active"
|
|
13
|
+
"snap": {"active"},
|
|
14
14
|
"decision": {"active", "deprecated"},
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -313,12 +313,5 @@ def validate_graph(base):
|
|
|
313
313
|
|
|
314
314
|
return {
|
|
315
315
|
"healthy": not issues,
|
|
316
|
-
"issue_count": len(issues),
|
|
317
|
-
"summary": {
|
|
318
|
-
"schema_version": config.get("schema_version", "1.0"),
|
|
319
|
-
"intent_count": len(intents),
|
|
320
|
-
"snap_count": len(snaps),
|
|
321
|
-
"decision_count": len(decisions),
|
|
322
|
-
},
|
|
323
316
|
"issues": issues,
|
|
324
317
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: intent-cli-python
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Semantic history for agent-driven development. Records what you did and why.
|
|
5
|
+
Author: Zeng Deyang
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dozybot001/Intent
|
|
8
|
+
Project-URL: Repository, https://github.com/dozybot001/Intent
|
|
9
|
+
Keywords: agent,git,semantic-history,intent,developer-tools
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# Intent
|
|
26
|
+
|
|
27
|
+
[中文](README.CN.md) | English
|
|
28
|
+
|
|
29
|
+
Semantic history for agent-driven development. Preserves **how the product took shape** and **how work resumes across sessions and agents**.
|
|
30
|
+
|
|
31
|
+
## Why
|
|
32
|
+
|
|
33
|
+
Git records how code changes. But it doesn't record **why you're on this path**, what you decided along the way, or where you left off.
|
|
34
|
+
|
|
35
|
+
Intent adds that missing layer: **semantic history** — a small set of formal objects that preserve product formation history and survive context loss.
|
|
36
|
+
|
|
37
|
+
> Development is moving from *writing code* to *guiding agents and distilling decisions*. The history layer should reflect that.
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
flowchart LR
|
|
41
|
+
subgraph traditional["Traditional Coding"]
|
|
42
|
+
direction TB
|
|
43
|
+
H1["Human"]
|
|
44
|
+
C1["Code"]
|
|
45
|
+
H1 -->|"Git"| C1
|
|
46
|
+
end
|
|
47
|
+
subgraph agent["Agent Driven Development"]
|
|
48
|
+
direction TB
|
|
49
|
+
H2["Human"]
|
|
50
|
+
AG["Agent"]
|
|
51
|
+
C2["Code"]
|
|
52
|
+
H2 -."❌ no semantic history".-> AG
|
|
53
|
+
AG -->|"Git"| C2
|
|
54
|
+
end
|
|
55
|
+
subgraph withintent["Agent with Intent"]
|
|
56
|
+
direction TB
|
|
57
|
+
H3["Human"]
|
|
58
|
+
AG2["Agent"]
|
|
59
|
+
C3["Code"]
|
|
60
|
+
H3 -->|"Intent"| AG2
|
|
61
|
+
AG2 -->|"Git"| C3
|
|
62
|
+
end
|
|
63
|
+
traditional ~~~ agent ~~~ withintent
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Three objects, one graph
|
|
67
|
+
|
|
68
|
+
| Object | What it captures |
|
|
69
|
+
|---|---|
|
|
70
|
+
| **Intent** | A goal recognized from your query |
|
|
71
|
+
| **Snap** | A semantic checkpoint that captures what changed, what was learned, and later feedback |
|
|
72
|
+
| **Decision** | A long-lived constraint that spans multiple intents |
|
|
73
|
+
|
|
74
|
+
Objects link automatically. Decisions auto-attach to every active intent; intents auto-attach to every active decision. Relationships are bidirectional and append-only.
|
|
75
|
+
|
|
76
|
+
```mermaid
|
|
77
|
+
flowchart LR
|
|
78
|
+
D1["🔶 Decision 1"]
|
|
79
|
+
D2["🔶 Decision 2"]
|
|
80
|
+
|
|
81
|
+
subgraph Intent1["🎯 Intent 1"]
|
|
82
|
+
direction LR
|
|
83
|
+
S1["Snap 1"] --> S2["Snap 2"] --> S3["..."]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
subgraph Intent2["🎯 Intent 2"]
|
|
87
|
+
direction LR
|
|
88
|
+
S4["Snap 1"] --> S5["Snap 2"] --> S6["..."]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
D1 -- auto-attach --> Intent1
|
|
92
|
+
D1 -- auto-attach --> Intent2
|
|
93
|
+
D2 -- auto-attach --> Intent2
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Install
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pipx install intent-cli-python # CLI
|
|
100
|
+
npx skills add dozybot001/Intent -g # Agent skill
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Requires Python 3.9+ and Git. The CLI provides the commands; the skill teaches the agent when to use them.
|
|
104
|
+
|
|
105
|
+
> **Tips:** Because `itt` is a new command, agents are not trained on it yet. We recommend typing `/` at the start of each session, selecting the skill, and pressing Enter to enter the workflow.
|
|
106
|
+
|
|
107
|
+
## IntHub
|
|
108
|
+
|
|
109
|
+
```mermaid
|
|
110
|
+
flowchart TB
|
|
111
|
+
Hub["IntHub — Collaboration Layer"]
|
|
112
|
+
Intent["Intent — Semantic History Layer"]
|
|
113
|
+
Git["Git — Code History Layer"]
|
|
114
|
+
Hub <--> Intent <--> Git
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
IntHub is the remote collaboration layer on top of Intent. The first path is **IntHub Local** — download from a [GitHub release](https://github.com/dozybot001/Intent/releases), then:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
itt hub link --api-base-url http://127.0.0.1:7210
|
|
121
|
+
itt hub sync
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Open `http://127.0.0.1:7210` in the browser.
|
|
125
|
+
|
|
126
|
+
## Docs
|
|
127
|
+
|
|
128
|
+
- [Vision](docs/EN/vision.md) — why semantic history matters
|
|
129
|
+
- [CLI Design](docs/EN/cli.md) — object model, commands, JSON contract
|
|
130
|
+
- [Roadmap](docs/EN/roadmap.md) — phase plan
|
|
131
|
+
- [Dogfooding](docs/EN/dogfooding.md) — cross-agent case study
|
|
132
|
+
- [IntHub Local](docs/EN/inthub-local.md) — run a local IntHub instance
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
intent_cli/__init__.py,sha256=_19_tcL7KLgyD54NiZ-8tcAMCDKyqu_9k5K2-4Lc8ng,1058
|
|
2
|
+
intent_cli/__main__.py,sha256=pWAakBzI50WtmbfDBrUudHbZYBPi8VJ_PlPbRRFGCP4,40
|
|
3
|
+
intent_cli/cli.py,sha256=ShTjzjhK6e7UeKf0dw4Z7nhGtK-OaX35PHv9n1VjBIo,3761
|
|
4
|
+
intent_cli/origin.py,sha256=m_vevJLJ5vUZDIuCC5k3K-LeVIl9YNtRsm7pBqXLQA0,1533
|
|
5
|
+
intent_cli/output.py,sha256=_ZmivWZfY_AOcGRCWFMx09lUGB-Ku0g5xyjrqL6UWKg,680
|
|
6
|
+
intent_cli/store.py,sha256=7GVgY6Y1Era_4ztMBtv8txfrpzF-6z9Kf3UgvWpr3lE,9846
|
|
7
|
+
intent_cli/commands/__init__.py,sha256=XtBJys-Al_64_nLnWGXoOFR2pnYLmqbzanB5Nuy1BUM,43
|
|
8
|
+
intent_cli/commands/common.py,sha256=P2MLcBf3S16Z_iuhxvQqAFV2ioAbvpgQd6XHj3ppROA,1193
|
|
9
|
+
intent_cli/commands/core.py,sha256=tCBHdOAJh-sS6nNA85ujLU_TF-TjqsQHQgLCm0qWgS4,12599
|
|
10
|
+
intent_cli/commands/hub.py,sha256=xiGF5IJNSPxg8PzOhQXUgBxewPhiQ4xAWPyUyLFdQ5s,3094
|
|
11
|
+
intent_cli/hub/__init__.py,sha256=ujLxjGwZo_JCKhCXSlx1lwtJYDWd0y5tAoo57F1VaKM,46
|
|
12
|
+
intent_cli/hub/client.py,sha256=QB6fsPZdAQD7iSnhCmbkBwPlEZVwl7QrYXE4E0S88to,1811
|
|
13
|
+
intent_cli/hub/payload.py,sha256=fjweASqpHIu54HvoQDQd-ibJ0UJx6zpzjIsllVhWmrs,1930
|
|
14
|
+
intent_cli/hub/runtime.py,sha256=O6dvJQMHbkMMa7yM7KV-wxu-IadykspHac4t9v8MD1I,1042
|
|
15
|
+
intent_cli_python-2.1.0.dist-info/licenses/LICENSE,sha256=XWjTStLaoDw-UgLwMecejVxeaHH8JibnSFYARGzRc6I,1068
|
|
16
|
+
intent_cli_python-2.1.0.dist-info/METADATA,sha256=YCQLSr36zpIfCo8rdwZ6r_v67zL1pCgNA-cyjz26T-s,4245
|
|
17
|
+
intent_cli_python-2.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
18
|
+
intent_cli_python-2.1.0.dist-info/entry_points.txt,sha256=Y1kziqgaGUgTHg3CCfc2Su1XoDvIMJ2vi-dPEDZfuTo,44
|
|
19
|
+
intent_cli_python-2.1.0.dist-info/top_level.txt,sha256=jkyOMCXA-G6FlEj69GA4SKn3RoO1KNL9w2iit7OUpuU,11
|
|
20
|
+
intent_cli_python-2.1.0.dist-info/RECORD,,
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: intent-cli-python
|
|
3
|
-
Version: 2.0.0
|
|
4
|
-
Summary: Semantic history for agent-driven development. Records what you did and why.
|
|
5
|
-
Author: Zeng Deyang
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/dozybot001/Intent
|
|
8
|
-
Project-URL: Repository, https://github.com/dozybot001/Intent
|
|
9
|
-
Keywords: agent,git,semantic-history,intent,developer-tools
|
|
10
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
-
Classifier: Environment :: Console
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
-
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
-
Requires-Python: >=3.9
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
License-File: LICENSE
|
|
23
|
-
Dynamic: license-file
|
|
24
|
-
|
|
25
|
-
# Intent CLI
|
|
26
|
-
|
|
27
|
-
[中文](README.CN.md) | English
|
|
28
|
-
|
|
29
|
-
Semantic history for agent-driven development. Records **what you did** and **why**.
|
|
30
|
-
|
|
31
|
-
Intent CLI gives AI agents a structured way to track goals, interactions, and decisions across sessions. Instead of losing context when a conversation ends, agents persist their understanding into three simple objects stored alongside your code.
|
|
32
|
-
|
|
33
|
-
## Why
|
|
34
|
-
|
|
35
|
-
Git records how code changes. But it doesn't record **why you're on this path**, what you decided along the way, or where you left off.
|
|
36
|
-
|
|
37
|
-
Today that context lives in chat logs, PR threads, and your head. It works — until the session ends, the agent forgets, or a teammate picks up your work blind.
|
|
38
|
-
|
|
39
|
-
Intent treats these as a missing layer: **semantic history**. Not more docs, not better commit messages — a small set of formal objects that capture goals, interactions, and decisions so they survive context loss.
|
|
40
|
-
|
|
41
|
-
> The shift is simple: development is moving from *writing code* to *guiding agents and distilling decisions*. The history layer should reflect that.
|
|
42
|
-
|
|
43
|
-
## Three objects, one graph
|
|
44
|
-
|
|
45
|
-
| Object | What it captures |
|
|
46
|
-
|---|---|
|
|
47
|
-
| **Intent** | A goal the agent identified from your query |
|
|
48
|
-
| **Snap** | One agent interaction — query, summary, feedback |
|
|
49
|
-
| **Decision** | A long-lived decision that spans multiple intents |
|
|
50
|
-
|
|
51
|
-
Objects link automatically: creating an intent attaches all active decisions; creating a decision attaches all active intents. Relationships are always bidirectional and append-only.
|
|
52
|
-
|
|
53
|
-
### How decisions are created
|
|
54
|
-
|
|
55
|
-
Decisions require human involvement. Two paths:
|
|
56
|
-
|
|
57
|
-
- **Explicit**: include `decision-[text]` (or `决定-[text]`) in your query and the agent creates it directly. E.g. "decision-all API responses use envelope format"
|
|
58
|
-
- **Agent-proposed**: the agent spots a potential long-term constraint in conversation and asks you to confirm before recording it
|
|
59
|
-
|
|
60
|
-
## Install
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
# Clone the repository
|
|
64
|
-
git clone https://github.com/dozybot001/Intent.git
|
|
65
|
-
|
|
66
|
-
# Install the CLI (pipx recommended)
|
|
67
|
-
pipx install intent-cli-python
|
|
68
|
-
|
|
69
|
-
# Or using pip
|
|
70
|
-
pip install intent-cli-python
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Requires Python 3.9+ and Git.
|
|
74
|
-
|
|
75
|
-
### IntHub boundary
|
|
76
|
-
|
|
77
|
-
`pipx install intent-cli-python` installs the CLI only.
|
|
78
|
-
|
|
79
|
-
This repository is the umbrella project for both `Intent` and `IntHub`, but the distribution boundary is narrower than the repository boundary:
|
|
80
|
-
|
|
81
|
-
- PyPI ships only the `itt` CLI
|
|
82
|
-
- IntHub Web is a separate static frontend and is a good fit for GitHub Pages
|
|
83
|
-
- IntHub API is a separate service and is not part of the PyPI package
|
|
84
|
-
|
|
85
|
-
If you are running IntHub from source inside this repository, the current local entrypoints are:
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
python -m apps.inthub_api --db-path .inthub/inthub.db
|
|
89
|
-
python -m apps.inthub_web --api-base-url http://127.0.0.1:8000
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Then use `itt hub login`, `itt hub link`, and `itt hub sync` from a local Intent workspace to populate the read-only IntHub project view.
|
|
93
|
-
|
|
94
|
-
### Versioning and releases
|
|
95
|
-
|
|
96
|
-
`Intent` is the umbrella project and monorepo. It does not maintain one shared project version anymore.
|
|
97
|
-
|
|
98
|
-
Release versions now belong to concrete deliverables:
|
|
99
|
-
|
|
100
|
-
- CLI releases use the PyPI package version from `pyproject.toml` and Git tags like `cli-v2.0.0`
|
|
101
|
-
- IntHub releases use their own track and Git tags like `hub-v0.1.0`
|
|
102
|
-
|
|
103
|
-
Historical bare tags such as `v1.3.0` remain as history, but new releases use deliverable-prefixed tags.
|
|
104
|
-
|
|
105
|
-
### Install the skills.sh skill
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
npx skills add dozybot001/Intent -g
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
This installs the `intent-cli` skill into your global skills library for supported agents such as Codex and Claude Code.
|
|
112
|
-
|
|
113
|
-
> **Tip:** `itt` is a new tool — current models have never seen it in training data. Your agent may forget to call it mid-conversation. A short nudge like *"use itt to record this"* is usually enough to bring it back on track.
|
|
114
|
-
>
|
|
115
|
-
> This isn't busywork — every record is a **semantic asset**. An upcoming platform, **IntHub**, will turn these assets into searchable, shareable project intelligence.
|
|
116
|
-
|
|
117
|
-
## Quick start
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
# Initialize in any git repo
|
|
121
|
-
itt init
|
|
122
|
-
|
|
123
|
-
# Agent identifies a new intent from user query
|
|
124
|
-
itt intent create "Fix the login timeout bug" \
|
|
125
|
-
--query "why does login timeout after 5s?"
|
|
126
|
-
|
|
127
|
-
# Record what the agent did
|
|
128
|
-
itt snap create "Raise timeout to 30s" \
|
|
129
|
-
--intent intent-001 \
|
|
130
|
-
--query "login timeout still fails on slow networks" \
|
|
131
|
-
--summary "Updated timeout config and ran the login test"
|
|
132
|
-
|
|
133
|
-
# Capture a long-lived decision
|
|
134
|
-
itt decision create "Timeout must stay configurable" \
|
|
135
|
-
--rationale "Different deployments have different latency envelopes"
|
|
136
|
-
|
|
137
|
-
# See the full object graph
|
|
138
|
-
itt inspect
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Commands
|
|
142
|
-
|
|
143
|
-
### Global
|
|
144
|
-
|
|
145
|
-
| Command | Description |
|
|
146
|
-
|---|---|
|
|
147
|
-
| `itt version` | Print version |
|
|
148
|
-
| `itt init` | Initialize `.intent/` in current git repo |
|
|
149
|
-
| `itt inspect` | Show the live object graph snapshot |
|
|
150
|
-
| `itt doctor` | Validate the object graph for broken references and invalid states |
|
|
151
|
-
|
|
152
|
-
### Intent
|
|
153
|
-
|
|
154
|
-
| Command | Description |
|
|
155
|
-
|---|---|
|
|
156
|
-
| `itt intent create TITLE --query Q` | Create a new intent |
|
|
157
|
-
| `itt intent list [--status S] [--decision ID]` | List intents |
|
|
158
|
-
| `itt intent show ID` | Show intent details |
|
|
159
|
-
| `itt intent activate ID` | Resume a suspended intent |
|
|
160
|
-
| `itt intent suspend ID` | Suspend an active intent |
|
|
161
|
-
| `itt intent done ID` | Mark intent as completed |
|
|
162
|
-
|
|
163
|
-
### Snap
|
|
164
|
-
|
|
165
|
-
| Command | Description |
|
|
166
|
-
|---|---|
|
|
167
|
-
| `itt snap create TITLE --intent ID` | Record an interaction snapshot |
|
|
168
|
-
| `itt snap list [--intent ID] [--status S]` | List snaps |
|
|
169
|
-
| `itt snap show ID` | Show snap details |
|
|
170
|
-
| `itt snap feedback ID TEXT` | Set or overwrite feedback |
|
|
171
|
-
| `itt snap revert ID` | Mark a snap as reverted |
|
|
172
|
-
|
|
173
|
-
### Decision
|
|
174
|
-
|
|
175
|
-
| Command | Description |
|
|
176
|
-
|---|---|
|
|
177
|
-
| `itt decision create TITLE --rationale R` | Create a long-lived decision |
|
|
178
|
-
| `itt decision list [--status S] [--intent ID]` | List decisions |
|
|
179
|
-
| `itt decision show ID` | Show decision details |
|
|
180
|
-
| `itt decision deprecate ID` | Deprecate a decision |
|
|
181
|
-
| `itt decision attach ID --intent ID` | Manually link a decision to an intent |
|
|
182
|
-
|
|
183
|
-
## Design principles
|
|
184
|
-
|
|
185
|
-
- **Agent-first**: designed to be called by AI agents, not typed by hand
|
|
186
|
-
- **Append-only history**: content fields are immutable after creation; correct mistakes in new snaps, don't rewrite old ones
|
|
187
|
-
- **Relationships only grow**: no detach — deprecate a decision instead
|
|
188
|
-
- **All output is JSON**: machine-readable by default
|
|
189
|
-
- **Zero dependencies**: pure Python, stdlib only
|
|
190
|
-
|
|
191
|
-
## Storage
|
|
192
|
-
|
|
193
|
-
All data lives in `.intent/` at your git repo root:
|
|
194
|
-
|
|
195
|
-
```
|
|
196
|
-
.intent/
|
|
197
|
-
config.json
|
|
198
|
-
intents/
|
|
199
|
-
intent-001.json
|
|
200
|
-
snaps/
|
|
201
|
-
snap-001.json
|
|
202
|
-
decisions/
|
|
203
|
-
decision-001.json
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
`.intent/` is local semantic-workspace metadata. It should stay out of Git history and should remain ignored by `.gitignore`.
|
|
207
|
-
|
|
208
|
-
## Docs
|
|
209
|
-
|
|
210
|
-
- [Vision](docs/EN/vision.md) — why semantic history matters. **If this project interests you, start here.**
|
|
211
|
-
- [CLI Design](docs/EN/cli.md) — object model, commands, JSON contract
|
|
212
|
-
- [Roadmap](docs/EN/roadmap.md) — phase plan
|
|
213
|
-
- [IntHub MVP](docs/EN/inthub-mvp.md) — first remote collaboration-layer scope
|
|
214
|
-
- [IntHub Sync Contract](docs/EN/inthub-sync-contract.md) — first sync, identity, and API contract
|
|
215
|
-
|
|
216
|
-
## License
|
|
217
|
-
|
|
218
|
-
MIT
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
intent_cli/__init__.py,sha256=_19_tcL7KLgyD54NiZ-8tcAMCDKyqu_9k5K2-4Lc8ng,1058
|
|
2
|
-
intent_cli/__main__.py,sha256=pWAakBzI50WtmbfDBrUudHbZYBPi8VJ_PlPbRRFGCP4,40
|
|
3
|
-
intent_cli/cli.py,sha256=WUDi9hNUqOin2ETkSJOrrdaGGUN66gzZe5HyhPMmCM0,5245
|
|
4
|
-
intent_cli/output.py,sha256=_ZmivWZfY_AOcGRCWFMx09lUGB-Ku0g5xyjrqL6UWKg,680
|
|
5
|
-
intent_cli/store.py,sha256=AOVXbxA1OJ7gIxlvYSNzsXylJLyx_57TEmn0e9kRQN4,10119
|
|
6
|
-
intent_cli/commands/__init__.py,sha256=XtBJys-Al_64_nLnWGXoOFR2pnYLmqbzanB5Nuy1BUM,43
|
|
7
|
-
intent_cli/commands/common.py,sha256=P2MLcBf3S16Z_iuhxvQqAFV2ioAbvpgQd6XHj3ppROA,1193
|
|
8
|
-
intent_cli/commands/core.py,sha256=X7G9ta0vfhneAN8DffW8zS0mNHhb7Zmauklu_sh7WG0,11874
|
|
9
|
-
intent_cli/commands/hub.py,sha256=Hk_ZZB9LFPeXlE0Wv6yDzMLZr530ORvEAj9BZQL9VWo,3280
|
|
10
|
-
intent_cli/hub/__init__.py,sha256=ujLxjGwZo_JCKhCXSlx1lwtJYDWd0y5tAoo57F1VaKM,46
|
|
11
|
-
intent_cli/hub/client.py,sha256=QB6fsPZdAQD7iSnhCmbkBwPlEZVwl7QrYXE4E0S88to,1811
|
|
12
|
-
intent_cli/hub/payload.py,sha256=fjweASqpHIu54HvoQDQd-ibJ0UJx6zpzjIsllVhWmrs,1930
|
|
13
|
-
intent_cli/hub/runtime.py,sha256=U2U9uipBjB0MIeA10i-T1lJQariYv99iSovcgl6ca0M,1043
|
|
14
|
-
intent_cli_python-2.0.0.dist-info/licenses/LICENSE,sha256=XWjTStLaoDw-UgLwMecejVxeaHH8JibnSFYARGzRc6I,1068
|
|
15
|
-
intent_cli_python-2.0.0.dist-info/METADATA,sha256=2cSVuK778T_ayTowh9siWhEsjviMhb2ubOOOxzgf7b4,8134
|
|
16
|
-
intent_cli_python-2.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
17
|
-
intent_cli_python-2.0.0.dist-info/entry_points.txt,sha256=Y1kziqgaGUgTHg3CCfc2Su1XoDvIMJ2vi-dPEDZfuTo,44
|
|
18
|
-
intent_cli_python-2.0.0.dist-info/top_level.txt,sha256=jkyOMCXA-G6FlEj69GA4SKn3RoO1KNL9w2iit7OUpuU,11
|
|
19
|
-
intent_cli_python-2.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|