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 +92 -0
- package/bin/clawd-migrate.js +26 -0
- package/lib/clawd_migrate/Documentation/GITHUB.md +75 -0
- package/lib/clawd_migrate/Documentation/MIGRATION_SOURCES.md +48 -0
- package/lib/clawd_migrate/Documentation/MIGRATION_TUI.md +51 -0
- package/lib/clawd_migrate/Documentation/NPM_PUBLISH.md +51 -0
- package/lib/clawd_migrate/Documentation/NPM_WALKTHROUGH.md +130 -0
- package/lib/clawd_migrate/Documentation/TESTS.md +43 -0
- package/lib/clawd_migrate/__init__.py +18 -0
- package/lib/clawd_migrate/__main__.py +101 -0
- package/lib/clawd_migrate/__pycache__/__init__.cpython-311.pyc +0 -0
- package/lib/clawd_migrate/__pycache__/__main__.cpython-311.pyc +0 -0
- package/lib/clawd_migrate/__pycache__/backup.cpython-311.pyc +0 -0
- package/lib/clawd_migrate/__pycache__/config.cpython-311.pyc +0 -0
- package/lib/clawd_migrate/__pycache__/discover.cpython-311.pyc +0 -0
- package/lib/clawd_migrate/__pycache__/migrate.cpython-311.pyc +0 -0
- package/lib/clawd_migrate/__pycache__/tui.cpython-311.pyc +0 -0
- package/lib/clawd_migrate/backup.py +66 -0
- package/lib/clawd_migrate/config.py +48 -0
- package/lib/clawd_migrate/discover.py +69 -0
- package/lib/clawd_migrate/migrate.py +106 -0
- package/lib/clawd_migrate/tui.py +173 -0
- package/package.json +33 -0
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())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
}
|