Monorepo management for opam overlays
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge verse commands into mainline monopam commands

- Add `monopam init` as top-level command (previously `monopam verse init`)
- Integrate verse pull into `monopam sync` when verse config exists
- Simplify `monopam verse` to only include `members` and `fork` subcommands
- Update all error hints and help text to reference `monopam init`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+66 -246
+51 -241
bin/main.ml
··· 55 55 | Ok config -> f config 56 56 | Error msg -> 57 57 Fmt.epr "Error loading config: %s@." msg; 58 - Fmt.epr "Run 'monopam verse init' first to create a workspace.@."; 58 + Fmt.epr "Run 'monopam init' first to create a workspace.@."; 59 59 `Error (false, "configuration error") 60 60 61 61 (* Status command *) ··· 446 446 let info = Cmd.info "opam" ~doc ~man in 447 447 Cmd.group info [ opam_sync_cmd ] 448 448 449 - (* Verse commands *) 450 - 451 - (* Helper to load verse config from XDG *) 452 - let with_verse_config env f = 453 - let fs = Eio.Stdenv.fs env in 454 - match Monopam.Verse_config.load ~fs () with 455 - | Ok config -> f config 456 - | Error msg -> 457 - Fmt.epr "Error loading opamverse config: %s@." msg; 458 - Fmt.epr "Run 'monopam verse init' to create a workspace.@."; 459 - `Error (false, "configuration error") 449 + (* Init command - initialize a new monopam workspace *) 460 450 461 - let verse_root_arg = 451 + let init_root_arg = 462 452 let doc = 463 453 "Path to workspace root directory. Defaults to current directory." 464 454 in ··· 467 457 & opt (some (conv (Fpath.of_string, Fpath.pp))) None 468 458 & info [ "root" ] ~docv:"PATH" ~doc) 469 459 470 - let verse_handle_arg = 471 - let doc = "Tangled handle (e.g., alice.bsky.social)" in 460 + let init_handle_arg = 461 + let doc = "Your handle (e.g., alice.bsky.social)" in 472 462 Arg.( 473 463 required & opt (some string) None & info [ "handle" ] ~docv:"HANDLE" ~doc) 474 464 475 - let verse_handle_opt_pos_arg = 476 - let doc = 477 - "Tangled handle. If not specified, operates on all tracked members." 478 - in 479 - Arg.(value & pos 0 (some string) None & info [] ~docv:"HANDLE" ~doc) 480 - 481 - let verse_init_cmd = 482 - let doc = "Initialize a new opamverse workspace" in 465 + let init_cmd = 466 + let doc = "Initialize a new monopam workspace" in 483 467 let man = 484 468 [ 485 469 `S Manpage.s_description; 486 470 `P 487 - "Creates a new opamverse workspace for federated monorepo \ 488 - collaboration. An opamverse workspace lets you browse and track other \ 489 - developers' monorepos alongside your own."; 471 + "Creates a new monopam workspace for monorepo development. The workspace \ 472 + lets you manage your own monorepo and optionally browse and track other \ 473 + developers' monorepos."; 490 474 `S "WORKSPACE STRUCTURE"; 491 475 `P 492 476 "The init command creates the following directory structure at the \ ··· 513 497 handle = \"yourname.bsky.social\""; 514 498 `S "HANDLE VALIDATION"; 515 499 `P 516 - "The handle you provide identifies you in the opamverse community. \ 500 + "The handle you provide identifies you in the community. \ 517 501 It should be a valid domain name (e.g., yourname.bsky.social or \ 518 502 your-domain.com)."; 519 503 `S "REGISTRY"; 520 504 `P 521 - "The opamverse registry is a git repository containing an \ 522 - opamverse.toml file that lists community members and their monorepo \ 523 - URLs. The default registry is at: \ 524 - https://tangled.org/eeg.cl.cam.ac.uk/opamverse"; 505 + "The registry is a git repository containing an opamverse.toml file \ 506 + that lists community members and their monorepo URLs. The default \ 507 + registry is at: https://tangled.org/eeg.cl.cam.ac.uk/opamverse"; 525 508 `S Manpage.s_examples; 526 - `P "Initialize a workspace in ~/tangled:"; 527 - `Pre "cd ~/tangled\nmonopam verse init --handle alice.bsky.social"; 509 + `P "Initialize a workspace in the current directory:"; 510 + `Pre "monopam init --handle alice.bsky.social"; 528 511 `P "Initialize with explicit root path:"; 529 - `Pre "monopam verse init --root ~/my-workspace --handle alice.bsky.social"; 512 + `Pre "monopam init --root ~/my-workspace --handle alice.bsky.social"; 530 513 ] 531 514 in 532 515 let info = Cmd.info "init" ~doc ~man in ··· 546 529 in 547 530 match Monopam.Verse.init ~proc ~fs ~root ~handle () with 548 531 | Ok () -> 549 - Fmt.pr "Monoverse workspace initialized at %a@." Fpath.pp root; 532 + Fmt.pr "Workspace initialized at %a@." Fpath.pp root; 550 533 `Ok () 551 534 | Error e -> 552 535 Fmt.epr "Error: %a@." Monopam.Verse.pp_error_with_hint e; 553 536 `Error (false, "init failed") 554 537 in 555 538 Cmd.v info 556 - Term.(ret (const run $ verse_root_arg $ verse_handle_arg $ logging_term)) 539 + Term.(ret (const run $ init_root_arg $ init_handle_arg $ logging_term)) 540 + 541 + (* Verse commands *) 542 + 543 + (* Helper to load verse config from XDG *) 544 + let with_verse_config env f = 545 + let fs = Eio.Stdenv.fs env in 546 + match Monopam.Verse_config.load ~fs () with 547 + | Ok config -> f config 548 + | Error msg -> 549 + Fmt.epr "Error loading opamverse config: %s@." msg; 550 + Fmt.epr "Run 'monopam init' to create a workspace.@."; 551 + `Error (false, "configuration error") 557 552 558 553 let verse_members_cmd = 559 554 let doc = "List registry members" in ··· 609 604 in 610 605 Cmd.v info Term.(ret (const run $ logging_term)) 611 606 612 - let verse_pull_cmd = 613 - let doc = "Sync all registry members to local workspace" in 614 - let man = 615 - [ 616 - `S Manpage.s_description; 617 - `P 618 - "Clones or pulls all members from the opamverse registry. For each \ 619 - member, syncs both their monorepo and opam overlay repository."; 620 - `S "WHAT IT DOES"; 621 - `P "For each member in the registry:"; 622 - `I ("1.", "Clones or pulls their monorepo to verse/<handle>/"); 623 - `I ("2.", "Clones or pulls their opam repo to verse/<handle>-opam/"); 624 - `S "SCOPE"; 625 - `P "With a handle argument: syncs only that specific member."; 626 - `P "Without arguments: syncs all members in the registry."; 627 - `S "ERROR HANDLING"; 628 - `P 629 - "If a sync fails for one member (e.g., network error), the error is \ 630 - reported but other members are still synced."; 631 - `S Manpage.s_examples; 632 - `Pre 633 - "# Sync all registry members\n\ 634 - monopam verse pull\n\n\ 635 - # Sync a specific member\n\ 636 - monopam verse pull alice.bsky.social\n\n\ 637 - # Browse their code\n\ 638 - ls verse/alice.bsky.social/"; 639 - ] 640 - in 641 - let info = Cmd.info "pull" ~doc ~man in 642 - let run handle () = 643 - Eio_main.run @@ fun env -> 644 - with_verse_config env @@ fun config -> 645 - let fs = Eio.Stdenv.fs env in 646 - let proc = Eio.Stdenv.process_mgr env in 647 - match Monopam.Verse.pull ~proc ~fs ~config ?handle () with 648 - | Ok () -> 649 - Fmt.pr "Sync completed.@."; 650 - `Ok () 651 - | Error e -> 652 - Fmt.epr "Error: %a@." Monopam.Verse.pp_error_with_hint e; 653 - `Error (false, "pull failed") 654 - in 655 - Cmd.v info Term.(ret (const run $ verse_handle_opt_pos_arg $ logging_term)) 656 - 657 - let verse_sync_cmd = 658 - let doc = "Sync the workspace" in 659 - let man = 660 - [ 661 - `S Manpage.s_description; 662 - `P 663 - "Synchronizes your entire opamverse workspace with the latest upstream \ 664 - changes. This is the command to run regularly to stay up to date."; 665 - `S "WHAT IT DOES"; 666 - `P "The sync command performs two operations:"; 667 - `I 668 - ( "1.", 669 - "Updates the registry: git pull in \ 670 - ~/.local/share/monopam/opamverse-registry/" ); 671 - `I ("2.", "Pulls all tracked members: git pull in each verse/<handle>/"); 672 - `S "USE CASES"; 673 - `P "Run sync when you want to:"; 674 - `I ("-", "See if any new members have joined the community"); 675 - `I ("-", "Get the latest code from all tracked members"); 676 - `I ("-", "Catch up after being away for a while"); 677 - `S "COMPARISON WITH PULL"; 678 - `P 679 - "'verse sync' updates the registry AND pulls members. 'verse pull' \ 680 - only pulls members (skips registry update)."; 681 - `S Manpage.s_examples; 682 - `Pre 683 - "# Daily sync routine\n\ 684 - cd ~/tangled\n\ 685 - monopam verse sync\n\ 686 - monopam verse status"; 687 - ] 688 - in 689 - let info = Cmd.info "sync" ~doc ~man in 690 - let run () = 691 - Eio_main.run @@ fun env -> 692 - with_verse_config env @@ fun config -> 693 - let fs = Eio.Stdenv.fs env in 694 - let proc = Eio.Stdenv.process_mgr env in 695 - match Monopam.Verse.sync ~proc ~fs ~config () with 696 - | Ok () -> 697 - Fmt.pr "Sync completed.@."; 698 - `Ok () 699 - | Error e -> 700 - Fmt.epr "Error: %a@." Monopam.Verse.pp_error_with_hint e; 701 - `Error (false, "sync failed") 702 - in 703 - Cmd.v info Term.(ret (const run $ logging_term)) 704 - 705 607 let verse_fork_cmd = 706 608 let doc = "Fork a package from a verse member's repository" in 707 609 let man = ··· 812 714 Cmd.v info Term.(ret (const run $ package_arg $ from_arg $ url_arg $ dry_run_arg $ logging_term)) 813 715 814 716 let verse_cmd = 815 - let doc = "Federated monorepo collaboration" in 717 + let doc = "Verse member operations" in 816 718 let man = 817 719 [ 818 720 `S Manpage.s_description; 819 721 `P 820 - "The opamverse system enables federated collaboration across multiple \ 821 - developers' monorepos. Each developer maintains their own monorepo \ 822 - (managed by standard monopam commands), and can track other \ 823 - developers' monorepos for code browsing, learning, and collaboration."; 824 - `P 825 - "Members are identified by tangled handles - decentralized identities \ 826 - from the AT Protocol network (the same system used by Bluesky)."; 827 - `S "QUICK START FOR NEW USERS"; 828 - `P "Run these commands in order to get started:"; 829 - `Pre 830 - "# Step 1: Create and initialize your workspace\n\ 831 - mkdir ~/tangled && cd ~/tangled\n\ 832 - monopam verse init --handle yourname.bsky.social\n\n\ 833 - # Step 2: Sync all community members\n\ 834 - monopam verse pull\n\n\ 835 - # Step 3: Browse their code\n\ 836 - ls verse/\n\ 837 - cd verse/alice.bsky.social && dune build\n\n\ 838 - # Step 4: Keep everything updated (run daily/weekly)\n\ 839 - monopam verse sync"; 840 - `S "KEY CONCEPTS"; 841 - `I 842 - ( "Workspace", 843 - "A directory containing your monorepo plus all registry members' \ 844 - repos" ); 845 - `I 846 - ( "Registry", 847 - "A git repository listing community members and their repo URLs" ); 848 - `I 849 - ( "Handle", 850 - "A tangled identity like 'alice.bsky.social' validated via AT \ 851 - Protocol" ); 852 - `S "WORKSPACE STRUCTURE"; 853 - `P "An opamverse workspace has this layout:"; 854 - `Pre 855 - "~/tangled/ # workspace root\n\ 856 - ├── mono/ # YOUR monorepo\n\ 857 - ├── src/ # YOUR fork checkouts\n\ 858 - ├── opam-repo/ # YOUR opam overlay\n\ 859 - └── verse/\n\ 860 - \ ├── alice.bsky.social/ # Alice's monorepo\n\ 861 - \ ├── alice.bsky.social-opam/ # Alice's opam overlay\n\ 862 - \ ├── bob.example.com/ # Bob's monorepo\n\ 863 - \ └── bob.example.com-opam/ # Bob's opam overlay"; 864 - `P "Configuration and data are stored in XDG directories:"; 865 - `Pre 866 - "~/.config/monopam/\n\ 867 - └── opamverse.toml # workspace configuration\n\n\ 868 - ~/.local/share/monopam/\n\ 869 - └── opamverse-registry/ # cloned registry git repo"; 870 - `S "COMMAND FLOW"; 871 - `P "The expected sequence of commands for typical workflows:"; 872 - `P "$(b,First-time setup) (once per machine):"; 873 - `Pre 874 - "monopam verse init --handle you.bsky.social # create workspace"; 875 - `P "$(b,Syncing all members):"; 876 - `Pre 877 - "monopam verse pull # clone/pull all \ 878 - members\n\ 879 - monopam verse status # check status"; 880 - `P "$(b,Daily maintenance):"; 881 - `Pre 882 - "monopam verse sync # update everything\n\ 883 - monopam verse status # check for changes"; 884 - `P "$(b,Working in your own monorepo):"; 885 - `Pre 886 - "cd ~/tangled/mono\n\ 887 - monopam sync # sync with upstreams\n\ 888 - # ... make edits ...\n\ 889 - git add -A && git commit\n\ 890 - monopam sync --remote # push to upstreams"; 891 - `S "INTEGRATION WITH MONOPAM"; 722 + "Commands for working with verse community members. The verse system \ 723 + enables federated collaboration across multiple developers' monorepos."; 892 724 `P 893 - "The verse system complements standard monopam commands. Your mono/ \ 894 - directory works exactly like a normal monopam-managed monorepo:"; 895 - `Pre 896 - "# Work in your monorepo\n\ 897 - cd ~/tangled/mono\n\ 898 - monopam status\n\ 899 - monopam sync # sync with upstreams\n\ 900 - # ... make changes ...\n\ 901 - git add -A && git commit\n\ 902 - monopam sync --remote # push to upstreams"; 725 + "Members are identified by handles - typically domain names like \ 726 + 'yourname.bsky.social' or 'your-domain.com'."; 727 + `S "NOTE"; 903 728 `P 904 - "The verse/ directories are for reading and learning from others' \ 905 - code. You generally don't push to them (unless you're a \ 906 - collaborator)."; 907 - `S "REGISTRY FORMAT"; 908 - `P "The registry is a git repository containing opamverse.toml:"; 909 - `Pre 910 - "[registry]\n\ 911 - name = \"tangled-community\"\n\n\ 912 - [[members]]\n\ 913 - handle = \"alice.bsky.social\"\n\ 914 - monorepo = \"https://github.com/alice/mono\""; 915 - `P "Default registry: https://tangled.org/eeg.cl.cam.ac.uk/opamverse"; 916 - `S "COMMANDS REFERENCE"; 917 - `I ("init", "Create a new workspace with config and directories"); 918 - `I ("status", "Show members and their git status"); 919 - `I ("members", "List all members in the registry"); 920 - `I ("pull [<handle>]", "Clone/pull all members (or specific member)"); 921 - `I ("sync", "Update registry and pull all members"); 729 + "The $(b,monopam init) command creates your workspace and \ 730 + $(b,monopam sync) automatically syncs verse members. These commands \ 731 + are for additional verse-specific operations."; 732 + `S "COMMANDS"; 733 + `I ("members", "List all members in the community registry"); 922 734 `I ("fork <pkg> --from <handle> --url <url>", "Fork a package from a verse member"); 923 - `S "HANDLES"; 924 - `P 925 - "Handles identify community members. They are typically domain names \ 926 - like 'yourname.bsky.social' or 'your-domain.com'."; 735 + `S Manpage.s_examples; 736 + `P "List all community members:"; 737 + `Pre "monopam verse members"; 738 + `P "Fork a package from another member:"; 739 + `Pre "monopam verse fork cohttp --from avsm.bsky.social --url git@github.com:me/cohttp.git"; 927 740 ] 928 741 in 929 742 let info = Cmd.info "verse" ~doc ~man in 930 743 Cmd.group info 931 744 [ 932 - verse_init_cmd; 933 745 verse_members_cmd; 934 - verse_pull_cmd; 935 - verse_sync_cmd; 936 746 verse_fork_cmd; 937 747 ] 938 748 ··· 1782 1592 `P "Inside the devcontainer, initialize your workspace:"; 1783 1593 `Pre 1784 1594 "cd ~/tangled\n\ 1785 - monopam verse init --handle yourname.bsky.social\n\ 1595 + monopam init --handle yourname.bsky.social\n\ 1786 1596 cd mono"; 1787 1597 `P "Daily workflow:"; 1788 1598 `Pre ··· 1830 1640 `I ("remote:+N", "Your checkout is N commits ahead of upstream"); 1831 1641 `I ("remote:-N", "Upstream is N commits ahead (run $(b,monopam sync))"); 1832 1642 `S "COMMON TASKS"; 1833 - `I ("Start fresh", "monopam verse init --handle you.bsky.social"); 1643 + `I ("Start fresh", "monopam init --handle you.bsky.social"); 1834 1644 `I ("Check status", "monopam status"); 1835 1645 `I ("Sync everything", "monopam sync"); 1836 1646 `I ("Sync and push upstream", "monopam sync --remote"); 1837 1647 `I ("Sync one package", "monopam sync <package-name>"); 1838 1648 `S "CONFIGURATION"; 1839 1649 `P 1840 - "Run $(b,monopam verse init --handle <handle>) to create a workspace. \ 1650 + "Run $(b,monopam init --handle <handle>) to create a workspace. \ 1841 1651 Configuration is stored in ~/.config/monopam/opamverse.toml."; 1842 1652 `P "Workspace structure:"; 1843 1653 `Pre ··· 1862 1672 in 1863 1673 let info = Cmd.info "monopam" ~version:"%%VERSION%%" ~doc ~man in 1864 1674 Cmd.group info 1865 - [ status_cmd; diff_cmd; pull_cmd; cherrypick_cmd; sync_cmd; changes_cmd; opam_cmd; doctor_cmd; verse_cmd; feature_cmd; fork_cmd; join_cmd; devcontainer_cmd; site_cmd ] 1675 + [ init_cmd; status_cmd; diff_cmd; pull_cmd; cherrypick_cmd; sync_cmd; changes_cmd; opam_cmd; doctor_cmd; verse_cmd; feature_cmd; fork_cmd; join_cmd; devcontainer_cmd; site_cmd ] 1866 1676 1867 1677 let () = exit (Cmd.eval main_cmd)
+1 -1
lib/feature.ml
··· 16 16 Some (Printf.sprintf "Run 'monopam feature remove %s' first if you want to recreate it" name) 17 17 | Feature_not_found name -> 18 18 Some (Printf.sprintf "Run 'monopam feature list' to see available features, or 'monopam feature add %s' to create it" name) 19 - | Config_error _ -> Some "Run 'monopam verse init' to create a workspace configuration" 19 + | Config_error _ -> Some "Run 'monopam init' to create a workspace configuration" 20 20 21 21 let pp_error_with_hint ppf e = 22 22 pp_error ppf e;
+1 -1
lib/fork_join.ml
··· 20 20 21 21 let error_hint = function 22 22 | Config_error _ -> 23 - Some "Run 'monopam verse init --handle <your-handle>' to create a workspace." 23 + Some "Run 'monopam init --handle <your-handle>' to create a workspace." 24 24 | Git_error (Git.Dirty_worktree _) -> 25 25 Some "Commit or stash your changes first: git status" 26 26 | Git_error _ -> None
+11 -1
lib/monopam.ml
··· 45 45 let error_hint = function 46 46 | Config_error _ -> 47 47 Some 48 - "Run 'monopam verse init --handle <your-handle>' to create a workspace." 48 + "Run 'monopam init --handle <your-handle>' to create a workspace." 49 49 | Repo_error (Opam_repo.No_dev_repo _) -> 50 50 Some 51 51 "Add a 'dev-repo' field to the package's opam file pointing to a git \ ··· 1544 1544 let sync ~proc ~fs ~config ?package ?(remote = false) ?(skip_push = false) 1545 1545 ?(skip_pull = false) () = 1546 1546 let fs_t = fs_typed fs in 1547 + 1548 + (* Step 0: Sync verse members if verse config exists and not skipping pull *) 1549 + (if not skip_pull then 1550 + match Verse_config.load ~fs:fs_t () with 1551 + | Error _ -> () (* No verse config = skip *) 1552 + | Ok verse_config -> 1553 + Log.app (fun m -> m "Syncing verse members..."); 1554 + match Verse.pull ~proc ~fs:fs_t ~config:verse_config () with 1555 + | Ok () -> () 1556 + | Error e -> Log.warn (fun m -> m "Verse sync: %a" Verse.pp_error e)); 1547 1557 1548 1558 (* Clone from verse registry if repos don't exist *) 1549 1559 match clone_from_verse_if_needed ~proc ~fs:fs_t ~config () with
+2 -2
lib/verse.ml
··· 26 26 let error_hint = function 27 27 | Config_error _ -> 28 28 Some 29 - "Run 'monopam verse init --handle <your-handle>' to create a workspace." 29 + "Run 'monopam init --handle <your-handle>' to create a workspace." 30 30 | Git_error (Git.Dirty_worktree _) -> 31 31 Some "Commit or stash your changes first: git status" 32 32 | Git_error (Git.Command_failed (cmd, _)) ··· 45 45 | Workspace_exists _ -> 46 46 Some "Use a different directory, or remove the existing workspace." 47 47 | Not_a_workspace _ -> 48 - Some "Run 'monopam verse init --handle <your-handle>' to create a workspace here." 48 + Some "Run 'monopam init --handle <your-handle>' to create a workspace here." 49 49 | Package_not_found (pkg, handle) -> 50 50 Some (Fmt.str "Run 'monopam verse pull %s' to sync their opam repo, then check package name: %s" handle pkg) 51 51 | Package_already_exists pkgs ->