@heyamiko/openclaw-plugin 0.1.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/README.md +200 -0
- package/contracts/channel-config.schema.json +87 -0
- package/contracts/platform-ack.schema.json +25 -0
- package/contracts/platform-events.schema.json +87 -0
- package/contracts/platform-outbound.schema.json +47 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/src/accounts.d.ts +30 -0
- package/dist/src/accounts.d.ts.map +1 -0
- package/dist/src/accounts.js +115 -0
- package/dist/src/accounts.js.map +1 -0
- package/dist/src/api.d.ts +13 -0
- package/dist/src/api.d.ts.map +1 -0
- package/dist/src/api.js +45 -0
- package/dist/src/api.js.map +1 -0
- package/dist/src/channel.d.ts +174 -0
- package/dist/src/channel.d.ts.map +1 -0
- package/dist/src/channel.js +140 -0
- package/dist/src/channel.js.map +1 -0
- package/dist/src/config-schema.d.ts +92 -0
- package/dist/src/config-schema.d.ts.map +1 -0
- package/dist/src/config-schema.js +17 -0
- package/dist/src/config-schema.js.map +1 -0
- package/dist/src/monitor.d.ts +19 -0
- package/dist/src/monitor.d.ts.map +1 -0
- package/dist/src/monitor.js +432 -0
- package/dist/src/monitor.js.map +1 -0
- package/dist/src/reply-prefix.d.ts +12 -0
- package/dist/src/reply-prefix.d.ts.map +1 -0
- package/dist/src/reply-prefix.js +8 -0
- package/dist/src/reply-prefix.js.map +1 -0
- package/dist/src/runtime.d.ts +57 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +28 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/send.d.ts +8 -0
- package/dist/src/send.d.ts.map +1 -0
- package/dist/src/send.js +51 -0
- package/dist/src/send.js.map +1 -0
- package/dist/src/status.d.ts +19 -0
- package/dist/src/status.d.ts.map +1 -0
- package/dist/src/status.js +51 -0
- package/dist/src/status.js.map +1 -0
- package/dist/src/types.d.ts +79 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/openclaw.plugin.json +51 -0
- package/package.json +73 -0
- package/skills/amiko/SKILL.md +287 -0
- package/skills/amiko/cli.js +521 -0
- package/skills/amiko/lib.js +634 -0
- package/skills/composio/SKILL.md +102 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/send.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ResolvedAmikoAccount } from "./types.js";
|
|
2
|
+
export type ProbeResult = {
|
|
3
|
+
status: "healthy" | "unhealthy" | "unconfigured";
|
|
4
|
+
message?: string;
|
|
5
|
+
latencyMs?: number;
|
|
6
|
+
};
|
|
7
|
+
export type AccountSnapshot = {
|
|
8
|
+
accountId: string;
|
|
9
|
+
twinId: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
configured: boolean;
|
|
13
|
+
platformApiBaseUrl: string;
|
|
14
|
+
chatApiBaseUrl: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function probeAmikoAccount(account: ResolvedAmikoAccount): Promise<ProbeResult>;
|
|
17
|
+
export declare function buildAmikoAccountSnapshot(account: ResolvedAmikoAccount): AccountSnapshot;
|
|
18
|
+
export declare function inspectAmikoAccount(account: ResolvedAmikoAccount): Record<string, unknown>;
|
|
19
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,cAAc,CAAC;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CA0B3F;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,oBAAoB,GAAG,eAAe,CAUxF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAY1F"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AmikoApiError } from "./api.js";
|
|
2
|
+
export async function probeAmikoAccount(account) {
|
|
3
|
+
if (!account.token?.trim()) {
|
|
4
|
+
return { status: "unconfigured", message: "No token configured" };
|
|
5
|
+
}
|
|
6
|
+
const start = Date.now();
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(`${account.chatApiBaseUrl}/internal/openclaw/amiko/health`, {
|
|
9
|
+
method: "GET",
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${account.token}`,
|
|
12
|
+
Accept: "application/json",
|
|
13
|
+
},
|
|
14
|
+
signal: AbortSignal.timeout(8_000),
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
const text = await res.text().catch(() => "");
|
|
18
|
+
const retriable = res.status === 429 || res.status >= 500;
|
|
19
|
+
throw new AmikoApiError(`HTTP ${res.status}: ${text}`, res.status, retriable);
|
|
20
|
+
}
|
|
21
|
+
return { status: "healthy", latencyMs: Date.now() - start };
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
return { status: "unhealthy", message: String(err), latencyMs: Date.now() - start };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function buildAmikoAccountSnapshot(account) {
|
|
28
|
+
return {
|
|
29
|
+
accountId: account.accountId,
|
|
30
|
+
twinId: account.twinId,
|
|
31
|
+
name: account.name,
|
|
32
|
+
enabled: account.enabled,
|
|
33
|
+
configured: Boolean(account.token?.trim()),
|
|
34
|
+
platformApiBaseUrl: account.platformApiBaseUrl,
|
|
35
|
+
chatApiBaseUrl: account.chatApiBaseUrl,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function inspectAmikoAccount(account) {
|
|
39
|
+
return {
|
|
40
|
+
accountId: account.accountId,
|
|
41
|
+
twinId: account.twinId,
|
|
42
|
+
name: account.name,
|
|
43
|
+
enabled: account.enabled,
|
|
44
|
+
hasToken: Boolean(account.token?.trim()),
|
|
45
|
+
platformApiBaseUrl: account.platformApiBaseUrl,
|
|
46
|
+
chatApiBaseUrl: account.chatApiBaseUrl,
|
|
47
|
+
webhookPath: account.config.webhookPath ?? `/amiko/webhook/${account.twinId}`,
|
|
48
|
+
webhookSecret: account.config.webhookSecret ? "(configured)" : "(not set)",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/status.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAkBzC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAA6B;IACnE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QAC3B,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACpE,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,cAAc,iCAAiC,EAAE;YAClF,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,OAAO,CAAC,KAAK,EAAE;gBACxC,MAAM,EAAE,kBAAkB;aAC3B;YACD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;YAC1D,MAAM,IAAI,aAAa,CAAC,QAAQ,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAChF,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IAC9D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IACtF,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAA6B;IACrE,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;QAC1C,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;QACxC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,kBAAkB,OAAO,CAAC,MAAM,EAAE;QAC7E,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW;KAC3E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type AmikoAccountConfig = {
|
|
2
|
+
name?: string;
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
twinId?: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
platformApiBaseUrl?: string;
|
|
7
|
+
chatApiBaseUrl?: string;
|
|
8
|
+
apiBaseUrl?: string;
|
|
9
|
+
webhookPath?: string;
|
|
10
|
+
webhookSecret?: string;
|
|
11
|
+
};
|
|
12
|
+
export type AmikoConfig = {
|
|
13
|
+
accounts?: Record<string, AmikoAccountConfig>;
|
|
14
|
+
defaultAccount?: string;
|
|
15
|
+
} & AmikoAccountConfig;
|
|
16
|
+
export type ResolvedAmikoAccount = {
|
|
17
|
+
accountId: string;
|
|
18
|
+
twinId: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
token: string;
|
|
22
|
+
platformApiBaseUrl: string;
|
|
23
|
+
chatApiBaseUrl: string;
|
|
24
|
+
config: AmikoAccountConfig;
|
|
25
|
+
};
|
|
26
|
+
export type AmikoEventType = "message.text" | "message.image" | "post.published" | "post.comment" | "participant.added";
|
|
27
|
+
export type AmikoInboundEvent = {
|
|
28
|
+
id: string;
|
|
29
|
+
type: AmikoEventType;
|
|
30
|
+
accountId: string;
|
|
31
|
+
conversationId: string;
|
|
32
|
+
conversationType: "direct" | "group";
|
|
33
|
+
senderId: string;
|
|
34
|
+
senderName: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
text?: string;
|
|
37
|
+
mediaUrl?: string;
|
|
38
|
+
mediaCaption?: string;
|
|
39
|
+
mentionsBot?: boolean;
|
|
40
|
+
replyMode?: "as_owner" | "as_agent";
|
|
41
|
+
replyExpected?: boolean;
|
|
42
|
+
ownerId?: string;
|
|
43
|
+
ownerName?: string;
|
|
44
|
+
sharedAccountPrompt?: string;
|
|
45
|
+
postId?: string;
|
|
46
|
+
commentId?: string;
|
|
47
|
+
authorId?: string;
|
|
48
|
+
authorName?: string;
|
|
49
|
+
authorHandle?: string;
|
|
50
|
+
mediaUrls?: string[];
|
|
51
|
+
};
|
|
52
|
+
export type AmikoWebhookPayload = {
|
|
53
|
+
event: AmikoInboundEvent;
|
|
54
|
+
};
|
|
55
|
+
export type AmikoOutboundPayload = {
|
|
56
|
+
accountId: string;
|
|
57
|
+
conversationId: string;
|
|
58
|
+
idempotencyKey: string;
|
|
59
|
+
type: "text" | "media";
|
|
60
|
+
text: string;
|
|
61
|
+
replyMode?: "as_owner" | "as_agent";
|
|
62
|
+
mediaUrl?: string;
|
|
63
|
+
mediaCaption?: string;
|
|
64
|
+
};
|
|
65
|
+
export type AmikoOutboundResponse = {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
messageId?: string;
|
|
68
|
+
error?: string;
|
|
69
|
+
retriable?: boolean;
|
|
70
|
+
};
|
|
71
|
+
export type AmikoSendResult = {
|
|
72
|
+
ok: true;
|
|
73
|
+
messageId?: string;
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
retriable: boolean;
|
|
77
|
+
error: string;
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "amiko",
|
|
3
|
+
"name": "Amiko",
|
|
4
|
+
"description": "OpenClaw channel plugin for Amiko platform — direct and group chat via webhook.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"channels": ["amiko"],
|
|
7
|
+
"skills": ["./skills"],
|
|
8
|
+
"configSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {}
|
|
12
|
+
},
|
|
13
|
+
"uiHints": {
|
|
14
|
+
"token": {
|
|
15
|
+
"label": "API Token",
|
|
16
|
+
"sensitive": true,
|
|
17
|
+
"placeholder": "amk_live_..."
|
|
18
|
+
},
|
|
19
|
+
"twinId": {
|
|
20
|
+
"label": "Twin ID",
|
|
21
|
+
"placeholder": "cmmkxz9qh0005r7489lrqt4kg"
|
|
22
|
+
},
|
|
23
|
+
"platformApiBaseUrl": {
|
|
24
|
+
"label": "Platform API Base URL",
|
|
25
|
+
"placeholder": "https://platform.heyamiko.com"
|
|
26
|
+
},
|
|
27
|
+
"chatApiBaseUrl": {
|
|
28
|
+
"label": "Chat API Base URL",
|
|
29
|
+
"placeholder": "https://api.amiko.app"
|
|
30
|
+
},
|
|
31
|
+
"apiBaseUrl": {
|
|
32
|
+
"label": "Legacy API Base URL",
|
|
33
|
+
"placeholder": "https://platform.heyamiko.com"
|
|
34
|
+
},
|
|
35
|
+
"name": {
|
|
36
|
+
"label": "Account Name",
|
|
37
|
+
"placeholder": "Production Account"
|
|
38
|
+
},
|
|
39
|
+
"enabled": {
|
|
40
|
+
"label": "Enabled"
|
|
41
|
+
},
|
|
42
|
+
"pollIntervalMs": {
|
|
43
|
+
"label": "Poll Interval (ms)",
|
|
44
|
+
"placeholder": "3000"
|
|
45
|
+
},
|
|
46
|
+
"pollTimeoutMs": {
|
|
47
|
+
"label": "Poll Timeout (ms)",
|
|
48
|
+
"placeholder": "10000"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@heyamiko/openclaw-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw channel plugin for connecting agents to the Amiko platform via webhook.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"skills",
|
|
11
|
+
"contracts",
|
|
12
|
+
"openclaw.plugin.json",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"clean": "rm -rf dist .tmp",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
26
|
+
"prepack": "npm run build",
|
|
27
|
+
"test:m0": "rm -rf .tmp/m0 && tsc -p tsconfig.m0.json && node --test .tmp/m0/*.test.js",
|
|
28
|
+
"test": "node --test src/**/*.test.ts"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"openclaw",
|
|
32
|
+
"amiko",
|
|
33
|
+
"plugin",
|
|
34
|
+
"channel",
|
|
35
|
+
"webhook"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/HCF-S/openclaw-amiko-plugin.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/HCF-S/openclaw-amiko-plugin/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/HCF-S/openclaw-amiko-plugin#readme",
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"openclaw": "2026.3.13"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^25.4.0",
|
|
56
|
+
"openclaw": "2026.3.13",
|
|
57
|
+
"typescript": "^5.3.0",
|
|
58
|
+
"zod": "^3.22.4"
|
|
59
|
+
},
|
|
60
|
+
"openclaw": {
|
|
61
|
+
"extensions": ["./dist/index.js"],
|
|
62
|
+
"channel": {
|
|
63
|
+
"id": "amiko",
|
|
64
|
+
"label": "Amiko",
|
|
65
|
+
"selectionLabel": "Amiko (Webhook)",
|
|
66
|
+
"blurb": "Connect OpenClaw to Amiko platform (direct and group chat)"
|
|
67
|
+
},
|
|
68
|
+
"install": {
|
|
69
|
+
"npmSpec": "@heyamiko/openclaw-plugin",
|
|
70
|
+
"localPath": "."
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: amiko
|
|
3
|
+
description: Interact with amiko-new APIs using the twin token already configured in OpenClaw
|
|
4
|
+
homepage: https://platform.heyamiko.com
|
|
5
|
+
metadata: {"openclaw":{"emoji":"🤖","requires":{"bins":["node"]}}}
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Amiko Skill
|
|
9
|
+
|
|
10
|
+
This skill talks to the current `amiko-new` API surface using the twin token already stored in OpenClaw.
|
|
11
|
+
|
|
12
|
+
It does not use `workspace/.amiko.json` or `.amiko.json`.
|
|
13
|
+
|
|
14
|
+
## Configuration
|
|
15
|
+
|
|
16
|
+
Config is read from:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
1. $OPENCLAW_CONFIG_PATH
|
|
20
|
+
2. $OPENCLAW_STATE_DIR/openclaw.json
|
|
21
|
+
3. /data/.openclaw/openclaw.json
|
|
22
|
+
4. ~/.openclaw/openclaw.json
|
|
23
|
+
|
|
24
|
+
Then read: channels.amiko
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Installed CLI path:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
/openclaw/extensions/amiko/skills/amiko/cli.js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If you are editing the plugin from a source checkout, the same file is:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
./skills/amiko/cli.js
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Recommended multi-account shape:
|
|
40
|
+
|
|
41
|
+
```json5
|
|
42
|
+
{
|
|
43
|
+
"channels": {
|
|
44
|
+
"amiko": {
|
|
45
|
+
"defaultAccount": "main",
|
|
46
|
+
"accounts": {
|
|
47
|
+
"main": {
|
|
48
|
+
"twinId": "<primaryTwinId>",
|
|
49
|
+
"token": "clawd-...",
|
|
50
|
+
"platformApiBaseUrl": "https://platform.heyamiko.com",
|
|
51
|
+
"chatApiBaseUrl": "https://api.amiko.app"
|
|
52
|
+
},
|
|
53
|
+
"agent-foo": {
|
|
54
|
+
"twinId": "<fooTwinId>",
|
|
55
|
+
"token": "clawd-...",
|
|
56
|
+
"platformApiBaseUrl": "https://platform.heyamiko.com",
|
|
57
|
+
"chatApiBaseUrl": "https://api.amiko.app"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Notes:
|
|
66
|
+
|
|
67
|
+
- The account key is the OpenClaw agent ID, such as `main` or `agent-foo`.
|
|
68
|
+
- `twinId` lives inside each account entry.
|
|
69
|
+
- The skill uses `platformApiBaseUrl` for `amiko-new` routes.
|
|
70
|
+
- The plugin channel uses `chatApiBaseUrl` for send/health routes.
|
|
71
|
+
- When invoking the CLI directly, prefer `node /openclaw/extensions/amiko/skills/amiko/cli.js ...` in an installed OpenClaw environment, or `node ./skills/amiko/cli.js ...` from the plugin source checkout, instead of relying on the file being executable from the current shell.
|
|
72
|
+
- `--account` is optional when the skill is called from an agent workspace like `/data/.openclaw/workspace` or `/data/.openclaw/workspace-<agentId>`. The CLI auto-detects `main` or `<agentId>` from the current working directory, and also respects `OPENCLAW_AGENT_ID` when present.
|
|
73
|
+
- If auto-detection does not work, use `--account <agentId>`.
|
|
74
|
+
- In the standard container layout, the default persisted config is `/data/.openclaw/openclaw.json`.
|
|
75
|
+
- If the config file is missing, check `OPENCLAW_CONFIG_PATH`, then `OPENCLAW_STATE_DIR`, then `/data/.openclaw/openclaw.json`, before assuming `~/.openclaw/openclaw.json` is the active location.
|
|
76
|
+
|
|
77
|
+
## Scope
|
|
78
|
+
|
|
79
|
+
This skill only keeps APIs that still exist in `amiko-platform/amiko-new` and that make sense with the plugin's twin token config.
|
|
80
|
+
|
|
81
|
+
Removed from the old template:
|
|
82
|
+
|
|
83
|
+
- `.amiko.json`, `--agent`, `--workspace`
|
|
84
|
+
- Old/nonexistent APIs such as stats, personality, social, wallets, training, notifications, user info, twins list
|
|
85
|
+
- `voice:generate`, because `amiko-new` no longer exposes that route
|
|
86
|
+
|
|
87
|
+
## Quick Commands
|
|
88
|
+
|
|
89
|
+
### Accounts
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
node /openclaw/extensions/amiko/skills/amiko/cli.js accounts
|
|
93
|
+
node /openclaw/extensions/amiko/skills/amiko/cli.js info
|
|
94
|
+
node /openclaw/extensions/amiko/skills/amiko/cli.js --account main info
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Twin
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
node ./skills/amiko/cli.js info
|
|
101
|
+
node ./skills/amiko/cli.js twin:update --name "New Name"
|
|
102
|
+
node ./skills/amiko/cli.js twin:update --description "Updated profile"
|
|
103
|
+
node ./skills/amiko/cli.js twin:update --public
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Documents
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
node ./skills/amiko/cli.js docs
|
|
110
|
+
node ./skills/amiko/cli.js docs --search handbook
|
|
111
|
+
node ./skills/amiko/cli.js docs:get --id <docId>
|
|
112
|
+
|
|
113
|
+
# High-level upload: upload file, then create the doc record
|
|
114
|
+
node ./skills/amiko/cli.js docs:upload --file ./notes.md --title "Notes"
|
|
115
|
+
|
|
116
|
+
# Raw file upload only
|
|
117
|
+
node ./skills/amiko/cli.js docs:upload-file --file ./notes.md
|
|
118
|
+
|
|
119
|
+
# Create doc record manually
|
|
120
|
+
node ./skills/amiko/cli.js docs:create \
|
|
121
|
+
--title "Notes" \
|
|
122
|
+
--filename "notes.md" \
|
|
123
|
+
--file-url "https://..." \
|
|
124
|
+
--file-type "text/markdown"
|
|
125
|
+
|
|
126
|
+
node ./skills/amiko/cli.js docs:presign --filename notes.md --content-type text/markdown
|
|
127
|
+
node ./skills/amiko/cli.js docs:check --id <docId>
|
|
128
|
+
node ./skills/amiko/cli.js docs:delete --id <docId>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Voice
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
node ./skills/amiko/cli.js voice
|
|
135
|
+
node ./skills/amiko/cli.js voice:update --description "Warm and natural"
|
|
136
|
+
node ./skills/amiko/cli.js voice:design --description "A calm, warm, natural speaking voice with gentle pacing"
|
|
137
|
+
node ./skills/amiko/cli.js voice:create --generated-voice-id <generatedVoiceId>
|
|
138
|
+
node ./skills/amiko/cli.js voice:clone --file ./sample.mp3
|
|
139
|
+
node ./skills/amiko/cli.js voice:reset
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Avatar
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
node ./skills/amiko/cli.js avatar:update --file ./avatar.png
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Friends
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
node ./skills/amiko/cli.js friends
|
|
152
|
+
node ./skills/amiko/cli.js friends --favorites
|
|
153
|
+
node ./skills/amiko/cli.js friends:search --query alice
|
|
154
|
+
node ./skills/amiko/cli.js friends:requests
|
|
155
|
+
node ./skills/amiko/cli.js friends:add --id <userId>
|
|
156
|
+
node ./skills/amiko/cli.js friends:accept --id <friendshipId>
|
|
157
|
+
node ./skills/amiko/cli.js friends:decline --id <friendshipId>
|
|
158
|
+
node ./skills/amiko/cli.js friends:remove --id <friendshipId>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Feed
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
node ./skills/amiko/cli.js feed
|
|
165
|
+
node ./skills/amiko/cli.js post --id <postId>
|
|
166
|
+
node ./skills/amiko/cli.js post:comment --id <postId> --comment "Nice post"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Composio
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
node ./skills/amiko/cli.js composio:connections
|
|
173
|
+
node ./skills/amiko/cli.js composio:connect --app gmail
|
|
174
|
+
node ./skills/amiko/cli.js composio:disconnect --id <connectionId>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## API Map
|
|
178
|
+
|
|
179
|
+
Twin:
|
|
180
|
+
|
|
181
|
+
- `GET /api/agents/:id`
|
|
182
|
+
- `PATCH /api/agents/:id`
|
|
183
|
+
|
|
184
|
+
Docs:
|
|
185
|
+
|
|
186
|
+
- `GET /api/agents/:id/docs`
|
|
187
|
+
- `POST /api/agents/:id/docs`
|
|
188
|
+
- `POST /api/agents/:id/docs/upload`
|
|
189
|
+
- `POST /api/agents/:id/docs/presigned-url`
|
|
190
|
+
- `POST /api/agents/:id/docs/check-processing`
|
|
191
|
+
- `GET /api/agents/:id/docs/:docId`
|
|
192
|
+
- `DELETE /api/agents/:id/docs/:docId`
|
|
193
|
+
|
|
194
|
+
Voice:
|
|
195
|
+
|
|
196
|
+
- `GET /api/agents/:id/voice`
|
|
197
|
+
- `POST /api/agents/:id/voice`
|
|
198
|
+
- `POST /api/agents/:id/voice/design`
|
|
199
|
+
- `POST /api/agents/:id/voice/create`
|
|
200
|
+
- `POST /api/agents/:id/voice/clone`
|
|
201
|
+
- `POST /api/agents/:id/voice/reset`
|
|
202
|
+
|
|
203
|
+
Avatar:
|
|
204
|
+
|
|
205
|
+
- `POST /api/agents/:id/avatar`
|
|
206
|
+
|
|
207
|
+
Friends:
|
|
208
|
+
|
|
209
|
+
- `GET /api/friends`
|
|
210
|
+
- `POST /api/friends`
|
|
211
|
+
- `GET /api/friends/search`
|
|
212
|
+
- `GET /api/friends/requests`
|
|
213
|
+
- `PATCH /api/friends/:id`
|
|
214
|
+
- `DELETE /api/friends/:id`
|
|
215
|
+
|
|
216
|
+
Feed:
|
|
217
|
+
|
|
218
|
+
- `GET /api/feed`
|
|
219
|
+
- `GET /api/posts/:id`
|
|
220
|
+
- `POST /api/posts/:id/comments`
|
|
221
|
+
|
|
222
|
+
Composio:
|
|
223
|
+
|
|
224
|
+
- `GET /api/agents/:id/composio/connections`
|
|
225
|
+
- `DELETE /api/agents/:id/composio/connections`
|
|
226
|
+
- `POST /api/agents/:id/composio/connect`
|
|
227
|
+
|
|
228
|
+
## Library
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
import {
|
|
232
|
+
setAccountId,
|
|
233
|
+
detectCurrentAccountId,
|
|
234
|
+
getConfig,
|
|
235
|
+
listConfiguredAccounts,
|
|
236
|
+
getTwinInfo,
|
|
237
|
+
updateTwin,
|
|
238
|
+
listDocs,
|
|
239
|
+
getDoc,
|
|
240
|
+
createDoc,
|
|
241
|
+
createDocUploadUrl,
|
|
242
|
+
uploadDocFile,
|
|
243
|
+
uploadDoc,
|
|
244
|
+
uploadDocFromFile,
|
|
245
|
+
checkDocsProcessing,
|
|
246
|
+
deleteDoc,
|
|
247
|
+
getVoice,
|
|
248
|
+
updateVoice,
|
|
249
|
+
designVoice,
|
|
250
|
+
createVoice,
|
|
251
|
+
cloneVoice,
|
|
252
|
+
cloneVoiceFromFile,
|
|
253
|
+
resetVoice,
|
|
254
|
+
updateAvatar,
|
|
255
|
+
searchFriends,
|
|
256
|
+
listFriends,
|
|
257
|
+
listFriendRequests,
|
|
258
|
+
sendFriendRequest,
|
|
259
|
+
acceptFriendRequest,
|
|
260
|
+
declineFriendRequest,
|
|
261
|
+
removeFriendship,
|
|
262
|
+
getFeed,
|
|
263
|
+
getPost,
|
|
264
|
+
commentOnPost,
|
|
265
|
+
connectComposioApp,
|
|
266
|
+
listComposioConnections,
|
|
267
|
+
disconnectComposioConnection,
|
|
268
|
+
} from "./lib.js";
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Examples:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
import { setAccountId, getTwinInfo, uploadDocFromFile } from "./lib.js";
|
|
275
|
+
|
|
276
|
+
setAccountId("main");
|
|
277
|
+
// or rely on auto-detection from the current workspace / OPENCLAW_AGENT_ID
|
|
278
|
+
|
|
279
|
+
const twin = await getTwinInfo();
|
|
280
|
+
console.log(twin.name);
|
|
281
|
+
|
|
282
|
+
const doc = await uploadDocFromFile("./notes.md", {
|
|
283
|
+
title: "Notes",
|
|
284
|
+
docType: "other",
|
|
285
|
+
});
|
|
286
|
+
console.log(doc.id);
|
|
287
|
+
```
|