@nubemclaw/channel-qq-bot 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.
package/LICENSE.md ADDED
@@ -0,0 +1,59 @@
1
+ # LICENSE — NubemClaw v3
2
+
3
+ **Copyright © 2026 Nubemsystems S.L.** All rights reserved.
4
+
5
+ NubemClaw v3 (this monorepo and every `@nubemclaw/*` package published
6
+ under it on the npm registry) is **proprietary software** distributed
7
+ publicly under the SPDX identifier **`UNLICENSED`**. Public
8
+ distribution via npm does NOT make this an open-source project. The
9
+ absence of a permissive license is intentional — the source is
10
+ visible for transparency and operator use, but the rights below apply.
11
+
12
+ ## Permitted
13
+
14
+ - Installing and running the published `@nubemclaw/*` packages from
15
+ npm for personal, internal-business, or research use.
16
+ - Reading the source code on the public repository.
17
+ - Submitting issues, pull requests, and feedback to the upstream
18
+ repository (`nubemsystemsdev/NubemClaw-v3`). Contributions are
19
+ accepted under the Contributor License Agreement (CLA) gated at PR
20
+ time; by submitting a contribution you grant Nubemsystems a
21
+ perpetual, worldwide, non-exclusive license to use the contribution
22
+ under this proprietary license.
23
+
24
+ ## NOT permitted (without prior written authorisation from Nubemsystems S.L.)
25
+
26
+ - Redistribution of the source or binaries outside the official npm
27
+ registry / GitHub release artifacts.
28
+ - Forking and republishing under a different name or scope.
29
+ - Removing or modifying the copyright notice in this file or in the
30
+ package manifests.
31
+ - Selling, sublicensing, or offering as a hosted SaaS that competes
32
+ with Nubemsystems' commercial offerings.
33
+ - Reverse-engineering the cloud control plane (when one exists) or
34
+ any non-source components shipped alongside.
35
+
36
+ ## Trademarks
37
+
38
+ "NubemClaw", "Nubemsystems", and associated logos are trademarks of
39
+ Nubemsystems S.L. They are not licensed by this document — using them
40
+ to identify your fork, derivative, or service requires explicit
41
+ written permission.
42
+
43
+ ## Warranty disclaimer
44
+
45
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
46
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
47
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
48
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
49
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
50
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
51
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
52
+ SOFTWARE.
53
+
54
+ ## Contact
55
+
56
+ Licensing inquiries: `licensing@nubemsystems.es`.
57
+
58
+ Operator (single point of contact for v3): José Luis Manzanares
59
+ Fernández, Nubemsystems S.L. (`joseluis.manzanares@nubemsystems.es`).
@@ -0,0 +1,30 @@
1
+ import { type Channel, type ChannelTarget, type RestFetch } from "@nubemclaw/channel-sdk";
2
+ /**
3
+ * F31.b — QQ Bot channel adapter (F31-fix.1: real provider body shape).
4
+ *
5
+ * QQ Bot OpenAPI group messages.
6
+ *
7
+ * Endpoint:
8
+ * POST https://api.sgroup.qq.com/v2/groups/{target}/messages
9
+ * Header: Authorization: Bot <token>
10
+ * Body: ({ content: text, msg_type: 0 })
11
+ *
12
+ * The package OWNS its lifecycle: it does NOT inherit from a unified
13
+ * factory across the 21 F31.b adapters. `createRestChannelBase`
14
+ * (channel-sdk) is a code-reuse helper for the HTTP send path; each
15
+ * package wires its own auth + body + (future) inbound parser.
16
+ */
17
+ export interface QqBotChannelConfig {
18
+ readonly token: string;
19
+ readonly endpoint?: string;
20
+ readonly fetch?: RestFetch;
21
+ readonly capabilities?: import("@nubemclaw/channel-sdk").ChannelCapabilities;
22
+ }
23
+ export declare const DEFAULT_QQBOT_ENDPOINT = "https://api.sgroup.qq.com/v2/groups/{target}/messages";
24
+ export declare const buildSendBody: (_target: ChannelTarget, text: string) => Record<string, unknown>;
25
+ export declare const buildSendRequest: (endpoint: string, token: string, target: ChannelTarget, text: string) => {
26
+ url: string;
27
+ init: RequestInit;
28
+ };
29
+ export declare const createQqBotChannel: (config: QqBotChannelConfig) => Channel;
30
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,SAAS,EAGf,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,wBAAwB,EAAE,mBAAmB,CAAC;CAC9E;AAED,eAAO,MAAM,sBAAsB,0DAA0D,CAAC;AAK9F,eAAO,MAAM,aAAa,GAAI,SAAS,aAAa,EAAE,MAAM,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAGzF,CAAC;AAEH,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,EAChB,OAAO,MAAM,EACb,QAAQ,aAAa,EACrB,MAAM,MAAM,KACX;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAalC,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,kBAAkB,KAAG,OAqB/D,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { TEXT_ONLY_REST_CAPABILITIES, createRestChannelBase, extractMessageText, } from "@nubemclaw/channel-sdk";
2
+ export const DEFAULT_QQBOT_ENDPOINT = "https://api.sgroup.qq.com/v2/groups/{target}/messages";
3
+ const replaceTargetPlaceholder = (endpoint, target) => endpoint.replace(/\{target\}/g, encodeURIComponent(target.id));
4
+ export const buildSendBody = (_target, text) => ({
5
+ content: text,
6
+ msg_type: 0,
7
+ });
8
+ export const buildSendRequest = (endpoint, token, target, text) => {
9
+ const url = replaceTargetPlaceholder(endpoint, target);
10
+ const headers = { "content-type": "application/json" };
11
+ headers["Authorization"] = `Bot ${token}`;
12
+ return {
13
+ url,
14
+ init: {
15
+ method: "POST",
16
+ headers,
17
+ body: JSON.stringify(buildSendBody(target, text)),
18
+ },
19
+ };
20
+ };
21
+ export const createQqBotChannel = (config) => {
22
+ const endpoint = config.endpoint ?? DEFAULT_QQBOT_ENDPOINT;
23
+ const sendImpl = async (args) => {
24
+ const text = extractMessageText(args.message);
25
+ const { url, init } = buildSendRequest(endpoint, config.token, args.target, text);
26
+ const res = await args.fetch(url, init);
27
+ let messageId;
28
+ try {
29
+ const body = (await res.json());
30
+ messageId = body.id ?? body.messageId ?? body.ts;
31
+ }
32
+ catch {
33
+ // Some channels return text/204 — that's fine.
34
+ }
35
+ return messageId !== undefined ? { status: res.status, messageId } : { status: res.status };
36
+ };
37
+ return createRestChannelBase({
38
+ id: "qq-bot",
39
+ capabilities: config.capabilities ?? TEXT_ONLY_REST_CAPABILITIES,
40
+ send: sendImpl,
41
+ ...(config.fetch !== undefined ? { fetch: config.fetch } : {}),
42
+ });
43
+ };
44
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,kBAAkB,GAMnB,MAAM,wBAAwB,CAAC;AAyBhC,MAAM,CAAC,MAAM,sBAAsB,GAAG,uDAAuD,CAAC;AAE9F,MAAM,wBAAwB,GAAG,CAAC,QAAgB,EAAE,MAAqB,EAAU,EAAE,CACnF,QAAQ,CAAC,OAAO,CAAC,aAAa,EAAE,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AAEjE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAsB,EAAE,IAAY,EAA2B,EAAE,CAAC,CAAC;IAC/F,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,CAAC;CACZ,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,QAAgB,EAChB,KAAa,EACb,MAAqB,EACrB,IAAY,EACwB,EAAE;IACtC,MAAM,GAAG,GAAG,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvD,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;IAC/E,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,KAAK,EAAE,CAAC;IAE1C,OAAO;QACL,GAAG;QACH,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;SAClD;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAA0B,EAAW,EAAE;IACxE,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,sBAAsB,CAAC;IAC3D,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAkB,EAA2B,EAAE;QACrE,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAClF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,SAA6B,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAqD,CAAC;YACpF,SAAS,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,EAAE,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,+CAA+C;QACjD,CAAC;QACD,OAAO,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAC9F,CAAC,CAAC;IACF,OAAO,qBAAqB,CAAC;QAC3B,EAAE,EAAE,QAAQ;QACZ,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,2BAA2B;QAChE,IAAI,EAAE,QAAQ;QACd,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./channel.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./channel.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@nubemclaw/channel-qq-bot",
3
+ "version": "2.0.0",
4
+ "description": "NubemClaw v3 — QQ Bot channel (F31.b). QQ Bot OpenAPI.",
5
+ "license": "UNLICENSED",
6
+ "files": [
7
+ "dist",
8
+ "src"
9
+ ],
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "dependencies": {
20
+ "zod": "^3.23.8",
21
+ "@nubemclaw/channel-sdk": "2.0.0",
22
+ "@nubemclaw/core": "2.0.0"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc -b",
26
+ "clean": "tsc -b --clean && rm -rf dist .cache"
27
+ }
28
+ }
@@ -0,0 +1,136 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import {
4
+ createQqBotChannel,
5
+ buildSendBody,
6
+ buildSendRequest,
7
+ DEFAULT_QQBOT_ENDPOINT,
8
+ } from "./channel.js";
9
+ import type { ChannelDeps } from "@nubemclaw/channel-sdk";
10
+
11
+ const fakeDeps = (): ChannelDeps => ({
12
+ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
13
+ state: { get: async () => undefined, set: async () => {}, delete: async () => {} },
14
+ allowlist: { allows: () => true, size: 0 },
15
+ onInbound: async () => {},
16
+ });
17
+
18
+ describe("createQqBotChannel", () => {
19
+ it("exposes a working Channel with id qq-bot", () => {
20
+ const ch = createQqBotChannel({ token: "t" });
21
+ expect(ch.id).toBe("qq-bot");
22
+ expect(ch.capabilities.mediaTypes).toContain("text");
23
+ });
24
+
25
+ it("DEFAULT endpoint constant is the canonical provider URL", () => {
26
+ expect(DEFAULT_QQBOT_ENDPOINT).toContain("api.sgroup.qq.com");
27
+ });
28
+
29
+ it("buildSendBody returns the provider-real body shape (audit-1 finding 1)", () => {
30
+ const body = buildSendBody({ kind: "chat", id: "target-1" }, "hello") as Record<
31
+ string,
32
+ unknown
33
+ > & {
34
+ chatId?: unknown;
35
+ content?: unknown;
36
+ channel?: unknown;
37
+ text?: unknown;
38
+ to?: unknown;
39
+ messages?: unknown;
40
+ msgtype?: unknown;
41
+ body?: unknown;
42
+ message?: unknown;
43
+ messaging_product?: unknown;
44
+ receive_id?: unknown;
45
+ msg_type?: unknown;
46
+ msgType?: unknown;
47
+ ship?: unknown;
48
+ app?: unknown;
49
+ recipient?: unknown;
50
+ conversation_id?: unknown;
51
+ payload?: unknown;
52
+ touser?: unknown;
53
+ number?: unknown;
54
+ recipients?: unknown;
55
+ type?: unknown;
56
+ broadcaster_id?: unknown;
57
+ sender_id?: unknown;
58
+ json?: unknown;
59
+ content_type?: unknown;
60
+ agentid?: unknown;
61
+ chatGuid?: unknown;
62
+ method?: unknown;
63
+ };
64
+ expect(body.content).toBe("hello");
65
+ expect(body.msg_type).toBe(0);
66
+ });
67
+
68
+ it("send() POSTs to the channel endpoint with token-bound auth", async () => {
69
+ const fetchSpy = vi.fn(
70
+ async () =>
71
+ new Response("{}", { status: 200, headers: { "content-type": "application/json" } }),
72
+ );
73
+ const ch = createQqBotChannel({ token: "tok-1", fetch: fetchSpy as unknown as typeof fetch });
74
+ await ch.init(fakeDeps());
75
+ await ch.send({ kind: "chat", id: "target-1" }, { type: "text", text: "hello" });
76
+ expect(fetchSpy).toHaveBeenCalledOnce();
77
+ const call = fetchSpy.mock.calls.at(0);
78
+ const init = call?.[1] as RequestInit | undefined;
79
+ expect(init?.method).toBe("POST");
80
+ const body = JSON.parse((init?.body ?? "{}") as string) as Record<string, unknown> & {
81
+ chatId?: unknown;
82
+ content?: unknown;
83
+ channel?: unknown;
84
+ text?: unknown;
85
+ to?: unknown;
86
+ messages?: unknown;
87
+ msgtype?: unknown;
88
+ body?: unknown;
89
+ message?: unknown;
90
+ messaging_product?: unknown;
91
+ receive_id?: unknown;
92
+ msg_type?: unknown;
93
+ msgType?: unknown;
94
+ ship?: unknown;
95
+ app?: unknown;
96
+ recipient?: unknown;
97
+ conversation_id?: unknown;
98
+ payload?: unknown;
99
+ touser?: unknown;
100
+ number?: unknown;
101
+ recipients?: unknown;
102
+ type?: unknown;
103
+ broadcaster_id?: unknown;
104
+ sender_id?: unknown;
105
+ json?: unknown;
106
+ content_type?: unknown;
107
+ agentid?: unknown;
108
+ chatGuid?: unknown;
109
+ method?: unknown;
110
+ };
111
+ expect(body.content).toBe("hello");
112
+ expect(body.msg_type).toBe(0);
113
+ });
114
+
115
+ it("send() throws on non-2xx response", async () => {
116
+ const fetchSpy = vi.fn(async () => new Response("server error", { status: 500 }));
117
+ const ch = createQqBotChannel({ token: "tok", fetch: fetchSpy as unknown as typeof fetch });
118
+ await ch.init(fakeDeps());
119
+ await expect(ch.send({ kind: "chat", id: "x" }, { type: "text", text: "msg" })).rejects.toThrow(
120
+ /qq-bot send failed with status 500/,
121
+ );
122
+ });
123
+
124
+ it("buildSendRequest substitutes {target} placeholders when present", () => {
125
+ const out = buildSendRequest(
126
+ "https://api.example/{target}/send",
127
+ "tok",
128
+ { kind: "chat", id: "abc123" },
129
+ "hi",
130
+ );
131
+ if (DEFAULT_QQBOT_ENDPOINT.includes("{target}")) {
132
+ expect(out.url).toContain("abc123");
133
+ }
134
+ expect(out.init.method).toBe("POST");
135
+ });
136
+ });
package/src/channel.ts ADDED
@@ -0,0 +1,86 @@
1
+ import {
2
+ TEXT_ONLY_REST_CAPABILITIES,
3
+ createRestChannelBase,
4
+ extractMessageText,
5
+ type Channel,
6
+ type ChannelTarget,
7
+ type RestFetch,
8
+ type RestSendArgs,
9
+ type RestSendResult,
10
+ } from "@nubemclaw/channel-sdk";
11
+
12
+ /**
13
+ * F31.b — QQ Bot channel adapter (F31-fix.1: real provider body shape).
14
+ *
15
+ * QQ Bot OpenAPI group messages.
16
+ *
17
+ * Endpoint:
18
+ * POST https://api.sgroup.qq.com/v2/groups/{target}/messages
19
+ * Header: Authorization: Bot <token>
20
+ * Body: ({ content: text, msg_type: 0 })
21
+ *
22
+ * The package OWNS its lifecycle: it does NOT inherit from a unified
23
+ * factory across the 21 F31.b adapters. `createRestChannelBase`
24
+ * (channel-sdk) is a code-reuse helper for the HTTP send path; each
25
+ * package wires its own auth + body + (future) inbound parser.
26
+ */
27
+
28
+ export interface QqBotChannelConfig {
29
+ readonly token: string;
30
+ readonly endpoint?: string;
31
+ readonly fetch?: RestFetch;
32
+ readonly capabilities?: import("@nubemclaw/channel-sdk").ChannelCapabilities;
33
+ }
34
+
35
+ export const DEFAULT_QQBOT_ENDPOINT = "https://api.sgroup.qq.com/v2/groups/{target}/messages";
36
+
37
+ const replaceTargetPlaceholder = (endpoint: string, target: ChannelTarget): string =>
38
+ endpoint.replace(/\{target\}/g, encodeURIComponent(target.id));
39
+
40
+ export const buildSendBody = (_target: ChannelTarget, text: string): Record<string, unknown> => ({
41
+ content: text,
42
+ msg_type: 0,
43
+ });
44
+
45
+ export const buildSendRequest = (
46
+ endpoint: string,
47
+ token: string,
48
+ target: ChannelTarget,
49
+ text: string,
50
+ ): { url: string; init: RequestInit } => {
51
+ const url = replaceTargetPlaceholder(endpoint, target);
52
+ const headers: Record<string, string> = { "content-type": "application/json" };
53
+ headers["Authorization"] = `Bot ${token}`;
54
+
55
+ return {
56
+ url,
57
+ init: {
58
+ method: "POST",
59
+ headers,
60
+ body: JSON.stringify(buildSendBody(target, text)),
61
+ },
62
+ };
63
+ };
64
+
65
+ export const createQqBotChannel = (config: QqBotChannelConfig): Channel => {
66
+ const endpoint = config.endpoint ?? DEFAULT_QQBOT_ENDPOINT;
67
+ const sendImpl = async (args: RestSendArgs): Promise<RestSendResult> => {
68
+ const text = extractMessageText(args.message);
69
+ const { url, init } = buildSendRequest(endpoint, config.token, args.target, text);
70
+ const res = await args.fetch(url, init);
71
+ let messageId: string | undefined;
72
+ try {
73
+ const body = (await res.json()) as { id?: string; messageId?: string; ts?: string };
74
+ messageId = body.id ?? body.messageId ?? body.ts;
75
+ } catch {
76
+ // Some channels return text/204 — that's fine.
77
+ }
78
+ return messageId !== undefined ? { status: res.status, messageId } : { status: res.status };
79
+ };
80
+ return createRestChannelBase({
81
+ id: "qq-bot",
82
+ capabilities: config.capabilities ?? TEXT_ONLY_REST_CAPABILITIES,
83
+ send: sendImpl,
84
+ ...(config.fetch !== undefined ? { fetch: config.fetch } : {}),
85
+ });
86
+ };
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./channel.js";