@folotoy/folotoy-openclaw-plugin 0.3.1 → 0.4.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/cli/install.js')
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/cli/install.ts"],"names":[],"mappings":""}
@@ -0,0 +1,113 @@
1
+ import { execSync } from 'node:child_process';
2
+ import qrcode from 'qrcode-terminal';
3
+ import { DEFAULT_MQTT_HOST, DEFAULT_MQTT_PORT } from '../config.js';
4
+ const PAIR_API_BASE = process.env.PAIR_API_BASE ?? 'https://pair.folotoy.cn';
5
+ const POLL_INTERVAL_MS = 3000;
6
+ const POLL_TIMEOUT_MS = 300_000; // 5 minutes
7
+ // ── Helpers ────────────────────────────────────────────
8
+ function checkOpenClaw() {
9
+ try {
10
+ execSync('openclaw --version', { stdio: 'pipe' });
11
+ }
12
+ catch {
13
+ console.error('Error: openclaw is not installed or not in PATH.');
14
+ console.error('Install it first: npm i -g openclaw');
15
+ process.exit(1);
16
+ }
17
+ }
18
+ function installPlugin() {
19
+ try {
20
+ const list = execSync('openclaw plugins list', { stdio: 'pipe' }).toString();
21
+ if (list.includes('folotoy-openclaw-plugin')) {
22
+ return; // already installed
23
+ }
24
+ }
25
+ catch {
26
+ // ignore
27
+ }
28
+ console.log('Installing FoloToy plugin...');
29
+ execSync('openclaw plugins install @folotoy/folotoy-openclaw-plugin', { stdio: 'inherit' });
30
+ }
31
+ async function createSession() {
32
+ const res = await fetch(`${PAIR_API_BASE}/api/pair`, { method: 'POST' });
33
+ if (!res.ok)
34
+ throw new Error(`Failed to create pairing session (HTTP ${res.status})`);
35
+ return res.json();
36
+ }
37
+ function displayQR(url) {
38
+ qrcode.generate(url, { small: true }, (qr) => {
39
+ console.log(qr);
40
+ });
41
+ console.log(`Or open this URL on your phone: ${url}\n`);
42
+ }
43
+ async function sleep(ms) {
44
+ return new Promise((r) => setTimeout(r, ms));
45
+ }
46
+ async function pollSession(sessionId) {
47
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
48
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
49
+ let i = 0;
50
+ while (Date.now() < deadline) {
51
+ process.stdout.write(`\r${frames[i++ % frames.length]} Waiting for pairing...`);
52
+ const res = await fetch(`${PAIR_API_BASE}/api/pair/${sessionId}`);
53
+ if (!res.ok)
54
+ throw new Error(`Poll failed (HTTP ${res.status})`);
55
+ const data = (await res.json());
56
+ if (data.status === 'completed') {
57
+ process.stdout.write('\r\x1b[32m✓\x1b[0m Paired successfully! \n');
58
+ return data;
59
+ }
60
+ if (data.status === 'expired') {
61
+ process.stdout.write('\r');
62
+ throw new Error('Pairing session expired. Please try again.');
63
+ }
64
+ await sleep(POLL_INTERVAL_MS);
65
+ }
66
+ throw new Error('Pairing timed out after 5 minutes.');
67
+ }
68
+ function writeConfig(result) {
69
+ execSync(`openclaw config set channels.folotoy.flow direct`, { stdio: 'pipe' });
70
+ execSync(`openclaw config set channels.folotoy.toy_sn ${result.toy_sn}`, { stdio: 'pipe' });
71
+ execSync(`openclaw config set channels.folotoy.toy_key ${result.toy_key}`, { stdio: 'pipe' });
72
+ const mqttHost = result.mqtt_host ?? DEFAULT_MQTT_HOST;
73
+ const mqttPort = result.mqtt_port ?? DEFAULT_MQTT_PORT;
74
+ execSync(`openclaw config set channels.folotoy.mqtt_host ${mqttHost}`, { stdio: 'pipe' });
75
+ execSync(`openclaw config set channels.folotoy.mqtt_port ${mqttPort}`, { stdio: 'pipe' });
76
+ }
77
+ // ── Main ───────────────────────────────────────────────
78
+ async function main() {
79
+ const command = process.argv[2];
80
+ if (command !== 'install') {
81
+ console.log('Usage: npx @folotoy/folotoy-openclaw-plugin install');
82
+ process.exit(command ? 1 : 0);
83
+ }
84
+ console.log('🧸 FoloToy OpenClaw Plugin Installer\n');
85
+ // Step 1: check prerequisites
86
+ console.log('Checking openclaw...');
87
+ checkOpenClaw();
88
+ console.log('✓ openclaw found\n');
89
+ // Step 2: install plugin if not present
90
+ installPlugin();
91
+ // Step 3: create pairing session
92
+ console.log('Creating pairing session...\n');
93
+ const session = await createSession();
94
+ // Step 4: display QR code
95
+ console.log('Scan this QR code with your phone,');
96
+ console.log('then scan your toy\'s QR code on the phone:\n');
97
+ displayQR(session.pair_url);
98
+ // Step 5: poll for result
99
+ const result = await pollSession(session.session_id);
100
+ // Step 6: write config
101
+ console.log('\nWriting configuration...');
102
+ writeConfig(result);
103
+ // Step 7: done
104
+ console.log('\n\x1b[32m✓ FoloToy plugin installed and configured!\x1b[0m');
105
+ console.log(` Toy SN: ${result.toy_sn}`);
106
+ console.log(` MQTT Host: ${result.mqtt_host ?? DEFAULT_MQTT_HOST}`);
107
+ console.log('\nRestart the gateway to apply: openclaw gateway start --force');
108
+ }
109
+ main().catch((err) => {
110
+ console.error(`\n\x1b[31mError:\x1b[0m ${err instanceof Error ? err.message : String(err)}`);
111
+ process.exit(1);
112
+ });
113
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/cli/install.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@folotoy/folotoy-openclaw-plugin",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Empower your FoloToy with OpenClaw AI capabilities.",
5
5
  "keywords": [
6
6
  "folotoy",
@@ -22,12 +22,18 @@
22
22
  },
23
23
  "main": "./src/index.ts",
24
24
  "types": "./src/index.ts",
25
+ "bin": {
26
+ "folotoy-openclaw-plugin": "./bin/folotoy.mjs"
27
+ },
25
28
  "files": [
26
29
  "src",
30
+ "bin",
31
+ "dist/cli",
27
32
  "openclaw.plugin.json"
28
33
  ],
29
34
  "scripts": {
30
35
  "build": "tsc",
36
+ "prepublishOnly": "npm run build",
31
37
  "dev": "tsc --watch",
32
38
  "typecheck": "tsc --noEmit",
33
39
  "test": "vitest run",
@@ -35,7 +41,8 @@
35
41
  "clean": "rm -rf dist"
36
42
  },
37
43
  "dependencies": {
38
- "mqtt": "^5.10.1"
44
+ "mqtt": "^5.10.1",
45
+ "qrcode-terminal": "^0.12.0"
39
46
  },
40
47
  "devDependencies": {
41
48
  "@types/node": "^22.13.10",
@@ -0,0 +1,147 @@
1
+ import { execSync } from 'node:child_process'
2
+ import qrcode from 'qrcode-terminal'
3
+ import { DEFAULT_MQTT_HOST, DEFAULT_MQTT_PORT } from '../config.js'
4
+
5
+ const PAIR_API_BASE = process.env.PAIR_API_BASE ?? 'https://pair.folotoy.cn'
6
+ const POLL_INTERVAL_MS = 3000
7
+ const POLL_TIMEOUT_MS = 300_000 // 5 minutes
8
+
9
+ // ── Types ──────────────────────────────────────────────
10
+
11
+ type CreateSessionResponse = {
12
+ session_id: string
13
+ pair_url: string
14
+ expires_at: string
15
+ }
16
+
17
+ type PollResponse =
18
+ | { status: 'pending' }
19
+ | { status: 'completed'; toy_sn: string; toy_key: string; mqtt_host?: string; mqtt_port?: number }
20
+ | { status: 'expired' }
21
+
22
+ // ── Helpers ────────────────────────────────────────────
23
+
24
+ function checkOpenClaw(): void {
25
+ try {
26
+ execSync('openclaw --version', { stdio: 'pipe' })
27
+ } catch {
28
+ console.error('Error: openclaw is not installed or not in PATH.')
29
+ console.error('Install it first: npm i -g openclaw')
30
+ process.exit(1)
31
+ }
32
+ }
33
+
34
+ function installPlugin(): void {
35
+ try {
36
+ const list = execSync('openclaw plugins list', { stdio: 'pipe' }).toString()
37
+ if (list.includes('folotoy-openclaw-plugin')) {
38
+ return // already installed
39
+ }
40
+ } catch {
41
+ // ignore
42
+ }
43
+ console.log('Installing FoloToy plugin...')
44
+ execSync('openclaw plugins install @folotoy/folotoy-openclaw-plugin', { stdio: 'inherit' })
45
+ }
46
+
47
+ async function createSession(): Promise<CreateSessionResponse> {
48
+ const res = await fetch(`${PAIR_API_BASE}/api/pair`, { method: 'POST' })
49
+ if (!res.ok) throw new Error(`Failed to create pairing session (HTTP ${res.status})`)
50
+ return res.json() as Promise<CreateSessionResponse>
51
+ }
52
+
53
+ function displayQR(url: string): void {
54
+ qrcode.generate(url, { small: true }, (qr: string) => {
55
+ console.log(qr)
56
+ })
57
+ console.log(`Or open this URL on your phone: ${url}\n`)
58
+ }
59
+
60
+ async function sleep(ms: number): Promise<void> {
61
+ return new Promise((r) => setTimeout(r, ms))
62
+ }
63
+
64
+ async function pollSession(sessionId: string): Promise<PollResponse & { status: 'completed' }> {
65
+ const deadline = Date.now() + POLL_TIMEOUT_MS
66
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
67
+ let i = 0
68
+
69
+ while (Date.now() < deadline) {
70
+ process.stdout.write(`\r${frames[i++ % frames.length]} Waiting for pairing...`)
71
+
72
+ const res = await fetch(`${PAIR_API_BASE}/api/pair/${sessionId}`)
73
+ if (!res.ok) throw new Error(`Poll failed (HTTP ${res.status})`)
74
+ const data = (await res.json()) as PollResponse
75
+
76
+ if (data.status === 'completed') {
77
+ process.stdout.write('\r\x1b[32m✓\x1b[0m Paired successfully! \n')
78
+ return data as PollResponse & { status: 'completed' }
79
+ }
80
+ if (data.status === 'expired') {
81
+ process.stdout.write('\r')
82
+ throw new Error('Pairing session expired. Please try again.')
83
+ }
84
+
85
+ await sleep(POLL_INTERVAL_MS)
86
+ }
87
+ throw new Error('Pairing timed out after 5 minutes.')
88
+ }
89
+
90
+ function writeConfig(result: { toy_sn: string; toy_key: string; mqtt_host?: string; mqtt_port?: number }): void {
91
+ execSync(`openclaw config set channels.folotoy.flow direct`, { stdio: 'pipe' })
92
+ execSync(`openclaw config set channels.folotoy.toy_sn ${result.toy_sn}`, { stdio: 'pipe' })
93
+ execSync(`openclaw config set channels.folotoy.toy_key ${result.toy_key}`, { stdio: 'pipe' })
94
+
95
+ const mqttHost = result.mqtt_host ?? DEFAULT_MQTT_HOST
96
+ const mqttPort = result.mqtt_port ?? DEFAULT_MQTT_PORT
97
+ execSync(`openclaw config set channels.folotoy.mqtt_host ${mqttHost}`, { stdio: 'pipe' })
98
+ execSync(`openclaw config set channels.folotoy.mqtt_port ${mqttPort}`, { stdio: 'pipe' })
99
+ }
100
+
101
+ // ── Main ───────────────────────────────────────────────
102
+
103
+ async function main() {
104
+ const command = process.argv[2]
105
+
106
+ if (command !== 'install') {
107
+ console.log('Usage: npx @folotoy/folotoy-openclaw-plugin install')
108
+ process.exit(command ? 1 : 0)
109
+ }
110
+
111
+ console.log('🧸 FoloToy OpenClaw Plugin Installer\n')
112
+
113
+ // Step 1: check prerequisites
114
+ console.log('Checking openclaw...')
115
+ checkOpenClaw()
116
+ console.log('✓ openclaw found\n')
117
+
118
+ // Step 2: install plugin if not present
119
+ installPlugin()
120
+
121
+ // Step 3: create pairing session
122
+ console.log('Creating pairing session...\n')
123
+ const session = await createSession()
124
+
125
+ // Step 4: display QR code
126
+ console.log('Scan this QR code with your phone,')
127
+ console.log('then scan your toy\'s QR code on the phone:\n')
128
+ displayQR(session.pair_url)
129
+
130
+ // Step 5: poll for result
131
+ const result = await pollSession(session.session_id)
132
+
133
+ // Step 6: write config
134
+ console.log('\nWriting configuration...')
135
+ writeConfig(result)
136
+
137
+ // Step 7: done
138
+ console.log('\n\x1b[32m✓ FoloToy plugin installed and configured!\x1b[0m')
139
+ console.log(` Toy SN: ${result.toy_sn}`)
140
+ console.log(` MQTT Host: ${result.mqtt_host ?? DEFAULT_MQTT_HOST}`)
141
+ console.log('\nRestart the gateway to apply: openclaw gateway start --force')
142
+ }
143
+
144
+ main().catch((err) => {
145
+ console.error(`\n\x1b[31mError:\x1b[0m ${err instanceof Error ? err.message : String(err)}`)
146
+ process.exit(1)
147
+ })
@@ -0,0 +1,3 @@
1
+ declare module 'qrcode-terminal' {
2
+ export function generate(text: string, opts?: { small?: boolean }, cb?: (qr: string) => void): void
3
+ }