dlab-cli 0.1.2__tar.gz → 0.2.0__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 (57) hide show
  1. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/PKG-INFO +3 -1
  2. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/__init__.py +1 -1
  3. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/cli.py +80 -5
  4. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/create_dpack.py +10 -2
  5. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/docker.py +2 -1
  6. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/js/parallel-agents.ts +6 -1
  7. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/local.py +2 -1
  8. dlab_cli-0.2.0/dlab/opencode_logparser.py +625 -0
  9. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/timeline.py +91 -157
  10. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/app.py +7 -0
  11. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/log_watcher.py +18 -23
  12. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/models.py +26 -6
  13. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/widgets/agent_list.py +14 -6
  14. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/widgets/artifacts_pane.py +24 -3
  15. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/widgets/log_view.py +119 -17
  16. dlab_cli-0.2.0/dlab/viewer/__init__.py +7 -0
  17. dlab_cli-0.2.0/dlab/viewer/html/__init__.py +0 -0
  18. dlab_cli-0.2.0/dlab/viewer/html/viewer.html +498 -0
  19. dlab_cli-0.2.0/dlab/viewer/layout.py +121 -0
  20. dlab_cli-0.2.0/dlab/viewer/server.py +408 -0
  21. dlab_cli-0.2.0/dlab/viewer/session_data.py +890 -0
  22. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab_cli.egg-info/PKG-INFO +3 -1
  23. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab_cli.egg-info/SOURCES.txt +10 -1
  24. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab_cli.egg-info/requires.txt +2 -0
  25. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/pyproject.toml +4 -1
  26. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_cli.py +189 -0
  27. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_create_dpack.py +42 -0
  28. dlab_cli-0.2.0/tests/test_opencode_logparser.py +724 -0
  29. dlab_cli-0.2.0/tests/test_viewer.py +806 -0
  30. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/LICENSE +0 -0
  31. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/README.md +0 -0
  32. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/config.py +0 -0
  33. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/create_dpack_wizard.py +0 -0
  34. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/create_parallel_agent_wizard.py +0 -0
  35. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/data/__init__.py +0 -0
  36. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/data/models.json +0 -0
  37. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/js/__init__.py +0 -0
  38. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/model_fallback.py +0 -0
  39. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/parallel_tool.py +0 -0
  40. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/session.py +0 -0
  41. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/__init__.py +0 -0
  42. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/widgets/__init__.py +0 -0
  43. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/widgets/search_popup.py +0 -0
  44. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab/tui/widgets/status_bar.py +0 -0
  45. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab_cli.egg-info/dependency_links.txt +0 -0
  46. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab_cli.egg-info/entry_points.txt +0 -0
  47. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/dlab_cli.egg-info/top_level.txt +0 -0
  48. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/setup.cfg +0 -0
  49. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_config.py +0 -0
  50. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_create_dpack_wizard.py +0 -0
  51. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_create_parallel_agent_wizard.py +0 -0
  52. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_docker.py +0 -0
  53. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_generate_dpack_integration.py +0 -0
  54. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_integration.py +0 -0
  55. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_model_fallback.py +0 -0
  56. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_parallel_tool.py +0 -0
  57. {dlab_cli-0.1.2 → dlab_cli-0.2.0}/tests/test_session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dlab-cli
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: A harness for agentic data science — run coding agents with domain skills, parallel subagents, and frozen Docker environments
5
5
  Author: DecisionAI
6
6
  License: Apache-2.0
@@ -18,6 +18,8 @@ Requires-Dist: pyyaml>=6.0
18
18
  Requires-Dist: textual>=2.0
19
19
  Requires-Dist: modal>=0.70
20
20
  Requires-Dist: dhub-cli>=0.10
21
+ Requires-Dist: fastapi>=0.100
22
+ Requires-Dist: uvicorn[standard]>=0.20
21
23
  Provides-Extra: dev
22
24
  Requires-Dist: pytest>=7.0; extra == "dev"
23
25
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
@@ -3,4 +3,4 @@ dlab: CLI wrapper that runs opencode in automated mode, sandboxed with Docker,
3
3
  and with parallel-agent capability.
4
4
  """
5
5
 
6
- __version__ = "0.1.2"
6
+ __version__ = "0.1.3"
@@ -273,6 +273,34 @@ def create_parser() -> argparse.ArgumentParser:
273
273
  help="Path to session work directory (default: cwd if it has _opencode_logs)",
274
274
  )
275
275
 
276
+ # View subcommand (browser-based session viewer)
277
+ view_parser = subparsers.add_parser(
278
+ "view",
279
+ help="Open browser-based session viewer with DAG visualization",
280
+ )
281
+ view_parser.add_argument(
282
+ "work_dir",
283
+ metavar="WORK_DIR",
284
+ help="Path to session work directory",
285
+ )
286
+ view_parser.add_argument(
287
+ "--port",
288
+ type=int,
289
+ default=0,
290
+ help="Port for the viewer server (default: auto-select)",
291
+ )
292
+ view_parser.add_argument(
293
+ "--no-open",
294
+ action="store_true",
295
+ help="Start server without opening browser",
296
+ )
297
+ view_parser.add_argument(
298
+ "--export",
299
+ metavar="FILE",
300
+ default=None,
301
+ help="Export self-contained HTML file instead of starting server",
302
+ )
303
+
276
304
  # Create decision-pack subcommand
277
305
  create_dpack_parser = subparsers.add_parser(
278
306
  "create-dpack",
@@ -401,13 +429,18 @@ def cmd_run(args: argparse.Namespace) -> int:
401
429
  if continue_mode:
402
430
  continue_dir = Path(args.continue_dir).resolve()
403
431
  if not continue_dir.exists():
404
- raise ValueError(f"Continue directory not found: {args.continue_dir}")
432
+ console.print(f"Oops, continue directory [bold]{args.continue_dir}[/bold] not found.")
433
+ return 1
405
434
 
406
435
  if args.work_dir:
407
436
  # Copy continue-dir to work-dir, then continue from there
408
437
  work_path = Path(args.work_dir).resolve()
409
438
  if work_path.exists():
410
- raise ValueError(f"Work directory already exists: {args.work_dir}")
439
+ console.print(
440
+ f"Oops, work directory [bold]{args.work_dir}[/bold] already exists.\n"
441
+ f"You can remove it with: [cyan]rm -rf {args.work_dir}[/cyan]"
442
+ )
443
+ return 1
411
444
  shutil.copytree(continue_dir, work_path)
412
445
  work_dir = str(work_path)
413
446
  print(f"Copied {continue_dir} to {work_dir}")
@@ -423,10 +456,9 @@ def cmd_run(args: argparse.Namespace) -> int:
423
456
  # Overwrite .opencode with latest from decision-pack (agent prompts may have changed)
424
457
  opencode_dir = Path(work_dir) / ".opencode"
425
458
  if opencode_dir.exists():
426
- if no_sandboxing:
427
- # Local mode: files are user-owned
459
+ try:
428
460
  shutil.rmtree(opencode_dir)
429
- else:
461
+ except PermissionError:
430
462
  # Docker mode: files may be root-owned (e.g. node_modules/)
431
463
  subprocess.run(
432
464
  ["sudo", "rm", "-rf", str(opencode_dir)],
@@ -893,6 +925,47 @@ def cmd_create_parallel_agent(args: argparse.Namespace) -> int:
893
925
  return 0
894
926
 
895
927
 
928
+ def cmd_view(args: argparse.Namespace) -> int:
929
+ """
930
+ Handle view mode - browser-based session viewer with DAG visualization.
931
+
932
+ Parameters
933
+ ----------
934
+ args : argparse.Namespace
935
+ Parsed command-line arguments.
936
+
937
+ Returns
938
+ -------
939
+ int
940
+ Exit code (0 for success, non-zero for failure).
941
+ """
942
+ work_dir: Path = Path(args.work_dir).resolve()
943
+
944
+ if not work_dir.exists():
945
+ print(f"Error: Work directory not found: {work_dir}", file=sys.stderr)
946
+ return 1
947
+
948
+ logs_dir: Path = work_dir / "_opencode_logs"
949
+ if not logs_dir.exists():
950
+ print(f"Error: No logs directory found: {logs_dir}", file=sys.stderr)
951
+ print("Make sure this is a valid dlab session directory.", file=sys.stderr)
952
+ return 1
953
+
954
+ # Export mode — no server dependencies needed
955
+ if args.export:
956
+ from dlab.viewer.server import export_viewer
957
+ output_path: Path = Path(args.export)
958
+ return export_viewer(work_dir, output_path)
959
+
960
+ from dlab.viewer import run_viewer
961
+
962
+ return run_viewer(
963
+ work_dir,
964
+ port=args.port,
965
+ open_browser=not args.no_open,
966
+ )
967
+
968
+
896
969
  def cmd_timeline(args: argparse.Namespace) -> int:
897
970
  """
898
971
  Handle timeline mode - display execution timeline for a session.
@@ -1062,6 +1135,8 @@ def main() -> None:
1062
1135
  exit_code = cmd_create_dpack(args)
1063
1136
  elif args.command == "timeline":
1064
1137
  exit_code = cmd_timeline(args)
1138
+ elif args.command == "view":
1139
+ exit_code = cmd_view(args)
1065
1140
  elif args.dpack or args.data or args.prompt or args.prompt_file or args.continue_dir:
1066
1141
  exit_code = cmd_run(args)
1067
1142
  else:
@@ -884,8 +884,16 @@ export default tool({
884
884
  args: {
885
885
  input: tool.schema.string().describe("Input to process"),
886
886
  },
887
- async run({ input }) {
888
- return `Processed: ${input}`
887
+ async execute(args) {
888
+ // Run a CLI command with Bun shell (e.g. Python, bash, etc.)
889
+ const result = await Bun.$`echo "Processing: ${args.input}"`.nothrow()
890
+ const stdout = result.stdout.toString()
891
+ const stderr = result.stderr.toString()
892
+
893
+ if (result.exitCode !== 0) {
894
+ return `ERROR (exit code ${result.exitCode}):\\n${stderr}`
895
+ }
896
+ return stdout.trim()
889
897
  },
890
898
  })
891
899
  """
@@ -527,7 +527,8 @@ def build_runner_script(
527
527
  return f'''#!/bin/bash
528
528
  set -o pipefail
529
529
  prompt=$(cat {prompt_file})
530
- opencode run --format json --log-level DEBUG --model "{model}" "$prompt" 2>&1 | tee /_opencode_logs/{log_prefix}.log
530
+ echo "$prompt" | python3 -c "import json,sys; print(json.dumps({{'type':'dlab_start','timestamp':int(__import__('time').time()*1000),'model':'{model}','agent':'{log_prefix}','prompt':sys.stdin.read().strip()}}))" > /_opencode_logs/{log_prefix}.log
531
+ opencode run --format json --log-level DEBUG --model "{model}" "$prompt" 2>&1 | tee -a /_opencode_logs/{log_prefix}.log
531
532
  '''
532
533
 
533
534
 
@@ -269,9 +269,11 @@ CRITICAL OUTPUT RULES:
269
269
  `
270
270
  const fullPrompt = subagentContext + args.prompts[i] + "\n\n" + (config.subagent_suffix_prompt || "")
271
271
 
272
- // Spawn - log file starts with JSON directly (no header)
273
272
  const logFile = join(logsDir, `instance-${i + 1}.log`)
274
273
 
274
+ // Write dlab_start event as first line (model, agent, prompt)
275
+ writeFileSync(logFile, JSON.stringify({type: "dlab_start", timestamp: Date.now(), model, agent: args.agent, prompt: fullPrompt}) + "\n")
276
+
275
277
  const proc = Bun.spawn(["opencode", "run", "--format", "json", "--log-level", "DEBUG", "--model", model, fullPrompt], {
276
278
  cwd: instanceDir,
277
279
  stdout: "pipe",
@@ -355,6 +357,9 @@ RULES:
355
357
 
356
358
  const consLogFile = join(logsDir, "consolidator.log")
357
359
 
360
+ // Write dlab_start event as first line
361
+ writeFileSync(consLogFile, JSON.stringify({type: "dlab_start", timestamp: Date.now(), model: consolidatorModel, agent: "consolidator", prompt: consolidatorPrompt}) + "\n")
362
+
358
363
  const consProc = Bun.spawn(["opencode", "run", "--format", "json", "--log-level", "DEBUG", "--model", consolidatorModel, consolidatorPrompt], {
359
364
  cwd: runDir,
360
365
  stdout: "pipe",
@@ -251,7 +251,8 @@ def run_opencode_local(
251
251
  runner_script: str = f'''#!/bin/bash
252
252
  set -o pipefail
253
253
  prompt=$(cat "{prompt_file}")
254
- opencode run --format json --log-level DEBUG --model "{model}" "$prompt" 2>&1 | tee "{log_path}"
254
+ echo "$prompt" | python3 -c "import json,sys; print(json.dumps({{'type':'dlab_start','timestamp':int(__import__('time').time()*1000),'model':'{model}','agent':'{log_prefix}','prompt':sys.stdin.read().strip()}}))" > "{log_path}"
255
+ opencode run --format json --log-level DEBUG --model "{model}" "$prompt" 2>&1 | tee -a "{log_path}"
255
256
  '''
256
257
  runner_file: Path = work_path / ".run_opencode.sh"
257
258
  runner_file.write_text(runner_script)