@clawops/cli 1.7.9 → 2.0.0

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.
Files changed (57) hide show
  1. package/README.md +412 -70
  2. package/dist/{apply-F3KY2HTB.js → apply-AM5HERE7.js} +8 -6
  3. package/dist/{aws-L4RR2LSE.js → aws-AMON7DA7.js} +20 -9
  4. package/dist/{azure-7OXQENQL.js → azure-T4G6OBJQ.js} +19 -8
  5. package/dist/{bootstrap-HL4DNUNG.js → bootstrap-DMRPWJGN.js} +28 -19
  6. package/dist/bootstrap.sh.tmpl +51 -31
  7. package/dist/channels-KBUR42IU.js +53 -0
  8. package/dist/{chunk-DQDAWNGB.js → chunk-3WYA2QDM.js} +1 -1
  9. package/dist/chunk-6TIYVHPX.js +152 -0
  10. package/dist/{chunk-3XK66DKK.js → chunk-AHAAEXWC.js} +41 -0
  11. package/dist/{chunk-GZN7PKP4.js → chunk-AX74VXTV.js} +292 -94
  12. package/dist/{chunk-C5X375RO.js → chunk-CKSPROD5.js} +59 -15
  13. package/dist/{chunk-UKTP332Z.js → chunk-GAZPPSKU.js} +6 -7
  14. package/dist/{chunk-FNS56UQY.js → chunk-GYV4KNW7.js} +319 -252
  15. package/dist/{chunk-PVUVI35Y.js → chunk-JW5A7E3X.js} +5 -1
  16. package/dist/chunk-LT4XMO5D.js +50 -0
  17. package/dist/{chunk-P5LKWRPL.js → chunk-LU2V3LXA.js} +3 -3
  18. package/dist/chunk-N44K3LHE.js +140 -0
  19. package/dist/chunk-NUDIHZEH.js +55 -0
  20. package/dist/chunk-SBLQH2TJ.js +56 -0
  21. package/dist/chunk-UFRO4H5W.js +228 -0
  22. package/dist/{chunk-A4QR4TXW.js → chunk-WHJMKDV7.js} +24 -4
  23. package/dist/chunk-YR3O2I3S.js +101 -0
  24. package/dist/cli.js +866 -577
  25. package/dist/config-validate-K2N6XLDX.js +76 -0
  26. package/dist/{context-5LCUG2QR.js → context-KHKWJJMC.js} +1 -1
  27. package/dist/{firewall-XQGO7HWK.js → firewall-T3NTPZJD.js} +10 -0
  28. package/dist/{gcp-UG6AWPIZ.js → gcp-2JBTD7ZM.js} +19 -8
  29. package/dist/{generate-OAGYT3VH.js → generate-2XV2GSP2.js} +4 -4
  30. package/dist/{harden-URPXLSVF.js → harden-PCDK3UQ5.js} +48 -11
  31. package/dist/health-5CCOXYGG.js +11 -0
  32. package/dist/logs-P7YCKVF4.js +15 -0
  33. package/dist/{mcp-wire-7E5JQY47.js → mcp-wire-BFEY2Y6H.js} +23 -17
  34. package/dist/{package-5AR4ZPK4.js → package-3HUW57XH.js} +3 -3
  35. package/dist/plugins-DTST3OER.js +49 -0
  36. package/dist/{pool-QGFQRMGU.js → pool-EX4ZTA5P.js} +2 -2
  37. package/dist/privileged-JFA6LLTV.js +17 -0
  38. package/dist/{remote-config-QDLN4UMG.js → remote-config-Z6IIZR3D.js} +3 -3
  39. package/dist/runtime-LZ7NA53V.js +42 -0
  40. package/dist/{server-FIZCOLHL.js → server-A5AB67GQ.js} +16 -12
  41. package/dist/spec-path-KQOOIQCA.js +9 -0
  42. package/dist/{ssh-CNJNLM3E.js → ssh-B3NZAU4I.js} +1 -1
  43. package/dist/upgrade-ULD4KFYC.js +88 -0
  44. package/dist/validate-BOQRWX4F.js +14 -0
  45. package/dist/{version-guard-VUCNZXQS.js → version-guard-GPSTCPT7.js} +3 -3
  46. package/dist/{versions-2UVAT6PQ.js → versions-W5LRQCAJ.js} +2 -2
  47. package/package.json +3 -3
  48. package/spec/deploy-plan.schema.json +114 -25
  49. package/spec/integrations.yaml +135 -28
  50. package/spec/mcp-tools.yaml +68 -35
  51. package/spec/models.yaml +9 -0
  52. package/spec/openclaw-2.0.config.schema.json +1 -0
  53. package/spec/openclaw-versions.yaml +116 -27
  54. package/dist/chunk-35WVQY3X.js +0 -122
  55. package/dist/chunk-6X45DDGH.js +0 -42
  56. package/dist/chunk-SEZMVVAS.js +0 -156
  57. package/dist/validate-CFQ4FH2T.js +0 -10
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/openclaw/upgrade.ts
4
+ function judgePreflight(report) {
5
+ const { targetVersion, foundVersion, status, reason } = report;
6
+ if (status === "indeterminate") {
7
+ return {
8
+ ok: false,
9
+ reason: reason ?? "the database schema could not be determined, so compatibility with the target release is unknown"
10
+ };
11
+ }
12
+ if (typeof foundVersion === "number" && typeof targetVersion === "number") {
13
+ if (foundVersion > targetVersion) {
14
+ return {
15
+ ok: false,
16
+ reason: `the state database is at schema ${foundVersion}, but the target release understands ${targetVersion}. This is a downgrade across a schema boundary: the older release would read a database written by a newer one. Restore a backup taken before the upgrade instead.`
17
+ };
18
+ }
19
+ if (foundVersion < targetVersion) {
20
+ return { ok: true, note: `state will migrate from schema ${foundVersion} to ${targetVersion}` };
21
+ }
22
+ return { ok: true };
23
+ }
24
+ return {
25
+ ok: false,
26
+ reason: reason ?? `preflight returned no schema version (status: ${status ?? "unknown"})`
27
+ };
28
+ }
29
+ function parsePreflight(stdout) {
30
+ try {
31
+ const parsed = JSON.parse(stdout.trim());
32
+ if (parsed !== null && typeof parsed === "object") return parsed;
33
+ } catch {
34
+ }
35
+ return void 0;
36
+ }
37
+ function snapshotCommand(image, stateDir, repo) {
38
+ return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${image} openclaw backup sqlite create --global --repository ${repo} --json`;
39
+ }
40
+ function preflightCommand(targetImage, stateDir, snapshotDb) {
41
+ return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${targetImage} openclaw database preflight ${snapshotDb} --json`;
42
+ }
43
+ function snapshotPathFrom(stdout) {
44
+ try {
45
+ const parsed = JSON.parse(stdout.trim());
46
+ if (parsed.ok === true && typeof parsed.snapshotPath === "string") return parsed.snapshotPath;
47
+ } catch {
48
+ }
49
+ return void 0;
50
+ }
51
+ function repairCommand(image, stateDir) {
52
+ return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${image} openclaw doctor --fix --non-interactive --yes`;
53
+ }
54
+ function describeOutcome(o, version) {
55
+ switch (o.kind) {
56
+ case "started":
57
+ return `Gateway updated to ${version}.`;
58
+ case "repaired":
59
+ return `Gateway updated to ${version} after a one-shot repair (doctor --fix).`;
60
+ case "rolled-back":
61
+ return `Rolled back to ${o.to}: the gateway did not start on ${version} \u2014 ${o.reason}. The previous image is running again. If it also fails, restore the snapshot taken before the upgrade.`;
62
+ case "failed":
63
+ return `Upgrade to ${version} failed and the rollback did not come up either \u2014 ${o.reason}. The state snapshot taken before the upgrade is at ${o.snapshotPath}; restore it with \`openclaw backup sqlite restore\` before retrying.`;
64
+ }
65
+ }
66
+ async function resolveUpgrade(steps, ctx) {
67
+ const first = await steps.gate();
68
+ if (first.ok) return { kind: "started" };
69
+ await steps.repair();
70
+ await steps.run(ctx.version);
71
+ const second = await steps.gate();
72
+ if (second.ok) return { kind: "repaired" };
73
+ const reason = second.reason ?? first.reason ?? "unknown";
74
+ if (!ctx.previousVersion) return { kind: "failed", reason, snapshotPath: ctx.snapshotPath };
75
+ await steps.run(ctx.previousVersion);
76
+ const back = await steps.gate();
77
+ return back.ok ? { kind: "rolled-back", to: ctx.previousVersion, reason } : { kind: "failed", reason, snapshotPath: ctx.snapshotPath };
78
+ }
79
+ export {
80
+ describeOutcome,
81
+ judgePreflight,
82
+ parsePreflight,
83
+ preflightCommand,
84
+ repairCommand,
85
+ resolveUpgrade,
86
+ snapshotCommand,
87
+ snapshotPathFrom
88
+ };
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assertValidPlan,
4
+ validatePlan,
5
+ validatePlanConfig,
6
+ validatePlanNetwork
7
+ } from "./chunk-YR3O2I3S.js";
8
+ import "./chunk-JW5A7E3X.js";
9
+ export {
10
+ assertValidPlan,
11
+ validatePlan,
12
+ validatePlanConfig,
13
+ validatePlanNetwork
14
+ };
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  assertSupportedVersion,
4
4
  loadVersionSpec
5
- } from "./chunk-UKTP332Z.js";
6
- import "./chunk-EI7CQQ7D.js";
7
- import "./chunk-PVUVI35Y.js";
5
+ } from "./chunk-GAZPPSKU.js";
6
+ import "./chunk-JW5A7E3X.js";
8
7
  import {
9
8
  UsageError
10
9
  } from "./chunk-KGXPLI7W.js";
10
+ import "./chunk-EI7CQQ7D.js";
11
11
 
12
12
  // src/cli/version-guard.ts
13
13
  async function defaultOpenclawVersion() {
@@ -9,9 +9,9 @@ import {
9
9
  isMovingTag,
10
10
  loadVersionSpec,
11
11
  resolveVersion
12
- } from "./chunk-UKTP332Z.js";
12
+ } from "./chunk-GAZPPSKU.js";
13
+ import "./chunk-JW5A7E3X.js";
13
14
  import "./chunk-EI7CQQ7D.js";
14
- import "./chunk-PVUVI35Y.js";
15
15
  export {
16
16
  MOVING_TAGS,
17
17
  _resetVersionSpecCache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawops/cli",
3
- "version": "1.7.9",
3
+ "version": "2.0.0",
4
4
  "description": "Deploy and manage self-hosted OpenClaw instances across clouds",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,13 +32,13 @@
32
32
  "test": "vitest run",
33
33
  "test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
34
34
  "test:changed": "vitest --changed",
35
- "test:mutation": "node scripts/dev/mutation-check.mjs",
36
35
  "test:integration": "vitest run --config vitest.integration.config.ts",
37
36
  "typecheck": "NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
38
37
  "lint": "eslint --max-warnings=0 src tests scripts",
39
38
  "gen:schemas": "tsx scripts/gen-schemas.ts",
40
39
  "changeset": "changeset",
41
- "release": "pnpm build && changeset publish"
40
+ "release": "pnpm build && changeset publish",
41
+ "test:mutation": "node scripts/dev/mutation-check.mjs"
42
42
  },
43
43
  "dependencies": {
44
44
  "@aws-sdk/client-cloudwatch-logs": "^3.1063.0",
@@ -4,7 +4,12 @@
4
4
  "title": "DeployPlan",
5
5
  "description": "The Maker plan artifact (borrowed from Clanker pattern). Generated by `clawops plan`, applied by `clawops apply`. Reviewable, diff-able, version-controllable.",
6
6
  "type": "object",
7
- "required": ["apiVersion", "kind", "metadata", "spec"],
7
+ "required": [
8
+ "apiVersion",
9
+ "kind",
10
+ "metadata",
11
+ "spec"
12
+ ],
8
13
  "properties": {
9
14
  "apiVersion": {
10
15
  "const": "clawops.dev/v1",
@@ -15,7 +20,10 @@
15
20
  },
16
21
  "metadata": {
17
22
  "type": "object",
18
- "required": ["name", "generatedAt"],
23
+ "required": [
24
+ "name",
25
+ "generatedAt"
26
+ ],
19
27
  "properties": {
20
28
  "name": {
21
29
  "type": "string",
@@ -38,7 +46,9 @@
38
46
  },
39
47
  "labels": {
40
48
  "type": "object",
41
- "additionalProperties": { "type": "string" },
49
+ "additionalProperties": {
50
+ "type": "string"
51
+ },
42
52
  "description": "User-supplied labels (env=prod, team=platform, etc.)"
43
53
  },
44
54
  "stackVersion": {
@@ -49,10 +59,21 @@
49
59
  },
50
60
  "spec": {
51
61
  "type": "object",
52
- "required": ["provider", "stackName", "instanceType", "openclaw", "network"],
62
+ "required": [
63
+ "provider",
64
+ "stackName",
65
+ "instanceType",
66
+ "openclaw",
67
+ "network"
68
+ ],
53
69
  "properties": {
54
70
  "provider": {
55
- "enum": ["aws", "gcp", "azure", "local"]
71
+ "enum": [
72
+ "aws",
73
+ "gcp",
74
+ "azure",
75
+ "local"
76
+ ]
56
77
  },
57
78
  "region": {
58
79
  "type": "string",
@@ -68,7 +89,9 @@
68
89
  },
69
90
  "openclaw": {
70
91
  "type": "object",
71
- "required": ["version"],
92
+ "required": [
93
+ "version"
94
+ ],
72
95
  "properties": {
73
96
  "version": {
74
97
  "type": "string",
@@ -81,7 +104,9 @@
81
104
  "channels": {
82
105
  "type": "object",
83
106
  "description": "OpenClaw channel integrations keyed by provider name (e.g. {\"discord\":{\"botToken\":\"...\"}}). Values are provider-specific config objects. Sensitive fields must use $secret references.",
84
- "additionalProperties": { "type": "object" }
107
+ "additionalProperties": {
108
+ "type": "object"
109
+ }
85
110
  }
86
111
  }
87
112
  },
@@ -90,11 +115,23 @@
90
115
  "description": "Secret references (R6: never inline values)",
91
116
  "items": {
92
117
  "type": "object",
93
- "required": ["name", "source"],
118
+ "required": [
119
+ "name",
120
+ "source"
121
+ ],
94
122
  "properties": {
95
- "name": { "type": "string" },
123
+ "name": {
124
+ "type": "string"
125
+ },
96
126
  "source": {
97
- "enum": ["env", "aws-sm", "aws-ssm", "gcp-sm", "azure-kv", "file"]
127
+ "enum": [
128
+ "env",
129
+ "aws-sm",
130
+ "aws-ssm",
131
+ "gcp-sm",
132
+ "azure-kv",
133
+ "file"
134
+ ]
98
135
  },
99
136
  "ref": {
100
137
  "type": "string",
@@ -105,26 +142,56 @@
105
142
  },
106
143
  "network": {
107
144
  "type": "object",
108
- "required": ["allowedSshCidrs", "allowedGatewayCidrs"],
145
+ "required": [
146
+ "allowedSshCidrs",
147
+ "allowedGatewayCidrs"
148
+ ],
109
149
  "properties": {
110
150
  "allowedSshCidrs": {
111
151
  "type": "array",
112
- "items": { "type": "string", "format": "cidr" },
152
+ "items": {
153
+ "type": "string",
154
+ "format": "cidr"
155
+ },
113
156
  "minItems": 0,
114
157
  "description": "Empty array = no SSH access (unusual but allowed). Defaults applied at plan generation, not in schema"
115
158
  },
116
159
  "allowedGatewayCidrs": {
117
160
  "type": "array",
118
- "items": { "type": "string", "format": "cidr" },
119
- "description": "CIDRs allowed to reach the OpenClaw gateway port (default 18789)"
161
+ "items": {
162
+ "type": "string",
163
+ "format": "cidr"
164
+ },
165
+ "description": "CIDRs allowed to reach the gateway port. Only meaningful with publishGateway \"all\" — under \"loopback\" nothing listens on a routable interface, so these rules would admit traffic to a closed port. clawops refuses that combination rather than creating rules that do nothing."
166
+ },
167
+ "gatewayPort": {
168
+ "type": "integer",
169
+ "minimum": 1,
170
+ "maximum": 65535,
171
+ "default": 18789,
172
+ "description": "Host port the gateway is published on. Carried in the plan so the firewall rules, the container publish flag, the startup config and the gateway URL all come from one place; it used to be a constant redeclared in six files, which made a port change a six-file edit that could half-apply."
120
173
  },
121
174
  "tailscale": {
122
175
  "type": "object",
123
176
  "description": "Optional Tailscale sidecar (borrowed pattern from schmitthub)",
124
177
  "properties": {
125
- "enabled": { "type": "boolean" },
126
- "authKeyRef": { "type": "string", "description": "Reference to a secret containing the Tailscale auth key" }
178
+ "enabled": {
179
+ "type": "boolean"
180
+ },
181
+ "authKeyRef": {
182
+ "type": "string",
183
+ "description": "Reference to a secret containing the Tailscale auth key"
184
+ }
127
185
  }
186
+ },
187
+ "publishGateway": {
188
+ "type": "string",
189
+ "enum": [
190
+ "loopback",
191
+ "all"
192
+ ],
193
+ "default": "loopback",
194
+ "description": "Which interface the gateway container publishes on. \"loopback\" (default) binds 127.0.0.1 on the host, so the gateway is reachable only via `clawops tunnel` or a reverse proxy running on that host. \"all\" binds 0.0.0.0. Deliberately separate from allowedGatewayCidrs: a firewall rule says who may connect, this says whether the port is listening at all, and exposure should be an explicit choice rather than inherited from a CIDR chosen for SSH. \"all\" serves plaintext HTTP unless you front it with TLS."
128
195
  }
129
196
  }
130
197
  },
@@ -135,12 +202,17 @@
135
202
  "type": "string",
136
203
  "description": "Public key contents (safe to inline). Private key never in plan"
137
204
  },
138
- "user": { "type": "string", "default": "ubuntu" }
205
+ "user": {
206
+ "type": "string",
207
+ "default": "ubuntu"
208
+ }
139
209
  }
140
210
  },
141
211
  "tags": {
142
212
  "type": "object",
143
- "additionalProperties": { "type": "string" },
213
+ "additionalProperties": {
214
+ "type": "string"
215
+ },
144
216
  "description": "Cloud-resource tags propagated to all created resources"
145
217
  }
146
218
  }
@@ -151,15 +223,23 @@
151
223
  "properties": {
152
224
  "create": {
153
225
  "type": "array",
154
- "items": { "$ref": "#/$defs/ResourceRef" }
226
+ "items": {
227
+ "$ref": "#/$defs/ResourceRef"
228
+ }
155
229
  },
156
230
  "update": {
157
231
  "type": "array",
158
232
  "items": {
159
233
  "type": "object",
160
- "required": ["resource", "before", "after"],
234
+ "required": [
235
+ "resource",
236
+ "before",
237
+ "after"
238
+ ],
161
239
  "properties": {
162
- "resource": { "$ref": "#/$defs/ResourceRef" },
240
+ "resource": {
241
+ "$ref": "#/$defs/ResourceRef"
242
+ },
163
243
  "before": {},
164
244
  "after": {}
165
245
  }
@@ -167,16 +247,23 @@
167
247
  },
168
248
  "delete": {
169
249
  "type": "array",
170
- "items": { "$ref": "#/$defs/ResourceRef" }
250
+ "items": {
251
+ "$ref": "#/$defs/ResourceRef"
252
+ }
171
253
  },
172
- "totalChanges": { "type": "integer" }
254
+ "totalChanges": {
255
+ "type": "integer"
256
+ }
173
257
  }
174
258
  }
175
259
  },
176
260
  "$defs": {
177
261
  "ResourceRef": {
178
262
  "type": "object",
179
- "required": ["urn", "type"],
263
+ "required": [
264
+ "urn",
265
+ "type"
266
+ ],
180
267
  "properties": {
181
268
  "urn": {
182
269
  "type": "string",
@@ -186,7 +273,9 @@
186
273
  "type": "string",
187
274
  "description": "Pulumi resource type token"
188
275
  },
189
- "name": { "type": "string" }
276
+ "name": {
277
+ "type": "string"
278
+ }
190
279
  }
191
280
  }
192
281
  }
@@ -1,9 +1,44 @@
1
1
  # spec/integrations.yaml — OpenClaw channel integration catalog for the setup wizard.
2
2
  # Read at runtime by src/cli/commands/setup.ts — not compiled or code-generated.
3
3
  #
4
- # Each integration maps to a key in openclaw.json's `channels` object.
4
+ # Each integration maps to a key in the `channels` object of OpenClaw's config.
5
5
  # infraRequired: true means a publicly reachable webhook URL must be registered
6
6
  # with the provider after the stack is deployed.
7
+ #
8
+ # ── Checked against OpenClaw 2026.9.2 (WO-60) ────────────────────────────────
9
+ #
10
+ # EVERY channel in 2.0 is an install-gated plugin. `openclaw channels list --all --json`
11
+ # reports all 31 as `origin: "installable"`, and none is bundled — the same shape as model
12
+ # providers (WO-43), which is why `plugin:` below carries the package to install.
13
+ #
14
+ # Configuring a channel without installing it produces a gateway that starts, reports
15
+ # healthy, and never connects to the channel.
16
+ #
17
+ # `dmPolicy` and `groupPolicy` are REQUIRED by the schema on every channel here, and Slack
18
+ # and WhatsApp require more. `defaults:` carries OpenClaw's own default for each of them, and
19
+ # the wizard writes them — a JSON Schema default does not satisfy `required`, so a config
20
+ # omitting them is REJECTED. Every channel the wizard wrote was invalid before this.
21
+ #
22
+ # Every credential field also accepts a SecretRef — `{source: env, provider, id}` — as well
23
+ # as a plain string, so a token need not be inlined into the config.
24
+ #
25
+ # The catalog was wrong in four ways before this pass, all of which would have produced a
26
+ # config OpenClaw rejects or ignores: the Teams key was `teams` (the schema has `msteams`),
27
+ # Discord's token field was `botToken` (it is `token`), WhatsApp's `phoneNumberId` and
28
+ # `accessToken` do not exist on that channel at all, and EVERY `envDefault` used an
29
+ # `OPENCLAW_*` name that OpenClaw does not read.
30
+ #
31
+ # `envVar` below is the variable `openclaw channels add --use-env` actually reads, taken from
32
+ # the binary's own error message. `useEnvSupported: false` means that flag is rejected for the
33
+ # channel outright — measured, not assumed.
34
+ #
35
+ # `plugin.version` is pinned to the runtime floor, and it is not decoration. Measured on
36
+ # 2026.9.2: `@openclaw/discord@2026.9.3` refuses to install with
37
+ #
38
+ # plugin "discord" requires plugin API >=2026.9.3, but this OpenClaw runtime exposes 2026.9.2
39
+ #
40
+ # The same drift that forced version pins on model providers in WO-43. `latest` would break
41
+ # the deploy the moment upstream publishes ahead of the runtime, which it already has.
7
42
 
8
43
  integrations:
9
44
  - id: discord
@@ -11,12 +46,21 @@ integrations:
11
46
  displayName: Discord
12
47
  description: Connect OpenClaw agents to a Discord server via bot
13
48
  infraRequired: false
49
+ plugin:
50
+ package: "@openclaw/discord"
51
+ source: npm
52
+ version: "2026.9.2"
53
+ useEnvSupported: true
54
+ requiredConfig: [dmPolicy, groupPolicy]
55
+ defaults:
56
+ dmPolicy: pairing
57
+ groupPolicy: allowlist
14
58
  fields:
15
- - name: botToken
59
+ - name: token
16
60
  label: Bot token
17
61
  description: Bot token from the Discord Developer Portal (Bot → Token)
18
62
  sensitive: true
19
- envDefault: OPENCLAW_DISCORD_TOKEN
63
+ envDefault: DISCORD_BOT_TOKEN
20
64
  setupUrl: https://discord.com/developers/applications
21
65
 
22
66
  - id: telegram
@@ -24,33 +68,69 @@ integrations:
24
68
  displayName: Telegram
25
69
  description: Connect OpenClaw agents to a Telegram bot
26
70
  infraRequired: false
71
+ # Bundled in the image: `channels add --channel telegram --use-env` succeeds with
72
+ # `--network none`, so there is nothing to download and no egress needed. It is listed
73
+ # as "installable" alongside the rest, which is why this looked unresolved at first.
74
+ plugin:
75
+ package: ""
76
+ source: bundled
77
+ useEnvSupported: true
78
+ requiredConfig: [dmPolicy, groupPolicy]
79
+ defaults:
80
+ dmPolicy: pairing
81
+ groupPolicy: allowlist
27
82
  fields:
28
83
  - name: botToken
29
84
  label: Bot token
30
85
  description: Token from @BotFather (/newbot)
31
86
  sensitive: true
32
- envDefault: OPENCLAW_TELEGRAM_TOKEN
87
+ envDefault: TELEGRAM_BOT_TOKEN
33
88
  setupUrl: https://t.me/BotFather
34
89
 
35
90
  - id: slack
36
91
  channelKey: slack
37
92
  displayName: Slack
38
- description: Connect OpenClaw agents to a Slack workspace via app
39
- infraRequired: true
40
- infraNote: >
41
- After deployment, register the gateway webhook URL in your Slack app
42
- configuration at api.slack.com (Event Subscriptions → Request URL).
93
+ description: Connect OpenClaw agents to a Slack workspace via app (Socket Mode)
94
+ # Socket Mode, not a webhook. `mode` defaults to "socket" in OpenClaw and it is what
95
+ # `channels add --use-env` drives: the gateway opens an OUTBOUND WebSocket to Slack, so
96
+ # there is no public URL to register and no inbound rule to open.
97
+ #
98
+ # This entry said infraRequired: true — the `http` mode setup — while declaring the
99
+ # socket credentials incompletely. It described one setup while the tooling installed
100
+ # the other, and the config it produced failed validation either way.
101
+ #
102
+ # `mode: http` is still available: it needs a public webhook at `webhookPath` and a
103
+ # signingSecret to verify inbound requests. That is a deliberate choice, not the default,
104
+ # and clawops does not configure it.
105
+ infraRequired: false
106
+ plugin:
107
+ package: "@openclaw/slack"
108
+ source: npm
109
+ version: "2026.9.2"
110
+ # Socket Mode is what --use-env drives, and it demands SLACK_APP_TOKEN:
111
+ # "Slack Socket Mode requires SLACK_APP_TOKEN when using --use-env."
112
+ useEnvSupported: true
113
+ # Slack requires more than the two policies: the schema also demands postAs, mode,
114
+ # webhookPath and userTokenReadOnly.
115
+ requiredConfig: [dmPolicy, groupPolicy, postAs, mode, webhookPath, userTokenReadOnly]
116
+ defaults:
117
+ dmPolicy: pairing
118
+ groupPolicy: allowlist
119
+ postAs: bot
120
+ mode: socket
121
+ webhookPath: /slack/events
122
+ userTokenReadOnly: true
43
123
  fields:
44
124
  - name: botToken
45
125
  label: Bot OAuth token
46
126
  description: "Bot User OAuth Token (starts with xoxb-)"
47
127
  sensitive: true
48
- envDefault: OPENCLAW_SLACK_TOKEN
49
- - name: signingSecret
50
- label: Signing secret
51
- description: App signing secret from Basic Information → App Credentials
128
+ envDefault: SLACK_BOT_TOKEN
129
+ - name: appToken
130
+ label: App-level token
131
+ description: "App-level token for Socket Mode (starts with xapp-), from Basic Information → App-Level Tokens"
52
132
  sensitive: true
53
- envDefault: OPENCLAW_SLACK_SIGNING_SECRET
133
+ envDefault: SLACK_APP_TOKEN
54
134
  setupUrl: https://api.slack.com/apps
55
135
 
56
136
  - id: whatsapp
@@ -62,27 +142,49 @@ integrations:
62
142
  After deployment, register the gateway webhook URL in the Meta Developer
63
143
  Console (WhatsApp → Configuration → Webhook). The gateway URL is printed
64
144
  at the end of setup.
65
- fields:
66
- - name: phoneNumberId
67
- label: Phone number ID
68
- description: Phone Number ID from Meta Developer Console → WhatsApp → API Setup
69
- sensitive: false
70
- envDefault: ""
71
- - name: accessToken
72
- label: Access token
73
- description: Permanent system user access token from Meta Business Suite
74
- sensitive: true
75
- envDefault: OPENCLAW_WHATSAPP_TOKEN
145
+ plugin:
146
+ package: "@openclaw/whatsapp"
147
+ source: npm
148
+ version: "2026.9.2"
149
+ # `openclaw channels add --use-env` answers: OpenClaw does not recognize option
150
+ # "--use-env". There is no non-interactive path for this channel.
151
+ useEnvSupported: false
152
+ requiredConfig: [dmPolicy, groupPolicy, mediaMaxMb]
153
+ defaults:
154
+ dmPolicy: pairing
155
+ groupPolicy: allowlist
156
+ mediaMaxMb: 50
157
+ # WhatsApp has no top-level credential fields in 2.0 — credentials live under
158
+ # `accounts.<name>`, with `defaultAccount` selecting one. The wizard cannot collect a
159
+ # flat token for it, so it is marked unsupported rather than collecting fields that go
160
+ # nowhere. `phoneNumberId` and `accessToken`, which this entry used to declare, are not
161
+ # properties of the whatsapp channel at all.
162
+ wizardSupported: false
163
+ unsupportedNote: >
164
+ WhatsApp uses per-account credentials (`channels.whatsapp.accounts.<name>`) rather
165
+ than a single token. Configure it with `openclaw channels add --channel whatsapp`
166
+ on the host after deployment.
167
+ fields: []
76
168
  setupUrl: https://developers.facebook.com/apps
77
169
 
78
- - id: teams
79
- channelKey: teams
170
+ - id: msteams
171
+ channelKey: msteams
80
172
  displayName: Microsoft Teams
81
173
  description: Connect OpenClaw agents to a Microsoft Teams channel via bot
82
174
  infraRequired: true
83
175
  infraNote: >
84
176
  After deployment, update the bot's messaging endpoint in Azure Bot Service
85
177
  to the gateway webhook URL.
178
+ plugin:
179
+ package: "@openclaw/msteams"
180
+ source: npm
181
+ version: "2026.9.2"
182
+ # Same as WhatsApp: --use-env is rejected outright.
183
+ useEnvSupported: false
184
+ requiredConfig: [dmPolicy, groupPolicy]
185
+ defaults:
186
+ dmPolicy: pairing
187
+ groupPolicy: allowlist
86
188
  fields:
87
189
  - name: appId
88
190
  label: App (client) ID
@@ -93,5 +195,10 @@ integrations:
93
195
  label: App password
94
196
  description: Client secret from the Azure app registration
95
197
  sensitive: true
96
- envDefault: OPENCLAW_TEAMS_APP_PASSWORD
198
+ envDefault: "" # no --use-env path; collected interactively on the host
199
+ - name: tenantId
200
+ label: Directory (tenant) ID
201
+ description: Azure AD tenant ID for the app registration
202
+ sensitive: false
203
+ envDefault: ""
97
204
  setupUrl: https://portal.azure.com/