clawd-migrate 0.1.0

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.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # clawd-migrate
2
+
3
+ Migrate from **moltbot** or **clawdbot** to **openclaw**. Preserves config, memory, and clawdbook (Moltbook) data. Works on any system.
4
+
5
+ ---
6
+
7
+ ## Quick start
8
+
9
+ **Requirements:** Node.js 14+ and Python 3.x.
10
+
11
+ ```bash
12
+ npx clawd-migrate
13
+ ```
14
+
15
+ That starts the **interactive menu** (lobster + guided steps). For full install options and all commands, see **[HOW_TO_RUN.md](HOW_TO_RUN.md)**.
16
+
17
+ ---
18
+
19
+ ## What it does
20
+
21
+ - **Discovers** your existing bot assets (memory files, config, clawdbook/Moltbook).
22
+ - **Backs up** everything into a timestamped folder before any changes.
23
+ - **Migrates** into the openclaw layout: `memory/`, `.config/openclaw/`, `.config/clawdbook/`, and preserves `projects/`.
24
+
25
+ Supports both **moltbot** and **clawdbot** source layouts; no machine-specific paths.
26
+
27
+ ---
28
+
29
+ ## Install and run
30
+
31
+ | Method | Command |
32
+ |--------|--------|
33
+ | Run without installing | `npx clawd-migrate` |
34
+ | Install globally | `npm install -g clawd-migrate` then `clawd-migrate` |
35
+ | Install in a project | `npm install clawd-migrate` then `npx clawd-migrate` |
36
+
37
+ Full details, CLI options, and Python-only usage: **[HOW_TO_RUN.md](HOW_TO_RUN.md)**.
38
+
39
+ ---
40
+
41
+ ## Commands (CLI)
42
+
43
+ ```bash
44
+ clawd-migrate # Interactive menu (default)
45
+ clawd-migrate discover [--root PATH]
46
+ clawd-migrate backup [--root PATH] [--backup-dir PATH]
47
+ clawd-migrate migrate [--root PATH] [--no-backup] [--output PATH]
48
+ ```
49
+
50
+ `--root` defaults to the current directory.
51
+
52
+ ---
53
+
54
+ ## What gets migrated
55
+
56
+ - **Memory/identity:** SOUL.md, USER.md, TOOLS.md, IDENTITY.md, AGENTS.md, MEMORY.md
57
+ - **Config:** `.config/moltbook/`, `.config/moltbot/` (credentials and API keys)
58
+ - **Clawdbook/Moltbook:** Kept under `.config/clawdbook` in the openclaw layout
59
+ - **Extra:** `projects/` (if present)
60
+
61
+ Backups go under `backups/` (or `--backup-dir`) with prefix `openclaw_migrate_backup_`.
62
+
63
+ ---
64
+
65
+ ## Tests
66
+
67
+ From the repo root:
68
+
69
+ ```bash
70
+ npm test
71
+ ```
72
+
73
+ Runs Python unit tests for discover, backup, and migrate.
74
+
75
+ ---
76
+
77
+ ## Documentation
78
+
79
+ | Doc | Description |
80
+ |-----|-------------|
81
+ | [HOW_TO_RUN.md](HOW_TO_RUN.md) | **How to run** – install, interactive mode, CLI, examples |
82
+ | [Documentation/GITHUB.md](Documentation/GITHUB.md) | Publishing this repo to GitHub |
83
+ | [Documentation/NPM_PUBLISH.md](Documentation/NPM_PUBLISH.md) | Publishing to npm |
84
+ | [Documentation/TESTS.md](Documentation/TESTS.md) | Running and writing tests |
85
+ | [Documentation/MIGRATION_SOURCES.md](Documentation/MIGRATION_SOURCES.md) | Moltbot/clawdbot support |
86
+ | [Documentation/MIGRATION_TUI.md](Documentation/MIGRATION_TUI.md) | Interactive TUI (lobster + menu) |
87
+
88
+ ---
89
+
90
+ ## License
91
+
92
+ MIT
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const path = require("path");
5
+ const { spawnSync } = require("child_process");
6
+
7
+ const libDir = path.join(__dirname, "..", "lib");
8
+ const env = { ...process.env, PYTHONPATH: libDir };
9
+
10
+ const candidates = process.platform === "win32" ? ["python"] : ["python3", "python"];
11
+ let result;
12
+ for (const python of candidates) {
13
+ result = spawnSync(
14
+ python,
15
+ ["-m", "clawd_migrate", ...process.argv.slice(2)],
16
+ { stdio: "inherit", env, shell: process.platform === "win32" }
17
+ );
18
+ if (result.error && result.error.code === "ENOENT") continue;
19
+ break;
20
+ }
21
+
22
+ if (result.status === null && result.error) {
23
+ console.error("clawd-migrate: Python not found. Install Python 3 and ensure it is on PATH.");
24
+ process.exit(1);
25
+ }
26
+ process.exit(result.status != null ? result.status : 1);
@@ -0,0 +1,75 @@
1
+ # This repository: clawd-migrate only
2
+
3
+ **This folder (`clawd_migrate`) is the full repository.** There is one Git repo here; everything you need to publish lives in this directory.
4
+
5
+ ---
6
+
7
+ ## First-time: push to a new GitHub repo
8
+
9
+ ### 1. Create an empty repo on GitHub
10
+
11
+ 1. Go to [github.com](https://github.com) and sign in.
12
+ 2. Click **New repository** (or **+** → **New repository**).
13
+ 3. **Repository name:** e.g. `clawd-migrate`
14
+ 4. **Public**
15
+ 5. Do **not** add a README, .gitignore, or license (you already have them in this folder).
16
+ 6. Click **Create repository**.
17
+
18
+ ### 2. Add, commit, and push from this folder
19
+
20
+ From **this folder** (the `clawd_migrate` directory):
21
+
22
+ ```bash
23
+ git add .
24
+ git status # confirm .gitignore is excluding lib/, __pycache__, node_modules/
25
+ git commit -m "Initial commit: clawd-migrate for moltbot/clawdbot to openclaw"
26
+ git branch -M main
27
+ git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO.git
28
+ git push -u origin main
29
+ ```
30
+
31
+ Replace `YOUR_USERNAME` and `YOUR_REPO` with your GitHub username and repo name (e.g. `clawd-migrate`).
32
+
33
+ **Using SSH instead of HTTPS:**
34
+
35
+ ```bash
36
+ git remote add origin git@github.com:YOUR_USERNAME/YOUR_REPO.git
37
+ git push -u origin main
38
+ ```
39
+
40
+ ---
41
+
42
+ ## After the first push
43
+
44
+ ### Link package.json to the repo (optional)
45
+
46
+ In `package.json`, set:
47
+
48
+ ```json
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/YOUR_USERNAME/YOUR_REPO.git"
52
+ }
53
+ ```
54
+
55
+ Then:
56
+
57
+ ```bash
58
+ git add package.json
59
+ git commit -m "Add repository URL to package.json"
60
+ git push
61
+ ```
62
+
63
+ ### Publish to npm (optional)
64
+
65
+ 1. [npmjs.com](https://www.npmjs.com) account and `npm login`.
66
+ 2. From this folder: `npm publish` (or `npm publish --access public` for a scoped package).
67
+
68
+ ---
69
+
70
+ ## What’s in this repo
71
+
72
+ - **Tracked:** All source (`.py`, `bin/`, `scripts/`, `tests/`, `Documentation/`), `package.json`, `README.md`, `HOW_TO_RUN.md`, `.gitignore`.
73
+ - **Ignored:** `lib/` (generated on npm publish), `__pycache__/`, `node_modules/`.
74
+
75
+ Clone this repo and run `npm test` to run tests, or install via npm once published.
@@ -0,0 +1,48 @@
1
+ # Migration Sources (moltbot / clawdbot) – Feature Tracking
2
+
3
+ ## Overview
4
+
5
+ **clawd_migrate** supports upgrading from **moltbot** or **clawdbot** to **openclaw**. The tool is generalized for any user's system: no machine-specific paths, no user-specific references. Safe to publish and use anywhere.
6
+
7
+ ## Status
8
+
9
+ - **Added:** 2025-02-01
10
+ - **Source systems:** moltbot, clawdbot (discovery and migration for both)
11
+
12
+ ## What Was Generalized
13
+
14
+ | Before | After |
15
+ |--------|--------|
16
+ | Clawdbot-only discovery | Unified discovery for moltbot + clawdbot |
17
+ | `discover_clawdbot_assets` | `discover_source_assets` (canonical); `discover_clawdbot_assets` kept as alias |
18
+ | Backup prefix `clawdbot_backup_` | `openclaw_migrate_backup_` (generic) |
19
+ | User-specific comments (e.g. PipelineGPT) | Generic descriptions only |
20
+ | Config: CLAWDBOT_* only | Config: SOURCE_* (union of moltbot + clawdbot paths) |
21
+
22
+ ## Source Layouts Supported
23
+
24
+ - **Memory/identity:** SOUL.md, USER.md, TOOLS.md, IDENTITY.md, AGENTS.md, MEMORY.md (either system).
25
+ - **Config (clawdbot):** `.config/moltbook/`, `.config/moltbook/credentials.json`.
26
+ - **Config (moltbot):** `.config/moltbot/`, `.config/moltbot/credentials.json`; moltbook paths also checked.
27
+ - **Extra:** `projects/` (both systems).
28
+ - **Clawdbook/Moltbook:** Credentials and API keys from moltbook/moltbot paths are kept separate under `.config/clawdbook` in openclaw.
29
+
30
+ ## Files Touched
31
+
32
+ - `config.py` – SOURCE_* names, moltbot paths, generic backup prefix, no machine-specific paths.
33
+ - `discover.py` – `discover_source_assets()`, uses SOURCE_*; `discover_clawdbot_assets` alias.
34
+ - `migrate.py` – Uses `discover_source_assets`; `is_clawdbook` includes moltbot paths.
35
+ - `backup.py` – Uses `discover_source_assets`.
36
+ - `__init__.py` – Exports `discover_source_assets` and `discover_clawdbot_assets`.
37
+ - `__main__.py` – Help/description text: "moltbot or clawdbot"; "Source root".
38
+ - `tui.py` – Banner and discover copy: "moltbot or clawdbot", "any user's system".
39
+
40
+ ## Backward Compatibility
41
+
42
+ - `discover_clawdbot_assets(root)` still works; it calls `discover_source_assets(root)`.
43
+ - Existing CLI flags and behavior unchanged; only copy and discovery scope updated.
44
+
45
+ ## Notes
46
+
47
+ - Adding a new source system (e.g. another bot) only requires extending `SOURCE_*` in `config.py` and any path logic in `discover.py` / `migrate.py`.
48
+ - No absolute paths or user names; all paths are relative to the chosen root (default: cwd).
@@ -0,0 +1,51 @@
1
+ # Migration TUI – Feature Tracking
2
+
3
+ ## Overview
4
+
5
+ Interactive terminal UI for **clawd_migrate** that shows a lobster ASCII art on load and guides users through the migration process (discover -> backup -> migrate). Supports **moltbot or clawdbot** as source; works on any user's system (no machine-specific paths).
6
+
7
+ ## Status
8
+
9
+ - **Added:** 2025-02-01
10
+ - **Entry points:** `python -m clawd_migrate` (no args) or `python -m clawd_migrate run`
11
+
12
+ ## Features
13
+
14
+ | Feature | Status |
15
+ |--------|--------|
16
+ | Lobster ASCII art on load (red lobster + cyan “CLAWD MIGRATE” text) | Done |
17
+ | Welcome banner and tagline | Done |
18
+ | Choose working directory (default: cwd) | Done |
19
+ | Menu: Discover assets | Done |
20
+ | Menu: Create backup only | Done |
21
+ | Menu: Run full migration (backup first) | Done |
22
+ | Menu: Migrate without backup (with warning) | Done |
23
+ | Menu: Change working directory | Done |
24
+ | Menu: Quit | Done |
25
+ | ANSI colors (Windows Terminal / PowerShell 7+ / Unix) | Done |
26
+ | Feature doc in Documentation/ | Done |
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ # Interactive mode (default)
32
+ python -m clawd_migrate
33
+
34
+ # Explicit interactive mode
35
+ python -m clawd_migrate run
36
+
37
+ # Existing CLI still works
38
+ python -m clawd_migrate discover [--root PATH]
39
+ python -m clawd_migrate backup [--root PATH] [--backup-dir PATH]
40
+ python -m clawd_migrate migrate [--root PATH] [--no-backup] [--output PATH]
41
+ ```
42
+
43
+ ## Files
44
+
45
+ - `tui.py` – TUI logic, lobster art, menu, discover/backup/migrate flows
46
+ - `__main__.py` – Wires no-args and `run` to TUI; keeps existing subcommands
47
+
48
+ ## Notes
49
+
50
+ - Colors use ANSI escape codes; supported in Windows Terminal and modern PowerShell.
51
+ - Lobster art is ASCII-only for broad terminal compatibility.
@@ -0,0 +1,51 @@
1
+ # NPM Publish – Feature Tracking
2
+
3
+ ## Overview
4
+
5
+ **clawd_migrate** is deployable to NPM so users can install and run it with `npm install clawd-migrate` or `npx clawd-migrate`. The NPM package wraps the Python tool; Node runs the bin script, which invokes Python with the bundled package.
6
+
7
+ ## Status
8
+
9
+ - **Added:** 2025-02-01
10
+ - **Package name:** `clawd-migrate`
11
+ - **Bin:** `clawd-migrate` → `bin/clawd-migrate.js`
12
+
13
+ ## Requirements for users
14
+
15
+ - **Node.js** 14+ (to run the npm-installed command)
16
+ - **Python** 3.x (required by the migration tool)
17
+
18
+ ## Package layout (published tarball)
19
+
20
+ - `package.json`
21
+ - `bin/clawd-migrate.js` – runs `python -m clawd_migrate` with `PYTHONPATH=lib`
22
+ - `lib/clawd_migrate/` – Python package (copied on prepublish from repo root)
23
+
24
+ ## Repo layout (development)
25
+
26
+ - Python source stays at repo root (`__init__.py`, `config.py`, etc.).
27
+ - `prepublishOnly` runs `scripts/copy-py.js`, which copies `.py` files and `Documentation/` into `lib/clawd_migrate/` so the npm pack includes the correct structure.
28
+
29
+ ## Files added
30
+
31
+ | File | Purpose |
32
+ |------|--------|
33
+ | `package.json` | NPM package manifest, bin, files, prepublishOnly |
34
+ | `bin/clawd-migrate.js` | Cross-platform bin: sets PYTHONPATH, runs Python module |
35
+ | `scripts/copy-py.js` | Copies Python package into `lib/clawd_migrate/` for publish |
36
+ | `README.md` | Install and usage for npm/GitHub |
37
+ | `Documentation/NPM_PUBLISH.md` | This doc |
38
+
39
+ ## Publishing (manual)
40
+
41
+ 1. Bump version in `package.json` (and optionally in `__init__.py`).
42
+ 2. From repo root: `npm run prepublishOnly` (optional; runs automatically on publish).
43
+ 3. `npm publish` (requires npm login and publish rights).
44
+
45
+ Scoped package (e.g. `@yourscope/clawd-migrate`): set `"name": "@yourscope/clawd-migrate"` in `package.json` and use `npm publish --access public` if the scope is unscoped by default.
46
+
47
+ ## Notes
48
+
49
+ - The bin script uses `python3` on non-Windows and `python` on Windows; users must have Python on PATH.
50
+ - Only `bin/` and `lib/` are included in the tarball (`files` in package.json).
51
+ - Do not commit `lib/` if it’s generated; it’s created during prepublish and packed from there.
@@ -0,0 +1,130 @@
1
+ # Step-by-step: Publish clawd-migrate to npm
2
+
3
+ Follow these in order to publish this package to npm so anyone can run `npx clawd-migrate`.
4
+
5
+ ---
6
+
7
+ ## Step 1: Create an npm account (if you don’t have one)
8
+
9
+ 1. Go to [npmjs.com/signup](https://www.npmjs.com/signup).
10
+ 2. Sign up with email (or GitHub).
11
+ 3. Confirm your email if prompted.
12
+
13
+ ---
14
+
15
+ ## Step 2: Log in to npm from the terminal
16
+
17
+ From any directory:
18
+
19
+ ```bash
20
+ npm login
21
+ ```
22
+
23
+ - **Username:** your npm username
24
+ - **Password:** your npm password
25
+ - **Email:** (your email, or one already on the account)
26
+ - **OTP:** if you have 2FA, enter the code from your app or email
27
+
28
+ You should see: `Logged in as YOUR_USERNAME on https://registry.npmjs.org/`.
29
+
30
+ ---
31
+
32
+ ## Step 3: (Optional) Set repository URL in package.json
33
+
34
+ If this repo is on GitHub, add the URL so the npm page links to it. In `package.json`, set:
35
+
36
+ ```json
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/YOUR_USERNAME/clawd-migrate.git"
40
+ }
41
+ ```
42
+
43
+ Replace `YOUR_USERNAME` with your GitHub username. You can also set `"author": "Your Name <you@example.com>"` if you want.
44
+
45
+ ---
46
+
47
+ ## Step 4: Check the package name
48
+
49
+ Open `package.json` and confirm:
50
+
51
+ - **name:** `clawd-migrate` (must be unique on npm; if taken, use a scoped name like `@yourusername/clawd-migrate`).
52
+ - **version:** e.g. `0.1.0` (you can leave as-is for first publish).
53
+
54
+ If you use a scoped name (e.g. `@yourusername/clawd-migrate`), you’ll need `npm publish --access public` in Step 6.
55
+
56
+ ---
57
+
58
+ ## Step 5: Dry run (see what will be published)
59
+
60
+ From the **clawd_migrate** folder (repo root):
61
+
62
+ ```bash
63
+ cd c:\Users\Douglas\clawd\clawd_migrate
64
+ npm pack --dry-run
65
+ ```
66
+
67
+ This lists the files that would go in the tarball. You should see `bin/` and `lib/` (and package.json); no Python source at root (it’s copied into `lib/clawd_migrate/` by the prepublish script).
68
+
69
+ Optional: create a real tarball and inspect it:
70
+
71
+ ```bash
72
+ npm pack
73
+ ```
74
+
75
+ That creates `clawd-migrate-0.1.0.tgz`. You can delete it after; it’s not needed for publish.
76
+
77
+ ---
78
+
79
+ ## Step 6: Publish
80
+
81
+ From the **clawd_migrate** folder:
82
+
83
+ ```bash
84
+ npm publish
85
+ ```
86
+
87
+ - If the **name** is unscoped (`clawd-migrate`): that’s it.
88
+ - If the **name** is scoped (e.g. `@yourusername/clawd-migrate`): run:
89
+
90
+ ```bash
91
+ npm publish --access public
92
+ ```
93
+
94
+ You should see something like: `+ clawd-migrate@0.1.0` (or your scoped name).
95
+
96
+ ---
97
+
98
+ ## Step 7: Verify
99
+
100
+ 1. Open [npmjs.com/package/clawd-migrate](https://www.npmjs.com/package/clawd-migrate) (or your scoped package URL).
101
+ 2. Install and run:
102
+
103
+ ```bash
104
+ npx clawd-migrate discover
105
+ ```
106
+
107
+ If that runs and shows JSON (or the interactive menu with no args), you’re done.
108
+
109
+ ---
110
+
111
+ ## Later: bump version and publish again
112
+
113
+ 1. Edit `package.json`: bump **version** (e.g. `0.1.0` → `0.1.1` or `0.2.0`). Optionally bump `__init__.py` `__version__` to match.
114
+ 2. From repo root:
115
+
116
+ ```bash
117
+ npm publish
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Troubleshooting
123
+
124
+ | Issue | What to do |
125
+ |-------|------------|
126
+ | `You must verify your email` | Verify the email in the link npm sent. |
127
+ | `Package name already taken` | Change **name** in package.json (e.g. `clawd-migrate-tools`) or use a scoped name: `@yourusername/clawd-migrate` and `npm publish --access public`. |
128
+ | `403 Forbidden` | You’re not logged in or don’t have permission; run `npm login` again. |
129
+ | `prepublishOnly` fails | Run `node scripts/copy-py.js` from repo root; fix any errors (e.g. missing files). |
130
+ | Users get “Python not found” | The package requires Python 3 on PATH; say so in README (already there). |
@@ -0,0 +1,43 @@
1
+ # Tests – Feature Tracking
2
+
3
+ ## Overview
4
+
5
+ **clawd_migrate** has Python unit tests for discover, backup, and migrate. They use a temporary directory with a fake moltbot/clawdbot layout and assert the expected files and layout.
6
+
7
+ ## Status
8
+
9
+ - **Added:** 2025-02-01
10
+ - **Runner:** `unittest` (stdlib; no extra deps)
11
+
12
+ ## What is tested
13
+
14
+ | Test | What it checks |
15
+ |------|----------------|
16
+ | `TestDiscover.test_discover_finds_memory_config_extra` | Discover returns memory (SOUL.md, USER.md, TOOLS.md), config, clawdbook, and extra (projects/) |
17
+ | `TestBackup.test_backup_creates_dir_and_copies_files` | Backup dir exists; SOUL.md, USER.md, credentials.json, projects/readme.txt, _manifest.txt present |
18
+ | `TestMigrate.test_migrate_creates_openclaw_layout` | Migration creates memory/, .config/openclaw/, .config/clawdbook/; copies memory and clawdbook; no errors; projects/ preserved |
19
+
20
+ ## How to run
21
+
22
+ From repo root (the `clawd_migrate` directory):
23
+
24
+ ```bash
25
+ # Via npm (sets PYTHONPATH to parent so package is found)
26
+ npm test
27
+
28
+ # Via Python (from parent of clawd_migrate so clawd_migrate is on path)
29
+ cd .. && PYTHONPATH=. python -m unittest discover -s clawd_migrate/tests -p "test_*.py" -v
30
+
31
+ # Or from repo root with PYTHONPATH to parent
32
+ PYTHONPATH=.. python -m unittest discover -s tests -p "test_*.py" -v
33
+ ```
34
+
35
+ ## Files
36
+
37
+ - `tests/__init__.py` – Marks tests package.
38
+ - `tests/test_migrate.py` – Discover, backup, migrate tests; adds parent of repo root to `sys.path` so `clawd_migrate` is importable.
39
+ - `scripts/run-tests.js` – Used by `npm test`; sets `PYTHONPATH` to parent dir and runs `python -m unittest discover`.
40
+
41
+ ## Bug fix in migrate
42
+
43
+ In-place migration (output_root same as root) was copying "extra" files onto themselves, causing `shutil.copy2` to raise "same file". Migrate now skips copying when `src_path.resolve() == dest.resolve()`.
@@ -0,0 +1,18 @@
1
+ """
2
+ clawd_migrate - Migrate from moltbot or clawdbot to openclaw while preserving
3
+ configuration, memory, and clawdbook (Moltbook) data. Works for any user's system.
4
+ """
5
+
6
+ __version__ = "0.1.0"
7
+
8
+ from .backup import create_backup
9
+ from .discover import discover_source_assets, discover_clawdbot_assets
10
+ from .migrate import run_migration
11
+
12
+ __all__ = [
13
+ "create_backup",
14
+ "discover_source_assets",
15
+ "discover_clawdbot_assets",
16
+ "run_migration",
17
+ "__version__",
18
+ ]
@@ -0,0 +1,101 @@
1
+ """
2
+ CLI for clawd_migrate. Usage:
3
+ python -m clawd_migrate # Interactive TUI (lobster + menu)
4
+ python -m clawd_migrate run # Same as above
5
+ python -m clawd_migrate discover [--root PATH]
6
+ python -m clawd_migrate backup [--root PATH] [--backup-dir PATH]
7
+ python -m clawd_migrate migrate [--root PATH] [--no-backup] [--output PATH]
8
+ """
9
+
10
+ import argparse
11
+ import json
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ from . import create_backup, discover_source_assets, run_migration
17
+ from . import __version__
18
+
19
+
20
+ def cmd_discover(root: Path) -> int:
21
+ assets = discover_source_assets(root)
22
+ print(json.dumps(assets, indent=2))
23
+ return 0
24
+
25
+
26
+ def cmd_backup(root: Path, backup_dir: Path) -> int:
27
+ path = create_backup(root=root, backup_dir=backup_dir)
28
+ print(f"Backup created: {path}")
29
+ return 0
30
+
31
+
32
+ def cmd_migrate(root: Path, no_backup: bool, output: Path) -> int:
33
+ if no_backup:
34
+ print("Warning: --no-backup set; no backup will be created.", file=sys.stderr)
35
+ result = run_migration(root=root, backup_first=not no_backup, output_root=output or root)
36
+ if result["backup_path"]:
37
+ print(f"Backup: {result['backup_path']}")
38
+ print(f"Memory copied: {len(result['memory_copied'])} files")
39
+ print(f"Config copied: {len(result['config_copied'])} files")
40
+ print(f"Clawdbook (safe): {len(result['clawdbook_copied'])} files")
41
+ if result["errors"]:
42
+ print("Errors:", file=sys.stderr)
43
+ for e in result["errors"]:
44
+ print(f" {e}", file=sys.stderr)
45
+ return 1
46
+ return 0
47
+
48
+
49
+ def main() -> int:
50
+ parser = argparse.ArgumentParser(
51
+ description="Migrate moltbot or clawdbot to openclaw; preserve config, memory, and clawdbook data."
52
+ )
53
+ parser.add_argument("--version", action="version", version=__version__)
54
+ sub = parser.add_subparsers(dest="command", required=False)
55
+
56
+ # interactive TUI (default when no command)
57
+ p_run = sub.add_parser("run", help="Interactive terminal UI (lobster + guided menu)")
58
+ p_run.set_defaults(func=lambda a: _run_tui())
59
+
60
+ # discover
61
+ p_discover = sub.add_parser("discover", help="List clawdbot assets (config, memory, clawdbook)")
62
+ p_discover.add_argument("--root", type=Path, default=None, help="Source root (default: cwd)")
63
+ p_discover.set_defaults(func=lambda a: cmd_discover(Path(a.root or os.getcwd())))
64
+
65
+ # backup
66
+ p_backup = sub.add_parser("backup", help="Create timestamped backup (no migration)")
67
+ p_backup.add_argument("--root", type=Path, default=None, help="Clawdbot root (default: cwd)")
68
+ p_backup.add_argument("--backup-dir", type=Path, default=None, help="Backup parent dir (default: root/backups)")
69
+ p_backup.set_defaults(
70
+ func=lambda a: cmd_backup(
71
+ Path(a.root or os.getcwd()),
72
+ Path(a.backup_dir) if a.backup_dir else None,
73
+ )
74
+ )
75
+
76
+ # migrate
77
+ p_migrate = sub.add_parser("migrate", help="Migrate to openclaw (backup first unless --no-backup)")
78
+ p_migrate.add_argument("--root", type=Path, default=None, help="Source root (default: cwd)")
79
+ p_migrate.add_argument("--no-backup", action="store_true", help="Skip backup (not recommended)")
80
+ p_migrate.add_argument("--output", type=Path, default=None, help="Openclaw output root (default: root)")
81
+ p_migrate.set_defaults(
82
+ func=lambda a: cmd_migrate(
83
+ Path(a.root or os.getcwd()),
84
+ a.no_backup,
85
+ Path(a.output) if a.output else None,
86
+ )
87
+ )
88
+
89
+ args = parser.parse_args()
90
+ if args.command is None:
91
+ return _run_tui()
92
+ return args.func(args)
93
+
94
+
95
+ def _run_tui() -> int:
96
+ from .tui import run_tui
97
+ return run_tui()
98
+
99
+
100
+ if __name__ == "__main__":
101
+ sys.exit(main())
@@ -0,0 +1,66 @@
1
+ """
2
+ Create a timestamped backup of source bot (moltbot/clawdbot) config, memory, and clawdbook data.
3
+ Never overwrite; always safe. Works for any user's system.
4
+ """
5
+
6
+ import os
7
+ import shutil
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import List, Optional
11
+
12
+ from .config import DEFAULT_BACKUP_DIR, BACKUP_PREFIX
13
+ from .discover import discover_source_assets
14
+
15
+
16
+ def create_backup(
17
+ root: Optional[Path] = None,
18
+ backup_dir: Optional[Path] = None,
19
+ asset_paths: Optional[dict] = None,
20
+ ) -> Path:
21
+ """
22
+ Copy all discovered (or provided) assets into a timestamped backup directory.
23
+ Returns the path to the backup directory.
24
+ """
25
+ root = Path(root or os.getcwd()).resolve()
26
+ backup_base = Path(backup_dir or root / DEFAULT_BACKUP_DIR)
27
+ backup_base.mkdir(parents=True, exist_ok=True)
28
+
29
+ stamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
30
+ backup_path = backup_base / f"{BACKUP_PREFIX}{stamp}"
31
+ backup_path.mkdir(parents=True, exist_ok=True)
32
+
33
+ assets = asset_paths or discover_source_assets(root)
34
+ all_paths: List[str] = (
35
+ assets.get("memory", [])
36
+ + assets.get("config", [])
37
+ + assets.get("clawdbook", [])
38
+ + assets.get("extra", [])
39
+ )
40
+ all_paths = list(dict.fromkeys(all_paths))
41
+
42
+ for src in all_paths:
43
+ src_path = Path(src)
44
+ if not src_path.is_file():
45
+ continue
46
+ try:
47
+ rel = src_path.relative_to(root)
48
+ except ValueError:
49
+ rel = src_path.name
50
+ dest = backup_path / rel
51
+ dest.parent.mkdir(parents=True, exist_ok=True)
52
+ shutil.copy2(src_path, dest)
53
+
54
+ # Also write a manifest so we know what was backed up
55
+ manifest = backup_path / "_manifest.txt"
56
+ with open(manifest, "w", encoding="utf-8") as f:
57
+ f.write(f"Backup at {stamp}\nRoot: {root}\n\n")
58
+ for k, v in assets.items():
59
+ if isinstance(v, list):
60
+ f.write(f"{k}:\n")
61
+ for p in v:
62
+ f.write(f" {p}\n")
63
+ else:
64
+ f.write(f"{k}: {v}\n")
65
+
66
+ return backup_path
@@ -0,0 +1,48 @@
1
+ """
2
+ Migration configuration: paths and patterns for source bots (moltbot / clawdbot)
3
+ vs openclaw target layout. Works for any user's system; no machine-specific paths.
4
+ """
5
+
6
+ # --- Source layouts (moltbot and clawdbot) ---
7
+ # Memory/identity files that may exist in either system
8
+ SOURCE_MEMORY_FILES = [
9
+ "SOUL.md", # Who the bot is
10
+ "USER.md", # Who the human is
11
+ "TOOLS.md", # Local notes, device names, env-specific
12
+ "IDENTITY.md", # Bot identity (if present)
13
+ "AGENTS.md", # Agent instructions (if present)
14
+ "MEMORY.md", # Long-term memory (if present)
15
+ ]
16
+
17
+ # Config paths: union of moltbot and clawdbot locations (credentials, API keys)
18
+ SOURCE_CONFIG_PATHS = [
19
+ # Clawdbot
20
+ ".config/moltbook/credentials.json",
21
+ ".config/moltbook/",
22
+ # Moltbot
23
+ ".config/moltbot/credentials.json",
24
+ ".config/moltbot/",
25
+ ".config/moltbook/", # Moltbot may also use moltbook for Clawdbook
26
+ ]
27
+
28
+ # Deduplicate while preserving order
29
+ SOURCE_CONFIG_PATHS = list(dict.fromkeys(SOURCE_CONFIG_PATHS))
30
+
31
+ # Extra paths: project docs and scripts (both systems)
32
+ SOURCE_EXTRA_DIRS = [
33
+ "projects/",
34
+ ]
35
+
36
+ # --- Openclaw (target) layout ---
37
+ OPENCLAW_MEMORY_DIR = "memory"
38
+ OPENCLAW_CONFIG_DIR = ".config/openclaw"
39
+ OPENCLAW_CLAWDBOOK_DIR = ".config/clawdbook"
40
+
41
+ # Backup (generic; no product name in prefix)
42
+ DEFAULT_BACKUP_DIR = "backups"
43
+ BACKUP_PREFIX = "openclaw_migrate_backup_"
44
+
45
+ # Backward compatibility: same names used by discover
46
+ CLAWDBOT_MEMORY_FILES = SOURCE_MEMORY_FILES
47
+ CLAWDBOT_CONFIG_PATHS = SOURCE_CONFIG_PATHS
48
+ CLAWDBOT_EXTRA_PATTERNS = SOURCE_EXTRA_DIRS
@@ -0,0 +1,69 @@
1
+ """
2
+ Discover source bot assets (moltbot or clawdbot): config, memory, and clawdbook data.
3
+ Works on any user's system; no machine-specific paths.
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from .config import (
11
+ SOURCE_CONFIG_PATHS,
12
+ SOURCE_MEMORY_FILES,
13
+ SOURCE_EXTRA_DIRS,
14
+ )
15
+
16
+
17
+ def discover_source_assets(root: Optional[Path] = None) -> dict:
18
+ """
19
+ Scan root (or cwd) for moltbot/clawdbot config, memory, and clawdbook data.
20
+ Returns paths that exist, grouped by type. Safe for any user's system.
21
+ """
22
+ root = Path(root or os.getcwd()).resolve()
23
+ out = {
24
+ "root": str(root),
25
+ "memory": [],
26
+ "config": [],
27
+ "clawdbook": [],
28
+ "extra": [],
29
+ }
30
+
31
+ # Memory files
32
+ for name in SOURCE_MEMORY_FILES:
33
+ p = root / name
34
+ if p.is_file():
35
+ out["memory"].append(str(p))
36
+
37
+ # Config paths (moltbook/clawdbook = credentials, API keys)
38
+ for rel in SOURCE_CONFIG_PATHS:
39
+ p = root / rel
40
+ if p.exists():
41
+ if p.is_dir():
42
+ for f in p.rglob("*"):
43
+ if f.is_file():
44
+ out["config"].append(str(f))
45
+ if str(p) not in [os.path.dirname(c) for c in out["config"]]:
46
+ out["clawdbook"].append(str(p))
47
+ else:
48
+ out["config"].append(str(p))
49
+ if "moltbook" in rel.lower() or "moltbot" in rel.lower() or "credentials" in rel.lower():
50
+ out["clawdbook"].append(str(p))
51
+
52
+ out["config"] = list(dict.fromkeys(out["config"]))
53
+
54
+ # Extra dirs (e.g. projects/)
55
+ for rel in SOURCE_EXTRA_DIRS:
56
+ rel_clean = rel.rstrip("/")
57
+ extra_path = root / rel_clean
58
+ if extra_path.is_dir():
59
+ for f in extra_path.rglob("*"):
60
+ if f.is_file():
61
+ out["extra"].append(str(f))
62
+
63
+ out["clawdbook"] = list(dict.fromkeys(out["clawdbook"]))
64
+ return out
65
+
66
+
67
+ def discover_clawdbot_assets(root: Optional[Path] = None) -> dict:
68
+ """Alias for discover_source_assets (backward compatibility)."""
69
+ return discover_source_assets(root)
@@ -0,0 +1,106 @@
1
+ """
2
+ Migrate moltbot or clawdbot assets to openclaw layout.
3
+ Always runs after a backup; never mutates originals without backup.
4
+ Works for any user's system.
5
+ """
6
+
7
+ import os
8
+ import shutil
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ from .config import (
13
+ OPENCLAW_MEMORY_DIR,
14
+ OPENCLAW_CONFIG_DIR,
15
+ OPENCLAW_CLAWDBOOK_DIR,
16
+ )
17
+ from .discover import discover_source_assets
18
+ from .backup import create_backup
19
+
20
+
21
+ def run_migration(
22
+ root: Optional[Path] = None,
23
+ backup_first: bool = True,
24
+ output_root: Optional[Path] = None,
25
+ ) -> dict:
26
+ """
27
+ Migrate moltbot/clawdbot config, memory, and clawdbook data to openclaw structure.
28
+ If backup_first is True (default), creates a timestamped backup before any copy.
29
+ output_root defaults to root (in-place migration into new dirs).
30
+ Returns summary: backup_path, memory_copied, config_copied, clawdbook_copied.
31
+ """
32
+ root = Path(root or os.getcwd()).resolve()
33
+ out_root = Path(output_root or root).resolve()
34
+ assets = discover_source_assets(root)
35
+ result = {
36
+ "backup_path": None,
37
+ "memory_copied": [],
38
+ "config_copied": [],
39
+ "clawdbook_copied": [],
40
+ "errors": [],
41
+ }
42
+
43
+ if backup_first:
44
+ result["backup_path"] = str(create_backup(root=root, asset_paths=assets))
45
+
46
+ memory_dir = out_root / OPENCLAW_MEMORY_DIR
47
+ config_dir = out_root / OPENCLAW_CONFIG_DIR
48
+ clawdbook_dir = out_root / OPENCLAW_CLAWDBOOK_DIR
49
+ memory_dir.mkdir(parents=True, exist_ok=True)
50
+ config_dir.mkdir(parents=True, exist_ok=True)
51
+ clawdbook_dir.mkdir(parents=True, exist_ok=True)
52
+
53
+ # Copy memory files into openclaw memory/
54
+ for src in assets.get("memory", []):
55
+ src_path = Path(src)
56
+ if src_path.is_file():
57
+ dest = memory_dir / src_path.name
58
+ try:
59
+ shutil.copy2(src_path, dest)
60
+ result["memory_copied"].append(str(dest))
61
+ except Exception as e:
62
+ result["errors"].append(f"memory {src}: {e}")
63
+
64
+ # Clawdbook/Moltbook data: keep separate and safe (credentials, API keys)
65
+ def is_clawdbook(path_str: str) -> bool:
66
+ p = path_str.lower()
67
+ return "moltbook" in p or "clawdbook" in p or "moltbot" in p
68
+
69
+ for src in assets.get("config", []):
70
+ src_path = Path(src)
71
+ if not src_path.is_file():
72
+ continue
73
+ if is_clawdbook(src):
74
+ dest = clawdbook_dir / src_path.name
75
+ try:
76
+ shutil.copy2(src_path, dest)
77
+ result["clawdbook_copied"].append(str(dest))
78
+ except Exception as e:
79
+ result["errors"].append(f"clawdbook {src}: {e}")
80
+ else:
81
+ dest = config_dir / src_path.name
82
+ try:
83
+ shutil.copy2(src_path, dest)
84
+ result["config_copied"].append(str(dest))
85
+ except Exception as e:
86
+ result["errors"].append(f"config {src}: {e}")
87
+
88
+ # Extra (e.g. projects/) -> openclaw projects under output root (preserve layout)
89
+ for src in assets.get("extra", []):
90
+ src_path = Path(src)
91
+ if src_path.is_file():
92
+ try:
93
+ rel = src_path.relative_to(root)
94
+ except ValueError:
95
+ rel = Path("projects") / src_path.name
96
+ dest = out_root / rel
97
+ if src_path.resolve() == dest.resolve():
98
+ continue # in-place: same file, skip copy
99
+ dest.parent.mkdir(parents=True, exist_ok=True)
100
+ try:
101
+ shutil.copy2(src_path, dest)
102
+ result["config_copied"].append(str(dest))
103
+ except Exception as e:
104
+ result["errors"].append(f"extra {src}: {e}")
105
+
106
+ return result
@@ -0,0 +1,173 @@
1
+ """
2
+ Interactive terminal UI for clawd_migrate.
3
+ Shows a lobster on load and guides users through discover -> backup -> migrate.
4
+ Works for moltbot or clawdbot on any user's system.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from . import create_backup, discover_source_assets, run_migration
13
+
14
+ # ANSI codes (work in Windows Terminal, PowerShell 7+, and Unix)
15
+ RESET = "\033[0m"
16
+ BOLD = "\033[1m"
17
+ DIM = "\033[2m"
18
+ CYAN = "\033[36m"
19
+ GREEN = "\033[32m"
20
+ YELLOW = "\033[33m"
21
+ MAGENTA = "\033[35m"
22
+ RED = "\033[31m"
23
+
24
+
25
+ def style(s: str, *codes: str) -> str:
26
+ return "".join(codes) + s + RESET
27
+
28
+
29
+ # ASCII lobster (body in RED, eyes in BOLD, "CLAWD MIGRATE" in CYAN)
30
+ LOBSTER_LINES = [
31
+ "",
32
+ " \\ ___ ",
33
+ " \\ / \\ ____ _ _ ____",
34
+ " \\| o o | / ___|| | __ _| | _| _ \\ _ __ ___ ___",
35
+ " \\___/ \\___ \\| |/ _` | |/ / | | | '_ ` _ \\ / _ \\",
36
+ " / \\ ___) | | (_| | < | |_| | | | | | | (_) |",
37
+ " (_______) |____/|_|\\__,_|_|\\_\\ |____/|_| |_| |_|\\___/",
38
+ "",
39
+ ]
40
+
41
+
42
+ def print_banner() -> None:
43
+ """Print the lobster ASCII art and welcome message."""
44
+ print()
45
+ for line in LOBSTER_LINES:
46
+ if not line.strip():
47
+ print(line)
48
+ continue
49
+ # Split lobster (left) from "CLAWD MIGRATE" text (right)
50
+ sep = " ____"
51
+ if sep in line and ("/ \\" in line or "|" in line):
52
+ left, right = line.split(sep, 1)
53
+ print(style(left, RED) + style(sep + right, CYAN))
54
+ elif "o o" in line:
55
+ left, right = line.split("o o", 1)
56
+ print(style(left, RED) + style("o", BOLD) + " " + style("o", BOLD) + style(right, RED))
57
+ else:
58
+ # Lobster-only line
59
+ print(style(line, RED))
60
+ print(style(" Migrate from moltbot or clawdbot to openclaw safely ", BOLD, CYAN))
61
+ print(style(" Discover -> Backup -> Migrate ", DIM))
62
+ print()
63
+
64
+
65
+ def get_root() -> Path:
66
+ """Prompt for root path or use cwd."""
67
+ cwd = Path(os.getcwd()).resolve()
68
+ print(style(f" Working directory: {cwd}", DIM))
69
+ raw = input(style(" Use this directory? [Y/n] or enter a path: ", YELLOW)).strip()
70
+ if not raw or raw.lower() == "y" or raw.lower() == "yes":
71
+ return cwd
72
+ p = Path(raw).expanduser().resolve()
73
+ if not p.is_dir():
74
+ print(style(f" Not a directory: {p}", RED))
75
+ return cwd
76
+ return p
77
+
78
+
79
+ def do_discover(root: Path) -> None:
80
+ """Run discover and print results in a readable way."""
81
+ print(style("\n Discovering assets (moltbot / clawdbot)...\n", CYAN))
82
+ assets = discover_source_assets(root)
83
+ print(style(" Root: ", DIM) + assets["root"])
84
+ for key in ("memory", "config", "clawdbook", "extra"):
85
+ items = assets.get(key, [])
86
+ label = key.capitalize()
87
+ print(style(f" {label}: {len(items)} item(s)", GREEN if items else DIM))
88
+ for path in items[:10]:
89
+ print(style(" ", DIM) + path)
90
+ if len(items) > 10:
91
+ print(style(f" ... and {len(items) - 10} more", DIM))
92
+ print()
93
+ if input(style(" Show full JSON? [y/N]: ", YELLOW)).strip().lower() == "y":
94
+ print(json.dumps(assets, indent=2))
95
+ print()
96
+
97
+
98
+ def do_backup(root: Path) -> None:
99
+ """Create a timestamped backup."""
100
+ print(style("\n Creating backup...\n", CYAN))
101
+ try:
102
+ path = create_backup(root=root)
103
+ print(style(f" Backup created: {path}", GREEN))
104
+ except Exception as e:
105
+ print(style(f" Error: {e}", RED))
106
+ print()
107
+
108
+
109
+ def do_migrate(root: Path, no_backup: bool) -> None:
110
+ """Run migration (optionally with backup first)."""
111
+ if not no_backup:
112
+ print(style(" Migration will create a backup first.", DIM))
113
+ else:
114
+ print(style(" Skipping backup (--no-backup).", YELLOW))
115
+ confirm = input(style(" Proceed with migration? [y/N]: ", YELLOW)).strip().lower()
116
+ if confirm != "y" and confirm != "yes":
117
+ print(style(" Cancelled.", DIM))
118
+ return
119
+ print(style("\n Migrating...\n", CYAN))
120
+ result = run_migration(root=root, backup_first=not no_backup, output_root=None)
121
+ if result["backup_path"]:
122
+ print(style(f" Backup: {result['backup_path']}", GREEN))
123
+ print(style(f" Memory copied: {len(result['memory_copied'])} files", GREEN))
124
+ print(style(f" Config copied: {len(result['config_copied'])} files", GREEN))
125
+ print(style(f" Clawdbook (safe): {len(result['clawdbook_copied'])} files", GREEN))
126
+ if result["errors"]:
127
+ print(style(" Errors:", RED))
128
+ for e in result["errors"]:
129
+ print(style(f" {e}", RED))
130
+ else:
131
+ print(style("\n Migration complete.", BOLD, GREEN))
132
+ print()
133
+
134
+
135
+ def main_menu(root: Path) -> bool:
136
+ """Show main menu; return False to exit."""
137
+ print(style(" [1] Discover assets (config, memory, clawdbook)", CYAN))
138
+ print(style(" [2] Create backup only (no migration)", CYAN))
139
+ print(style(" [3] Run full migration (backup first, then migrate)", CYAN))
140
+ print(style(" [4] Migrate without backup (not recommended)", YELLOW))
141
+ print(style(" [5] Change working directory", DIM))
142
+ print(style(" [q] Quit", DIM))
143
+ print()
144
+ choice = input(style(" Choose [1-5 / q]: ", BOLD, MAGENTA)).strip().lower()
145
+ if choice == "q" or choice == "quit":
146
+ return False
147
+ if choice == "1":
148
+ do_discover(root)
149
+ return True
150
+ if choice == "2":
151
+ do_backup(root)
152
+ return True
153
+ if choice == "3":
154
+ do_migrate(root, no_backup=False)
155
+ return True
156
+ if choice == "4":
157
+ do_migrate(root, no_backup=True)
158
+ return True
159
+ if choice == "5":
160
+ new_root = get_root()
161
+ return main_menu(new_root)
162
+ print(style(" Unknown option.", RED))
163
+ return True
164
+
165
+
166
+ def run_tui() -> int:
167
+ """Entry point for the interactive TUI. Returns exit code."""
168
+ print_banner()
169
+ root = get_root()
170
+ while main_menu(root):
171
+ pass
172
+ print(style("\n Bye!\n", DIM))
173
+ return 0
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "clawd-migrate",
3
+ "version": "0.1.0",
4
+ "description": "Migrate from moltbot or clawdbot to openclaw. Preserves config, memory, and clawdbook (Moltbook) data.",
5
+ "author": "",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "clawd-migrate": "bin/clawd-migrate.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "lib"
13
+ ],
14
+ "scripts": {
15
+ "prepublishOnly": "node scripts/copy-py.js",
16
+ "test": "node scripts/run-tests.js"
17
+ },
18
+ "keywords": [
19
+ "openclaw",
20
+ "moltbot",
21
+ "clawdbot",
22
+ "migration",
23
+ "clawdbook",
24
+ "moltbook"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": ""
29
+ },
30
+ "engines": {
31
+ "node": ">=14.0.0"
32
+ }
33
+ }