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
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.
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.
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.
Mirrors Tencent's migration-tool compatibility report: 🔵 Blue no change, 🟢 Green auto-converted, 🔴 Red preserved verbatim + tmb-uncertain, never force-converted.
After transform a hard gate runs (residual source-SDK scan / sqlfluff / pglast); on failure a tool-using agent loop self-repairs until verification passes.
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.
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[].
Engine, knowledge, and recipes are separated — adding a migration capability is just a new SKILL.md, no code change.
Assess → Plan → Transform → Heal → Report pipeline, shared by every recipe; additive / structural recipes generate net-new files via 04b-create.md.
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.
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:
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.
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
② Create an API key
/api/keys) and click New key — an optional note name (e.g. tmb-prod) helps.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
/v1. tmb appends /chat/completions and /models automatically — stop at /v1, don't include /chat/completions.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).tmb config use <name> sets endpoint+model only and preserves the key — switch between official and self-built TokenHub without re-entering credentials.tmb plan first to gauge scale before a large run./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.creates[] / services[], the Agent self-heal loop — all behave exactly the same on every backend.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.
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.
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 id | Languages | Target |
|---|---|---|
aws-sdk-to-tencent | java · python · cpp · go · javascript · csharp · php · ruby | Tencent Cloud SDK + TencentDB |
azure-sdk-to-tencent | java · python · cpp · go · javascript · csharp · php · ruby | Tencent Cloud SDK + TencentDB |
gcp-sdk-to-tencent | java · python · cpp · go · javascript · csharp · php · ruby | Tencent Cloud SDK + TencentDB |
aliyun-sdk-to-tencent | java · python · cpp · go · javascript · csharp · php · ruby | Tencent Cloud SDK + TencentDB |
onprem-to-tencent | java · python · cpp · go · javascript · csharp · php · ruby | Tencent Cloud managed services + TencentDB |
lang-modernize | java · python · cpp · go · javascript · csharp · php · ruby | Newer language version (same language) |
oracle-to-tdsql-pg-ora | sql | TDSQL for PostgreSQL (Oracle-compat mode) |
oracle-to-tdsql-pg-pure | sql | TDSQL for PostgreSQL (PG mode) / community PG |
oracle-to-tdsql-c | sql | TDSQL-C PostgreSQL |
sqlserver-to-tdsql-pg | sql | TDSQL for PostgreSQL |
mysql-to-tdsql-pg | sql | TDSQL for PostgreSQL |
mysql-to-tencentdb-mysql | sql | TencentDB for MySQL / TDSQL for MySQL |
sql-modernize-tencent | sql | Same TencentDB dialect modernization |
containerize-tencent | java · python · cpp · go · javascript · csharp · php · ruby | Tencent TKE + TCR (containerize & deploy, 12-factor) |
monolith-to-microservices | java · python · cpp · go · javascript · csharp · php · ruby | Tencent TKE microservices (split by bounded context) |
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 situation | Recipe |
|---|---|
| app code on AWS / Azure / GCP / Alibaba SDKs | aws-sdk-to-tencent / azure-… / gcp-… / aliyun-… |
| self-hosted MySQL / Redis / Kafka / ES / MinIO / MQ | onprem-to-tencent |
| Oracle / SQL Server / MySQL stored procs | oracle-to-tdsql-pg-ora\| -pure\| -c / sqlserver-to-tdsql-pg / mysql-to-tdsql-pg\| -tencentdb-mysql |
| same dialect, modernize SQL | sql-modernize-tencent |
| language version upgrade (no cloud) | lang-modernize |
| package as a container on TKE | containerize-tencent |
| split a monolith into services | monolith-to-microservices |
# 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
# 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
# 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
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
# 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/"
# 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
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
Mirrors Tencent's migration-tool compatibility report — never force-converts; leaves the manual work to humans.
Fully compatible; no change.
Auto-converted by tmb.
Manual: preserved verbatim + tmb-uncertain, never force-converted.
--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.
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.
--jobs N parallelizes the per-file transform across N workers (each an independent LLM call writing to a distinct path).
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.
The loop is capped by --max-steps and its transcript is checkpointed; tmb resume --job <ts> continues a paused / cap-hit run.
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.
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.
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.
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
bin/tmb through a full run (assess→plan→transform→verify→report→restore), test_creates.py (the additive creates[] pipeline, base_guide injection, and the containerize / microservice verify gates), test_facts.py (the deterministic facts layer across all 8 languages + symbol-level complexity hotspots), test_trace.py (the traceability matrix), test_astsvc.py (the server-analysis client: endpoint resolution incl. the localhost auto-probe, symbols pass-through, shape validation, and the total-fallback contract), and test_parity.py (the parity guard: when the local service is up, it asserts the server tier and the built-in regex layer produce semantically identical facts dicts — the silent fallback never changes results). All LLM calls mocked; the Go service ships its own go test ./... extraction fixtures.java-springboot → containerize-tencent, java-monolith → monolith-to-microservices). Runs tmb run against the real gateway concurrently and asserts deliverables, plan recipe, the gate, and source-marker-gone / target-marker-present in out/.