@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.
@@ -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.