arctx-cli 0.2.0b4__tar.gz → 0.3.1b1__tar.gz

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 (100) hide show
  1. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/.gitignore +3 -0
  2. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/PKG-INFO +3 -3
  3. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/pyproject.toml +3 -3
  4. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/append_batch.py +11 -19
  5. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/__init__.py +48 -30
  6. arctx_cli-0.3.1b1/src/arctx_cli/commands/_targets.py +38 -0
  7. arctx_cli-0.3.1b1/src/arctx_cli/commands/add.py +113 -0
  8. arctx_cli-0.3.1b1/src/arctx_cli/commands/asset.py +138 -0
  9. arctx_cli-0.3.1b1/src/arctx_cli/commands/attach.py +115 -0
  10. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/cut.py +33 -20
  11. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/dump.py +2 -2
  12. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/export.py +3 -3
  13. arctx_cli-0.3.1b1/src/arctx_cli/commands/guide.py +135 -0
  14. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/init.py +3 -2
  15. arctx_cli-0.3.1b1/src/arctx_cli/commands/lane.py +630 -0
  16. arctx_cli-0.3.1b1/src/arctx_cli/commands/log.py +76 -0
  17. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/migrate.py +7 -2
  18. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/node.py +2 -2
  19. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/outcomes.py +7 -7
  20. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/payload.py +19 -19
  21. arctx_cli-0.3.1b1/src/arctx_cli/commands/pr.py +272 -0
  22. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/reachable.py +10 -16
  23. arctx_cli-0.3.1b1/src/arctx_cli/commands/reparent.py +128 -0
  24. arctx_cli-0.3.1b1/src/arctx_cli/commands/serve.py +55 -0
  25. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/show.py +72 -19
  26. arctx_cli-0.2.0b4/src/arctx_cli/commands/transition.py → arctx_cli-0.3.1b1/src/arctx_cli/commands/step.py +46 -46
  27. arctx_cli-0.3.1b1/src/arctx_cli/commands/sync_cmd.py +179 -0
  28. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/trace.py +2 -0
  29. arctx_cli-0.3.1b1/src/arctx_cli/commands/uncut.py +99 -0
  30. arctx_cli-0.3.1b1/src/arctx_cli/commands/web.py +70 -0
  31. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/context.py +15 -5
  32. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/command/__init__.py +7 -7
  33. arctx_cli-0.2.0b4/src/arctx_cli/commands/git.py → arctx_cli-0.3.1b1/src/arctx_cli/ext/git/__init__.py +23 -23
  34. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/branch.py +11 -11
  35. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/cherry_pick.py +25 -25
  36. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/commit.py +23 -37
  37. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/hook.py +60 -60
  38. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/merge.py +25 -43
  39. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/repo.py +7 -7
  40. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/reset.py +16 -16
  41. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/revert.py +32 -32
  42. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/verify.py +10 -10
  43. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/git/worktree.py +5 -5
  44. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext_registry.py +20 -4
  45. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/main.py +6 -1
  46. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/paths.py +8 -0
  47. arctx_cli-0.3.1b1/tests/cli/test_asset_extension_cli.py +98 -0
  48. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_basic.py +70 -72
  49. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_branch.py +9 -9
  50. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_cherry_pick.py +12 -12
  51. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_commit.py +13 -13
  52. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_commit_guard.py +15 -15
  53. arctx_cli-0.3.1b1/tests/cli/test_dag_core_cli.py +310 -0
  54. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_git_namespace_cli.py +1 -1
  55. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_git_repo.py +4 -4
  56. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_hook_post_merge.py +13 -13
  57. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_hook_post_rewrite.py +21 -21
  58. arctx_cli-0.3.1b1/tests/cli/test_lane.py +303 -0
  59. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_merge.py +30 -74
  60. arctx_cli-0.3.1b1/tests/cli/test_migrate.py +48 -0
  61. arctx_cli-0.3.1b1/tests/cli/test_pr.py +97 -0
  62. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_reset.py +22 -22
  63. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_revert.py +19 -19
  64. arctx_cli-0.3.1b1/tests/cli/test_serve_api.py +794 -0
  65. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_show_history.py +20 -20
  66. arctx_cli-0.3.1b1/tests/cli/test_sync.py +53 -0
  67. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_verify.py +17 -17
  68. arctx_cli-0.2.0b4/src/arctx_cli/commands/anchor.py +0 -82
  69. arctx_cli-0.2.0b4/src/arctx_cli/commands/guide.py +0 -360
  70. arctx_cli-0.2.0b4/src/arctx_cli/commands/sync.py +0 -244
  71. arctx_cli-0.2.0b4/src/arctx_cli/commands/view.py +0 -82
  72. arctx_cli-0.2.0b4/src/arctx_cli/commands/work_session.py +0 -330
  73. arctx_cli-0.2.0b4/src/arctx_cli/ext/git/__init__.py +0 -1
  74. arctx_cli-0.2.0b4/tests/cli/test_git_worktree.py +0 -190
  75. arctx_cli-0.2.0b4/tests/cli/test_work_session.py +0 -115
  76. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/README.md +0 -0
  77. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/__init__.py +0 -0
  78. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/alias.py +0 -0
  79. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/alias_cmd.py +0 -0
  80. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/current.py +0 -0
  81. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/ext.py +0 -0
  82. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/graph.py +0 -0
  83. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/list.py +0 -0
  84. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/commands/use.py +0 -0
  85. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/ext/__init__.py +0 -0
  86. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/payload_builder.py +0 -0
  87. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/src/arctx_cli/workspace.py +0 -0
  88. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/__init__.py +0 -0
  89. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/__init__.py +0 -0
  90. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_alias_cli.py +0 -0
  91. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_alias_resolve.py +0 -0
  92. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_command_extension.py +0 -0
  93. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_ext_cli.py +0 -0
  94. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_hook_install.py +0 -0
  95. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_init_extension.py +0 -0
  96. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_init_hooks.py +0 -0
  97. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_init_stag_id.py +0 -0
  98. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/cli/test_paths.py +0 -0
  99. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/fixtures/__init__.py +0 -0
  100. {arctx_cli-0.2.0b4 → arctx_cli-0.3.1b1}/tests/fixtures/dummy_ext.py +0 -0
@@ -5,6 +5,8 @@ __pycache__/
5
5
  .pytest_cache/
6
6
  .mypy_cache/
7
7
  .ruff_cache/
8
+ .uv-cache/
9
+ .venv/
8
10
  .coverage
9
11
  htmlcov/
10
12
 
@@ -27,4 +29,5 @@ dist/
27
29
  # IDE / Agent state directories
28
30
  .claude/
29
31
  .antigravitycli/
32
+ .codex/
30
33
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arctx-cli
3
- Version: 0.2.0b4
4
- Summary: CLI for ARCTX — append-only DAG for reasoning history and parallel agent work
3
+ Version: 0.3.1b1
4
+ Summary: CLI for ARCTX — append-only DAG editing and refactoring
5
5
  Project-URL: Homepage, https://github.com/takumiecd/arctx
6
6
  Project-URL: Repository, https://github.com/takumiecd/arctx
7
7
  Author: Takumi Ishida
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Classifier: Topic :: Utilities
19
19
  Requires-Python: >=3.10
20
- Requires-Dist: arctx>=0.2.0b4
20
+ Requires-Dist: arctx>=0.3.1b1
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: black>=23.0; extra == 'dev'
23
23
  Requires-Dist: mypy>=1.0; extra == 'dev'
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arctx-cli"
7
- version = "0.2.0b4"
8
- description = "CLI for ARCTX — append-only DAG for reasoning history and parallel agent work"
7
+ version = "0.3.1b1"
8
+ description = "CLI for ARCTX — append-only DAG editing and refactoring"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  license = {text = "MIT"}
@@ -25,7 +25,7 @@ classifiers = [
25
25
  "Topic :: Utilities",
26
26
  ]
27
27
  dependencies = [
28
- "arctx>=0.2.0b4",
28
+ "arctx>=0.3.1b1",
29
29
  ]
30
30
 
31
31
  [project.optional-dependencies]
@@ -9,9 +9,8 @@ def graph_counts(handle) -> dict[str, set[str]]:
9
9
  """Capture graph record IDs before a mutation."""
10
10
  return {
11
11
  "nodes": set(handle.run_graph.nodes),
12
- "transitions": set(handle.run_graph.transitions),
12
+ "steps": set(handle.run_graph.steps),
13
13
  "payloads": set(handle.run_graph.payloads),
14
- "views": {view.view_id for view in handle.run_graph.views.values()},
15
14
  "work_events": {event.event_id for event in handle.run_graph.work_events},
16
15
  }
17
16
 
@@ -21,18 +20,18 @@ def maybe_append_or_save(
21
20
  store,
22
21
  handle,
23
22
  user_id: str | None,
24
- work_session_id: str | None,
23
+ lane_id: str | None,
25
24
  before: dict[str, set[str]],
26
25
  ) -> None:
27
26
  """Use append_batch for capable stores, otherwise fall back to save_run."""
28
- if user_id is None or work_session_id is None or not hasattr(store, "append_batch"):
27
+ if user_id is None or lane_id is None or not hasattr(store, "append_batch"):
29
28
  store.save_run(handle)
30
29
  return
31
30
  store.append_batch(
32
31
  build_append_batch(
33
32
  handle,
34
33
  user_id=user_id,
35
- work_session_id=work_session_id,
34
+ lane_id=lane_id,
36
35
  before=before,
37
36
  )
38
37
  )
@@ -42,7 +41,7 @@ def build_append_batch(
42
41
  handle,
43
42
  *,
44
43
  user_id: str,
45
- work_session_id: str,
44
+ lane_id: str,
46
45
  before: dict[str, set[str]],
47
46
  ) -> AppendBatch:
48
47
  """Build an append batch from records added since *before*."""
@@ -52,21 +51,14 @@ def build_append_batch(
52
51
  node = handle.run_graph.nodes[node_id]
53
52
  records.append(GraphRecordEnvelope("node", node.node_id, node))
54
53
 
55
- for transition_id in _new_ids(handle.run_graph.transitions, before, "transitions"):
56
- transition = handle.run_graph.transitions[transition_id]
57
- records.append(GraphRecordEnvelope("transition", transition.transition_id, transition))
54
+ for step_id in _new_ids(handle.run_graph.steps, before, "steps"):
55
+ step = handle.run_graph.steps[step_id]
56
+ records.append(GraphRecordEnvelope("step", step.step_id, step))
58
57
 
59
58
  for payload_id in _new_ids(handle.run_graph.payloads, before, "payloads"):
60
59
  payload = handle.run_graph.payloads[payload_id]
61
60
  records.append(GraphRecordEnvelope("payload", payload.payload_id, payload))
62
61
 
63
- before_view_ids = before.get("views", set())
64
- new_views = [
65
- view for view in handle.run_graph.views.values() if view.view_id not in before_view_ids
66
- ]
67
- for view in new_views:
68
- records.append(GraphRecordEnvelope("view", view.view_id, view))
69
-
70
62
  new_events = [
71
63
  event
72
64
  for event in handle.run_graph.work_events
@@ -75,12 +67,12 @@ def build_append_batch(
75
67
  if not new_events:
76
68
  raise RuntimeError("append batch requires at least one work event")
77
69
 
78
- session = handle.run_graph.work_sessions[work_session_id]
70
+ session = handle.run_graph.lanes[lane_id]
79
71
  return AppendBatch(
80
72
  run_id=handle.run_id,
81
73
  user_id=user_id,
82
- work_session_id=work_session_id,
83
- work_session=session,
74
+ lane_id=lane_id,
75
+ lane=session,
84
76
  events=tuple(new_events),
85
77
  records=tuple(records),
86
78
  )
@@ -7,10 +7,14 @@ from arctx.ext import CliCommand
7
7
 
8
8
  def core_cli_commands() -> list[CliCommand]:
9
9
  """Return the built-in, extension-independent CLI commands."""
10
+ from arctx_cli.commands.add import add_parser as add_add_parser
11
+ from arctx_cli.commands.add import cli_add
10
12
  from arctx_cli.commands.alias_cmd import add_parser as add_alias_parser
11
13
  from arctx_cli.commands.alias_cmd import cli_alias
12
- from arctx_cli.commands.anchor import add_parser as add_anchor_parser
13
- from arctx_cli.commands.anchor import cli_anchor
14
+ from arctx_cli.commands.asset import add_parser as add_asset_parser
15
+ from arctx_cli.commands.asset import cli_asset
16
+ from arctx_cli.commands.attach import add_parser as add_attach_parser
17
+ from arctx_cli.commands.attach import cli_attach
14
18
  from arctx_cli.commands.current import add_parser as add_current_parser
15
19
  from arctx_cli.commands.current import cli_current
16
20
  from arctx_cli.commands.cut import add_parser as add_cut_parser
@@ -27,36 +31,48 @@ def core_cli_commands() -> list[CliCommand]:
27
31
  from arctx_cli.commands.guide import cli_guide
28
32
  from arctx_cli.commands.init import add_parser as add_init_parser
29
33
  from arctx_cli.commands.init import cli_init
34
+ from arctx_cli.commands.lane import add_parser as add_lane_parser
35
+ from arctx_cli.commands.lane import cli_lane
30
36
  from arctx_cli.commands.list import add_parser as add_list_parser
31
37
  from arctx_cli.commands.list import cli_list
38
+ from arctx_cli.commands.log import add_parser as add_log_parser
39
+ from arctx_cli.commands.log import cli_log
32
40
  from arctx_cli.commands.migrate import add_parser as add_migrate_parser
33
41
  from arctx_cli.commands.migrate import cli_migrate
34
- from arctx_cli.commands.node import add_parser as add_node_parser
35
- from arctx_cli.commands.node import cli_node
36
- from arctx_cli.commands.outcomes import add_parser as add_outcomes_parser
37
- from arctx_cli.commands.outcomes import cli_outcomes
38
- from arctx_cli.commands.payload import add_parser as add_payload_parser
39
- from arctx_cli.commands.payload import cli_payload
40
- from arctx_cli.commands.reachable import add_parser as add_reachable_parser
41
- from arctx_cli.commands.reachable import cli_reachable
42
+ from arctx_cli.commands.reparent import add_parser as add_reparent_parser
43
+ from arctx_cli.commands.reparent import cli_reparent
44
+ from arctx_cli.commands.uncut import add_parser as add_uncut_parser
45
+ from arctx_cli.commands.uncut import cli_uncut
46
+ from arctx_cli.commands.pr import (
47
+ add_accept_parser,
48
+ add_propose_parser,
49
+ add_reject_parser,
50
+ cli_accept,
51
+ cli_propose,
52
+ cli_reject,
53
+ )
54
+ from arctx_cli.commands.serve import add_parser as add_serve_parser
55
+ from arctx_cli.commands.serve import cli_serve
56
+ from arctx_cli.commands.sync_cmd import (
57
+ add_pull_parser,
58
+ add_push_parser,
59
+ add_remote_parser,
60
+ cli_pull,
61
+ cli_push,
62
+ cli_remote,
63
+ )
42
64
  from arctx_cli.commands.show import add_parser as add_show_parser
43
65
  from arctx_cli.commands.show import cli_show
44
- from arctx_cli.commands.sync import add_parser as add_sync_parser
45
- from arctx_cli.commands.sync import cli_sync
46
- from arctx_cli.commands.trace import add_parser as add_trace_parser
47
- from arctx_cli.commands.trace import cli_trace
48
- from arctx_cli.commands.transition import add_parser as add_transition_parser
49
- from arctx_cli.commands.transition import cli_transition
50
66
  from arctx_cli.commands.use import add_parser as add_use_parser
51
67
  from arctx_cli.commands.use import cli_use
52
- from arctx_cli.commands.view import add_parser as add_view_parser
53
- from arctx_cli.commands.view import cli_view
54
- from arctx_cli.commands.work_session import add_parser as add_work_session_parser
55
- from arctx_cli.commands.work_session import cli_work_session
68
+ from arctx_cli.commands.web import add_parser as add_web_parser
69
+ from arctx_cli.commands.web import cli_web
56
70
 
57
71
  return [
72
+ CliCommand("add", add_add_parser, cli_add),
58
73
  CliCommand("alias", add_alias_parser, cli_alias),
59
- CliCommand("anchor", add_anchor_parser, cli_anchor),
74
+ CliCommand("asset", add_asset_parser, cli_asset),
75
+ CliCommand("attach", add_attach_parser, cli_attach),
60
76
  CliCommand("current", add_current_parser, cli_current),
61
77
  CliCommand("ext", add_ext_parser, cli_ext),
62
78
  CliCommand("dump", add_dump_parser, cli_dump),
@@ -64,20 +80,22 @@ def core_cli_commands() -> list[CliCommand]:
64
80
  CliCommand("graph", add_graph_parser, cli_graph),
65
81
  CliCommand("guide", add_guide_parser, cli_guide),
66
82
  CliCommand("init", add_init_parser, cli_init),
83
+ CliCommand("lane", add_lane_parser, cli_lane),
67
84
  CliCommand("list", add_list_parser, cli_list),
85
+ CliCommand("log", add_log_parser, cli_log),
68
86
  CliCommand("migrate", add_migrate_parser, cli_migrate),
69
- CliCommand("node", add_node_parser, cli_node),
70
- CliCommand("outcomes", add_outcomes_parser, cli_outcomes),
71
- CliCommand("payload", add_payload_parser, cli_payload),
72
- CliCommand("reachable", add_reachable_parser, cli_reachable),
73
87
  CliCommand("cut", add_cut_parser, cli_cut),
88
+ CliCommand("uncut", add_uncut_parser, cli_uncut),
89
+ CliCommand("reparent", add_reparent_parser, cli_reparent),
90
+ CliCommand("propose", add_propose_parser, cli_propose),
91
+ CliCommand("accept", add_accept_parser, cli_accept),
92
+ CliCommand("reject", add_reject_parser, cli_reject),
93
+ CliCommand("remote", add_remote_parser, cli_remote),
94
+ CliCommand("push", add_push_parser, cli_push),
95
+ CliCommand("serve", add_serve_parser, cli_serve),
74
96
  CliCommand("show", add_show_parser, cli_show),
75
- CliCommand("sync", add_sync_parser, cli_sync),
76
- CliCommand("trace", add_trace_parser, cli_trace),
77
- CliCommand("transition", add_transition_parser, cli_transition),
78
97
  CliCommand("use", add_use_parser, cli_use),
79
- CliCommand("view", add_view_parser, cli_view),
80
- CliCommand("work-session", add_work_session_parser, cli_work_session),
98
+ CliCommand("web", add_web_parser, cli_web),
81
99
  ]
82
100
 
83
101
 
@@ -0,0 +1,38 @@
1
+ """Helpers for resolving user-facing DAG record IDs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal
6
+
7
+
8
+ TargetKind = Literal["node", "step", "payload"]
9
+
10
+
11
+ def resolve_target_kind(handle, record_id: str) -> TargetKind:
12
+ """Resolve a record id to its internal target kind."""
13
+ graph = handle.run_graph
14
+ matches: list[TargetKind] = []
15
+ if record_id in graph.nodes:
16
+ matches.append("node")
17
+ if record_id in graph.steps:
18
+ matches.append("step")
19
+ if record_id in graph.payloads:
20
+ matches.append("payload")
21
+ if not matches:
22
+ raise KeyError(f"unknown record_id: {record_id}")
23
+ if len(matches) > 1:
24
+ raise ValueError(f"ambiguous record_id {record_id!r}: {matches}")
25
+ return matches[0]
26
+
27
+
28
+ def step_view(step) -> dict:
29
+ """Return a user-facing Step view for an internal Step."""
30
+ return {
31
+ "kind": "step",
32
+ "id": step.step_id,
33
+ "step_id": step.step_id,
34
+ "step_id": step.step_id,
35
+ "input_node_ids": list(step.input_node_ids),
36
+ "output_node_id": step.output_node_id,
37
+ "metadata": dict(step.metadata),
38
+ }
@@ -0,0 +1,113 @@
1
+ """User-facing arctx add commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+
9
+ from arctx_cli.append_batch import graph_counts, maybe_append_or_save
10
+ from arctx_cli.commands._targets import step_view
11
+ from arctx_cli.context import (
12
+ resolve_run_id_from_args,
13
+ resolve_store,
14
+ resolve_user_id_from_args,
15
+ resolve_lane_id_from_args,
16
+ )
17
+ from arctx_cli.payload_builder import build_payload, parse_field_args, parse_json_object
18
+
19
+
20
+ def add_parser(subparsers) -> argparse.ArgumentParser:
21
+ parser = subparsers.add_parser("add", help="Add a Step from input Nodes")
22
+ parser.add_argument(
23
+ "--from",
24
+ action="append",
25
+ required=True,
26
+ dest="input_nodes",
27
+ metavar="NODE_ID",
28
+ help="Input node (repeatable for multi-input steps)",
29
+ )
30
+ parser.add_argument("--title", default=None)
31
+ parser.add_argument("--type", dest="payload_kind", default=None)
32
+ parser.add_argument("--payload-type", default="step_payload")
33
+ parser.add_argument("--field", action="append", default=None, help="Payload field as key=value")
34
+ parser.add_argument("--json", default=None, help="Payload fields as a JSON object")
35
+ parser.add_argument("--run", default=None)
36
+ parser.add_argument("--store-dir", default=None)
37
+ parser.add_argument("--user", default=None)
38
+ parser.add_argument("--lane", default=None)
39
+
40
+ return parser
41
+
42
+
43
+ def run_add_step_command(
44
+ *,
45
+ run_id: str,
46
+ input_node_ids: list[str],
47
+ title: str | None,
48
+ payload_kind: str | None,
49
+ payload_type: str,
50
+ field_data: dict,
51
+ json_data: dict,
52
+ store_dir: str,
53
+ user_id: str | None = None,
54
+ lane_id: str | None = None,
55
+ ) -> dict:
56
+ store = resolve_store(store_dir)
57
+ if not store.run_path(run_id).exists():
58
+ raise KeyError(f"unknown run_id: {run_id}")
59
+ handle = store.load_run(run_id)
60
+ data = dict(json_data or {})
61
+ data.update(field_data or {})
62
+ if title is not None:
63
+ data.setdefault("title", title)
64
+ data.setdefault("text", title)
65
+ if payload_kind is not None:
66
+ data.setdefault("type", payload_kind)
67
+ else:
68
+ data.setdefault("type", "step")
69
+
70
+ payload = build_payload(
71
+ payload_type=payload_type,
72
+ target_kind="step",
73
+ target_id="pending",
74
+ payload_id="pending",
75
+ json_data={},
76
+ field_data=data,
77
+ )
78
+ before = graph_counts(handle)
79
+ step = handle.add_step(
80
+ input_node_ids,
81
+ payload,
82
+ user_id=user_id,
83
+ lane_id=lane_id,
84
+ )
85
+ maybe_append_or_save(
86
+ store=store,
87
+ handle=handle,
88
+ user_id=user_id,
89
+ lane_id=lane_id,
90
+ before=before,
91
+ )
92
+ return {"step": step_view(step)}
93
+
94
+
95
+ def cli_add(args) -> int:
96
+ try:
97
+ result = run_add_step_command(
98
+ run_id=resolve_run_id_from_args(args),
99
+ input_node_ids=args.input_nodes,
100
+ title=args.title,
101
+ payload_kind=args.payload_kind,
102
+ payload_type=args.payload_type,
103
+ field_data=parse_field_args(args.field),
104
+ json_data=parse_json_object(args.json),
105
+ store_dir=args.store_dir,
106
+ user_id=resolve_user_id_from_args(args),
107
+ lane_id=resolve_lane_id_from_args(args),
108
+ )
109
+ print(json.dumps(result["step"], ensure_ascii=False, indent=2))
110
+ return 0
111
+ except (KeyError, ValueError, json.JSONDecodeError) as exc:
112
+ print(f"error: {exc}", file=sys.stderr)
113
+ return 1
@@ -0,0 +1,138 @@
1
+ """arctx asset command — attach and manage file assets (core payload)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+
9
+ from arctx_cli.append_batch import graph_counts, maybe_append_or_save
10
+ from arctx_cli.context import (
11
+ resolve_run_id_from_args,
12
+ resolve_store,
13
+ resolve_user_id_from_args,
14
+ resolve_lane_id_from_args,
15
+ )
16
+
17
+
18
+ def add_parser(subparsers) -> argparse.ArgumentParser:
19
+ """Register the ``asset`` command namespace."""
20
+ asset_parser = subparsers.add_parser("asset", help="Asset management commands")
21
+ asset_sub = asset_parser.add_subparsers(dest="asset_command", required=True)
22
+
23
+ sp_attach = asset_sub.add_parser("attach", help="Attach a file to a Node or Step")
24
+ sp_attach.add_argument("file_path", help="Path to the file to attach")
25
+ sp_attach.add_argument("--target", required=True, dest="target_id", help="Node or Step ID")
26
+ sp_attach.add_argument("--run", default=None)
27
+ sp_attach.add_argument("--store-dir", default=None)
28
+ sp_attach.add_argument("--user", default=None)
29
+ sp_attach.add_argument("--lane", default=None)
30
+
31
+ sp_list = asset_sub.add_parser("list", help="List assets for a Node or Step")
32
+ sp_list.add_argument("--target", required=True, dest="target_id", help="Node or Step ID")
33
+ sp_list.add_argument("--run", default=None)
34
+ sp_list.add_argument("--store-dir", default=None)
35
+
36
+ sp_show = asset_sub.add_parser("show", help="Show details of a specific asset payload")
37
+ sp_show.add_argument("payload_id", help="Payload ID of the asset")
38
+ sp_show.add_argument("--run", default=None)
39
+ sp_show.add_argument("--store-dir", default=None)
40
+
41
+ return asset_parser
42
+
43
+
44
+ def cli_asset(args) -> int:
45
+ """Dispatch ``arctx asset`` subcommands."""
46
+ if args.asset_command == "attach":
47
+ return _cli_asset_attach(args)
48
+ if args.asset_command == "list":
49
+ return _cli_asset_list(args)
50
+ if args.asset_command == "show":
51
+ return _cli_asset_show(args)
52
+ print(f"unknown asset subcommand: {args.asset_command}", file=sys.stderr)
53
+ return 1
54
+
55
+
56
+ def _cli_asset_attach(args) -> int:
57
+ store = resolve_store(args.store_dir)
58
+ run_id = resolve_run_id_from_args(args)
59
+ user_id = resolve_user_id_from_args(args)
60
+ lane_id = resolve_lane_id_from_args(args)
61
+
62
+ if not store.run_path(run_id).exists():
63
+ print(f"error: unknown run_id: {run_id}", file=sys.stderr)
64
+ return 1
65
+
66
+ handle = store.load_run(run_id)
67
+ if (
68
+ args.target_id not in handle.run_graph.nodes
69
+ and args.target_id not in handle.run_graph.steps
70
+ ):
71
+ print(f"error: target_id not found: {args.target_id}", file=sys.stderr)
72
+ return 1
73
+
74
+ try:
75
+ before = graph_counts(handle)
76
+ payload = handle.attach_asset(
77
+ args.target_id,
78
+ args.file_path,
79
+ user_id=user_id,
80
+ lane_id=lane_id,
81
+ )
82
+ except (KeyError, ValueError, FileNotFoundError) as exc:
83
+ print(f"error: {exc}", file=sys.stderr)
84
+ return 1
85
+
86
+ maybe_append_or_save(
87
+ store=store,
88
+ handle=handle,
89
+ user_id=user_id,
90
+ lane_id=lane_id,
91
+ before=before,
92
+ )
93
+ print(json.dumps(payload.to_dict(), ensure_ascii=False, indent=2))
94
+ return 0
95
+
96
+
97
+ def _cli_asset_list(args) -> int:
98
+ store = resolve_store(args.store_dir)
99
+ run_id = resolve_run_id_from_args(args)
100
+
101
+ if not store.run_path(run_id).exists():
102
+ print(f"error: unknown run_id: {run_id}", file=sys.stderr)
103
+ return 1
104
+
105
+ handle = store.load_run(run_id)
106
+ if args.target_id in handle.run_graph.nodes:
107
+ raw_payloads = handle.run_graph.payloads_for_node(args.target_id)
108
+ elif args.target_id in handle.run_graph.steps:
109
+ raw_payloads = handle.run_graph.payloads_for_step(args.target_id)
110
+ else:
111
+ print(f"error: target_id not found: {args.target_id}", file=sys.stderr)
112
+ return 1
113
+
114
+ payloads = [
115
+ p.to_dict()
116
+ for p in raw_payloads
117
+ if getattr(p, "payload_type", None) == "asset"
118
+ ]
119
+ print(json.dumps(payloads, ensure_ascii=False, indent=2))
120
+ return 0
121
+
122
+
123
+ def _cli_asset_show(args) -> int:
124
+ store = resolve_store(args.store_dir)
125
+ run_id = resolve_run_id_from_args(args)
126
+
127
+ if not store.run_path(run_id).exists():
128
+ print(f"error: unknown run_id: {run_id}", file=sys.stderr)
129
+ return 1
130
+
131
+ handle = store.load_run(run_id)
132
+ payload = handle.run_graph.payloads.get(args.payload_id)
133
+ if payload is None or getattr(payload, "payload_type", None) != "asset":
134
+ print(f"error: asset payload not found: {args.payload_id}", file=sys.stderr)
135
+ return 1
136
+
137
+ print(json.dumps(payload.to_dict(), ensure_ascii=False, indent=2))
138
+ return 0
@@ -0,0 +1,115 @@
1
+ """User-facing arctx attach command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+
9
+ from arctx_cli.commands._targets import resolve_target_kind
10
+ from arctx_cli.append_batch import graph_counts, maybe_append_or_save
11
+ from arctx_cli.context import (
12
+ resolve_run_id_from_args,
13
+ resolve_store,
14
+ resolve_user_id_from_args,
15
+ resolve_lane_id_from_args,
16
+ )
17
+ from arctx_cli.payload_builder import build_payload, parse_field_args, parse_json_object
18
+
19
+
20
+ def add_parser(subparsers) -> argparse.ArgumentParser:
21
+ parser = subparsers.add_parser("attach", help="Attach a Payload to a Node or Step")
22
+ parser.add_argument("target_id")
23
+ parser.add_argument("--type", dest="payload_kind", default="payload")
24
+ parser.add_argument("--payload-type", default=None)
25
+ parser.add_argument("--field", action="append", default=None, help="Payload field as key=value")
26
+ parser.add_argument("--json", default=None, help="Payload fields as a JSON object")
27
+ parser.add_argument("--run", default=None)
28
+ parser.add_argument("--store-dir", default=None)
29
+ parser.add_argument("--user", default=None)
30
+ parser.add_argument("--lane", default=None)
31
+ return parser
32
+
33
+
34
+ def run_attach_command(
35
+ *,
36
+ run_id: str,
37
+ target_id: str,
38
+ payload_kind: str,
39
+ payload_type: str | None,
40
+ field_data: dict,
41
+ json_data: dict,
42
+ store_dir: str,
43
+ user_id: str | None = None,
44
+ lane_id: str | None = None,
45
+ ) -> dict:
46
+ store = resolve_store(store_dir)
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
+ target_kind = resolve_target_kind(handle, target_id)
51
+ if target_kind == "payload":
52
+ raise ValueError("cannot attach a payload to another payload")
53
+
54
+ data = dict(json_data or {})
55
+ data.update(field_data or {})
56
+ data.setdefault("type", payload_kind)
57
+ internal_payload_type = payload_type or (
58
+ "node_payload" if target_kind == "node" else "step_payload"
59
+ )
60
+ before = graph_counts(handle)
61
+ payload = build_payload(
62
+ payload_type=internal_payload_type,
63
+ target_kind=target_kind, # type: ignore[arg-type]
64
+ target_id=target_id,
65
+ payload_id=handle._next_id("pl"),
66
+ json_data={},
67
+ field_data=data,
68
+ )
69
+ if payload.target_kind == "node":
70
+ attached = handle.attach(
71
+ payload.target_id,
72
+ payload,
73
+ user_id=user_id,
74
+ lane_id=lane_id,
75
+ )
76
+ else:
77
+ handle.run_graph.attach_payload(payload)
78
+ handle.record_work_event(
79
+ user_id=user_id,
80
+ lane_id=lane_id,
81
+ event_type="payload_attached",
82
+ target_kind="step",
83
+ target_id=payload.target_id,
84
+ created_records=(payload.payload_id,),
85
+ )
86
+ attached = payload
87
+
88
+ maybe_append_or_save(
89
+ store=store,
90
+ handle=handle,
91
+ user_id=user_id,
92
+ lane_id=lane_id,
93
+ before=before,
94
+ )
95
+ return {"payload": attached.to_dict()}
96
+
97
+
98
+ def cli_attach(args) -> int:
99
+ try:
100
+ result = run_attach_command(
101
+ run_id=resolve_run_id_from_args(args),
102
+ target_id=args.target_id,
103
+ payload_kind=args.payload_kind,
104
+ payload_type=args.payload_type,
105
+ field_data=parse_field_args(args.field),
106
+ json_data=parse_json_object(args.json),
107
+ store_dir=args.store_dir,
108
+ user_id=resolve_user_id_from_args(args),
109
+ lane_id=resolve_lane_id_from_args(args),
110
+ )
111
+ print(json.dumps(result["payload"], ensure_ascii=False, indent=2))
112
+ return 0
113
+ except (KeyError, ValueError, json.JSONDecodeError) as exc:
114
+ print(f"error: {exc}", file=sys.stderr)
115
+ return 1