skills/odoorpc-agent-skill/SKILL.md
Use proactively for Odoo work. Connect AI coding agents to Odoo through Python OdooRPC profiles, inspect live records, troubleshoot models, fields, access rights, modules, companies, connectors, imports, synchronization issues, odoo.conf, addons paths, backup restore, filestore checks, and guarded create/update/delete workflows.
npx skillsauth add imhansiy/my-skills odoorpc-agent-skillInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill gives an AI coding agent a safe, reusable workflow for connecting to Odoo with Python and OdooRPC.
It is intentionally not tied to Claude Code. Any agent that can read SKILL.md and run local scripts can use it: Claude Code, OpenCode, Codex, Manus, Cursor-like agents, local terminal agents, or other Agent Skills-compatible clients.
The skill is meant to be used actively. If the user asks about Odoo records, Odoo fields, Odoo business documents, Odoo connector data, Odoo settings, Odoo errors, Odoo version differences, or Odoo database state, do not treat this as a generic explanation task only. First consider whether a configured Odoo profile can answer the question with real data.
Use this skill whenever the request is about Odoo and one of these is true:
odoo.conf diagnosis, database selection, addons path detection, module documentation, module manifest work, Odoo.sh backup restore, filestore recovery, or admin/account repair.Do not wait for the user to explicitly say “use OdooRPC”. If an Odoo profile exists and the user asks a database-specific Odoo question, use read-only commands to inspect the live system before answering, unless the user explicitly says not to connect.
Treat these as strong signals to use or consider this skill:
Odoo, Odoo.sh, Odoo Online, partner, customer, vendor, contact, lead, opportunity, quotation, sale order, purchase order, invoice, bill, payment, journal entry, product, variant, stock, inventory, picking, delivery, receipt, warehouse, route, lot, serial, project, task, user, company, access rights, record rule, module, connector, import, sync, XML-RPC, JSON-RPC, odoorpc.客户, 联系人, 供应商, 报价单, 销售订单, 采购订单, 发票, 账单, 付款, 会计凭证, 产品, 规格, 变体, 库存, 调拨, 出库, 入库, 仓库, 批次, 序列号, 项目, 任务, 用户, 公司, 多公司, 权限, 记录规则, 模块, 连接器, 导入, 同步, 接口, 字段, 模型, 数据库, 模块介绍页, 恢复备份, 附件, 文件存储, 管理员密码, 登录密码.| User intent | Agent behavior |
|---|---|
| “查一下 / 看一下 / 为什么显示这样” | Use read-only commands if a profile exists. Inspect model, fields, and sample records before explaining. |
| “这个字段叫什么 / 这个模型有什么字段” | Use fields against the relevant model. Do not guess if the database is reachable. |
| “有多少 / 哪些记录 / 列出” | Use count or search-read with a small --limit. |
| “帮我建一个记录” | Prepare a dry-run create; execute only with explicit user instruction and --confirm CREATE. |
| “帮我改成 / 修一下数据” | Search/read exact targets, show IDs and before snapshot, then dry-run update. Execute only with explicit instruction and --confirm UPDATE. |
| “删掉 / 清理掉” | Prefer archive/deactivate when possible. Delete only after exact IDs, before snapshot, explicit instruction, --execute, and --confirm DELETE. |
| “连接这个 Odoo” | Save or update a named profile in ~/.config/odoorpc/config.yaml, then test login and detect version. |
| “以后都用这个库 / 这个客户系统” | Set the profile as default only if the user implies it should become the default. |
| “看一下 odoo.conf / 当前库 / addons 路径” | Inspect the active config file, distinguish active values from commented examples, and report database/addons/data_dir clearly. |
| “重置 admin 登录密码” | First distinguish Odoo login password from admin_passwd and PostgreSQL password; prefer odoo-bin shell + ORM. |
| “生成模块介绍页 / README 转 description” | Confirm the target is an Odoo module, inspect __manifest__.py, and create or update static/description/index.html. |
| “恢复 Odoo.sh 备份” | Identify backup type, confirm target database and filestore/data_dir, prefer safe restore paths, and warn about non-neutralized backups. |
Use the bundled scripts first. Do not write ad-hoc OdooRPC code unless the scripts are insufficient.
Preferred command style from the skill directory:
uv run scripts/odoo_config.py --help
uv run scripts/odoo_query.py --help
uv run scripts/odoo_mutate.py --help
Fallback when uv is unavailable:
python3 -m pip install --user "odoorpc>=0.10,<1" "PyYAML>=6,<7"
python3 scripts/odoo_config.py --help
python3 scripts/odoo_query.py --help
python3 scripts/odoo_mutate.py --help
All examples in this skill use bash syntax. On Windows PowerShell, apply these rules:
No heredoc <<<. Use --password directly instead of --password-stdin with <<<:
# bash (does NOT work in PowerShell)
printf '%s' 'SECRET' | uv run scripts/odoo_config.py set-profile --password-stdin ...
# PowerShell (correct)
uv run scripts/odoo_config.py set-profile ... --password 'SECRET'
--profile goes BEFORE the subcommand, not after:
# WRONG — PowerShell treats --profile as unrecognized
uv run scripts/odoo_query.py test --profile winston-test
# CORRECT
uv run scripts/odoo_query.py --profile winston-test test
Multi-line backslash \ does not work. Use a single long line or PowerShell backtick ` for line continuation.
Single quotes in passwords. If the password contains special characters (e.g. &, !), wrap it in single quotes '...' in PowerShell. Double quotes may trigger variable expansion.
Connection profiles are stored under the user directory:
~/.config/odoorpc/config.yaml
For compatibility with common typos, the scripts also detect ~/.config/odoorpc/config.ymal; however, agents should create and update config.yaml as the canonical file.
The config file supports multiple Odoo connections:
default_profile: local
profiles:
local:
host: 127.0.0.1
port: 8069
protocol: jsonrpc
database: odoo
username: admin
password: admin
timeout: 30
odoo_version: "19.0"
production:
host: odoo.example.com
port: 443
protocol: jsonrpc+ssl
database: prod_db
username: [email protected]
password: "paste-api-key-or-password-here"
timeout: 30
odoo_version: "19.0"
Supported protocols usually include jsonrpc, jsonrpc+ssl, xmlrpc, and xmlrpc+ssl.
odoo_version is optional but strongly recommended. Store the major/minor Odoo version as a string such as "16.0", "17.0", "18.0", or "19.0". Agents should use it to choose version-aware model fields, workflows, and documentation assumptions. If the user provides the Odoo version with their connection details, save it in the profile. If the user does not know the version, connect once and run scripts/odoo_config.py detect-version --profile <name> --save.
When the user provides Odoo connection details and asks the agent to connect, save them automatically into a named profile unless the user explicitly says not to save.
Use scripts/odoo_config.py set-profile; never manually echo secrets into shell history when avoidable. Prefer --password-stdin for passwords/API keys:
printf '%s' 'SECRET_VALUE' | uv run scripts/odoo_config.py set-profile \
--profile customer-dev \
--host odoo.example.com \
--port 443 \
--protocol jsonrpc+ssl \
--database customer_db \
--username [email protected] \
--password-stdin \
--odoo-version 19.0 \
--set-default
If the user provides multiple Odoo systems, ask or infer a clear profile name such as local, dev, staging, production, customer-a-prod, or nordic-match-prod. If the user does not provide a name, create a short descriptive one from the host and purpose.
If the user did not provide the Odoo version, detect and save it after the profile is created:
uv run scripts/odoo_config.py detect-version --profile customer-dev --save
The config script sets restrictive permissions where supported:
~/.config/odoorpc: 0700~/.config/odoorpc/config.yaml: 0600Odoo.sh and Odoo Online instances use auto-generated database names that do NOT match the subdomain. For example, calipokehouse-test.odoo.com might have database shanghaimeowai-winston1-dev-31537573.
Do not guess the database name. Instead, query the /web/database/list endpoint:
import requests
r = requests.post(
'https://<HOST>/web/database/list',
json={'jsonrpc': '2.0', 'method': 'call', 'params': {}},
timeout=15
)
print(r.json().get('result'))
# Returns: ["shanghaimeowai-winston1-dev-31537573"]
Or with curl:
curl -s -X POST 'https://<HOST>/web/database/list' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"call","params":{}}'
Then use the returned database name in the profile:
uv run scripts/odoo_config.py set-profile \
--profile winston-test \
--host calipokehouse-test.odoo.com \
--port 443 \
--protocol jsonrpc+ssl \
--database shanghaimeowai-winston1-dev-31537573 \
--username admin \
--password 'actual-password' \
--odoo-version 19.0
Typical Odoo.sh database name format: <org>-<project>-<branch>-<random_digits>
If /web/database/list returns an empty list or is blocked, the instance may have dbfilter configured or list_db = False. In that case, ask the user for the database name directly.
scripts/odoo_config.py — create/list/show/remove local Odoo connection profiles and detect/save Odoo server versions.scripts/odoo_query.py — read-only Odoo operations: test login, inspect fields, search/read, count, and method calls marked as read-only.scripts/odoo_mutate.py — guarded create/update/delete operations with dry-run defaults, snapshots, confirmation phrases, deny rules, and JSON output.scripts/odoo_common.py — shared helper library used by the above scripts.All scripts output JSON on stdout and diagnostics on stderr. Prefer --limit to keep output small.
Use this workflow for inspection, troubleshooting, and reporting.
uv run scripts/odoo_config.py list
uv run scripts/odoo_query.py --profile local test
Important: --profile must come BEFORE the subcommand (test, fields, search-read, etc.), not after.
To persist the detected server version into the profile config:
uv run scripts/odoo_config.py detect-version --profile local --save
uv run scripts/odoo_query.py --profile local fields --model res.partner --fields name,email,phone,is_company
uv run scripts/odoo_query.py --profile local search-read \
--model res.partner \
--domain-json '[["is_company", "=", true]]' \
--fields name,email,phone \
--limit 10
Use JSON domains only. Do not use Python eval for domains.
Use res.partner. Start with name, display_name, email, phone, mobile, is_company, parent_id, company_id, country_id, vat, customer_rank, and supplier_rank.
uv run scripts/odoo_query.py --profile local search-read \
--model res.partner \
--domain-json '[["name", "ilike", "ACME"]]' \
--fields name,display_name,email,phone,is_company,parent_id,company_id,country_id,customer_rank,supplier_rank \
--limit 10
Use sale.order. Start with name, partner_id, state, date_order, amount_total, currency_id, company_id, user_id, and invoice_status.
uv run scripts/odoo_query.py --profile local search-read \
--model sale.order \
--domain-json '[["name", "ilike", "S"]]' \
--fields name,partner_id,state,date_order,amount_total,currency_id,company_id,user_id,invoice_status \
--limit 10
Use product.template for product-level information and product.product for variants. Start with name, default_code, barcode, active, sale_ok, purchase_ok, type, categ_id, list_price, standard_price, and qty_available when available.
uv run scripts/odoo_query.py --profile local search-read \
--model product.product \
--domain-json '[["default_code", "ilike", "SKU"]]' \
--fields name,display_name,default_code,barcode,active,sale_ok,purchase_ok,type,categ_id,list_price,standard_price,qty_available \
--limit 10
Use stock.quant for on-hand quantities, stock.picking for transfers, and stock.move for stock moves. Stock mutation is high risk; default to read-only diagnostics.
uv run scripts/odoo_query.py --profile local search-read \
--model stock.picking \
--domain-json '[["name", "ilike", "WH"]]' \
--fields name,partner_id,picking_type_id,location_id,location_dest_id,state,scheduled_date,origin,company_id \
--limit 10
Use account.move. Accounting documents are protected. Read them freely when authorized, but do not post, cancel, reset, delete, or change them unless the user gives precise instructions and accepts business/legal risk.
uv run scripts/odoo_query.py --profile local search-read \
--model account.move \
--domain-json '[["name", "ilike", "INV"]]' \
--fields name,partner_id,move_type,state,payment_state,invoice_date,amount_total,currency_id,company_id \
--limit 10
Use res.company and res.partner. For a company record, inspect name, parent_id, partner_id, country_id, and related partner address fields. Do not mutate company_id, company_ids, parent_id, or company address fields without explicit approval.
uv run scripts/odoo_query.py --profile local search-read \
--model res.company \
--domain-json '[["name", "ilike", "Schaeffler"]]' \
--fields name,parent_id,partner_id,country_id,company_registry,vat \
--limit 10
Use res.users, ir.model.access, and ir.rule in read-only mode. Never change groups or record rules unless the user explicitly asks for an admin/security change and the impact is reviewed.
Creating records is state-changing and must be deliberate.
Default behavior is dry-run:
uv run scripts/odoo_mutate.py --profile local create \
--model res.partner \
--values-json '{"name":"Demo Customer","email":"[email protected]"}'
Actual creation requires --execute and a confirmation phrase:
uv run scripts/odoo_mutate.py --profile local create \
--model res.partner \
--values-json '{"name":"Demo Customer","email":"[email protected]"}' \
--execute \
--confirm CREATE
Updating existing records is high risk. Follow all rules below.
--execute is passed.--confirm UPDATE is passed.--quiet-mail when appropriate.Dry-run update:
uv run scripts/odoo_mutate.py --profile local update \
--model res.partner \
--ids 12,13 \
--values-json '{"category_id":[[6,0,[3]]]}'
Execute update:
uv run scripts/odoo_mutate.py --profile local update \
--model res.partner \
--ids 12,13 \
--values-json '{"category_id":[[6,0,[3]]]}' \
--execute \
--confirm UPDATE \
--quiet-mail
Deletion is the most dangerous operation.
active.--execute is passed.--confirm DELETE is passed.Dry-run delete:
uv run scripts/odoo_mutate.py --profile local delete \
--model res.partner \
--ids 99
Execute delete:
uv run scripts/odoo_mutate.py --profile local delete \
--model res.partner \
--ids 99 \
--execute \
--confirm DELETE
Use these playbooks when the task is about a local/self-hosted Odoo deployment, module source tree, database restore, or server configuration rather than only live RPC record lookup.
admin_passwd, or PostgreSQL db_user/db_password.odoo.conf and identify db_name, db_host, db_port, db_user, db_password, addons_path, and data_dir where available.odoo-bin shell + ORM for Odoo data changes when possible; avoid hand-writing password hashes or blindly editing business tables.admin passwordApply when the user asks to reset the Odoo backend login user admin, not the database user and not the Odoo database manager master password.
Required checks:
admin.odoo.conf or the running command/container.res.users record with login='admin' exists.odoo-bin shell + ORM; only fall back to direct SQL when the framework path is impossible and the user understands the risk.Preferred ORM snippet:
user = env['res.users'].search([('login', '=', 'admin')], limit=1)
assert user, 'admin user not found'
user.write({'password': '<NEW_PASSWORD>'})
env.cr.commit()
print(f'password updated for user id={user.id} login={user.login}')
Report using this shape:
已处理当前 Odoo 数据库:<DB_NAME>
- 目标用户:admin
- 已执行操作:将 Odoo 登录密码改为 <REDACTED_OR_USER_PROVIDED_VALUE>
- 未修改:admin_passwd / PostgreSQL 用户密码
- 建议验证:使用 admin 登录 Odoo 页面
Apply when the user asks to generate an Odoo module introduction page, convert a module README into an app description page, maintain module documentation, or create static/description/index.html.
Execution rules:
__manifest__.py or __openerp__.py.README.md, README.rst, docs/, manifest metadata, screenshots, and module source structure as inputs.static/description/index.html and create the directory if needed.Report using this shape:
已生成/更新 Odoo 模块介绍页:<MODULE_PATH>/static/description/index.html
- 来源文档:<README_OR_GENERATED_FROM_MODULE_STRUCTURE>
- 已覆盖内容:功能介绍、安装/配置、使用方式、注意事项
- 未处理:<KNOWN_GAPS_OR_NONE>
odoo.confApply when the user asks which database is running, which config is active, where addons are loaded from, or why Odoo is using the wrong database/module path.
Execution rules:
db_name, db_host, db_port, db_user, addons_path, data_dir, dbfilter, and relevant service/container command-line overrides.db_password, API keys, and other secrets.Report using this shape:
已识别当前 Odoo 配置:<ODOO_CONF_PATH>
- 数据库:<DB_NAME_OR_FILTER>
- 数据库连接:<HOST>:<PORT> / user=<DB_USER>
- addons_path:<PATHS>
- data_dir:<DATA_DIR_OR_DEFAULT_NEEDS_CONFIRMATION>
- 注意事项:<MULTI_DB_OR_DBFILTER_OR_OVERRIDES>
odoo.confApply when the user asks to restore an Odoo.sh backup locally or on a server, configure odoo.conf after restore, recover a dump.sql/.dump/zip backup, or align filestore and addons paths.
Required checks:
dump.sql, PostgreSQL custom .dump, or a nofs backup without real filestore content.data_dir; filestore must live under <data_dir>/filestore/<DB_NAME>/.addons_path includes all custom and enterprise modules required by the restored database.Preferred restore paths:
python odoo-bin db load <DB_NAME> <BACKUP_ZIP> --force
python odoo-bin db load <DB_NAME> <BACKUP_ZIP> --force --neutralize
createdb -U <DB_USER> <DB_NAME>
psql -U <DB_USER> -d <DB_NAME> -f dump.sql
pg_restore --no-owner -U <DB_USER> -d <DB_NAME> backup.dump
Windows fallback:
db load cannot read a local path on Windows, do not conclude the backup is corrupt.dump.sql or .dump, creating the database, restoring with psql/pg_restore, then copying real filestore content if present.nofs backup rule:
nofs or an empty filestore/ as a successful database-only backup with missing attachments.Default database naming rule:
<project_name>_<YYYYMMDDHHMMSS>
If renaming after restore, use a filestore-aware process and update odoo.conf db_name; do not rename PostgreSQL only and leave filestore/config stale.
Validation checklist:
odoo.conf points to the restored database and correct data_dir.odoo-bin shell --no-http --stop-after-init or an equivalent startup check.nofs backups.Report using this shape:
已处理 Odoo 备份恢复:<BACKUP_PATH>
- 恢复目标数据库:<DB_NAME>
- 恢复方式:db load / psql / pg_restore
- filestore 状态:已恢复 / 缺失 / 待确认
- 已更新配置:db_name, db_host, db_port, db_user, db_password, addons_path, data_dir[, dbfilter]
- 当前风险:<RISKS>
- 建议验证:启动 Odoo,打开目标数据库并检查附件与模块加载情况
admin_passwd with the Odoo login user admin password.db_user/db_password with Odoo user credentials.res_users.password hashes by hand unless ORM is impossible.odoo.conf, data_dir, and filestore expectations are aligned.odoo.conf after restoring or renaming a database.Treat these as protected. Read-only access is allowed; mutation requires exceptional user authorization and manual review.
Protected models include:
res.usersres.companyir.config_parameterir.model.accessir.ruleir.module.moduleaccount.moveaccount.move.linestock.movestock.pickingstock.quantpayment.*account.*stock.*ir.*Protected fields include:
passwordgroups_idcompany_idcompany_idsparent_id on companiesactive on users/companiesstate, payment_state, move_typestate, location_id, location_dest_id, quantity, reserved_quantity, inventory_quantitytoken, secret, key, password, or credentialThe mutation script enforces a denylist by default. Do not bypass it casually.
When doing batch creates/updates that could trigger chatter messages or emails, prefer quiet context:
{
"mail_create_nosubscribe": true,
"mail_notrack": true,
"tracking_disable": true,
"mail_auto_subscribe_no_notify": true
}
Use --quiet-mail in scripts/odoo_mutate.py to apply this context.
odoo_version or a detected server version.If a task requires a custom Odoo method call, first inspect the model and method behavior. For non-mutating calls, use scripts/odoo_query.py call-readonly only when the method is known to be safe. For mutating business workflows such as confirming sale orders, validating pickings, posting invoices, reconciling payments, changing stock quantities, cancelling documents, resetting posted documents, installing modules, changing access rules, or modifying users/companies, do not call raw methods until the user explicitly authorizes the workflow and the agent explains the expected effect.
tools
Manage CLIProxyAPI or CLIProxyAPIPlus only: save CLIProxyAPI Management API URL and password under the user's home .config/cliproxyapi directory, call every CLIProxyAPI Management API endpoint, manage config/auth files/provider keys/model aliases/logs/usage/OAuth URLs, and work on Windows, Linux, and macOS without opening the web UI.
development
Triggered when the user requests an image (e.g., architecture, flowchart, sequence diagram) or needs a technical illustration inserted into a document. This skill dynamically generates and inserts images using the official PlantUML public API.
testing
Standardize responses with a fixed execution structure. Use when users ask for workflow-style outputs, consistent task breakdowns, or reusable delivery checklists.
development
Automatically exposes local services using LocalTunnel via npx and provides the public URL along with the access password. Use when starting a new service or when external web access is requested.