@folotoy/folotoy-openclaw-plugin 0.7.0 → 0.8.1
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 +11 -3
- package/bin/folotoy.mjs +7 -1
- package/dist/config-schema.d.ts +33 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config-schema.js +50 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -37
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +100 -41
- package/package.json +10 -4
- package/src/config-schema.ts +71 -0
- package/src/config.ts +1 -1
- package/src/index.ts +25 -37
- package/dist/cli/install.d.ts +0 -2
- package/dist/cli/install.d.ts.map +0 -1
- package/dist/cli/install.js +0 -155
- package/dist/cli/install.js.map +0 -1
- package/dist/cli/package-info.d.ts +0 -12
- package/dist/cli/package-info.d.ts.map +0 -1
- package/dist/cli/package-info.js +0 -42
- package/dist/cli/package-info.js.map +0 -1
- package/dist/cli/preset.d.ts +0 -6
- package/dist/cli/preset.d.ts.map +0 -1
- package/dist/cli/preset.js +0 -45
- package/dist/cli/preset.js.map +0 -1
- package/src/__tests__/channel.test.ts +0 -126
- package/src/__tests__/mqtt.test.ts +0 -14
- package/src/__tests__/package-info.test.ts +0 -39
- package/src/__tests__/preset.test.ts +0 -85
- package/src/__tests__/soothing.test.ts +0 -47
- package/src/__tests__/test-message.mjs +0 -190
- package/src/cli/install.ts +0 -195
- package/src/cli/package-info.ts +0 -48
- package/src/cli/preset.ts +0 -54
- package/src/cli/qrcode-terminal.d.ts +0 -3
- package/src/presets/single-soothing.json +0 -3
package/src/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { OpenClawPluginApi, ChannelPlugin, PluginRuntime } from 'openclaw/plugin-sdk/core'
|
|
2
|
+
import { buildChannelConfigSchema } from 'openclaw/plugin-sdk'
|
|
2
3
|
import { resolveCredentials, createMqttClient, buildInboundTopic, buildOutboundTopic, buildNotificationTopic } from './mqtt.js'
|
|
3
4
|
import { createSoothingPicker } from './soothing.js'
|
|
4
5
|
import { stripMarkdown } from './strip-markdown.js'
|
|
5
|
-
import {
|
|
6
|
+
import { DEFAULT_SUMMARY_ENABLED, DEFAULT_SUMMARY_MAX_CHARS, DEFAULT_SENTENCE_SPLIT_ENABLED, DEFAULT_SENTENCE_SPLIT_DELIMITERS, DEFAULT_SOOTHING_LOOP_ENABLED, DEFAULT_SOOTHING_LOOP_INTERVAL_MS, flatToPluginConfig } from './config.js'
|
|
6
7
|
import type { FlatChannelConfig } from './config.js'
|
|
8
|
+
import { FolotoyConfigSchema } from './config-schema.js'
|
|
7
9
|
import type { MqttClient } from 'mqtt'
|
|
8
10
|
|
|
9
11
|
type InboundMessage = {
|
|
@@ -42,40 +44,12 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
|
|
|
42
44
|
capabilities: {
|
|
43
45
|
chatTypes: ['direct'],
|
|
44
46
|
},
|
|
47
|
+
// ChannelPlugin's per-channel configSchema is left empty here on purpose:
|
|
48
|
+
// the plugin-level configSchema (set on the default-exported plugin object
|
|
49
|
+
// below) is what OpenClaw 2026.4.x's web UI reads. Mirrors what
|
|
50
|
+
// openclaw-weixin and other 2026.4.x-compatible plugins do.
|
|
45
51
|
configSchema: {
|
|
46
|
-
schema: {
|
|
47
|
-
type: 'object',
|
|
48
|
-
properties: {
|
|
49
|
-
flow: { type: 'string', enum: ['direct', 'api'], default: 'direct' },
|
|
50
|
-
toy_sn: { type: 'string' },
|
|
51
|
-
toy_key: { type: 'string' },
|
|
52
|
-
api_url: { type: 'string', default: 'https://api.folotoy.cn' },
|
|
53
|
-
api_key: { type: 'string' },
|
|
54
|
-
mqtt_host: { type: 'string', default: DEFAULT_MQTT_HOST },
|
|
55
|
-
mqtt_port: { type: 'number', default: DEFAULT_MQTT_PORT },
|
|
56
|
-
summary_enabled: { type: 'boolean', default: DEFAULT_SUMMARY_ENABLED },
|
|
57
|
-
summary_max_chars: { type: 'number', default: DEFAULT_SUMMARY_MAX_CHARS },
|
|
58
|
-
sentence_split_enabled: { type: 'boolean', default: DEFAULT_SENTENCE_SPLIT_ENABLED },
|
|
59
|
-
sentence_split_delimiters: { type: 'string', default: DEFAULT_SENTENCE_SPLIT_DELIMITERS },
|
|
60
|
-
soothing_loop_enabled: { type: 'boolean', default: DEFAULT_SOOTHING_LOOP_ENABLED },
|
|
61
|
-
soothing_loop_interval_ms: { type: 'number', default: DEFAULT_SOOTHING_LOOP_INTERVAL_MS },
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
uiHints: {
|
|
65
|
-
flow: { label: 'Auth Flow' },
|
|
66
|
-
toy_sn: { label: 'Toy SN' },
|
|
67
|
-
toy_key: { label: 'Toy Key', sensitive: true },
|
|
68
|
-
api_url: { label: 'API URL', placeholder: 'https://api.folotoy.com' },
|
|
69
|
-
api_key: { label: 'API Key', sensitive: true },
|
|
70
|
-
mqtt_host: { label: 'MQTT Host', placeholder: DEFAULT_MQTT_HOST },
|
|
71
|
-
mqtt_port: { label: 'MQTT Port' },
|
|
72
|
-
summary_enabled: { label: 'Enable Summary' },
|
|
73
|
-
summary_max_chars: { label: 'Summary Max Characters' },
|
|
74
|
-
sentence_split_enabled: { label: 'Enable Sentence Splitting' },
|
|
75
|
-
sentence_split_delimiters: { label: 'Sentence Delimiters' },
|
|
76
|
-
soothing_loop_enabled: { label: 'Enable Soothing Loop' },
|
|
77
|
-
soothing_loop_interval_ms: { label: 'Soothing Loop Interval (ms)' },
|
|
78
|
-
},
|
|
52
|
+
schema: { type: 'object', additionalProperties: false, properties: {} },
|
|
79
53
|
},
|
|
80
54
|
config: {
|
|
81
55
|
listAccountIds: (cfg) => {
|
|
@@ -455,7 +429,21 @@ export function sendNotification({ text, accountId }: { text: string; accountId?
|
|
|
455
429
|
return { channel: 'folotoy', messageId: String(msgId) }
|
|
456
430
|
}
|
|
457
431
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Plugin manifest exposed to OpenClaw. Top-level fields (id/name/description/
|
|
434
|
+
* configSchema) are what the OpenClaw 2026.4.x web UI reads to render the
|
|
435
|
+
* config form — the per-channel configSchema on `folotoyChannel` above is
|
|
436
|
+
* intentionally empty so the UI uses this one.
|
|
437
|
+
*/
|
|
438
|
+
const folotoyPlugin = {
|
|
439
|
+
id: 'folotoy-openclaw-plugin',
|
|
440
|
+
name: 'FoloToy',
|
|
441
|
+
description: 'Empower your FoloToy with OpenClaw AI capabilities.',
|
|
442
|
+
configSchema: buildChannelConfigSchema(FolotoyConfigSchema),
|
|
443
|
+
register(api: OpenClawPluginApi) {
|
|
444
|
+
subagent = api.runtime.subagent
|
|
445
|
+
api.registerChannel({ plugin: folotoyChannel })
|
|
446
|
+
},
|
|
461
447
|
}
|
|
448
|
+
|
|
449
|
+
export default folotoyPlugin
|
package/dist/cli/install.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/cli/install.ts"],"names":[],"mappings":""}
|
package/dist/cli/install.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
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
|
-
import { getInstallSpec, getPluginName } from './package-info.js';
|
|
5
|
-
import { loadPreset, listPresets } from './preset.js';
|
|
6
|
-
const PAIR_API_BASE = process.env.PAIR_API_BASE ?? 'https://pair.folotoy.cn';
|
|
7
|
-
const POLL_INTERVAL_MS = 3000;
|
|
8
|
-
const POLL_TIMEOUT_MS = 300_000; // 5 minutes
|
|
9
|
-
// ── Helpers ────────────────────────────────────────────
|
|
10
|
-
function checkOpenClaw() {
|
|
11
|
-
try {
|
|
12
|
-
execSync('openclaw --version', { stdio: 'pipe' });
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
console.error('Error: openclaw is not installed or not in PATH.');
|
|
16
|
-
console.error('Install it first: npm i -g openclaw');
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function installPlugin() {
|
|
21
|
-
try {
|
|
22
|
-
const list = execSync('openclaw plugins list', { stdio: 'pipe' }).toString();
|
|
23
|
-
if (list.includes('folotoy-openclaw-plugin')) {
|
|
24
|
-
return; // already installed
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
// ignore
|
|
29
|
-
}
|
|
30
|
-
const spec = getInstallSpec();
|
|
31
|
-
console.log(`Installing FoloToy plugin (${spec})...`);
|
|
32
|
-
execSync(`openclaw plugins install ${spec}`, { stdio: 'inherit' });
|
|
33
|
-
}
|
|
34
|
-
async function createSession() {
|
|
35
|
-
const res = await fetch(`${PAIR_API_BASE}/api/pair`, { method: 'POST' });
|
|
36
|
-
if (!res.ok)
|
|
37
|
-
throw new Error(`Failed to create pairing session (HTTP ${res.status})`);
|
|
38
|
-
return res.json();
|
|
39
|
-
}
|
|
40
|
-
function displayQR(url) {
|
|
41
|
-
qrcode.generate(url, { small: true }, (qr) => {
|
|
42
|
-
console.log(qr);
|
|
43
|
-
});
|
|
44
|
-
console.log(`Or open this URL on your phone: ${url}\n`);
|
|
45
|
-
}
|
|
46
|
-
async function sleep(ms) {
|
|
47
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
48
|
-
}
|
|
49
|
-
async function pollSession(sessionId) {
|
|
50
|
-
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
51
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
52
|
-
let i = 0;
|
|
53
|
-
while (Date.now() < deadline) {
|
|
54
|
-
process.stdout.write(`\r${frames[i++ % frames.length]} Waiting for pairing...`);
|
|
55
|
-
const res = await fetch(`${PAIR_API_BASE}/api/pair/${sessionId}`);
|
|
56
|
-
if (!res.ok)
|
|
57
|
-
throw new Error(`Poll failed (HTTP ${res.status})`);
|
|
58
|
-
const data = (await res.json());
|
|
59
|
-
if (data.status === 'completed') {
|
|
60
|
-
process.stdout.write('\r\x1b[32m✓\x1b[0m Paired successfully! \n');
|
|
61
|
-
return data;
|
|
62
|
-
}
|
|
63
|
-
if (data.status === 'expired') {
|
|
64
|
-
process.stdout.write('\r');
|
|
65
|
-
throw new Error('Pairing session expired. Please try again.');
|
|
66
|
-
}
|
|
67
|
-
await sleep(POLL_INTERVAL_MS);
|
|
68
|
-
}
|
|
69
|
-
throw new Error('Pairing timed out after 5 minutes.');
|
|
70
|
-
}
|
|
71
|
-
function restartGateway() {
|
|
72
|
-
try {
|
|
73
|
-
execSync('openclaw gateway restart', { stdio: 'inherit' });
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
console.warn('⚠ Failed to restart gateway. You can restart manually: openclaw gateway restart');
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function writeConfig(result, preset = {}) {
|
|
80
|
-
execSync(`openclaw config set channels.folotoy.flow direct`, { stdio: 'pipe' });
|
|
81
|
-
execSync(`openclaw config set channels.folotoy.toy_sn ${result.toy_sn}`, { stdio: 'pipe' });
|
|
82
|
-
execSync(`openclaw config set channels.folotoy.toy_key ${result.toy_key}`, { stdio: 'pipe' });
|
|
83
|
-
const mqttHost = result.mqtt_host ?? DEFAULT_MQTT_HOST;
|
|
84
|
-
const mqttPort = result.mqtt_port ?? DEFAULT_MQTT_PORT;
|
|
85
|
-
execSync(`openclaw config set channels.folotoy.mqtt_host ${mqttHost}`, { stdio: 'pipe' });
|
|
86
|
-
execSync(`openclaw config set channels.folotoy.mqtt_port ${mqttPort}`, { stdio: 'pipe' });
|
|
87
|
-
for (const [key, value] of Object.entries(preset)) {
|
|
88
|
-
execSync(`openclaw config set channels.folotoy.${key} ${value}`, { stdio: 'pipe' });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function parsePresetArg(argv) {
|
|
92
|
-
for (let i = 0; i < argv.length; i++) {
|
|
93
|
-
const arg = argv[i];
|
|
94
|
-
if (arg === '--preset') {
|
|
95
|
-
const value = argv[i + 1];
|
|
96
|
-
if (!value || value.startsWith('--')) {
|
|
97
|
-
throw new Error('--preset requires a name (e.g. --preset single-soothing)');
|
|
98
|
-
}
|
|
99
|
-
return value;
|
|
100
|
-
}
|
|
101
|
-
if (arg && arg.startsWith('--preset=')) {
|
|
102
|
-
return arg.slice('--preset='.length);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return undefined;
|
|
106
|
-
}
|
|
107
|
-
// ── Main ───────────────────────────────────────────────
|
|
108
|
-
async function main() {
|
|
109
|
-
const argv = process.argv.slice(2);
|
|
110
|
-
const command = argv[0];
|
|
111
|
-
if (command !== 'install') {
|
|
112
|
-
const presets = listPresets();
|
|
113
|
-
console.log(`Usage: npx ${getPluginName()} install [--preset <name>]`);
|
|
114
|
-
if (presets.length)
|
|
115
|
-
console.log(`Available presets: ${presets.join(', ')}`);
|
|
116
|
-
process.exit(command ? 1 : 0);
|
|
117
|
-
}
|
|
118
|
-
// Resolve preset before any side effects (pairing, plugin install) so a bad
|
|
119
|
-
// preset name fails fast without making the user scan a QR.
|
|
120
|
-
const presetName = parsePresetArg(argv.slice(1));
|
|
121
|
-
const preset = presetName ? loadPreset(presetName) : {};
|
|
122
|
-
console.log('🧸 FoloToy OpenClaw Plugin Installer\n');
|
|
123
|
-
if (presetName) {
|
|
124
|
-
console.log(`Using preset: ${presetName} → ${JSON.stringify(preset)}\n`);
|
|
125
|
-
}
|
|
126
|
-
// Step 1: check prerequisites
|
|
127
|
-
console.log('Checking openclaw...');
|
|
128
|
-
checkOpenClaw();
|
|
129
|
-
console.log('✓ openclaw found\n');
|
|
130
|
-
// Step 2: install plugin if not present
|
|
131
|
-
installPlugin();
|
|
132
|
-
// Step 3: create pairing session
|
|
133
|
-
console.log('Creating pairing session...\n');
|
|
134
|
-
const session = await createSession();
|
|
135
|
-
// Step 4: display QR code
|
|
136
|
-
console.log('Scan this QR code with your phone,');
|
|
137
|
-
console.log('then scan your toy\'s QR code on the phone:\n');
|
|
138
|
-
displayQR(session.pair_url);
|
|
139
|
-
// Step 5: poll for result
|
|
140
|
-
const result = await pollSession(session.session_id);
|
|
141
|
-
// Step 6: write config
|
|
142
|
-
console.log('\nWriting configuration...');
|
|
143
|
-
writeConfig(result, preset);
|
|
144
|
-
// Step 7: restart gateway
|
|
145
|
-
console.log('\nRestarting gateway...');
|
|
146
|
-
restartGateway();
|
|
147
|
-
// Step 8: done
|
|
148
|
-
console.log('\n\x1b[32m✓ FoloToy plugin installed and configured!\x1b[0m');
|
|
149
|
-
console.log(` Toy SN: ${result.toy_sn}`);
|
|
150
|
-
}
|
|
151
|
-
main().catch((err) => {
|
|
152
|
-
console.error(`\n\x1b[31mError:\x1b[0m ${err instanceof Error ? err.message : String(err)}`);
|
|
153
|
-
process.exit(1);
|
|
154
|
-
});
|
|
155
|
-
//# sourceMappingURL=install.js.map
|
package/dist/cli/install.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/cli/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export declare function findPackageRoot(): string;
|
|
2
|
-
export declare function getPluginName(): string;
|
|
3
|
-
export declare function getPluginVersion(): string;
|
|
4
|
-
/**
|
|
5
|
-
* Returns the npm spec string `<name>@<version>` to pass to
|
|
6
|
-
* `openclaw plugins install`. Pinning the exact version prevents OpenClaw
|
|
7
|
-
* from rejecting the install when `latest` happens to point at a prerelease
|
|
8
|
-
* — without this, `openclaw plugins install <bare-name>` resolves through
|
|
9
|
-
* the `latest` dist-tag and refuses prereleases for safety.
|
|
10
|
-
*/
|
|
11
|
-
export declare function getInstallSpec(): string;
|
|
12
|
-
//# sourceMappingURL=package-info.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package-info.d.ts","sourceRoot":"","sources":["../../src/cli/package-info.ts"],"names":[],"mappings":"AAIA,wBAAgB,eAAe,IAAI,MAAM,CAQxC;AAkBD,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
package/dist/cli/package-info.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
export function findPackageRoot() {
|
|
5
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
while (true) {
|
|
7
|
-
if (existsSync(join(dir, 'package.json')))
|
|
8
|
-
return dir;
|
|
9
|
-
const parent = dirname(dir);
|
|
10
|
-
if (parent === dir)
|
|
11
|
-
throw new Error('package.json not found while resolving package root');
|
|
12
|
-
dir = parent;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
let cached;
|
|
16
|
-
function readPackageInfo() {
|
|
17
|
-
if (cached)
|
|
18
|
-
return cached;
|
|
19
|
-
const pkg = JSON.parse(readFileSync(join(findPackageRoot(), 'package.json'), 'utf8'));
|
|
20
|
-
if (typeof pkg.name !== 'string' || typeof pkg.version !== 'string') {
|
|
21
|
-
throw new Error('package.json is missing name or version');
|
|
22
|
-
}
|
|
23
|
-
cached = { name: pkg.name, version: pkg.version };
|
|
24
|
-
return cached;
|
|
25
|
-
}
|
|
26
|
-
export function getPluginName() {
|
|
27
|
-
return readPackageInfo().name;
|
|
28
|
-
}
|
|
29
|
-
export function getPluginVersion() {
|
|
30
|
-
return readPackageInfo().version;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Returns the npm spec string `<name>@<version>` to pass to
|
|
34
|
-
* `openclaw plugins install`. Pinning the exact version prevents OpenClaw
|
|
35
|
-
* from rejecting the install when `latest` happens to point at a prerelease
|
|
36
|
-
* — without this, `openclaw plugins install <bare-name>` resolves through
|
|
37
|
-
* the `latest` dist-tag and refuses prereleases for safety.
|
|
38
|
-
*/
|
|
39
|
-
export function getInstallSpec() {
|
|
40
|
-
return `${getPluginName()}@${getPluginVersion()}`;
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=package-info.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package-info.js","sourceRoot":"","sources":["../../src/cli/package-info.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,UAAU,eAAe;IAC7B,IAAI,GAAG,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YAAE,OAAO,GAAG,CAAA;QACrD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,KAAK,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;QAC1F,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC;AAID,IAAI,MAA+B,CAAA;AAEnC,SAAS,eAAe;IACtB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAA;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACrB,CAAA;IAC1C,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IACD,MAAM,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;IACjD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,eAAe,EAAE,CAAC,IAAI,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,eAAe,EAAE,CAAC,OAAO,CAAA;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,GAAG,aAAa,EAAE,IAAI,gBAAgB,EAAE,EAAE,CAAA;AACnD,CAAC"}
|
package/dist/cli/preset.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare const PRESET_WHITELIST: readonly ["soothing_loop_enabled"];
|
|
2
|
-
export type PresetKey = (typeof PRESET_WHITELIST)[number];
|
|
3
|
-
export type Preset = Partial<Record<PresetKey, boolean>>;
|
|
4
|
-
export declare function listPresets(dir?: string): string[];
|
|
5
|
-
export declare function loadPreset(name: string, dir?: string): Preset;
|
|
6
|
-
//# sourceMappingURL=preset.d.ts.map
|
package/dist/cli/preset.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../src/cli/preset.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,oCAAqC,CAAA;AAClE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AACzD,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;AAMxD,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAAoB,GAAG,MAAM,EAAE,CAM/D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAoB,GAAG,MAAM,CAiC1E"}
|
package/dist/cli/preset.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { findPackageRoot } from './package-info.js';
|
|
4
|
-
export const PRESET_WHITELIST = ['soothing_loop_enabled'];
|
|
5
|
-
function presetDir() {
|
|
6
|
-
return join(findPackageRoot(), 'src', 'presets');
|
|
7
|
-
}
|
|
8
|
-
export function listPresets(dir = presetDir()) {
|
|
9
|
-
if (!existsSync(dir))
|
|
10
|
-
return [];
|
|
11
|
-
return readdirSync(dir)
|
|
12
|
-
.filter((f) => f.endsWith('.json'))
|
|
13
|
-
.map((f) => f.slice(0, -'.json'.length))
|
|
14
|
-
.sort();
|
|
15
|
-
}
|
|
16
|
-
export function loadPreset(name, dir = presetDir()) {
|
|
17
|
-
const file = join(dir, `${name}.json`);
|
|
18
|
-
if (!existsSync(file)) {
|
|
19
|
-
const available = listPresets(dir);
|
|
20
|
-
throw new Error(`Preset "${name}" not found. Available: ${available.length ? available.join(', ') : '(none)'}`);
|
|
21
|
-
}
|
|
22
|
-
let parsed;
|
|
23
|
-
try {
|
|
24
|
-
parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
throw new Error(`Preset "${name}" is not valid JSON: ${err.message}`);
|
|
28
|
-
}
|
|
29
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
30
|
-
throw new Error(`Preset "${name}" must be a JSON object`);
|
|
31
|
-
}
|
|
32
|
-
const allowed = PRESET_WHITELIST;
|
|
33
|
-
const result = {};
|
|
34
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
35
|
-
if (!allowed.includes(key)) {
|
|
36
|
-
throw new Error(`Preset "${name}" has unknown key "${key}". Allowed: ${PRESET_WHITELIST.join(', ')}`);
|
|
37
|
-
}
|
|
38
|
-
if (typeof value !== 'boolean') {
|
|
39
|
-
throw new Error(`Preset "${name}" key "${key}" must be a boolean (got ${typeof value})`);
|
|
40
|
-
}
|
|
41
|
-
result[key] = value;
|
|
42
|
-
}
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
//# sourceMappingURL=preset.js.map
|
package/dist/cli/preset.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"preset.js","sourceRoot":"","sources":["../../src/cli/preset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,uBAAuB,CAAU,CAAA;AAIlE,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,CAAA;AAClD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc,SAAS,EAAE;IACnD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAA;IAC/B,OAAO,WAAW,CAAC,GAAG,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;SACvC,IAAI,EAAE,CAAA;AACX,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,MAAc,SAAS,EAAE;IAChE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,CAAA;IACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,IAAI,KAAK,CACb,WAAW,IAAI,2BAA2B,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAC/F,CAAA;IACH,CAAC;IAED,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IACjD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,wBAAyB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAA;IAClF,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,yBAAyB,CAAC,CAAA;IAC3D,CAAC;IAED,MAAM,OAAO,GAAG,gBAAqC,CAAA;IACrD,MAAM,MAAM,GAAW,EAAE,CAAA;IACzB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAiC,CAAC,EAAE,CAAC;QAC7E,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,sBAAsB,GAAG,eAAe,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACvG,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,UAAU,GAAG,4BAA4B,OAAO,KAAK,GAAG,CAAC,CAAA;QAC1F,CAAC;QACD,MAAM,CAAC,GAAgB,CAAC,GAAG,KAAK,CAAA;IAClC,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import { EventEmitter } from 'events'
|
|
3
|
-
import { buildInboundTopic, buildOutboundTopic } from '../mqtt.js'
|
|
4
|
-
|
|
5
|
-
// Replicate the message parsing logic from index.ts for unit testing
|
|
6
|
-
type InboundMessage = {
|
|
7
|
-
msgId: number
|
|
8
|
-
identifier: 'chat_input'
|
|
9
|
-
inputParams: { text: string; recording_id: number }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type OutboundMessage = {
|
|
13
|
-
msgId: number
|
|
14
|
-
identifier: 'chat_output'
|
|
15
|
-
outParams: { content: string; recording_id: number; order: number; is_finished: boolean }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function makeMockClient() {
|
|
19
|
-
const emitter = new EventEmitter()
|
|
20
|
-
return Object.assign(emitter, {
|
|
21
|
-
subscribe: vi.fn((_topic: string, cb: (err: null) => void) => cb(null)),
|
|
22
|
-
publish: vi.fn(),
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function setupSubscriber(client: ReturnType<typeof makeMockClient>, toy_sn: string, onMessage: (msgId: number, text: string, recording_id: number) => void) {
|
|
27
|
-
const topic = buildInboundTopic(toy_sn)
|
|
28
|
-
client.subscribe(topic, () => {})
|
|
29
|
-
client.on('message', (_topic: string, payload: Buffer) => {
|
|
30
|
-
if (_topic !== topic) return
|
|
31
|
-
try {
|
|
32
|
-
const msg = JSON.parse(payload.toString()) as InboundMessage
|
|
33
|
-
if (msg.identifier !== 'chat_input' || typeof msg.inputParams?.text !== 'string') return
|
|
34
|
-
onMessage(msg.msgId, msg.inputParams.text, msg.inputParams.recording_id)
|
|
35
|
-
} catch {
|
|
36
|
-
// ignore
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
describe('inbound message parsing', () => {
|
|
42
|
-
const toy_sn = 'SN001'
|
|
43
|
-
const inboundTopic = buildInboundTopic(toy_sn)
|
|
44
|
-
|
|
45
|
-
it('calls onMessage with msgId, text and recording_id on valid chat_input', () => {
|
|
46
|
-
const client = makeMockClient()
|
|
47
|
-
const onMessage = vi.fn()
|
|
48
|
-
setupSubscriber(client, toy_sn, onMessage)
|
|
49
|
-
|
|
50
|
-
const msg: InboundMessage = { msgId: 42, identifier: 'chat_input', inputParams: { text: 'hello', recording_id: 100 } }
|
|
51
|
-
client.emit('message', inboundTopic, Buffer.from(JSON.stringify(msg)))
|
|
52
|
-
|
|
53
|
-
expect(onMessage).toHaveBeenCalledWith(42, 'hello', 100)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('ignores messages on other topics', () => {
|
|
57
|
-
const client = makeMockClient()
|
|
58
|
-
const onMessage = vi.fn()
|
|
59
|
-
setupSubscriber(client, toy_sn, onMessage)
|
|
60
|
-
|
|
61
|
-
const msg: InboundMessage = { msgId: 1, identifier: 'chat_input', inputParams: { text: 'hi', recording_id: 1 } }
|
|
62
|
-
client.emit('message', '/openapi/folotoy/OTHER/thing/command/call', Buffer.from(JSON.stringify(msg)))
|
|
63
|
-
|
|
64
|
-
expect(onMessage).not.toHaveBeenCalled()
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('ignores messages with unknown identifier', () => {
|
|
68
|
-
const client = makeMockClient()
|
|
69
|
-
const onMessage = vi.fn()
|
|
70
|
-
setupSubscriber(client, toy_sn, onMessage)
|
|
71
|
-
|
|
72
|
-
client.emit('message', inboundTopic, Buffer.from(JSON.stringify({ msgId: 1, identifier: 'other', inputParams: { text: 'hi', recording_id: 1 } })))
|
|
73
|
-
|
|
74
|
-
expect(onMessage).not.toHaveBeenCalled()
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('ignores malformed JSON', () => {
|
|
78
|
-
const client = makeMockClient()
|
|
79
|
-
const onMessage = vi.fn()
|
|
80
|
-
setupSubscriber(client, toy_sn, onMessage)
|
|
81
|
-
|
|
82
|
-
client.emit('message', inboundTopic, Buffer.from('not json'))
|
|
83
|
-
|
|
84
|
-
expect(onMessage).not.toHaveBeenCalled()
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
describe('outbound message format', () => {
|
|
89
|
-
const toy_sn = 'SN001'
|
|
90
|
-
const outboundTopic = buildOutboundTopic(toy_sn)
|
|
91
|
-
|
|
92
|
-
it('publishes chat_output with recording_id, order and is_finished', () => {
|
|
93
|
-
const client = makeMockClient()
|
|
94
|
-
const outMsg: OutboundMessage = {
|
|
95
|
-
msgId: 42,
|
|
96
|
-
identifier: 'chat_output',
|
|
97
|
-
outParams: { content: 'world', recording_id: 100, order: 1, is_finished: false },
|
|
98
|
-
}
|
|
99
|
-
client.publish(outboundTopic, JSON.stringify(outMsg))
|
|
100
|
-
|
|
101
|
-
expect(client.publish).toHaveBeenCalledOnce()
|
|
102
|
-
const [t, payload] = client.publish.mock.calls[0] as [string, string]
|
|
103
|
-
expect(t).toBe(outboundTopic)
|
|
104
|
-
expect(JSON.parse(payload)).toEqual({
|
|
105
|
-
msgId: 42,
|
|
106
|
-
identifier: 'chat_output',
|
|
107
|
-
outParams: { content: 'world', recording_id: 100, order: 1, is_finished: false },
|
|
108
|
-
})
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('publishes finish message with is_finished=true', () => {
|
|
112
|
-
const client = makeMockClient()
|
|
113
|
-
const finishMsg: OutboundMessage = {
|
|
114
|
-
msgId: 42,
|
|
115
|
-
identifier: 'chat_output',
|
|
116
|
-
outParams: { content: '', recording_id: 100, order: 2, is_finished: true },
|
|
117
|
-
}
|
|
118
|
-
client.publish(outboundTopic, JSON.stringify(finishMsg))
|
|
119
|
-
|
|
120
|
-
const [, payload] = client.publish.mock.calls[0] as [string, string]
|
|
121
|
-
const parsed = JSON.parse(payload)
|
|
122
|
-
expect(parsed.outParams.is_finished).toBe(true)
|
|
123
|
-
expect(parsed.outParams.order).toBe(2)
|
|
124
|
-
expect(parsed.outParams.content).toBe('')
|
|
125
|
-
})
|
|
126
|
-
})
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { buildInboundTopic, buildOutboundTopic } from '../mqtt.js'
|
|
3
|
-
|
|
4
|
-
describe('buildInboundTopic', () => {
|
|
5
|
-
it('builds the correct inbound topic for a given SN', () => {
|
|
6
|
-
expect(buildInboundTopic('SN001')).toBe('/openapi/folotoy/SN001/thing/command/call')
|
|
7
|
-
})
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
describe('buildOutboundTopic', () => {
|
|
11
|
-
it('builds the correct outbound topic for a given SN', () => {
|
|
12
|
-
expect(buildOutboundTopic('SN001')).toBe('/openapi/folotoy/SN001/thing/command/callAck')
|
|
13
|
-
})
|
|
14
|
-
})
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import {
|
|
5
|
-
findPackageRoot,
|
|
6
|
-
getInstallSpec,
|
|
7
|
-
getPluginName,
|
|
8
|
-
getPluginVersion,
|
|
9
|
-
} from '../cli/package-info.js'
|
|
10
|
-
|
|
11
|
-
describe('package-info', () => {
|
|
12
|
-
it('findPackageRoot resolves to a directory containing package.json', () => {
|
|
13
|
-
const root = findPackageRoot()
|
|
14
|
-
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
15
|
-
expect(typeof pkg.name).toBe('string')
|
|
16
|
-
expect(typeof pkg.version).toBe('string')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('getPluginName matches package.json name', () => {
|
|
20
|
-
const root = findPackageRoot()
|
|
21
|
-
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
22
|
-
expect(getPluginName()).toBe(pkg.name)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('getPluginVersion matches package.json version', () => {
|
|
26
|
-
const root = findPackageRoot()
|
|
27
|
-
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
28
|
-
expect(getPluginVersion()).toBe(pkg.version)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('getInstallSpec is "<name>@<version>" — pins the exact version so OpenClaw will install prereleases', () => {
|
|
32
|
-
expect(getInstallSpec()).toBe(`${getPluginName()}@${getPluginVersion()}`)
|
|
33
|
-
// Sanity: it must contain an `@` separator after the scope's leading `@`.
|
|
34
|
-
const spec = getInstallSpec()
|
|
35
|
-
const lastAt = spec.lastIndexOf('@')
|
|
36
|
-
expect(lastAt).toBeGreaterThan(0)
|
|
37
|
-
expect(spec.slice(lastAt + 1)).toMatch(/^\d+\.\d+\.\d+/)
|
|
38
|
-
})
|
|
39
|
-
})
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { listPresets, loadPreset, PRESET_WHITELIST } from '../cli/preset.js'
|
|
6
|
-
|
|
7
|
-
let dir: string
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
dir = mkdtempSync(join(tmpdir(), 'preset-test-'))
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
rmSync(dir, { recursive: true, force: true })
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
function writePreset(name: string, body: unknown): void {
|
|
18
|
-
writeFileSync(join(dir, `${name}.json`), JSON.stringify(body))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
describe('loadPreset', () => {
|
|
22
|
-
it('returns parsed preset for whitelisted boolean key', () => {
|
|
23
|
-
writePreset('single-soothing', { soothing_loop_enabled: false })
|
|
24
|
-
expect(loadPreset('single-soothing', dir)).toEqual({ soothing_loop_enabled: false })
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('throws when preset file does not exist, listing available presets', () => {
|
|
28
|
-
writePreset('alpha', { soothing_loop_enabled: true })
|
|
29
|
-
writePreset('beta', { soothing_loop_enabled: false })
|
|
30
|
-
expect(() => loadPreset('missing', dir)).toThrow(/Preset "missing" not found.*alpha, beta/)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('throws when preset has unknown key', () => {
|
|
34
|
-
writePreset('bad', { soothing_loop_enabled: false, summary_enabled: true })
|
|
35
|
-
expect(() => loadPreset('bad', dir)).toThrow(/unknown key "summary_enabled"/)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('throws when whitelisted key has wrong type', () => {
|
|
39
|
-
writePreset('bad', { soothing_loop_enabled: 'false' })
|
|
40
|
-
expect(() => loadPreset('bad', dir)).toThrow(/must be a boolean \(got string\)/)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('throws when JSON is not an object', () => {
|
|
44
|
-
writeFileSync(join(dir, 'arr.json'), '[1, 2, 3]')
|
|
45
|
-
expect(() => loadPreset('arr', dir)).toThrow(/must be a JSON object/)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('throws when JSON is malformed', () => {
|
|
49
|
-
writeFileSync(join(dir, 'bad.json'), '{ not json')
|
|
50
|
-
expect(() => loadPreset('bad', dir)).toThrow(/not valid JSON/)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('whitelist contains only soothing_loop_enabled (current scope)', () => {
|
|
54
|
-
expect(PRESET_WHITELIST).toEqual(['soothing_loop_enabled'])
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
describe('shipped presets', () => {
|
|
59
|
-
it('single-soothing resolves from default package presets dir and disables the soothing loop', () => {
|
|
60
|
-
expect(loadPreset('single-soothing')).toEqual({ soothing_loop_enabled: false })
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('lists single-soothing among the default presets', () => {
|
|
64
|
-
expect(listPresets()).toContain('single-soothing')
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
describe('listPresets', () => {
|
|
69
|
-
it('returns sorted preset names without .json extension', () => {
|
|
70
|
-
writePreset('zeta', {})
|
|
71
|
-
writePreset('alpha', {})
|
|
72
|
-
writePreset('beta', {})
|
|
73
|
-
expect(listPresets(dir)).toEqual(['alpha', 'beta', 'zeta'])
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('returns empty array for nonexistent directory', () => {
|
|
77
|
-
expect(listPresets(join(dir, 'does-not-exist'))).toEqual([])
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('ignores non-json files', () => {
|
|
81
|
-
writePreset('valid', {})
|
|
82
|
-
writeFileSync(join(dir, 'README.md'), 'hi')
|
|
83
|
-
expect(listPresets(dir)).toEqual(['valid'])
|
|
84
|
-
})
|
|
85
|
-
})
|