arctx-cli 0.2.0b2__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.
Files changed (49) hide show
  1. arctx_cli/__init__.py +1 -0
  2. arctx_cli/alias.py +238 -0
  3. arctx_cli/append_batch.py +90 -0
  4. arctx_cli/commands/__init__.py +85 -0
  5. arctx_cli/commands/alias_cmd.py +174 -0
  6. arctx_cli/commands/anchor.py +82 -0
  7. arctx_cli/commands/current.py +69 -0
  8. arctx_cli/commands/cut.py +89 -0
  9. arctx_cli/commands/dump.py +72 -0
  10. arctx_cli/commands/ext.py +236 -0
  11. arctx_cli/commands/git.py +216 -0
  12. arctx_cli/commands/graph.py +73 -0
  13. arctx_cli/commands/guide.py +360 -0
  14. arctx_cli/commands/init.py +223 -0
  15. arctx_cli/commands/list.py +45 -0
  16. arctx_cli/commands/migrate.py +135 -0
  17. arctx_cli/commands/node.py +55 -0
  18. arctx_cli/commands/outcomes.py +58 -0
  19. arctx_cli/commands/payload.py +192 -0
  20. arctx_cli/commands/reachable.py +75 -0
  21. arctx_cli/commands/show.py +113 -0
  22. arctx_cli/commands/sync.py +244 -0
  23. arctx_cli/commands/trace.py +46 -0
  24. arctx_cli/commands/transition.py +212 -0
  25. arctx_cli/commands/use.py +67 -0
  26. arctx_cli/commands/view.py +82 -0
  27. arctx_cli/commands/work_session.py +330 -0
  28. arctx_cli/context.py +38 -0
  29. arctx_cli/ext/__init__.py +1 -0
  30. arctx_cli/ext/command/__init__.py +110 -0
  31. arctx_cli/ext/git/__init__.py +1 -0
  32. arctx_cli/ext/git/branch.py +140 -0
  33. arctx_cli/ext/git/cherry_pick.py +144 -0
  34. arctx_cli/ext/git/commit.py +205 -0
  35. arctx_cli/ext/git/hook.py +758 -0
  36. arctx_cli/ext/git/merge.py +204 -0
  37. arctx_cli/ext/git/reset.py +138 -0
  38. arctx_cli/ext/git/revert.py +157 -0
  39. arctx_cli/ext/git/verify.py +140 -0
  40. arctx_cli/ext/git/worktree.py +173 -0
  41. arctx_cli/ext_registry.py +34 -0
  42. arctx_cli/main.py +133 -0
  43. arctx_cli/paths.py +27 -0
  44. arctx_cli/payload_builder.py +23 -0
  45. arctx_cli/workspace.py +64 -0
  46. arctx_cli-0.2.0b2.dist-info/METADATA +48 -0
  47. arctx_cli-0.2.0b2.dist-info/RECORD +49 -0
  48. arctx_cli-0.2.0b2.dist-info/WHEEL +4 -0
  49. arctx_cli-0.2.0b2.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,244 @@
1
+ """arctx CLI sync command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+
10
+ from arctx_cli.context import resolve_run_id_from_args, resolve_store, resolve_user_id_from_args
11
+
12
+
13
+ def _sync_local():
14
+ from arctx.core.sync import local
15
+
16
+ return local
17
+
18
+
19
+ def add_parser(subparsers) -> argparse.ArgumentParser:
20
+ parser = subparsers.add_parser(
21
+ "sync",
22
+ help="Synchronize a local run with a file-backed shared DAG",
23
+ )
24
+ commands = parser.add_subparsers(dest="sync_command", required=True)
25
+
26
+ init = commands.add_parser("init", help="Initialize sync for a local run")
27
+ _add_common_args(init)
28
+ init.add_argument("--shared-run", required=True, dest="shared_run_id")
29
+ init.add_argument("--workspace", default=None, dest="workspace_id")
30
+ init.add_argument("--user", default=None)
31
+
32
+ status = commands.add_parser("status", help="Show local/shared sync status")
33
+ _add_common_args(status)
34
+ status.add_argument("--shared-run", default=None, dest="shared_run_id")
35
+
36
+ push = commands.add_parser("push", help="Push local records to the shared DAG")
37
+ _add_common_args(push)
38
+ push.add_argument("--shared-run", default=None, dest="shared_run_id")
39
+ push.add_argument("--workspace", default=None, dest="workspace_id")
40
+ push.add_argument("--user", default=None)
41
+
42
+ pull = commands.add_parser("pull", help="Pull shared DAG records into the local run")
43
+ _add_common_args(pull)
44
+ pull.add_argument("--shared-run", default=None, dest="shared_run_id")
45
+
46
+ return parser
47
+
48
+
49
+ def _add_common_args(parser: argparse.ArgumentParser) -> None:
50
+ parser.add_argument("--run", default=None)
51
+ parser.add_argument("--store-dir", default=None)
52
+ parser.add_argument("--remote", default=None)
53
+ parser.add_argument("--remote-dir", default=None)
54
+
55
+
56
+ def run_sync_init_command(
57
+ *,
58
+ run_id: str,
59
+ shared_run_id: str,
60
+ store_dir: str,
61
+ remote: str = "local-shared",
62
+ remote_dir: str | None = None,
63
+ workspace_id: str | None = None,
64
+ actor_id: str = "user",
65
+ ) -> dict:
66
+ store = resolve_store(store_dir)
67
+ if not store.run_path(run_id).exists():
68
+ raise KeyError(f"unknown run_id: {run_id}")
69
+ handle = store.load_run(run_id)
70
+ sync = _sync_local()
71
+ return sync.sync_init(
72
+ handle=handle,
73
+ run_path=store.run_path(run_id),
74
+ remote=remote,
75
+ shared_run_id=shared_run_id,
76
+ remote_dir=remote_dir or str(sync.default_remote_dir(store_dir)),
77
+ workspace_id=workspace_id or _default_workspace_id(),
78
+ actor_id=actor_id,
79
+ )
80
+
81
+
82
+ def run_sync_status_command(
83
+ *,
84
+ run_id: str,
85
+ store_dir: str,
86
+ shared_run_id: str | None = None,
87
+ remote: str | None = None,
88
+ remote_dir: str | None = None,
89
+ ) -> dict:
90
+ store = resolve_store(store_dir)
91
+ if not store.run_path(run_id).exists():
92
+ raise KeyError(f"unknown run_id: {run_id}")
93
+ handle = store.load_run(run_id)
94
+ cfg = _resolve_sync_config(
95
+ run_path=store.run_path(run_id),
96
+ shared_run_id=shared_run_id,
97
+ remote=remote,
98
+ remote_dir=remote_dir,
99
+ store_dir=store_dir,
100
+ )
101
+ sync = _sync_local()
102
+ return sync.sync_status(
103
+ handle=handle,
104
+ remote=cfg["remote"],
105
+ shared_run_id=cfg["shared_run_id"],
106
+ remote_dir=cfg["remote_dir"],
107
+ )
108
+
109
+
110
+ def run_sync_push_command(
111
+ *,
112
+ run_id: str,
113
+ store_dir: str,
114
+ shared_run_id: str | None = None,
115
+ remote: str | None = None,
116
+ remote_dir: str | None = None,
117
+ workspace_id: str | None = None,
118
+ actor_id: str = "user",
119
+ ) -> dict:
120
+ store = resolve_store(store_dir)
121
+ if not store.run_path(run_id).exists():
122
+ raise KeyError(f"unknown run_id: {run_id}")
123
+ handle = store.load_run(run_id)
124
+ cfg = _resolve_sync_config(
125
+ run_path=store.run_path(run_id),
126
+ shared_run_id=shared_run_id,
127
+ remote=remote,
128
+ remote_dir=remote_dir,
129
+ store_dir=store_dir,
130
+ )
131
+ sync = _sync_local()
132
+ return sync.sync_push(
133
+ handle=handle,
134
+ remote=cfg["remote"],
135
+ shared_run_id=cfg["shared_run_id"],
136
+ remote_dir=cfg["remote_dir"],
137
+ workspace_id=workspace_id or cfg.get("workspace_id") or _default_workspace_id(),
138
+ actor_id=actor_id,
139
+ )
140
+
141
+
142
+ def run_sync_pull_command(
143
+ *,
144
+ run_id: str,
145
+ store_dir: str,
146
+ shared_run_id: str | None = None,
147
+ remote: str | None = None,
148
+ remote_dir: str | None = None,
149
+ ) -> dict:
150
+ store = resolve_store(store_dir)
151
+ if not store.run_path(run_id).exists():
152
+ raise KeyError(f"unknown run_id: {run_id}")
153
+ handle = store.load_run(run_id)
154
+ cfg = _resolve_sync_config(
155
+ run_path=store.run_path(run_id),
156
+ shared_run_id=shared_run_id,
157
+ remote=remote,
158
+ remote_dir=remote_dir,
159
+ store_dir=store_dir,
160
+ )
161
+ sync = _sync_local()
162
+ result = sync.sync_pull(
163
+ handle=handle,
164
+ remote=cfg["remote"],
165
+ shared_run_id=cfg["shared_run_id"],
166
+ remote_dir=cfg["remote_dir"],
167
+ )
168
+ store.save_run(handle)
169
+ return result
170
+
171
+
172
+ def cli_sync(args) -> int:
173
+ if args.sync_command == "init":
174
+ result = run_sync_init_command(
175
+ run_id=resolve_run_id_from_args(args),
176
+ shared_run_id=args.shared_run_id,
177
+ store_dir=args.store_dir,
178
+ remote=args.remote or "local-shared",
179
+ remote_dir=args.remote_dir,
180
+ workspace_id=args.workspace_id,
181
+ actor_id=resolve_user_id_from_args(args),
182
+ )
183
+ elif args.sync_command == "status":
184
+ result = run_sync_status_command(
185
+ run_id=resolve_run_id_from_args(args),
186
+ store_dir=args.store_dir,
187
+ shared_run_id=args.shared_run_id,
188
+ remote=args.remote,
189
+ remote_dir=args.remote_dir,
190
+ )
191
+ elif args.sync_command == "push":
192
+ result = run_sync_push_command(
193
+ run_id=resolve_run_id_from_args(args),
194
+ store_dir=args.store_dir,
195
+ shared_run_id=args.shared_run_id,
196
+ remote=args.remote,
197
+ remote_dir=args.remote_dir,
198
+ workspace_id=args.workspace_id,
199
+ actor_id=resolve_user_id_from_args(args),
200
+ )
201
+ elif args.sync_command == "pull":
202
+ result = run_sync_pull_command(
203
+ run_id=resolve_run_id_from_args(args),
204
+ store_dir=args.store_dir,
205
+ shared_run_id=args.shared_run_id,
206
+ remote=args.remote,
207
+ remote_dir=args.remote_dir,
208
+ )
209
+ else:
210
+ return 1
211
+
212
+ print(json.dumps(result, ensure_ascii=False, indent=2))
213
+ return 0
214
+
215
+
216
+ def _resolve_sync_config(
217
+ *,
218
+ run_path: Path,
219
+ shared_run_id: str | None,
220
+ remote: str | None,
221
+ remote_dir: str | None,
222
+ store_dir: str,
223
+ ) -> dict[str, str]:
224
+ if shared_run_id is None or remote is None or remote_dir is None:
225
+ try:
226
+ cfg = _sync_local().load_sync_config(run_path)
227
+ except RuntimeError:
228
+ if shared_run_id is None:
229
+ raise
230
+ cfg = {}
231
+ else:
232
+ cfg = {}
233
+ return {
234
+ "shared_run_id": shared_run_id or cfg["shared_run_id"],
235
+ "remote": remote or cfg.get("remote", "local-shared"),
236
+ "remote_dir": remote_dir
237
+ or cfg.get("remote_dir", str(_sync_local().default_remote_dir(store_dir))),
238
+ "workspace_id": cfg.get("workspace_id", _default_workspace_id()),
239
+ "actor_id": cfg.get("actor_id", "user"),
240
+ }
241
+
242
+
243
+ def _default_workspace_id() -> str:
244
+ return os.environ.get("ARCTX_WORKSPACE_ID") or "local"
@@ -0,0 +1,46 @@
1
+ """arctx CLI trace command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+
8
+ from arctx_cli.context import resolve_store, resolve_run_id_from_args
9
+
10
+
11
+ def add_parser(subparsers) -> argparse.ArgumentParser:
12
+ parser = subparsers.add_parser("trace", help="Trace observed history from a node")
13
+ parser.add_argument("--run", default=None)
14
+ parser.add_argument("--from-node", required=True)
15
+ parser.add_argument("--depth", type=int, default=None)
16
+ parser.add_argument("--store-dir", default=None)
17
+ return parser
18
+
19
+
20
+ def run_trace_command(
21
+ *,
22
+ run_id: str,
23
+ from_node_id: str,
24
+ depth: int | None,
25
+ store_dir: str,
26
+ ) -> dict:
27
+ store = resolve_store(store_dir)
28
+ if not store.run_path(run_id).exists():
29
+ raise KeyError(f"unknown run_id: {run_id}")
30
+ handle = store.load_run(run_id)
31
+ history = handle.trace(
32
+ from_node_id,
33
+ depth=depth,
34
+ )
35
+ return {"history": history.to_dict()}
36
+
37
+
38
+ def cli_trace(args) -> int:
39
+ result = run_trace_command(
40
+ run_id=resolve_run_id_from_args(args),
41
+ from_node_id=args.from_node,
42
+ depth=args.depth,
43
+ store_dir=args.store_dir,
44
+ )
45
+ print(json.dumps(result["history"], ensure_ascii=False, indent=2))
46
+ return 0
@@ -0,0 +1,212 @@
1
+ """arctx CLI transition command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+
9
+ from arctx_cli.commands.outcomes import run_outcomes_command
10
+ from arctx_cli.commands.show import run_show_command
11
+ from arctx_cli.context import (
12
+ resolve_run_id_from_args,
13
+ resolve_store,
14
+ resolve_user_id_from_args,
15
+ resolve_work_session_id_from_args,
16
+ )
17
+ from arctx_cli.append_batch import graph_counts, maybe_append_or_save
18
+ from arctx_cli.payload_builder import build_payload, parse_field_args, parse_json_object
19
+ from arctx.core.schema.payloads import TransitionPayload
20
+
21
+
22
+ def add_parser(subparsers) -> argparse.ArgumentParser:
23
+ parser = subparsers.add_parser(
24
+ "transition",
25
+ help="Create and inspect transitions",
26
+ )
27
+ transition_sub = parser.add_subparsers(dest="transition_command", required=True)
28
+
29
+ sp_create = transition_sub.add_parser(
30
+ "create",
31
+ help="Create one Transition and one output Node from input nodes",
32
+ )
33
+ sp_create.add_argument("--run", default=None)
34
+ sp_create.add_argument(
35
+ "--from",
36
+ action="append",
37
+ required=True,
38
+ dest="input_nodes",
39
+ metavar="NODE_ID",
40
+ help="Input node (repeatable for multi-node transitions)",
41
+ )
42
+ sp_create.add_argument("--payload-type", default="transition_payload")
43
+ sp_create.add_argument("--field", action="append", default=None, help="Payload field as key=value")
44
+ sp_create.add_argument("--json", default=None, help="Payload fields as a JSON object")
45
+ sp_create.add_argument("--store-dir", default=None)
46
+ sp_create.add_argument("--user", default=None)
47
+ sp_create.add_argument("--work-session", default=None)
48
+
49
+ sp_show = transition_sub.add_parser("show", help="Show one transition")
50
+ sp_show.add_argument("transition_id")
51
+ sp_show.add_argument("--with-payloads", action="store_true")
52
+ sp_show.add_argument("--run", default=None)
53
+ sp_show.add_argument("--store-dir", default=None)
54
+
55
+ sp_output = transition_sub.add_parser("output", help="Show a transition output node")
56
+ sp_output.add_argument("transition_id")
57
+ sp_output.add_argument("--run", default=None)
58
+ sp_output.add_argument("--store-dir", default=None)
59
+
60
+ sp_inputs = transition_sub.add_parser("inputs", help="Show transition input nodes")
61
+ sp_inputs.add_argument("transition_id")
62
+ sp_inputs.add_argument("--run", default=None)
63
+ sp_inputs.add_argument("--store-dir", default=None)
64
+
65
+ sp_payloads = transition_sub.add_parser("payloads", help="Show transition payloads")
66
+ sp_payloads.add_argument("transition_id")
67
+ sp_payloads.add_argument("--run", default=None)
68
+ sp_payloads.add_argument("--store-dir", default=None)
69
+ return parser
70
+
71
+
72
+ def run_transition_command(
73
+ *,
74
+ run_id: str,
75
+ input_node_ids: list[str],
76
+ payload_type: str,
77
+ content: dict,
78
+ store_dir: str,
79
+ user_id: str | None = None,
80
+ work_session_id: str | None = None,
81
+ ) -> dict:
82
+ store = resolve_store(store_dir)
83
+ if not store.run_path(run_id).exists():
84
+ raise KeyError(f"unknown run_id: {run_id}")
85
+ handle = store.load_run(run_id)
86
+ payload = TransitionPayload(
87
+ payload_id="pending",
88
+ target_id="pending",
89
+ type=payload_type,
90
+ content=content,
91
+ )
92
+ before = graph_counts(handle)
93
+ transition = handle.transition(
94
+ input_node_ids,
95
+ payload,
96
+ user_id=user_id,
97
+ work_session_id=work_session_id,
98
+ )
99
+ maybe_append_or_save(
100
+ store=store,
101
+ handle=handle,
102
+ user_id=user_id,
103
+ work_session_id=work_session_id,
104
+ before=before,
105
+ )
106
+ return {"transition": transition.to_dict()}
107
+
108
+
109
+ def run_transition_create_command(
110
+ *,
111
+ run_id: str,
112
+ input_node_ids: list[str],
113
+ payload_type: str,
114
+ field_data: dict,
115
+ json_data: dict,
116
+ store_dir: str,
117
+ user_id: str | None = None,
118
+ work_session_id: str | None = None,
119
+ ) -> dict:
120
+ store = resolve_store(store_dir)
121
+ if not store.run_path(run_id).exists():
122
+ raise KeyError(f"unknown run_id: {run_id}")
123
+ handle = store.load_run(run_id)
124
+ payload = build_payload(
125
+ payload_type=payload_type,
126
+ target_kind="transition",
127
+ target_id="pending",
128
+ payload_id="pending",
129
+ json_data=json_data,
130
+ field_data=field_data,
131
+ )
132
+ before = graph_counts(handle)
133
+ transition = handle.transition(
134
+ input_node_ids,
135
+ payload,
136
+ user_id=user_id,
137
+ work_session_id=work_session_id,
138
+ )
139
+ maybe_append_or_save(
140
+ store=store,
141
+ handle=handle,
142
+ user_id=user_id,
143
+ work_session_id=work_session_id,
144
+ before=before,
145
+ )
146
+ return {"transition": transition.to_dict()}
147
+
148
+
149
+ def cli_transition(args) -> int:
150
+ try:
151
+ if args.transition_command == "create":
152
+ result = run_transition_create_command(
153
+ run_id=resolve_run_id_from_args(args),
154
+ input_node_ids=args.input_nodes,
155
+ payload_type=args.payload_type,
156
+ field_data=parse_field_args(args.field),
157
+ json_data=parse_json_object(args.json),
158
+ store_dir=args.store_dir,
159
+ user_id=resolve_user_id_from_args(args),
160
+ work_session_id=resolve_work_session_id_from_args(args),
161
+ )
162
+ print(json.dumps(result["transition"], ensure_ascii=False, indent=2))
163
+ return 0
164
+ if args.transition_command == "show":
165
+ result = run_show_command(
166
+ run_id=resolve_run_id_from_args(args),
167
+ node_id=None,
168
+ transition_id=args.transition_id,
169
+ payload_id=None,
170
+ with_payloads=args.with_payloads,
171
+ outputs=True,
172
+ store_dir=args.store_dir,
173
+ )
174
+ print(json.dumps(result, ensure_ascii=False, indent=2))
175
+ return 0
176
+ if args.transition_command == "output":
177
+ result = run_outcomes_command(
178
+ run_id=resolve_run_id_from_args(args),
179
+ transition_id=args.transition_id,
180
+ include_payloads=False,
181
+ store_dir=args.store_dir,
182
+ )
183
+ print(json.dumps(result, ensure_ascii=False, indent=2))
184
+ return 0
185
+ if args.transition_command == "inputs":
186
+ result = run_show_command(
187
+ run_id=resolve_run_id_from_args(args),
188
+ node_id=None,
189
+ transition_id=args.transition_id,
190
+ payload_id=None,
191
+ with_payloads=False,
192
+ outputs=False,
193
+ store_dir=args.store_dir,
194
+ )
195
+ print(json.dumps({"input_node_ids": result["input_node_ids"]}, ensure_ascii=False, indent=2))
196
+ return 0
197
+ if args.transition_command == "payloads":
198
+ result = run_show_command(
199
+ run_id=resolve_run_id_from_args(args),
200
+ node_id=None,
201
+ transition_id=args.transition_id,
202
+ payload_id=None,
203
+ with_payloads=True,
204
+ outputs=False,
205
+ store_dir=args.store_dir,
206
+ )
207
+ print(json.dumps(result["payloads"], ensure_ascii=False, indent=2))
208
+ return 0
209
+ except (KeyError, ValueError, json.JSONDecodeError) as exc:
210
+ print(f"error: {exc}", file=sys.stderr)
211
+ return 1
212
+ return 0
@@ -0,0 +1,67 @@
1
+ """arctx CLI use command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from arctx_cli.context import resolve_store
8
+ from arctx_cli.paths import find_repo_root, resolve_store_dir, arctx_id_path, write_arctx_id
9
+
10
+
11
+ def add_parser(subparsers) -> argparse.ArgumentParser:
12
+ """Register the ``use`` subcommand parser."""
13
+ parser = subparsers.add_parser(
14
+ "use", help="Set the current run (writes <gitdir>/arctx-id)"
15
+ )
16
+ parser.add_argument("run_id", help="Run identifier")
17
+ parser.add_argument(
18
+ "--store-dir",
19
+ default=None,
20
+ help="Directory where runs are stored (default: <ARCTX_HOME>/runs)",
21
+ )
22
+ return parser
23
+
24
+
25
+ def run_use_command(
26
+ *,
27
+ run_id: str,
28
+ store_dir: str | None,
29
+ ) -> dict:
30
+ """Set the current run by writing its id to ``<gitdir>/arctx-id``.
31
+
32
+ Parameters
33
+ ----------
34
+ run_id:
35
+ Identifier of the run.
36
+ store_dir:
37
+ Directory where runs are stored.
38
+
39
+ Returns
40
+ -------
41
+ dict with ``run_id`` and ``arctx_id_path`` keys.
42
+
43
+ Raises
44
+ ------
45
+ KeyError
46
+ If the run_id does not exist in the store.
47
+ RuntimeError
48
+ If not inside a git repository.
49
+ """
50
+ resolved_store_dir = store_dir if store_dir is not None else resolve_store_dir()
51
+ store = resolve_store(resolved_store_dir)
52
+ run_path = store.run_path(run_id)
53
+ if not run_path.exists():
54
+ raise KeyError(f"unknown run_id: {run_id}")
55
+ repo_root = find_repo_root()
56
+ write_arctx_id(repo_root, run_id)
57
+ return {"run_id": run_id, "arctx_id_path": str(arctx_id_path(repo_root))}
58
+
59
+
60
+ def cli_use(args) -> int:
61
+ """Entry point for ``arctx use`` subcommand."""
62
+ result = run_use_command(
63
+ run_id=args.run_id,
64
+ store_dir=args.store_dir,
65
+ )
66
+ print(result["run_id"])
67
+ return 0
@@ -0,0 +1,82 @@
1
+ """arctx CLI view subcommands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+
8
+ from arctx_cli.append_batch import graph_counts, maybe_append_or_save
9
+ from arctx_cli.context import (
10
+ resolve_run_id_from_args,
11
+ resolve_store,
12
+ resolve_user_id_from_args,
13
+ resolve_work_session_id_from_args,
14
+ )
15
+
16
+
17
+ def add_parser(subparsers) -> argparse.ArgumentParser:
18
+ parser = subparsers.add_parser("view", help="Manage GraphViews")
19
+ view_sub = parser.add_subparsers(dest="view_command", required=True)
20
+
21
+ # view create
22
+ create = view_sub.add_parser("create", help="Create a new GraphView")
23
+ create.add_argument("--name", required=True)
24
+ create.add_argument("--root-node", required=True, dest="root_node")
25
+ create.add_argument("--run", default=None)
26
+ create.add_argument("--store-dir", default=None)
27
+ create.add_argument("--user", default=None)
28
+ create.add_argument("--work-session", default=None)
29
+
30
+ # view list
31
+ lst = view_sub.add_parser("list", help="List all GraphViews")
32
+ lst.add_argument("--run", default=None)
33
+ lst.add_argument("--store-dir", default=None)
34
+
35
+ # view show
36
+ show = view_sub.add_parser("show", help="Show a GraphView")
37
+ show.add_argument("view_name")
38
+ show.add_argument("--run", default=None)
39
+ show.add_argument("--store-dir", default=None)
40
+
41
+ return parser
42
+
43
+
44
+ def cli_view(args) -> int:
45
+ store = resolve_store(args.store_dir)
46
+ run_id = resolve_run_id_from_args(args)
47
+ if not store.run_path(run_id).exists():
48
+ raise KeyError(f"unknown run_id: {run_id}")
49
+ handle = store.load_run(run_id)
50
+
51
+ if args.view_command == "create":
52
+ user_id = resolve_user_id_from_args(args)
53
+ work_session_id = resolve_work_session_id_from_args(args)
54
+ before = graph_counts(handle)
55
+ view = handle.view_create(args.name, root_node_id=args.root_node)
56
+ handle.record_work_event(
57
+ user_id=user_id,
58
+ work_session_id=work_session_id,
59
+ event_type="view_created",
60
+ target_kind="view",
61
+ target_id=view.view_id,
62
+ created_records=(view.view_id,),
63
+ summary=view.name,
64
+ )
65
+ maybe_append_or_save(
66
+ store=store,
67
+ handle=handle,
68
+ user_id=user_id,
69
+ work_session_id=work_session_id,
70
+ before=before,
71
+ )
72
+ print(json.dumps(view.to_dict(), ensure_ascii=False, indent=2))
73
+
74
+ elif args.view_command == "list":
75
+ views = handle.view_list()
76
+ print(json.dumps([v.to_dict() for v in views], ensure_ascii=False, indent=2))
77
+
78
+ elif args.view_command == "show":
79
+ view = handle.view_show(args.view_name)
80
+ print(json.dumps(view.to_dict(), ensure_ascii=False, indent=2))
81
+
82
+ return 0