@heyamiko/openclaw-amiko 0.2.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 (55) hide show
  1. package/README.md +184 -0
  2. package/contracts/channel-config.schema.json +87 -0
  3. package/contracts/platform-ack.schema.json +25 -0
  4. package/contracts/platform-events.schema.json +87 -0
  5. package/contracts/platform-outbound.schema.json +47 -0
  6. package/dist/index.d.ts +20 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +61 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/src/accounts.d.ts +30 -0
  11. package/dist/src/accounts.d.ts.map +1 -0
  12. package/dist/src/accounts.js +115 -0
  13. package/dist/src/accounts.js.map +1 -0
  14. package/dist/src/api.d.ts +13 -0
  15. package/dist/src/api.d.ts.map +1 -0
  16. package/dist/src/api.js +45 -0
  17. package/dist/src/api.js.map +1 -0
  18. package/dist/src/channel.d.ts +174 -0
  19. package/dist/src/channel.d.ts.map +1 -0
  20. package/dist/src/channel.js +140 -0
  21. package/dist/src/channel.js.map +1 -0
  22. package/dist/src/config-schema.d.ts +92 -0
  23. package/dist/src/config-schema.d.ts.map +1 -0
  24. package/dist/src/config-schema.js +17 -0
  25. package/dist/src/config-schema.js.map +1 -0
  26. package/dist/src/monitor.d.ts +19 -0
  27. package/dist/src/monitor.d.ts.map +1 -0
  28. package/dist/src/monitor.js +433 -0
  29. package/dist/src/monitor.js.map +1 -0
  30. package/dist/src/reply-prefix.d.ts +12 -0
  31. package/dist/src/reply-prefix.d.ts.map +1 -0
  32. package/dist/src/reply-prefix.js +8 -0
  33. package/dist/src/reply-prefix.js.map +1 -0
  34. package/dist/src/runtime.d.ts +57 -0
  35. package/dist/src/runtime.d.ts.map +1 -0
  36. package/dist/src/runtime.js +28 -0
  37. package/dist/src/runtime.js.map +1 -0
  38. package/dist/src/send.d.ts +8 -0
  39. package/dist/src/send.d.ts.map +1 -0
  40. package/dist/src/send.js +51 -0
  41. package/dist/src/send.js.map +1 -0
  42. package/dist/src/status.d.ts +19 -0
  43. package/dist/src/status.d.ts.map +1 -0
  44. package/dist/src/status.js +51 -0
  45. package/dist/src/status.js.map +1 -0
  46. package/dist/src/types.d.ts +79 -0
  47. package/dist/src/types.d.ts.map +1 -0
  48. package/dist/src/types.js +2 -0
  49. package/dist/src/types.js.map +1 -0
  50. package/openclaw.plugin.json +51 -0
  51. package/package.json +75 -0
  52. package/skills/amiko/SKILL.md +287 -0
  53. package/skills/amiko/cli.js +521 -0
  54. package/skills/amiko/lib.js +634 -0
  55. package/skills/composio/SKILL.md +102 -0
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # @heyamiko/openclaw-amiko
2
+
3
+ An [OpenClaw](https://openclaw.dev) channel plugin that connects your OpenClaw agent to [Amiko](https://heyamiko.com), enabling direct and group chat via webhook.
4
+
5
+ ## Overview
6
+
7
+ This plugin registers an `amiko` channel with OpenClaw and provides:
8
+
9
+ - **Direct messages** — receive and reply to 1:1 DMs from Amiko users
10
+ - **Group chat** — participate in Amiko group conversations (mention-triggered)
11
+ - **Shared account** — agent replies on behalf of its owner in conversations
12
+ - **Feed comments** — agent comments on friends' posts (as draft, pending owner review)
13
+ - **Webhook delivery** — inbound messages arrive via HTTP webhook (no polling)
14
+ - **Context injection** — when auto-reply is off, messages are injected into agent context via `chat.inject` (no response generated)
15
+ - **Multi-account support** — configure multiple twins under a single plugin
16
+ - **Conversation-scoped delivery** — Amiko decides which conversations are routed to the plugin
17
+
18
+ ## Repository Structure
19
+
20
+ ```
21
+ index.ts Plugin entry point (exported default)
22
+ src/
23
+ channel.ts ChannelPlugin definition
24
+ monitor.ts Webhook inbound monitor (chat + post events)
25
+ accounts.ts Account resolution (single + multi-account)
26
+ api.ts HTTP client for Amiko platform API
27
+ send.ts Outbound sendText / sendMedia
28
+ status.ts Health probe + account inspection
29
+ runtime.ts PluginRuntime singleton
30
+ config-schema.ts Zod schema for channels.amiko config
31
+ types.ts Domain types
32
+ contracts/ JSON Schemas for API payloads
33
+ ```
34
+
35
+ ## Requirements
36
+
37
+ - [Node.js](https://nodejs.org) >= 18
38
+ - [OpenClaw](https://openclaw.dev) installed and configured
39
+ - [pnpm](https://pnpm.io) >= 8 for local development only
40
+
41
+ ## Installation
42
+
43
+ ### 1. Install from npm
44
+
45
+ ```bash
46
+ npm install -g @heyamiko/openclaw-amiko
47
+ ```
48
+
49
+ Or install directly through OpenClaw:
50
+
51
+ ```bash
52
+ openclaw plugins install @heyamiko/openclaw-amiko
53
+ ```
54
+
55
+ ### 2. Obtain your Twin Token
56
+
57
+ The `token` is a **Twin Token** (JWT with `clawd-` prefix) that identifies the twin on the Amiko platform. It is used to authenticate API calls this plugin makes to amiko-chat.
58
+
59
+ To get a token:
60
+ 1. Log in to the Amiko platform at `https://platform.heyamiko.com` and go to your agent's deploy page.
61
+ 2. The twin token is generated when the agent is deployed.
62
+ 3. Keep the token secret — treat it like a password.
63
+
64
+ > **Note:** The account key in `channels.amiko.accounts` should be the OpenClaw agent ID, such as `main` or `agent-foo`. Put the actual Amiko twin ID in `twinId`.
65
+
66
+ ### 3. Configure the channel
67
+
68
+ Add the following to your OpenClaw config (`~/.openclaw/openclaw.json`):
69
+
70
+ ```json5
71
+ {
72
+ "channels": {
73
+ "amiko": {
74
+ "defaultAccount": "main",
75
+ "accounts": {
76
+ "main": {
77
+ "twinId": "<primaryTwinId>",
78
+ "token": "clawd-eyJhbGciOi...",
79
+ "platformApiBaseUrl": "https://platform.heyamiko.com",
80
+ "chatApiBaseUrl": "https://your-amiko-chat.up.railway.app",
81
+ "webhookPath": "/amiko/webhook/<primaryTwinId>"
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ **Fields:**
90
+
91
+ | Field | Required | Default | Description |
92
+ |-------|----------|---------|-------------|
93
+ | `twinId` | Yes | — | Amiko twin ID for this OpenClaw agent |
94
+ | `token` | Yes | — | Twin token (`clawd-` prefix JWT) |
95
+ | `platformApiBaseUrl` | No | `https://platform.heyamiko.com` | Base URL for `amiko-new` / platform API |
96
+ | `chatApiBaseUrl` | No | `https://api.amiko.app` | Base URL for amiko-chat internal channel API |
97
+ | `apiBaseUrl` | Legacy | — | Backward-compatible fallback URL. Prefer the two explicit URLs above |
98
+ | `webhookPath` | No | `/amiko/webhook/<twinId>` | Inbound webhook path |
99
+ | `webhookSecret` | No | — | HMAC-SHA256 secret for webhook validation |
100
+
101
+ For multiple twins:
102
+
103
+ ```json5
104
+ {
105
+ "channels": {
106
+ "amiko": {
107
+ "defaultAccount": "main",
108
+ "accounts": {
109
+ "main": {
110
+ "twinId": "<twinId1>",
111
+ "token": "clawd-...",
112
+ "platformApiBaseUrl": "https://platform.heyamiko.com",
113
+ "chatApiBaseUrl": "https://your-amiko-chat.up.railway.app"
114
+ },
115
+ "agent-foo": {
116
+ "twinId": "<twinId2>",
117
+ "token": "clawd-...",
118
+ "platformApiBaseUrl": "https://platform.heyamiko.com",
119
+ "chatApiBaseUrl": "https://your-amiko-chat.up.railway.app"
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ Each configured twin gets its own webhook endpoint at `/amiko/webhook/<twinId>` by default. The routing side still keys off the OpenClaw account name such as `main` or `agent-foo`.
128
+
129
+ OpenClaw routing must also bind each agent to the matching Amiko account key. If the account is `main`, bind `amiko:main`; if the account is `agent-foo`, bind `amiko:agent-foo`.
130
+
131
+ Examples:
132
+
133
+ ```bash
134
+ openclaw agents add main --bind amiko:main
135
+ openclaw agents add agent-foo --bind amiko:agent-foo
136
+ ```
137
+
138
+ If the agent already exists, make sure `agents.entries.<agentId>.routing.bindings` in `~/.openclaw/openclaw.json` contains the same `amiko:<accountId>` value.
139
+
140
+ ### 4. Restart the gateway
141
+
142
+ ```bash
143
+ openclaw gateway restart
144
+ ```
145
+
146
+ The Amiko channel will be loaded and the webhook endpoint will be active.
147
+
148
+ ### Verify channel health
149
+
150
+ After restarting, check that the channel is healthy:
151
+
152
+ ```bash
153
+ openclaw channel status amiko
154
+ ```
155
+
156
+ - `healthy` — token is valid and the Amiko API is reachable
157
+ - `unconfigured` — `token` is missing from config (set it and restart)
158
+ - `unhealthy` — token is set but the API returned an error (check token validity)
159
+
160
+ ## Webhook Events
161
+
162
+ The plugin handles two event types on the same webhook endpoint:
163
+
164
+ | Event | Source | Behavior |
165
+ |-------|--------|----------|
166
+ | `message.text` | amiko-chat | Chat message. `replyExpected=true` → agent responds. `replyExpected=false` → `chat.inject` (context only). |
167
+ | `post.published` | amiko-new | Friend posted. Agent decides to comment (draft) or skip (`<empty-response/>`). |
168
+
169
+ ## Development
170
+
171
+ ### Local development setup
172
+
173
+ ```bash
174
+ git clone https://github.com/HCF-S/openclaw-amiko-plugin
175
+ cd openclaw-amiko-plugin
176
+ pnpm install
177
+ pnpm run build
178
+ ```
179
+
180
+ ### Type check
181
+
182
+ ```bash
183
+ pnpm run typecheck
184
+ ```
@@ -0,0 +1,87 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://openclaw.dev/schemas/amiko/channel-config.schema.json",
4
+ "title": "AmikoChannelConfig",
5
+ "description": "Schema for channels.amiko in OpenClaw config. OCP-001.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "token": {
10
+ "type": "string",
11
+ "description": "Bearer token for amiko-platform API authentication. Sensitive.",
12
+ "uiHints": { "sensitive": true }
13
+ },
14
+ "twinId": {
15
+ "type": "string",
16
+ "description": "Amiko twin ID used by this account."
17
+ },
18
+ "platformApiBaseUrl": {
19
+ "type": "string",
20
+ "format": "uri",
21
+ "description": "Base URL of amiko-new / platform API.",
22
+ "default": "https://platform.heyamiko.com"
23
+ },
24
+ "chatApiBaseUrl": {
25
+ "type": "string",
26
+ "format": "uri",
27
+ "description": "Base URL of amiko-chat internal channel API.",
28
+ "default": "https://api.amiko.app"
29
+ },
30
+ "apiBaseUrl": {
31
+ "type": "string",
32
+ "format": "uri",
33
+ "description": "Legacy fallback URL. Prefer platformApiBaseUrl and chatApiBaseUrl."
34
+ },
35
+ "name": {
36
+ "type": "string",
37
+ "description": "Human-readable account label."
38
+ },
39
+ "enabled": {
40
+ "type": "boolean",
41
+ "description": "Whether this account is active. Defaults to true.",
42
+ "default": true
43
+ },
44
+ "pollIntervalMs": {
45
+ "type": "integer",
46
+ "minimum": 1000,
47
+ "maximum": 60000,
48
+ "description": "Polling interval in milliseconds.",
49
+ "default": 3000
50
+ },
51
+ "pollTimeoutMs": {
52
+ "type": "integer",
53
+ "minimum": 1000,
54
+ "maximum": 30000,
55
+ "description": "Per-request HTTP timeout in milliseconds.",
56
+ "default": 10000
57
+ },
58
+ "defaultAccount": {
59
+ "type": "string",
60
+ "description": "Which OpenClaw agent/account key to treat as default in multi-account mode."
61
+ },
62
+ "accounts": {
63
+ "type": "object",
64
+ "description": "Per-account config overrides keyed by OpenClaw agent ID (for example main, agent-foo).",
65
+ "additionalProperties": {
66
+ "$ref": "#/$defs/AmikoAccountConfig"
67
+ }
68
+ }
69
+ },
70
+ "$defs": {
71
+ "AmikoAccountConfig": {
72
+ "type": "object",
73
+ "additionalProperties": false,
74
+ "properties": {
75
+ "token": { "type": "string", "uiHints": { "sensitive": true } },
76
+ "twinId": { "type": "string" },
77
+ "platformApiBaseUrl": { "type": "string", "format": "uri" },
78
+ "chatApiBaseUrl": { "type": "string", "format": "uri" },
79
+ "apiBaseUrl": { "type": "string", "format": "uri" },
80
+ "name": { "type": "string" },
81
+ "enabled": { "type": "boolean" },
82
+ "pollIntervalMs": { "type": "integer", "minimum": 1000, "maximum": 60000 },
83
+ "pollTimeoutMs": { "type": "integer", "minimum": 1000, "maximum": 30000 }
84
+ }
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://openclaw.dev/schemas/amiko/platform-ack.schema.json",
4
+ "title": "AmikoAckPayload",
5
+ "description": "Request body schema for POST /internal/openclaw/amiko/acks. OCP-002.",
6
+ "type": "object",
7
+ "required": ["accountId", "cursor", "eventIds"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "accountId": {
11
+ "type": "string",
12
+ "description": "Account scope. Must match the Bearer token's account."
13
+ },
14
+ "cursor": {
15
+ "type": "string",
16
+ "description": "Cursor of the last event in the acknowledged batch."
17
+ },
18
+ "eventIds": {
19
+ "type": "array",
20
+ "items": { "type": "string" },
21
+ "minItems": 1,
22
+ "description": "IDs of all events in the acknowledged batch."
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,87 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://openclaw.dev/schemas/amiko/platform-events.schema.json",
4
+ "title": "AmikoEventsResponse",
5
+ "description": "Response schema for GET /internal/openclaw/amiko/events. OCP-002.",
6
+ "type": "object",
7
+ "required": ["events", "nextCursor", "hasMore"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "events": {
11
+ "type": "array",
12
+ "items": { "$ref": "#/$defs/AmikoInboundEvent" }
13
+ },
14
+ "nextCursor": {
15
+ "type": ["string", "null"],
16
+ "description": "Cursor to use in the next poll. Null when no events returned."
17
+ },
18
+ "hasMore": {
19
+ "type": "boolean",
20
+ "description": "True if more events exist beyond the returned limit."
21
+ }
22
+ },
23
+ "$defs": {
24
+ "AmikoInboundEvent": {
25
+ "type": "object",
26
+ "required": ["id", "type", "accountId", "conversationId", "conversationType", "senderId", "senderName", "timestamp", "cursor"],
27
+ "additionalProperties": true,
28
+ "properties": {
29
+ "id": {
30
+ "type": "string",
31
+ "description": "Stable globally unique event ID for deduplication."
32
+ },
33
+ "type": {
34
+ "type": "string",
35
+ "enum": ["message.text", "message.image", "participant.added"],
36
+ "description": "Event type."
37
+ },
38
+ "accountId": {
39
+ "type": "string",
40
+ "description": "Account this event belongs to."
41
+ },
42
+ "conversationId": {
43
+ "type": "string",
44
+ "description": "Platform conversation identifier."
45
+ },
46
+ "conversationType": {
47
+ "type": "string",
48
+ "enum": ["direct", "group"],
49
+ "description": "Direct or group conversation."
50
+ },
51
+ "senderId": {
52
+ "type": "string",
53
+ "description": "Platform user ID of the sender."
54
+ },
55
+ "senderName": {
56
+ "type": "string",
57
+ "description": "Display name of the sender at send time."
58
+ },
59
+ "timestamp": {
60
+ "type": "integer",
61
+ "description": "Unix timestamp in milliseconds."
62
+ },
63
+ "cursor": {
64
+ "type": "string",
65
+ "description": "Monotonic cursor value for this event."
66
+ },
67
+ "text": {
68
+ "type": "string",
69
+ "description": "Message text body. Present for message.text type."
70
+ },
71
+ "mediaUrl": {
72
+ "type": "string",
73
+ "format": "uri",
74
+ "description": "Signed media URL. Present for message.image type."
75
+ },
76
+ "mediaCaption": {
77
+ "type": "string",
78
+ "description": "Optional caption for media messages."
79
+ },
80
+ "mentionsBot": {
81
+ "type": "boolean",
82
+ "description": "True if the message includes a @mention of the bot. Used for group routing."
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://openclaw.dev/schemas/amiko/platform-outbound.schema.json",
4
+ "title": "AmikoOutboundPayload",
5
+ "description": "Request body schema for POST /internal/openclaw/amiko/messages. OCP-002.",
6
+ "type": "object",
7
+ "required": ["accountId", "conversationId", "idempotencyKey", "type", "text"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "accountId": {
11
+ "type": "string",
12
+ "description": "Account scope. Must match the Bearer token's account."
13
+ },
14
+ "conversationId": {
15
+ "type": "string",
16
+ "description": "Target conversation for delivery."
17
+ },
18
+ "idempotencyKey": {
19
+ "type": "string",
20
+ "description": "Client-generated key for deduplication. Format: <accountId>:<conversationId>:<uuid-v4>."
21
+ },
22
+ "type": {
23
+ "type": "string",
24
+ "enum": ["text", "media"],
25
+ "description": "Message type."
26
+ },
27
+ "text": {
28
+ "type": "string",
29
+ "description": "Message text content. For type=media, used as caption fallback."
30
+ },
31
+ "mediaUrl": {
32
+ "type": "string",
33
+ "format": "uri",
34
+ "description": "Media URL. Required when type=media."
35
+ },
36
+ "mediaCaption": {
37
+ "type": "string",
38
+ "description": "Optional caption for media messages."
39
+ }
40
+ },
41
+ "if": {
42
+ "properties": { "type": { "const": "media" } }
43
+ },
44
+ "then": {
45
+ "required": ["mediaUrl"]
46
+ }
47
+ }
@@ -0,0 +1,20 @@
1
+ declare const _default: {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ configSchema: {};
6
+ register(api: {
7
+ runtime: any;
8
+ config?: any;
9
+ registerChannel: (params: {
10
+ plugin: any;
11
+ }) => void;
12
+ registerHttpRoute?: (params: any) => void;
13
+ registerHttpHandler?: (handler: (req: any, res: any) => boolean | Promise<boolean>) => void;
14
+ }): void;
15
+ };
16
+ export default _default;
17
+ export { amikoPlugin } from "./src/channel.js";
18
+ export { setAmikoRuntime, getAmikoRuntime } from "./src/runtime.js";
19
+ export type { ResolvedAmikoAccount, AmikoConfig, AmikoAccountConfig } from "./src/types.js";
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;kBAWgB;QACZ,OAAO,EAAE,GAAG,CAAC;QACb,MAAM,CAAC,EAAE,GAAG,CAAC;QACb,eAAe,EAAE,CAAC,MAAM,EAAE;YAAE,MAAM,EAAE,GAAG,CAAA;SAAE,KAAK,IAAI,CAAC;QACnD,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;QAC1C,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;KAC7F;;AAbH,wBA8DE;AAGF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,YAAY,EAAE,oBAAoB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ import { listAmikoAccountIds, resolveAmikoAccount } from "./src/accounts.js";
2
+ import { amikoPlugin } from "./src/channel.js";
3
+ import { dispatchWebhookRequest, setAmikoRuntime } from "./src/runtime.js";
4
+ export default {
5
+ id: "amiko",
6
+ name: "Amiko",
7
+ description: "Connect OpenClaw bot to Amiko platform (direct and group chat via webhook)",
8
+ configSchema: {},
9
+ register(api) {
10
+ setAmikoRuntime(api.runtime);
11
+ api.registerChannel({ plugin: amikoPlugin });
12
+ const sendChannelNotStarted = (res) => {
13
+ const json = JSON.stringify({ ok: false, error: "Amiko channel not started yet" });
14
+ res.statusCode = 503;
15
+ res.setHeader("Content-Type", "application/json");
16
+ res.setHeader("Content-Length", Buffer.byteLength(json));
17
+ res.end(json);
18
+ };
19
+ const registeredPaths = new Set();
20
+ const cfg = api.config ?? {};
21
+ for (const accountId of listAmikoAccountIds(cfg)) {
22
+ try {
23
+ const account = resolveAmikoAccount({ cfg, accountId });
24
+ const webhookPath = account.config.webhookPath ?? `/amiko/webhook/${account.twinId}`;
25
+ if (registeredPaths.has(webhookPath))
26
+ continue;
27
+ registeredPaths.add(webhookPath);
28
+ api.registerHttpRoute?.({
29
+ path: webhookPath,
30
+ auth: "plugin",
31
+ match: "exact",
32
+ replaceExisting: true,
33
+ handler: async (req, res) => {
34
+ const handled = await dispatchWebhookRequest(req, res);
35
+ if (handled)
36
+ return true;
37
+ sendChannelNotStarted(res);
38
+ return true;
39
+ },
40
+ });
41
+ }
42
+ catch {
43
+ // Skip malformed accounts; startAccount/status will surface the real error.
44
+ }
45
+ }
46
+ api.registerHttpHandler?.(async (req, res) => {
47
+ const handled = await dispatchWebhookRequest(req, res);
48
+ if (handled)
49
+ return true;
50
+ const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
51
+ if (!pathname.startsWith("/amiko/webhook/"))
52
+ return false;
53
+ sendChannelNotStarted(res);
54
+ return true;
55
+ });
56
+ },
57
+ };
58
+ // Named exports for programmatic use
59
+ export { amikoPlugin } from "./src/channel.js";
60
+ export { setAmikoRuntime, getAmikoRuntime } from "./src/runtime.js";
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { AmikoConfig, AmikoAccountConfig, ResolvedAmikoAccount } from "./types.js";
2
+ export declare const DEFAULT_ACCOUNT_ID = "main";
3
+ export declare const DEFAULT_PLATFORM_API_BASE_URL = "https://platform.heyamiko.com";
4
+ export declare const DEFAULT_CHAT_API_BASE_URL = "https://api.amiko.app";
5
+ export declare function normalizeAccountId(id: string): string;
6
+ export declare function listAmikoAccountIds(cfg: {
7
+ channels?: {
8
+ amiko?: AmikoConfig;
9
+ };
10
+ }): string[];
11
+ export declare function resolveDefaultAmikoAccountId(cfg: {
12
+ channels?: {
13
+ amiko?: AmikoConfig;
14
+ };
15
+ }): string;
16
+ export declare function resolveAmikoAccountConfig(amiko: AmikoConfig, accountId: string): AmikoAccountConfig;
17
+ export declare function resolveAmikoAccount(params: {
18
+ cfg: {
19
+ channels?: {
20
+ amiko?: AmikoConfig;
21
+ };
22
+ };
23
+ accountId: string;
24
+ }): ResolvedAmikoAccount;
25
+ export declare function listEnabledAmikoAccounts(cfg: {
26
+ channels?: {
27
+ amiko?: AmikoConfig;
28
+ };
29
+ }): string[];
30
+ //# sourceMappingURL=accounts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../src/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAExF,eAAO,MAAM,kBAAkB,SAAS,CAAC;AACzC,eAAO,MAAM,6BAA6B,kCAAkC,CAAC;AAC7E,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAEjE,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AAmBD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAKzF;AAED,wBAAgB,4BAA4B,CAAC,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,CAchG;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,MAAM,GAChB,kBAAkB,CAuCpB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,GAAG,EAAE;QAAE,QAAQ,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,WAAW,CAAA;SAAE,CAAA;KAAE,CAAC;IAC5C,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,oBAAoB,CAiCvB;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAS9F"}
@@ -0,0 +1,115 @@
1
+ export const DEFAULT_ACCOUNT_ID = "main";
2
+ export const DEFAULT_PLATFORM_API_BASE_URL = "https://platform.heyamiko.com";
3
+ export const DEFAULT_CHAT_API_BASE_URL = "https://api.amiko.app";
4
+ export function normalizeAccountId(id) {
5
+ return id.toLowerCase().trim();
6
+ }
7
+ function normalizeBaseUrl(url, fallback) {
8
+ const value = String(url || "").trim();
9
+ if (!value)
10
+ return fallback;
11
+ return value.replace(/\/+$/, "").replace(/\/api$/, "");
12
+ }
13
+ function getAccountEntries(amiko) {
14
+ if (!amiko?.accounts || Object.keys(amiko.accounts).length === 0) {
15
+ return [];
16
+ }
17
+ return Object.entries(amiko.accounts).map(([accountId, config]) => [
18
+ normalizeAccountId(accountId),
19
+ config,
20
+ ]);
21
+ }
22
+ export function listAmikoAccountIds(cfg) {
23
+ const amiko = cfg.channels?.amiko;
24
+ const accounts = getAccountEntries(amiko);
25
+ if (accounts.length === 0)
26
+ return [DEFAULT_ACCOUNT_ID];
27
+ return accounts.map(([accountId]) => accountId).sort();
28
+ }
29
+ export function resolveDefaultAmikoAccountId(cfg) {
30
+ const amiko = cfg.channels?.amiko;
31
+ if (!amiko)
32
+ return DEFAULT_ACCOUNT_ID;
33
+ if (amiko.defaultAccount)
34
+ return normalizeAccountId(amiko.defaultAccount);
35
+ const accounts = getAccountEntries(amiko);
36
+ if (accounts.length === 0)
37
+ return DEFAULT_ACCOUNT_ID;
38
+ const hasMain = accounts.some(([accountId]) => accountId === DEFAULT_ACCOUNT_ID);
39
+ if (hasMain)
40
+ return DEFAULT_ACCOUNT_ID;
41
+ return accounts
42
+ .map(([accountId]) => accountId)
43
+ .sort()[0] ?? DEFAULT_ACCOUNT_ID;
44
+ }
45
+ export function resolveAmikoAccountConfig(amiko, accountId) {
46
+ const normalizedAccountId = normalizeAccountId(accountId);
47
+ if (!amiko.accounts || Object.keys(amiko.accounts).length === 0) {
48
+ return {
49
+ name: amiko.name,
50
+ enabled: amiko.enabled,
51
+ twinId: amiko.twinId,
52
+ token: amiko.token,
53
+ platformApiBaseUrl: amiko.platformApiBaseUrl ?? amiko.apiBaseUrl,
54
+ chatApiBaseUrl: amiko.chatApiBaseUrl ?? amiko.apiBaseUrl,
55
+ apiBaseUrl: amiko.apiBaseUrl,
56
+ webhookPath: amiko.webhookPath,
57
+ webhookSecret: amiko.webhookSecret,
58
+ };
59
+ }
60
+ const accountMap = Object.fromEntries(getAccountEntries(amiko));
61
+ const perAccount = accountMap[normalizedAccountId] ?? {};
62
+ return {
63
+ name: perAccount.name ?? amiko.name,
64
+ enabled: perAccount.enabled ?? amiko.enabled,
65
+ twinId: perAccount.twinId ?? amiko.twinId,
66
+ token: perAccount.token ?? amiko.token,
67
+ platformApiBaseUrl: perAccount.platformApiBaseUrl ??
68
+ perAccount.apiBaseUrl ??
69
+ amiko.platformApiBaseUrl ??
70
+ amiko.apiBaseUrl,
71
+ chatApiBaseUrl: perAccount.chatApiBaseUrl ??
72
+ perAccount.apiBaseUrl ??
73
+ amiko.chatApiBaseUrl ??
74
+ amiko.apiBaseUrl,
75
+ apiBaseUrl: perAccount.apiBaseUrl ?? amiko.apiBaseUrl,
76
+ webhookPath: perAccount.webhookPath ?? amiko.webhookPath,
77
+ webhookSecret: perAccount.webhookSecret ?? amiko.webhookSecret,
78
+ };
79
+ }
80
+ export function resolveAmikoAccount(params) {
81
+ const { cfg, accountId } = params;
82
+ const normalizedAccountId = normalizeAccountId(accountId);
83
+ const amiko = cfg.channels?.amiko ?? {};
84
+ const config = resolveAmikoAccountConfig(amiko, normalizedAccountId);
85
+ if (!config.twinId?.trim()) {
86
+ throw new Error(`Amiko account "${normalizedAccountId}" has no twinId configured`);
87
+ }
88
+ if (!config.token?.trim()) {
89
+ throw new Error(`Amiko account "${normalizedAccountId}" has no token configured`);
90
+ }
91
+ const platformApiBaseUrl = normalizeBaseUrl(config.platformApiBaseUrl ?? config.apiBaseUrl, DEFAULT_PLATFORM_API_BASE_URL);
92
+ const chatApiBaseUrl = normalizeBaseUrl(config.chatApiBaseUrl ?? config.apiBaseUrl, DEFAULT_CHAT_API_BASE_URL);
93
+ return {
94
+ accountId: normalizedAccountId,
95
+ twinId: config.twinId,
96
+ name: config.name,
97
+ enabled: config.enabled !== false,
98
+ token: config.token,
99
+ platformApiBaseUrl,
100
+ chatApiBaseUrl,
101
+ config,
102
+ };
103
+ }
104
+ export function listEnabledAmikoAccounts(cfg) {
105
+ return listAmikoAccountIds(cfg).filter((id) => {
106
+ try {
107
+ const account = resolveAmikoAccount({ cfg, accountId: id });
108
+ return account.enabled;
109
+ }
110
+ catch {
111
+ return false;
112
+ }
113
+ });
114
+ }
115
+ //# sourceMappingURL=accounts.js.map