中文EN
Python 3.8+ · zero deps LLM-driven Tencent Cloud TokenHub compatible Migration + Modernization Local CLI · Open Source

A CLI to migrate & modernize onto Tencent Cloud

tmb uses LLMs to migrate and modernize application code (Java / Python / C++ / Go / JavaScript / C# / PHP / Ruby; Kotlin & Scala via the Java guide) and SQL / stored procedures onto Tencent Cloud: code → Tencent Cloud SDK + TencentDB (cloud-native modernization), SQL → TDSQL for PostgreSQL / TDSQL-C / TencentDB for MySQL.

It also handles packaging & architecture: containerize-tencent wraps an app (or each split service) into a container + Tencent TKE deployment — TCR images, 12-factor config externalization, health probes, stateless / graceful-shutdown / observability, emitting brand-new Dockerfile / compose / k8s manifests; monolith-to-microservices decomposes a monolith into bounded-context services — HTTP/gRPC/CKafka between them, per-service data, API contracts. Both are additive / structural (net-new files via the plan's creates[]).

curl -fsSL https://tmb.slowpapa.com/install.sh | sh

01What it does

One command migrates and modernizes a legacy project from another cloud / database / on-prem middleware onto Tencent Cloud — not just lifting code, but re-platforming to cloud-native (Tencent Cloud SDK + managed services + cloud databases). Produces a reviewable migration plan, a transformed code tree, a test plan, and an execution report.

{ }

App code migration & modernization

AWS / Azure / GCP / Alibaba Cloud SDK → Tencent Cloud SDK + TencentDB; on-prem middleware (MySQL/Redis/Kafka/ES/Celery/MinIO/RabbitMQ/MongoDB) → Tencent Cloud managed services (re-platforming); same-language version modernization.

SQL

SQL / stored-proc migration & modernization

Oracle / SQL Server / MySQL → TDSQL for PostgreSQL (Oracle-compat / pure-PG mode) / TDSQL-C / TencentDB for MySQL, incl. PL/SQL → PL/pgSQL and same-dialect modernization.

⚖

Blue / Green / Red grading

Mirrors Tencent's migration-tool compatibility report: 🔵 Blue no change, 🟢 Green auto-converted, 🔴 Red preserved verbatim + tmb-uncertain, never force-converted.

↻

Self-heal + Agent mode

After transform a hard gate runs (residual source-SDK scan / sqlfluff / pglast); on failure a tool-using agent loop self-repairs until verification passes.

≡

Deterministic facts + traceability

Before any LLM call, the facts are computed across all 8 languages: import graph (fan-in/fan-out), cyclomatic complexity, entry points, dead-code and duplicate candidates — the LLM grades from facts, never guesses. After execution, a per-file traceability matrix audits plan coverage vs. actual outcome, unaccounted files target 0. All code analysis runs on server/tmb-ast (Go + tree-sitter, 12 grammars): one /analyze call returns the complete facts dict, auto-probed zero-config on a local deploy, with silent fallback to the built-in regex layer (line-for-line same semantics) when unreachable.

📦

Packaging & architecture

containerize-tencent: containerize + deploy to Tencent TKE (TCR images, 12-factor config externalization, health probes, stateless / graceful-shutdown / observability), emitting new Dockerfile / compose / k8s manifests; monolith-to-microservices: split a monolith into bounded-context microservices (HTTP/gRPC/CKafka between services, per-service data, API contracts). Both are additive / structural, producing net-new files via the plan's creates[].

02Three layers, no duplication

Engine, knowledge, and recipes are separated — adding a migration capability is just a new SKILL.md, no code change.

Engine prompts/00–06 +04b

Assess → Plan → Transform → Heal → Report pipeline, shared by every recipe; additive / structural recipes generate net-new files via 04b-create.md.

Knowledge domains/

Source→target mapping tables and Tencent Cloud SDK usage references, injected per file language; plus container.md / microservice.md cross-cutting base guides, always injected for the containerize / decomposition recipes.

Skills skills/<id>/

Thin recipe manifests (target / intensity / scope / red-flags) — the unit a user invokes.

How a run composes:

# system = prompts/00-system.md
# user   = prompts/0N-<phase>.md
#           {{DOMAIN_GUIDE}} <- domains/<x>.md      (from the skill)
#           {{FACTS}}         <- local inventory / file content
#           {{CHANGE_SPEC}}   <- plan.json

The four-phase pipeline:

Assess inventory → overview.md Plan → plan.json + plan.md + test-plan.md Transform → out/ migrated tree (+creates[] new files) Heal → hard gate + agent repair Report → report.md

03Install

Zero external dependencies (Python 3.8+ stdlib only). One line installs the tmb command:

# one-shot install (downloads the source tree + symlinks the command)
curl -fsSL https://tmb.slowpapa.com/install.sh | sh

# install as a proper Python package (optional; global tmb + tab-completion)
pip install -e ~/.tmb-cli/tmb-cli

Or clone and run in place:

git clone https://github.com/awaili/tmb-cli
cd tmb-cli
./bin/tmb --help

Configure (point tmb at an LLM backend) — pick one:

# A) Tencent Cloud TokenHub (recommended; zero local setup, OpenAI-compatible)
tmb config use tokenhub            # Guangzhou; use tokenhub-intl for Singapore
tmb config set api_key <TokenHub-API-Key>

# B) a local Ollama-gateway (default)
tmb config use local
tmb config set api_key <key-or-dummy>

# C) self-built TokenHub / any OpenAI-compatible gateway (no code change)
tmb config set endpoint https://<your-tokenhub>/v1   # e.g. https://th.quickpapa.com/v1
tmb config set api_key  <your-API-Key>
tmb config set model    <model id>                     # a model actually deployed on your TokenHub
# config lives in ~/.tmb/config.json; env overrides: TMB_ENDPOINT / TMB_MODEL / TMB_API_KEY

Tencent Cloud TokenHub model ids: glm-5.3 · deepseek-v4-pro · kimi-k3 · qwen3.5-plus · minimax-m3 · hy3 (tmb config set model <id>). Get an API Key at the TokenHub console.

How do I connect a self-built TokenHub / my own OpenAI-compatible gateway? (walkthrough: th.quickpapa.com, from sign-up to first run)

tmb's engine (tmb/llm.py) speaks only the OpenAI Chat Completions protocol — it appends /chat/completions to endpoint and adds an Authorization: Bearer <key> header. There is no Tencent-official-TokenHub-specific logic, so any OpenAI-compatible gateway works out of the box. Below: from zero to a working run against th.quickpapa.com (a self-hosted OpenAI/Claude dual-protocol LLM relay).

① Register an account

  1. Open https://th.quickpapa.com/register and enter email + password (min 8 chars).
  2. A verification email is sent; click the link (valid for 24h) to activate.
  3. After verifying, a ¥10 trial credit is auto-credited and usable immediately.
  4. Existing accounts log in at /login; "Forgot password?" for resets.

② Create an API key

  1. After login, open the Keys / Tokens tab (/api/keys) and click New key — an optional note name (e.g. tmb-prod) helps.
  2. The full key (sk-…) is shown only once at creation — copy it immediately; the list later shows only a prefix. You can revoke anytime.

③ Point tmb at this gateway

# persist to config (recommended)
tmb config set endpoint https://th.quickpapa.com/v1
tmb config set api_key  sk-xxxxxxxx                # the key from step 2
tmb config set model    tokenhub-pro               # see model ids below; also glm-5.3 / deepseek-v4.1-flash / kimi-k3 / qwen3.5 / minimax-m3
tmb config                                         # confirm
tmb models                                         # list gateway models; verifies connectivity

# ad-hoc (env vars, not persisted):
#   export TMB_ENDPOINT=https://th.quickpapa.com/v1  TMB_API_KEY=sk-…  TMB_MODEL=tokenhub-pro

# team-wide: add an entry to PRESETS in tmb/config.py, then switch with one command
#   tmb config use tokenhub-self   # preset sets endpoint+model only; api_key is not overwritten

Quick self-check (without tmb): curl -H "Authorization: Bearer sk-…" https://th.quickpapa.com/v1/models — if it lists models, you're connected.

④ Notes

  • endpoint must include /v1. tmb appends /chat/completions and /models automatically — stop at /v1, don't include /chat/completions.
  • model is whatever the gateway actually serves, not Tencent's official TokenHub name. List with tmb models or the curl above (e.g. tokenhub-pro, glm-5.3, deepseek-v4.1-flash, kimi-k3, qwen3.5, minimax-m3, claude-sonnet-4-5, gpt-5).
  • api_key is never overwritten by a preset. tmb config use <name> sets endpoint+model only and preserves the key — switch between official and self-built TokenHub without re-entering credentials.
  • Billing: this relay bills per token with peak (09:00–18:00) / off-peak tiers; cache hits billed at cache rate, up to 90% off input cost; failed requests are not charged. Trial credit is limited — run tmb plan first to gauge scale before a large run.
  • Same gateway also feeds Claude Code / Codex: the relay speaks Anthropic (/v1/messages + x-api-key) and OpenAI Responses (/v1/responses) protocols too — set ANTHROPIC_BASE_URL=https://th.quickpapa.com + ANTHROPIC_API_KEY for Claude Code. tmb uses only the OpenAI path.
  • Identical capabilities: blue/green/red grading, verify gates, creates[] / services[], the Agent self-heal loop — all behave exactly the same on every backend.

04CLI commands

tmb models                              # list gateway models
tmb skills list                         # list installed recipes
tmb skills show <id>                    # show a recipe manifest
tmb assess  [PATH] -r <skill> [-o DIR]  # produce overview.md
tmb plan    [PATH] -r <skill> [-o DIR]  # plan.json + plan.md + test-plan.md
tmb transform [PATH] [--plan F|--job TS] [--apply] [--heal-rounds N] [--build CMD] [--test CMD]
tmb run     [PATH] -r <skill> [flags]   # assess + plan + transform + report in one shot
tmb resume  [--job TS] [--max-steps N]  # resume an agentic fix loop
tmb report  [--job TS]                  # (re)generate execution report
tmb restore [--job TS]                  # roll back source from a job backup (--apply jobs)
tmb config  get|set|use <k> <v>         # endpoint / model / api_key; use local|tokenhub|tokenhub-intl switches backend

-r/--recipe takes a recipe id (e.g. aws-sdk-to-tencent, containerize-tencent, monolith-to-microservices); transform/run flags: --apply (write back to source, prompts unless --yes), --yes (skip that prompt), --model (override model), --heal-rounds N, --agent (tool-using agent fix loop), --max-steps N, --build CMD, --test CMD, --jobs N (per-file parallelism), --mem-threshold / --mem-keep (agent memory compression).

Containerize / decompose (additive / structural recipes) — same invocation, but out/ also gains brand-new creates[] files absent from source:

# containerize a service + emit TKE manifests (Dockerfile / docker-compose / k8s/*.yaml)
tmb run ./my-svc -r containerize-tencent
tmb plan  ./my-svc -r containerize-tencent        # plan only; inspect creates[] first
tmb transform ./my-svc --apply --yes              # write Dockerfile + k8s/ into source

# decompose a monolith (services[] map + per-service modules + contracts)
tmb run ./monolith -r monolith-to-microservices

# pair them: split first, then containerize each service
tmb run ./monolith -r monolith-to-microservices --apply --yes
tmb run ./monolith/services/order-svc -r containerize-tencent

For these recipes --build / --test validate the new artifacts (not mvn / sqlfluff): --build "docker build -t verify .", --test "kubectl apply --dry-run=client -f k8s/". Structural gate: containerize checks Dockerfile + Deployment+Service + TCR image + no literal secrets; microservice checks ≥2 self-contained services + no cross-service in-process import.

Prompt samples — what the engine actually sends the LLM (click to expand)

Every call = shared system (00-system.md) + one phase user prompt. The Transform phase has two user templates:

04-transform.md  -> items[]   rewrite an EXISTING source file (carries {{FILE_CONTENT}}, edits in place)
04b-create.md     -> creates[] generate a BRAND-NEW file (no original, built from CHANGE_SPEC)

Ex 1 — generate a Dockerfile (containerize-tencent · kind=containerfile)

File: Dockerfile   Kind: containerfile
CHANGE_SPEC: Multi-stage: builder FROM maven:3.9-eclipse-temurin-21
  mvn -q -o package -DskipTests; runtime FROM eclipse-temurin:21-jre
  COPY target/*.jar; ENTRYPOINT java -jar; EXPOSE 8080;
  HEALTHCHECK /actuator/health; USER 1000
GLOBAL_CONVENTIONS: images -> TCR; credentials via Secret/env only, never literal;
  /actuator/health probes; 12-factor (PORT/DB_URL externalized)
DOMAIN_GUIDE: domains/container.md (base only)
-> outputs the full Dockerfile, no fences/commentary

Ex 2 — generate an OpenAPI contract (monolith-to-microservices · kind=contract)

File: contracts/order-svc.openapi.yaml   Kind: contract
CHANGE_SPEC: OpenAPI 3: POST /orders, GET /orders/{id};
  schemas; auth; idempotency-key header
GLOBAL_CONVENTIONS: each service self-contained; no in-process import across services;
  per-service DB/schema; shared code in common-lib
DOMAIN_GUIDE: domains/microservice.md (base only)

Contrast — rewriting an existing OrderService.java (billingService.charge() -> HTTP to billing-svc /charge) goes through 04-transform.md, which additionally injects {{FILE_CONTENT}} (the original) so it edits in place rather than generating from scratch.

0515 migration recipes

Each recipe is a skills/<id>/SKILL.md manifest. Adding a capability is mkdir skills/<new-id> — no code change. containerize-tencent and monolith-to-microservices are additive / structural recipes that emit brand-new Dockerfile / compose / k8s manifests and extracted service modules via the plan's creates[].

Recipe idLanguagesTarget
aws-sdk-to-tencentjava · python · cpp · go · javascript · csharp · php · rubyTencent Cloud SDK + TencentDB
azure-sdk-to-tencentjava · python · cpp · go · javascript · csharp · php · rubyTencent Cloud SDK + TencentDB
gcp-sdk-to-tencentjava · python · cpp · go · javascript · csharp · php · rubyTencent Cloud SDK + TencentDB
aliyun-sdk-to-tencentjava · python · cpp · go · javascript · csharp · php · rubyTencent Cloud SDK + TencentDB
onprem-to-tencentjava · python · cpp · go · javascript · csharp · php · rubyTencent Cloud managed services + TencentDB
lang-modernizejava · python · cpp · go · javascript · csharp · php · rubyNewer language version (same language)
oracle-to-tdsql-pg-orasqlTDSQL for PostgreSQL (Oracle-compat mode)
oracle-to-tdsql-pg-puresqlTDSQL for PostgreSQL (PG mode) / community PG
oracle-to-tdsql-csqlTDSQL-C PostgreSQL
sqlserver-to-tdsql-pgsqlTDSQL for PostgreSQL
mysql-to-tdsql-pgsqlTDSQL for PostgreSQL
mysql-to-tencentdb-mysqlsqlTencentDB for MySQL / TDSQL for MySQL
sql-modernize-tencentsqlSame TencentDB dialect modernization
containerize-tencentjava · python · cpp · go · javascript · csharp · php · rubyTencent TKE + TCR (containerize & deploy, 12-factor)
monolith-to-microservicesjava · python · cpp · go · javascript · csharp · php · rubyTencent TKE microservices (split by bounded context)

06Scenarios: which recipe do I use?

Real go-to-cloud situations rarely match a recipe name verbatim. Worked examples with real fixtures (runnable under test/): starting code → which recipe → exact commands → what the output looks like.

Your situationRecipe
app code on AWS / Azure / GCP / Alibaba SDKsaws-sdk-to-tencent / azure-… / gcp-… / aliyun-…
self-hosted MySQL / Redis / Kafka / ES / MinIO / MQonprem-to-tencent
Oracle / SQL Server / MySQL stored procsoracle-to-tdsql-pg-ora\| -pure\| -c / sqlserver-to-tdsql-pg / mysql-to-tdsql-pg\| -tencentdb-mysql
same dialect, modernize SQLsql-modernize-tencent
language version upgrade (no cloud)lang-modernize
package as a container on TKEcontainerize-tencent
split a monolith into servicesmonolith-to-microservices
Ex 1 — AWS Python (S3+SQS+DynamoDB) → Tencent Cloud · aws-sdk-to-tencent (expand)
# before: test/py-aws-multi/app.py
import boto3
s3  = boto3.client("s3",       region_name="us-east-1")
sqs = boto3.client("sqs",      region_name="us-east-1")
dyn = boto3.client("dynamodb", region_name="us-east-1")

Source cloud = AWS, app code → aws-sdk-to-tencent. S3→COS, SQS→CKafka/CMQ, DynamoDB→TencentDB. The DB is NoSQL, so no SQL-dialect migration — one recipe covers it. Gate: residual boto3 = 0.

tmb run ./py-aws-multi -r aws-sdk-to-tencent
Ex 2 — AWS Java (S3) + Oracle DB → Tencent Cloud (composite, two steps) · expand
# before: test/java-aws-s3/S3Uploader.java
static AmazonS3 s3 = AmazonS3ClientBuilder.standard().withRegion("us-east-1").build();
Class.forName("oracle.jdbc.OracleDriver");
DriverManager.getConnection("jdbc:oracle:thin:@dbhost:1521:ORCL", "u", "p");

Key: the app-SDK swap (S3→COS) is aws-sdk-to-tencent; but the Oracle DB is a SQL migration, out of that recipe's scope. Embedded SQL is converted in-app; stored procs / packages need a separate oracle-to-tdsql-pg-ora pass. Different DB dialect → split into an app pass + a SQL pass.

tmb run ./java-aws-s3 -r aws-sdk-to-tencent      # app + embedded SQL
tmb run ./db        -r oracle-to-tdsql-pg-ora    # PL/SQL packages
Ex 3 — On-prem middleware Python → managed services · onprem-to-tencent · expand
# before: test/onprem-py-app/app.py
DB_URL = "mysql+pymysql://app:secret@127.0.0.1:3306/orders"
r = redis.Redis(host="localhost", port=6379, db=0)
producer = KafkaProducer(bootstrap_servers="localhost:9092")
minio_client = Minio("localhost:9000", access_key="miniokey", secret_key="miniosecret")
celery_app = Celery("worker", broker="redis://localhost:6379/1", ...)

Self-hosted middleware → managed → onprem-to-tencent. MySQL→TencentDB for MySQL, Redis→TencentDB for Redis, Kafka→CKafka, ES→Tencent Cloud ES, MinIO→COS, Celery kept (broker→Redis/CKafka). Most data-plane libs stay; configs retarget; MinIO→COS is an SDK swap. Gate: no literal 127.0.0.1 / localhost.

tmb run ./onprem-py-app -r onprem-to-tencent
Ex 4 — Oracle PL/SQL package → TDSQL PG (pick the variant) · expand

Same Oracle, different targets: TDSQL PG Oracle-compat mode → oracle-to-tdsql-pg-ora (light, most PL/SQL kept); pure PG / community PG → oracle-to-tdsql-pg-pure (strict PL/pgSQL); TDSQL-C → oracle-to-tdsql-c. UTL_FILE & other non-portable bits → red, preserved + tmb-uncertain. Gate: sqlfluff + pglast.

tmb run ./db -r oracle-to-tdsql-pg-ora
Ex 5 — Spring Boot (VM) → container on TKE · containerize-tencent · expand
# before: test/java-springboot/application.yml
server:
  port: 8080
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/orders
    password: changeme

This is packaging, not an SDK migration → containerize-tencent. Emits brand-new creates[]: multi-stage Dockerfile, k8s/deployment.yaml + service.yaml, Secret; and externalizes config (port→${PORT:8080}, url→${DB_URL}, password→${DB_PASS}). Image→TCR, probes hit /actuator/health. --build/--test validate the new artifacts.

tmb run ./java-springboot -r containerize-tencent \
  --build "docker build -t v ." --test "kubectl apply --dry-run=client -f k8s/"
Ex 6 — Monolith → microservices, then containerize each · monolith-to-microservices · expand
# before: test/java-monolith/OrderService.java — in-process cross-boundary calls
private final BillingService billingService = new BillingService();
private final InventoryService inventoryService = new InventoryService();
public String placeOrder(String order) {
    inventoryService.check(order);
    billingService.charge(order);
    return "ok";
}

Structural refactor → monolith-to-microservices (split along orders/billing/inventory bounded contexts). creates[]: per-service pom + entrypoint, OpenAPI contracts, common-lib; items[]: billingService.charge() → HTTP to billing-svc /charge (timeout/retry/idempotency); services[] is the decomposition map. A cross-context @Transactional → red (saga/outbox). Then containerize each service.

tmb run ./java-monolith -r monolith-to-microservices --apply --yes
tmb run ./java-monolith/services/order-svc -r containerize-tencent
Ex 7 — Composite: Java 8 on AWS VM → Java 21 + Tencent + TKE · three steps · expand

Three passes, each consuming the previous output: 1. lang-modernize (javax→jakarta, 8→21) 2. aws-sdk-to-tencent (SDK→Tencent) 3. containerize-tencent (package on TKE).

tmb run ./app -r lang-modernize        --apply --yes   # 1. language
tmb run ./app -r aws-sdk-to-tencent   --apply --yes   # 2. cloud
tmb run ./app -r containerize-tencent --apply --yes   # 3. package

07Blue / Green / Red grading

Mirrors Tencent's migration-tool compatibility report — never force-converts; leaves the manual work to humans.

🔵

Blue

Fully compatible; no change.

🟢

Green

Auto-converted by tmb.

🔴

Red

Manual: preserved verbatim + tmb-uncertain, never force-converted.

08Agent mode: a tool-using self-heal loop

--agent turns the fix phase into a real tool-using agent loop: the model is given tools — read_file / write_file / list_dir / grep / run_build / run_verify / read_plan / spawn_subagent — and drives observe → act → observe until run_verify (and run_build / run_test if configured) pass, then stops.

Sub-agent delegation

The model can call spawn_subagent(task) to spawn a depth-limited (1 level) child agent on an isolated subtask; it returns its summary to the parent.

Parallel fan-out

--jobs N parallelizes the per-file transform across N workers (each an independent LLM call writing to a distinct path).

Memory compression

Above --mem-threshold, older complete turns are LLM-summarized into one compact block; the system prompt and goal are never compressed. Transcript in agent_transcript.json; tmb resume rehydrates from the compacted state.

Resume

The loop is capped by --max-steps and its transcript is checkpointed; tmb resume --job <ts> continues a paused / cap-hit run.

09Safety

Default writes to out/ tree

Transforms land in .tmb/out/<job>/out/ (a full mirror); --apply writes back to source only after backing up to .tmb/backup/<ts>/. tmb restore rolls back in one command.

Agent writes only out/

In --agent mode write_file writes only to out/, never source directly; run_build runs the given shell command inside out/ — review the command before running untrusted builds.

Hard gate

Residual source-SDK scan is a hard gate: code output must contain zero boto3/aws-sdk/oss2/aliyun-sdk/google-cloud/azure-sdk/oracle.jdbc references; SQL runs sqlfluff/pglast/psql checks. CLI exits 2 on gate failure.

10Tests

Two layers, both offline-friendly.

python3 -m pytest            # 396/396 unit tests, no gateway needed
python3 test/run_tests.py    # 30/30 real-gateway E2E demos