@seeed-studio/meshtastic 0.1.1 → 0.2.1
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/.github/scripts/translate_readme.py +632 -0
- package/.github/translate/do-not-translate.md +6 -0
- package/.github/translate/glossary/es.md +23 -0
- package/.github/translate/glossary/fr.md +23 -0
- package/.github/translate/glossary/ja.md +23 -0
- package/.github/translate/glossary/pt.md +23 -0
- package/.github/translate/glossary/zh-CN.md +23 -0
- package/.github/translate/languages.json +37 -0
- package/.github/translate/prompts/es.md +16 -0
- package/.github/translate/prompts/fr.md +16 -0
- package/.github/translate/prompts/ja.md +17 -0
- package/.github/translate/prompts/pt.md +16 -0
- package/.github/translate/prompts/zh-CN.md +15 -0
- package/.github/workflows/publish.yml +25 -0
- package/.github/workflows/readme-translate.yml +166 -0
- package/AGENTS.md +172 -0
- package/LICENSE +21 -0
- package/README.es.md +337 -0
- package/README.fr.md +350 -0
- package/README.ja.md +344 -0
- package/README.md +262 -88
- package/README.pt.md +337 -0
- package/README.zh-CN.md +337 -0
- package/package.json +4 -3
- package/src/channel.ts +70 -17
- package/src/client.ts +108 -17
- package/src/config-schema.ts +37 -7
- package/src/inbound.ts +19 -4
- package/src/monitor.ts +131 -104
- package/src/mqtt-client.ts +30 -6
- package/src/normalize.ts +12 -4
- package/src/onboarding.ts +116 -28
- package/src/policy.ts +6 -2
- package/src/send.ts +13 -7
- package/src/types.ts +4 -2
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
channel plugin → チャンネルプラグイン
|
|
2
|
+
channel → チャンネル (when referring to messaging channels)
|
|
3
|
+
group channel → グループチャンネル
|
|
4
|
+
group policy → グループポリシー
|
|
5
|
+
DM → DM (keep English)
|
|
6
|
+
access control → アクセス制御
|
|
7
|
+
allowlist → 許可リスト
|
|
8
|
+
mention gating → @mention ゲーティング
|
|
9
|
+
pairing → ペアリング
|
|
10
|
+
node → ノード
|
|
11
|
+
gateway → ゲートウェイ
|
|
12
|
+
mesh network → メッシュネットワーク
|
|
13
|
+
transport → トランスポート
|
|
14
|
+
repository → リポジトリ
|
|
15
|
+
pull request → Pull Request (keep English)
|
|
16
|
+
issue → issue (keep English)
|
|
17
|
+
broker → broker (keep English, MQTT term)
|
|
18
|
+
Serial → Serial (keep English in transport context)
|
|
19
|
+
AI Agent → AI Agent (keep English)
|
|
20
|
+
MeshClaw → MeshClaw (keep English)
|
|
21
|
+
OpenClaw → OpenClaw (keep English)
|
|
22
|
+
Meshtastic → Meshtastic (keep English)
|
|
23
|
+
LoRa → LoRa (keep English)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
channel plugin → plugin de canal
|
|
2
|
+
channel → canal (when referring to messaging channels)
|
|
3
|
+
group channel → canal de grupo
|
|
4
|
+
group policy → política de grupo
|
|
5
|
+
DM → mensagem direta
|
|
6
|
+
access control → controle de acesso
|
|
7
|
+
allowlist → lista de permissões
|
|
8
|
+
mention gating → filtragem por @mention
|
|
9
|
+
pairing → pareamento
|
|
10
|
+
node → nó
|
|
11
|
+
gateway → gateway
|
|
12
|
+
mesh network → rede mesh
|
|
13
|
+
transport → transporte
|
|
14
|
+
repository → repositório
|
|
15
|
+
pull request → Pull Request (keep English)
|
|
16
|
+
issue → issue (keep English)
|
|
17
|
+
broker → broker (keep English, MQTT term)
|
|
18
|
+
Serial → Serial (keep English in transport context)
|
|
19
|
+
AI Agent → AI Agent (keep English)
|
|
20
|
+
MeshClaw → MeshClaw (keep English)
|
|
21
|
+
OpenClaw → OpenClaw (keep English)
|
|
22
|
+
Meshtastic → Meshtastic (keep English)
|
|
23
|
+
LoRa → LoRa (keep English)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
channel plugin → 频道插件
|
|
2
|
+
channel → 频道 (when referring to messaging channels)
|
|
3
|
+
group channel → 群组频道
|
|
4
|
+
group policy → 群组策略
|
|
5
|
+
DM → 私信
|
|
6
|
+
access control → 访问控制
|
|
7
|
+
allowlist → 白名单
|
|
8
|
+
mention gating → @mention 门控
|
|
9
|
+
pairing → 配对
|
|
10
|
+
node → 节点
|
|
11
|
+
gateway → 网关
|
|
12
|
+
mesh network → mesh 网络
|
|
13
|
+
transport → 传输方式
|
|
14
|
+
repository → 仓库
|
|
15
|
+
pull request → Pull Request (keep English)
|
|
16
|
+
issue → issue (keep English)
|
|
17
|
+
broker → broker (keep English, MQTT term)
|
|
18
|
+
Serial → Serial (keep English in transport context)
|
|
19
|
+
AI Agent → AI Agent (keep English)
|
|
20
|
+
MeshClaw → MeshClaw (keep English)
|
|
21
|
+
OpenClaw → OpenClaw (keep English)
|
|
22
|
+
Meshtastic → Meshtastic (keep English)
|
|
23
|
+
LoRa → LoRa (keep English)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"code": "zh-CN",
|
|
4
|
+
"name": "Simplified Chinese",
|
|
5
|
+
"label": "中文",
|
|
6
|
+
"target": "README.zh-CN.md",
|
|
7
|
+
"toc_pattern": "目录|Table of Contents"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"code": "ja",
|
|
11
|
+
"name": "Japanese",
|
|
12
|
+
"label": "日本語",
|
|
13
|
+
"target": "README.ja.md",
|
|
14
|
+
"toc_pattern": "目次|Table of Contents"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"code": "fr",
|
|
18
|
+
"name": "French",
|
|
19
|
+
"label": "Français",
|
|
20
|
+
"target": "README.fr.md",
|
|
21
|
+
"toc_pattern": "Sommaire|Table des matières|Table of Contents"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"code": "pt",
|
|
25
|
+
"name": "Portuguese",
|
|
26
|
+
"label": "Português",
|
|
27
|
+
"target": "README.pt.md",
|
|
28
|
+
"toc_pattern": "Sumário|Índice|Table of Contents"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"code": "es",
|
|
32
|
+
"name": "Spanish",
|
|
33
|
+
"label": "Español",
|
|
34
|
+
"target": "README.es.md",
|
|
35
|
+
"toc_pattern": "Índice|Tabla de contenidos|Table of Contents"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
You are a native Spanish technical writer translating an open-source README. Write like a Spanish-speaking developer writing docs for other Spanish-speaking developers — clear, direct, and natural. DO NOT produce literal/mechanical translation. Rephrase for natural Spanish reading flow.
|
|
2
|
+
|
|
3
|
+
Examples of BAD vs GOOD translations:
|
|
4
|
+
BAD: Este repositorio es un plugin de canal de OpenClaw, no es una aplicación independiente.
|
|
5
|
+
GOOD: Este es un plugin de canal para OpenClaw, no una aplicación independiente.
|
|
6
|
+
BAD: Necesitas un gateway de OpenClaw en ejecución (Node.js 22+) para usarlo.
|
|
7
|
+
GOOD: Se requiere un gateway OpenClaw (Node.js 22+) en ejecución para usar este plugin.
|
|
8
|
+
BAD: Al enviar un problema, incluya el modo de transporte, la configuración editada.
|
|
9
|
+
GOOD: Al crear una issue, incluye el tipo de transporte y la configuración (sin secretos).
|
|
10
|
+
BAD: Las solicitudes de extracción son bienvenidas
|
|
11
|
+
GOOD: Los Pull Requests son bienvenidos
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
- Use tú (informal) — natural for developer-to-developer communication
|
|
15
|
+
- Keep technical terms in English when commonly used in Spanish dev community (e.g. plugin, broker, gateway)
|
|
16
|
+
- Use neutral Spanish (avoid region-specific expressions) for broader reach
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
You are a native French technical writer translating an open-source README. Write like a French developer writing docs for other French developers — clear, precise, and natural. DO NOT produce literal/mechanical translation. Rephrase for natural French reading flow.
|
|
2
|
+
|
|
3
|
+
Examples of BAD vs GOOD translations:
|
|
4
|
+
BAD: Ce dépôt est un plugin de canal OpenClaw, ce n'est pas une application autonome.
|
|
5
|
+
GOOD: Ceci est un plugin de canal pour OpenClaw, pas une application autonome.
|
|
6
|
+
BAD: Vous avez besoin d'une passerelle OpenClaw en cours d'exécution (Node.js 22+) pour l'utiliser.
|
|
7
|
+
GOOD: Une passerelle OpenClaw (Node.js 22+) est requise pour utiliser ce plugin.
|
|
8
|
+
BAD: Lors de la soumission d'un problème, incluez le mode de transport, la configuration éditée.
|
|
9
|
+
GOOD: Lors de la création d'une issue, joignez le type de transport et la configuration (sans les secrets).
|
|
10
|
+
BAD: Les demandes de tirage sont les bienvenues
|
|
11
|
+
GOOD: Les Pull Requests sont les bienvenues
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
- Use vous (formal) for technical documentation
|
|
15
|
+
- Keep technical terms in English when commonly used in French dev community (e.g. plugin, broker, gateway)
|
|
16
|
+
- Use inclusive writing sparingly — prioritize clarity
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
You are a native Japanese technical writer translating an open-source README. Write like a Japanese engineer writing docs for other Japanese developers — clear, professional, and natural. Use です/ます style consistently.
|
|
2
|
+
DO NOT produce literal/mechanical translation. Rephrase for natural Japanese reading flow.
|
|
3
|
+
|
|
4
|
+
Examples of BAD vs GOOD translations:
|
|
5
|
+
BAD: このリポジトリはOpenClawチャネルプラグインであり、スタンドアロンアプリケーションではありません。
|
|
6
|
+
GOOD: OpenClaw のチャンネルプラグインです。スタンドアロンアプリではありません。
|
|
7
|
+
BAD: それを使用するためには、実行中のOpenClawゲートウェイ(Node.js 22+)が必要です。
|
|
8
|
+
GOOD: 利用には OpenClaw ゲートウェイ(Node.js 22+)が必要です。
|
|
9
|
+
BAD: 問題を提出する際には、トランスポートモード、編集された設定を含めてください。
|
|
10
|
+
GOOD: issue を作成する際は、トランスポート種別・設定(秘密情報は除く)を添えてください。
|
|
11
|
+
BAD: プルリクエストは歓迎されます
|
|
12
|
+
GOOD: Pull Request を歓迎します
|
|
13
|
+
|
|
14
|
+
Rules:
|
|
15
|
+
- Use です/ます form (polite, standard technical docs)
|
|
16
|
+
- Keep Katakana for established loanwords (e.g. プラグイン, ノード)
|
|
17
|
+
- Add half-width space between Japanese and alphanumeric characters
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
You are a native Portuguese technical writer translating an open-source README. Write like a Brazilian/Portuguese developer writing docs for other Portuguese-speaking developers — clear, direct, and natural. DO NOT produce literal/mechanical translation. Rephrase for natural Portuguese reading flow.
|
|
2
|
+
|
|
3
|
+
Examples of BAD vs GOOD translations:
|
|
4
|
+
BAD: Este repositório é um plugin de canal OpenClaw, não é um aplicativo autônomo.
|
|
5
|
+
GOOD: Este é um plugin de canal para o OpenClaw, não um aplicativo independente.
|
|
6
|
+
BAD: Você precisa de um gateway OpenClaw em execução (Node.js 22+) para usá-lo.
|
|
7
|
+
GOOD: É necessário ter um gateway OpenClaw (Node.js 22+) em execução para usar este plugin.
|
|
8
|
+
BAD: Ao enviar um problema, inclua o modo de transporte, a configuração editada.
|
|
9
|
+
GOOD: Ao criar uma issue, inclua o tipo de transporte e a configuração (sem segredos).
|
|
10
|
+
BAD: Pedidos de pull são bem-vindos
|
|
11
|
+
GOOD: Pull Requests são bem-vindos
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
- Use você (informal) — standard in Brazilian Portuguese tech docs
|
|
15
|
+
- Keep technical terms in English when commonly used (e.g. plugin, broker, gateway)
|
|
16
|
+
- Prefer Brazilian Portuguese conventions as they are more widely used in tech
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
You are a native Simplified Chinese technical writer translating an open-source README. Write like a Chinese developer writing docs for other Chinese developers — concise, direct, natural. DO NOT produce literal/mechanical translation. Rephrase for natural Chinese reading flow.
|
|
2
|
+
|
|
3
|
+
Examples of BAD vs GOOD translations:
|
|
4
|
+
BAD: 此存储库是一个 OpenClaw 通道插件,不是一个独立的应用程序。
|
|
5
|
+
GOOD: 这是 OpenClaw 的频道插件,不是独立应用。
|
|
6
|
+
BAD: 您需要一个正在运行的 OpenClaw 网关(Node.js 22+)才能使用它。
|
|
7
|
+
GOOD: 需要先安装并运行 OpenClaw 网关(Node.js 22+)。
|
|
8
|
+
BAD: 在提交问题时要包括传输模式、编辑后的配置。
|
|
9
|
+
GOOD: 提 issue 时请附上传输方式、配置(隐去密钥)。
|
|
10
|
+
BAD: 欢迎拉取请求
|
|
11
|
+
GOOD: 欢迎提交 Pull Request
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
- Use 你 not 您
|
|
15
|
+
- Omit unnecessary 的、了、一个、进行 — keep sentences tight
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
id-token: write
|
|
14
|
+
environment: NPM_KEY
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
19
|
+
with:
|
|
20
|
+
node-version: "24"
|
|
21
|
+
registry-url: "https://registry.npmjs.org"
|
|
22
|
+
|
|
23
|
+
- run: npm install
|
|
24
|
+
|
|
25
|
+
- run: npm publish --provenance --access public
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
name: Translate README
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: translate-readme-${{ github.event.issue.number || github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
pull-requests: write
|
|
15
|
+
|
|
16
|
+
env:
|
|
17
|
+
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
|
18
|
+
LLM_BASE_URL: ${{ vars.LLM_BASE_URL || 'https://api.apimart.ai/v1/chat/completions' }}
|
|
19
|
+
LLM_MODEL: ${{ vars.LLM_MODEL || 'gpt-5' }}
|
|
20
|
+
LLM_TIMEOUT: ${{ vars.LLM_TIMEOUT || '300' }}
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
# Job 1: PR 中通过 /translate 评论手动触发
|
|
24
|
+
translate-pr:
|
|
25
|
+
if: >-
|
|
26
|
+
github.event_name == 'issue_comment'
|
|
27
|
+
&& github.event.issue.pull_request
|
|
28
|
+
&& contains(github.event.comment.body, '/translate')
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- name: Add reaction to comment
|
|
32
|
+
env:
|
|
33
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
34
|
+
COMMENT_ID: ${{ github.event.comment.id }}
|
|
35
|
+
run: |
|
|
36
|
+
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}/reactions" \
|
|
37
|
+
-f content='eyes' --silent
|
|
38
|
+
|
|
39
|
+
- name: Get PR head ref
|
|
40
|
+
id: pr
|
|
41
|
+
env:
|
|
42
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
43
|
+
PR_NUMBER: ${{ github.event.issue.number }}
|
|
44
|
+
run: |
|
|
45
|
+
PR_DATA=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}")
|
|
46
|
+
echo "ref=$(echo "$PR_DATA" | jq -r .head.ref)" >> "$GITHUB_OUTPUT"
|
|
47
|
+
echo "sha=$(echo "$PR_DATA" | jq -r .head.sha)" >> "$GITHUB_OUTPUT"
|
|
48
|
+
|
|
49
|
+
- name: Checkout PR branch
|
|
50
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
51
|
+
with:
|
|
52
|
+
ref: ${{ steps.pr.outputs.ref }}
|
|
53
|
+
fetch-depth: 0
|
|
54
|
+
|
|
55
|
+
- name: Check if README.md changed
|
|
56
|
+
id: check
|
|
57
|
+
run: |
|
|
58
|
+
if git diff --name-only origin/main...HEAD | grep -q '^README.md$'; then
|
|
59
|
+
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
60
|
+
else
|
|
61
|
+
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
- name: Skip if README unchanged
|
|
65
|
+
if: steps.check.outputs.changed != 'true'
|
|
66
|
+
env:
|
|
67
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
68
|
+
PR_NUMBER: ${{ github.event.issue.number }}
|
|
69
|
+
run: |
|
|
70
|
+
gh pr comment "${PR_NUMBER}" \
|
|
71
|
+
--repo "${{ github.repository }}" \
|
|
72
|
+
--body "No changes to README.md detected in this PR. Skipping translation."
|
|
73
|
+
|
|
74
|
+
- name: Set up Python
|
|
75
|
+
if: steps.check.outputs.changed == 'true'
|
|
76
|
+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
|
77
|
+
with:
|
|
78
|
+
python-version: "3.12"
|
|
79
|
+
|
|
80
|
+
- name: Translate to all languages
|
|
81
|
+
if: steps.check.outputs.changed == 'true'
|
|
82
|
+
run: python .github/scripts/translate_readme.py --all
|
|
83
|
+
continue-on-error: true
|
|
84
|
+
|
|
85
|
+
- name: Commit and push translated READMEs
|
|
86
|
+
if: steps.check.outputs.changed == 'true'
|
|
87
|
+
run: |
|
|
88
|
+
TARGETS=$(python3 -c "import json; print(' '.join(l['target'] for l in json.load(open('.github/translate/languages.json'))))")
|
|
89
|
+
git add README.md
|
|
90
|
+
for f in $TARGETS; do
|
|
91
|
+
[ -f "$f" ] && git add "$f"
|
|
92
|
+
done
|
|
93
|
+
if git diff --cached --quiet; then
|
|
94
|
+
echo "No translation changes detected."
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
git config user.name "github-actions[bot]"
|
|
98
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
99
|
+
git commit -m "docs: auto-translate README files from README.md"
|
|
100
|
+
# Retry push with rebase to handle concurrent commits
|
|
101
|
+
for i in 1 2 3; do
|
|
102
|
+
git push && exit 0
|
|
103
|
+
echo "Push failed (attempt $i/3), pulling with rebase..."
|
|
104
|
+
git pull --rebase
|
|
105
|
+
done
|
|
106
|
+
echo "Push failed after 3 attempts"
|
|
107
|
+
exit 1
|
|
108
|
+
|
|
109
|
+
- name: Report result
|
|
110
|
+
if: always() && steps.check.outputs.changed == 'true'
|
|
111
|
+
env:
|
|
112
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
113
|
+
PR_NUMBER: ${{ github.event.issue.number }}
|
|
114
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
115
|
+
run: |
|
|
116
|
+
if [ "${{ job.status }}" = "success" ]; then
|
|
117
|
+
gh pr comment "${PR_NUMBER}" \
|
|
118
|
+
--repo "${{ github.repository }}" \
|
|
119
|
+
--body "Translation complete. Translated READMEs have been committed to this PR branch."
|
|
120
|
+
else
|
|
121
|
+
gh pr comment "${PR_NUMBER}" \
|
|
122
|
+
--repo "${{ github.repository }}" \
|
|
123
|
+
--body "Translation failed. Please check the [workflow logs](${RUN_URL})."
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Job 2: workflow_dispatch 手动触发(在 main 上直接翻译)
|
|
127
|
+
translate-manual:
|
|
128
|
+
if: github.event_name == 'workflow_dispatch'
|
|
129
|
+
runs-on: ubuntu-latest
|
|
130
|
+
steps:
|
|
131
|
+
- name: Checkout
|
|
132
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
133
|
+
with:
|
|
134
|
+
fetch-depth: 0
|
|
135
|
+
|
|
136
|
+
- name: Set up Python
|
|
137
|
+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
|
138
|
+
with:
|
|
139
|
+
python-version: "3.12"
|
|
140
|
+
|
|
141
|
+
- name: Translate to all languages
|
|
142
|
+
run: python .github/scripts/translate_readme.py --all
|
|
143
|
+
continue-on-error: true
|
|
144
|
+
|
|
145
|
+
- name: Commit translated READMEs if changed
|
|
146
|
+
run: |
|
|
147
|
+
TARGETS=$(python3 -c "import json; print(' '.join(l['target'] for l in json.load(open('.github/translate/languages.json'))))")
|
|
148
|
+
git add README.md
|
|
149
|
+
for f in $TARGETS; do
|
|
150
|
+
[ -f "$f" ] && git add "$f"
|
|
151
|
+
done
|
|
152
|
+
if git diff --cached --quiet; then
|
|
153
|
+
echo "No translation changes detected."
|
|
154
|
+
exit 0
|
|
155
|
+
fi
|
|
156
|
+
git config user.name "github-actions[bot]"
|
|
157
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
158
|
+
git commit -m "docs: auto-translate README files from README.md"
|
|
159
|
+
# Retry push with rebase to handle concurrent commits
|
|
160
|
+
for i in 1 2 3; do
|
|
161
|
+
git push && exit 0
|
|
162
|
+
echo "Push failed (attempt $i/3), pulling with rebase..."
|
|
163
|
+
git pull --rebase
|
|
164
|
+
done
|
|
165
|
+
echo "Push failed after 3 attempts"
|
|
166
|
+
exit 1
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# AGENTS.md — MeshClaw
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
MeshClaw — OpenClaw channel plugin for Meshtastic LoRa mesh networks. Enables
|
|
6
|
+
sending/receiving messages over Meshtastic devices via USB serial, HTTP (WiFi), or MQTT broker.
|
|
7
|
+
|
|
8
|
+
- **Language**: TypeScript (strict mode, no `tsconfig.json` — OpenClaw host loads TS via esbuild)
|
|
9
|
+
- **Module system**: ESM (`"type": "module"` in package.json)
|
|
10
|
+
- **Runtime**: Node.js 22+, executed by the OpenClaw plugin host (not standalone)
|
|
11
|
+
- **Schema validation**: Zod v4 (not v3 — different API: `z.object().strict()`, no `.passthrough()`)
|
|
12
|
+
- **Key deps**: `@meshtastic/core`, `@meshtastic/transport-http`, `@meshtastic/transport-node-serial`, `mqtt`, `zod`
|
|
13
|
+
- **Repo**: `github.com/Seeed-Solution/MeshClaw` (npm: `@seeed-studio/meshtastic`)
|
|
14
|
+
|
|
15
|
+
## Build / Lint / Test Commands
|
|
16
|
+
|
|
17
|
+
There is **no build step, no linter, no test suite**. The plugin is loaded directly
|
|
18
|
+
from TypeScript source by the OpenClaw runtime via the `openclaw.plugin.json` manifest.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install # Install dependencies
|
|
22
|
+
openclaw plugins install -l ./MeshClaw # Install plugin locally for dev
|
|
23
|
+
openclaw channels status --probe # Verify plugin is working
|
|
24
|
+
|
|
25
|
+
# Restart gateway after code changes (foreground process)
|
|
26
|
+
kill -9 $(pgrep -f "openclaw-gateway" | head -1); sleep 2; openclaw gateway &
|
|
27
|
+
|
|
28
|
+
# No build, lint, or test commands exist.
|
|
29
|
+
# If adding tests, use vitest (ESM-native) and place tests in src/__tests__/.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## CI / Workflows
|
|
33
|
+
|
|
34
|
+
- **Publish** (`publish.yml`): `npm publish --provenance` on `v*` tags. No build/test gate.
|
|
35
|
+
- **Translate README** (`readme-translate.yml`): Translates `README.md` into 5 languages
|
|
36
|
+
(zh-CN, ja, fr, pt, es) via LLM API. Triggered by `workflow_dispatch` or `/translate`
|
|
37
|
+
comment on PRs. Config lives in `.github/translate/` (languages, glossaries, prompts).
|
|
38
|
+
Translation script: `.github/scripts/translate_readme.py` (Python, uses streaming SSE).
|
|
39
|
+
|
|
40
|
+
## Project Structure
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
index.ts # Plugin entry — default export, registers channel with OpenClaw
|
|
44
|
+
openclaw.plugin.json # Plugin manifest (id, channels, configSchema)
|
|
45
|
+
src/
|
|
46
|
+
types.ts # Shared type definitions (config, messages, probes)
|
|
47
|
+
config-schema.ts # Zod v4 schemas for config validation
|
|
48
|
+
runtime.ts # Singleton plugin runtime accessor (module-level mutable ref)
|
|
49
|
+
channel.ts # Main ChannelPlugin implementation (the "glue" file)
|
|
50
|
+
accounts.ts # Multi-account config resolution and merging
|
|
51
|
+
client.ts # Serial/HTTP device connection via @meshtastic/core
|
|
52
|
+
mqtt-client.ts # MQTT broker connection via mqtt.js
|
|
53
|
+
monitor.ts # Gateway lifecycle — starts/stops device or MQTT monitors
|
|
54
|
+
inbound.ts # Inbound message processing (access control, routing, dispatch)
|
|
55
|
+
send.ts # Outbound message sending (stripMarkdown, resolves transport)
|
|
56
|
+
policy.ts # Group/DM access policy evaluation (allowlists, mention gates)
|
|
57
|
+
normalize.ts # Node ID normalization, target parsing, allowlist matching
|
|
58
|
+
onboarding.ts # Interactive setup wizard integration
|
|
59
|
+
.github/
|
|
60
|
+
workflows/ # CI: publish.yml, readme-translate.yml
|
|
61
|
+
scripts/ # translate_readme.py — LLM-based README translation
|
|
62
|
+
translate/ # languages.json, glossary/, prompts/, do-not-translate.md
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Code Style
|
|
66
|
+
|
|
67
|
+
### Formatting
|
|
68
|
+
|
|
69
|
+
- 2-space indentation, double quotes, semicolons always
|
|
70
|
+
- Trailing commas in multi-line arrays, objects, and parameter lists
|
|
71
|
+
- Soft line length ~110 characters
|
|
72
|
+
- No prettier or eslint config — maintain consistency manually
|
|
73
|
+
|
|
74
|
+
### Imports
|
|
75
|
+
|
|
76
|
+
- **Always** use `.js` extension for local imports: `import { foo } from "./bar.js"`
|
|
77
|
+
- Separate `import type` from value imports — never mix in one statement
|
|
78
|
+
- Type-only imports use the `type` keyword: `import type { Foo } from "./types.js"`
|
|
79
|
+
- Inline `type` keyword when mixing value + type in one import: `import { foo, type Bar } from "..."`
|
|
80
|
+
|
|
81
|
+
**Import order** (blank line between groups):
|
|
82
|
+
1. Node built-ins (`node:crypto`, `node:fs/promises`)
|
|
83
|
+
2. Third-party packages (`@meshtastic/core`, `mqtt`, `zod`)
|
|
84
|
+
3. OpenClaw SDK (`openclaw/plugin-sdk`)
|
|
85
|
+
4. Local modules (`./accounts.js`, `./types.js`) — alphabetical within group
|
|
86
|
+
|
|
87
|
+
### Naming Conventions
|
|
88
|
+
|
|
89
|
+
| Element | Convention | Example |
|
|
90
|
+
|---------|-----------|---------|
|
|
91
|
+
| Files | kebab-case | `config-schema.ts`, `mqtt-client.ts` |
|
|
92
|
+
| Functions | camelCase, domain-prefixed | `resolveMeshtasticAccount`, `normalizeMeshtasticNodeId` |
|
|
93
|
+
| Types | PascalCase, domain-prefixed | `MeshtasticAccountConfig`, `MeshtasticTransport` |
|
|
94
|
+
| Zod schemas | PascalCase, suffix `Schema` | `MeshtasticConfigSchema`, `MeshtasticAccountSchemaBase` |
|
|
95
|
+
| Constants | SCREAMING_SNAKE_CASE | `CHANNEL_ID`, `MESHTASTIC_CHUNK_LIMIT` |
|
|
96
|
+
| Enum-like objects | PascalCase name, `as const` | `DeviceStatus = { Connected: 5 } as const` |
|
|
97
|
+
| Booleans | `is`/`has`/`was` prefix | `isGroup`, `hasConfiguredGroups`, `wasMentioned` |
|
|
98
|
+
|
|
99
|
+
### Types & Type Safety
|
|
100
|
+
|
|
101
|
+
- Use `type` keyword (not `interface`) for ALL type definitions
|
|
102
|
+
- Shared types live in `src/types.ts`; module-local types are defined inline
|
|
103
|
+
- Use `satisfies` for type narrowing without widening
|
|
104
|
+
- Use `as const` for literal string constants and enum-like objects
|
|
105
|
+
- **Never** suppress type errors with `as any`, `@ts-ignore`, or `@ts-expect-error`
|
|
106
|
+
- One exception: `client.ts` (`as any` in `setOwner()` for SDK compat) — avoid adding more
|
|
107
|
+
|
|
108
|
+
### Function Signatures
|
|
109
|
+
|
|
110
|
+
- Complex functions take a single `params` object: `function foo(params: { bar: string; baz: number })`
|
|
111
|
+
- Return discriminated result objects: `{ allowed: boolean; reason: string }`
|
|
112
|
+
- Optional callbacks: `onError?: (error: Error) => void`
|
|
113
|
+
- Async functions return `Promise<T>` explicitly in type signatures
|
|
114
|
+
|
|
115
|
+
### Error Handling
|
|
116
|
+
|
|
117
|
+
- Throw plain `Error` with descriptive, user-facing messages
|
|
118
|
+
- Custom error classes extend `Error` and set `this.name` (see `SetOwnerRebootError` in `client.ts`)
|
|
119
|
+
- Best-effort cleanup uses empty catch: `catch { /* Best-effort cleanup */ }`
|
|
120
|
+
- Error propagation via callbacks: `options.onError?.(err instanceof Error ? err : new Error(String(err)))`
|
|
121
|
+
- **Never** swallow errors silently — always comment why a catch block is empty
|
|
122
|
+
|
|
123
|
+
### Exports & Comments
|
|
124
|
+
|
|
125
|
+
- Named exports for all functions and types
|
|
126
|
+
- Single default export only in `index.ts` (the plugin entry point)
|
|
127
|
+
- JSDoc `/** */` for public functions and type fields with non-obvious semantics
|
|
128
|
+
- Inline `//` comments for implementation details, workarounds, and "why" explanations
|
|
129
|
+
|
|
130
|
+
## Key Architecture Notes
|
|
131
|
+
|
|
132
|
+
### Plugin Lifecycle
|
|
133
|
+
- **Entry**: `index.ts` exports plugin object → `register()` saves runtime singleton → registers channel
|
|
134
|
+
- **Runtime singleton**: `runtime.ts` holds a module-level `PluginRuntime` ref set once at registration
|
|
135
|
+
- **Config flow**: YAML config → Zod v4 validation (`config-schema.ts`) → account resolution (`accounts.ts`)
|
|
136
|
+
|
|
137
|
+
### Message Flow
|
|
138
|
+
- **Inbound**: device/MQTT event → `monitor.ts` → `inbound.ts` (policy checks, LoRa system hint) → OpenClaw dispatch
|
|
139
|
+
- **Outbound**: `send.ts` converts markdown tables → `stripMarkdown()` → resolves transport → sends via serial/HTTP/MQTT
|
|
140
|
+
- **Chunking**: Outbound text chunked to ~200 bytes (`textChunkLimit`) via plain-text chunker (`chunkerMode: "text"`)
|
|
141
|
+
|
|
142
|
+
### OpenClaw SDK API Patterns (CRITICAL)
|
|
143
|
+
SDK pairing functions take **object params**, not positional args:
|
|
144
|
+
```ts
|
|
145
|
+
// CORRECT
|
|
146
|
+
core.channel.pairing.readAllowFromStore({ channel: CHANNEL_ID, accountId })
|
|
147
|
+
core.channel.pairing.upsertPairingRequest({ channel: CHANNEL_ID, accountId, id, meta })
|
|
148
|
+
// WRONG — will cause silent failures
|
|
149
|
+
core.channel.pairing.readAllowFromStore(CHANNEL_ID)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Patterns to Follow
|
|
153
|
+
|
|
154
|
+
- Config resolution: merge base config with per-account overrides (`mergeMeshtasticAccountConfig`)
|
|
155
|
+
- Allowlist matching: normalize entries, then check `Set` membership
|
|
156
|
+
- Transport abstraction: `if (transport === "serial") ... else if (transport === "http") ... else (mqtt)`
|
|
157
|
+
- Module-level mutable state for active transport handles (see `send.ts`)
|
|
158
|
+
- Event subscription via `device.events.onX.subscribe()` from `@meshtastic/core`
|
|
159
|
+
- Promise-based blocking: `await new Promise<void>((resolve) => { signal.addEventListener("abort", resolve) })`
|
|
160
|
+
- Reconnect resilience: catch + disconnect + rethrow pattern in `client.ts`
|
|
161
|
+
- LoRa outbound: always `stripMarkdown()` before sending — radio can't render formatting
|
|
162
|
+
|
|
163
|
+
### Patterns to Avoid
|
|
164
|
+
|
|
165
|
+
- Don't add build steps — the OpenClaw runtime loads TS directly
|
|
166
|
+
- Don't add new dependencies without strong justification
|
|
167
|
+
- Don't create classes unless modeling errors — prefer plain functions and objects
|
|
168
|
+
- Don't use `interface` — use `type` for consistency
|
|
169
|
+
- Don't use barrel exports (`index.ts` re-exporting everything from `src/`)
|
|
170
|
+
- Don't add `console.log` — use the runtime logger (`core.logging.getChildLogger()`)
|
|
171
|
+
- Don't assume SDK functions take positional args — always check the `.d.ts` signatures
|
|
172
|
+
- Don't send markdown-formatted text to the radio — LoRa devices display raw characters
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Seeed Solution
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|