manyplug 1.3.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 ADDED
@@ -0,0 +1,34 @@
1
+ ![ManyPlug Banner](banner.png)
2
+
3
+ CLI plugin manager for ManyBot.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @freakk.dev/manyplug
9
+ # or
10
+ npm link # for development
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```bash
16
+ # Get help
17
+ manyplug help [command]
18
+
19
+ # Create new plugin
20
+ manyplug init my-plugin --category games
21
+
22
+ # Install from repository
23
+ manyplug install many-ai
24
+
25
+ # Install plugin from local path
26
+ manyplug install --local ../my-plugin
27
+
28
+ # List installed plugins
29
+ manyplug list
30
+
31
+ # Validate manyplug.json
32
+ manyplug validate
33
+ manyplug validate ./my-plugin
34
+ ```
package/banner.png ADDED
Binary file
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { createRequire } from 'module';
5
+ import { installCommand } from '../src/install.js';
6
+ import { listCommand } from '../src/list.js';
7
+ import { removeCommand } from '../src/remove.js';
8
+ import { enableCommand, disableCommand } from '../src/enable.js';
9
+ import { syncCommand, updateCommand } from '../src/sync.js';
10
+ import { initCommand } from '../src/init.js';
11
+ import { validateCommand } from '../src/validate.js';
12
+
13
+ const pkg = createRequire(import.meta.url)('../package.json');
14
+
15
+ // ------------------------------------------------------------
16
+ // help
17
+ // ------------------------------------------------------------
18
+
19
+ function printHelp(cmd) {
20
+ if (!cmd) {
21
+ console.log(`manyplug ${pkg.version} — plugin manager for ManyBot`);
22
+ console.log('https://git.stxerr.dev/manyplug.git\n');
23
+ console.log('commands: init install remove list enable disable sync update validate');
24
+ console.log('options: -v/--version help <command>');
25
+ return;
26
+ }
27
+
28
+ const c = program.commands.find(c => c.name() === cmd || c.aliases().includes(cmd));
29
+ if (!c) { console.error(`unknown command: ${cmd}`); process.exit(1); }
30
+
31
+ const args = c.registeredArguments
32
+ .map(a => (a.required ? `<${a._name}>` : `[${a._name}]`)).join(' ');
33
+
34
+ console.log(`manyplug ${c.name()} ${args}`);
35
+ console.log(c.description());
36
+
37
+ if (c.aliases().length)
38
+ console.log(`aliases: ${c.aliases().join(', ')}`);
39
+
40
+ const opts = c.options;
41
+ if (opts.length) {
42
+ console.log('\noptions:');
43
+ for (const o of opts)
44
+ console.log(` ${o.flags.padEnd(22)} ${o.description}`);
45
+ }
46
+ }
47
+
48
+ // ------------------------------------------------------------
49
+ // program
50
+ // ------------------------------------------------------------
51
+
52
+ program
53
+ .name('manyplug')
54
+ .version(pkg.version, '-v, --version')
55
+ .helpOption(false);
56
+
57
+ program.command('help [command]').description('show help for a command')
58
+ .action(cmd => { printHelp(cmd); process.exit(0); });
59
+
60
+ program.command('init [name]').description('create new plugin boilerplate')
61
+ .option('-c, --category <cat>', 'category (games media utility service admin fun)', 'utility')
62
+ .option('--service', 'mark as background service plugin', false)
63
+ .action(initCommand);
64
+
65
+ program.command('install [plugins...]').description('install plugins from registry or local path')
66
+ .option('-l, --local <path>', 'install from local path')
67
+ .option('-y, --yes', 'skip confirmation')
68
+ .option('--needed', 'skip already up-to-date plugins')
69
+ .action(installCommand);
70
+
71
+ program.command('remove [plugins...]').alias('rm').description('remove installed plugins')
72
+ .option('-y, --yes', 'skip confirmation')
73
+ .option('--remove-deps', 'also uninstall npm dependencies')
74
+ .action(removeCommand);
75
+
76
+ program.command('list').alias('ls').description('list installed plugins (enabled only by default)')
77
+ .option('-a, --all', 'include disabled plugins')
78
+ .action(listCommand);
79
+
80
+ program.command('enable [plugins...]').description('enable plugins')
81
+ .action(enableCommand);
82
+
83
+ program.command('disable [plugins...]').description('disable plugins')
84
+ .action(disableCommand);
85
+
86
+ program.command('validate [path]').alias('val').description('validate manyplug.json')
87
+ .action(validateCommand);
88
+
89
+ program.command('sync').description('sync local registry with remote')
90
+ .option('-f, --force', 'save even if nothing changed')
91
+ .action(syncCommand);
92
+
93
+ program.command('update').description('install/update all plugins from remote')
94
+ .option('-y, --yes', 'skip confirmation')
95
+ .action(updateCommand);
96
+
97
+ // ------------------------------------------------------------
98
+
99
+ if (process.argv.length <= 2) { printHelp(); process.exit(0); }
100
+
101
+ program.on('command:*', ([op]) => {
102
+ console.error(`unknown command: ${op}`);
103
+ console.error('run "manyplug help" for usage');
104
+ process.exit(1);
105
+ });
106
+
107
+ program.parse();
package/config.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "mirrors": [
3
+ {
4
+ "name": "Stxerr.dev",
5
+ "fetch": "https://git.stxerr.dev/manyplug-repo.git/plain",
6
+ "git": "https://git.stxerr.dev/manyplug-repo.git"
7
+ },
8
+ {
9
+ "name": "Codeberg",
10
+ "fetch": "https://codeberg.org/synt-xerror/manyplug-repo/raw/branch/master",
11
+ "git": "https://codeberg.org/synt-xerror/manyplug-repo"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,273 @@
1
+ .TH MANYPLUG 1 "2026-04-20" "v0.1.0" "ManyPlug Manual"
2
+ .SH NAME
3
+ manyplug \- CLI plugin manager for ManyBot
4
+ .SH SYNOPSIS
5
+ .B manyplug
6
+ .I command
7
+ .RI [ options ]
8
+ .RI [ arguments ]
9
+ .SH DESCRIPTION
10
+ .B manyplug
11
+ is a command-line plugin manager designed specifically for ManyBot, a WhatsApp bot framework.
12
+ It provides a complete plugin management system for creating, installing, enabling, disabling,
13
+ removing, and validating plugins.
14
+ .PP
15
+ Plugins can be installed from remote registries (via git) or from local paths.
16
+ Each plugin follows a standardized structure with metadata defined in
17
+ .BR manyplug.json .
18
+ .SH COMMANDS
19
+ .SS "init [name]"
20
+ Create a new plugin boilerplate with the proper directory structure.
21
+ .PP
22
+ Options:
23
+ .RS
24
+ .TP
25
+ .BR \-c ", " \-\-category " <cat>"
26
+ Plugin category: games, media, utility, service, admin, or fun (default: utility)
27
+ .TP
28
+ .B \-\-service
29
+ Mark the plugin as a service plugin (runs in background)
30
+ .RE
31
+ .SS "install [plugins...]"
32
+ Install one or more plugins from the registry or a local path.
33
+ .PP
34
+ Options:
35
+ .RS
36
+ .TP
37
+ .BR \-l ", " \-\-local " <path>"
38
+ Install from a local directory instead of the registry (single plugin only)
39
+ .TP
40
+ .BR \-y ", " \-\-yes
41
+ Skip confirmation prompts
42
+ .TP
43
+ .B \-\-needed
44
+ Skip reinstalling plugins that are already up-to-date (pacman-style)
45
+ .RE
46
+ .SS "remove [plugins...]"
47
+ Remove installed plugins. Aliased as
48
+ .BR rm .
49
+ .PP
50
+ Options:
51
+ .RS
52
+ .TP
53
+ .BR \-y ", " \-\-yes
54
+ Skip confirmation prompts
55
+ .TP
56
+ .B \-\-remove\-deps
57
+ Also remove npm dependencies installed for the plugin
58
+ .RE
59
+ .SS "list"
60
+ List all installed plugins (enabled only by default). Aliased as
61
+ .BR ls .
62
+ .PP
63
+ Options:
64
+ .RS
65
+ .TP
66
+ .BR \-a ", " \-\-all
67
+ Include disabled plugins in the listing
68
+ .RE
69
+ .SS "enable [plugins...]"
70
+ Enable one or more installed plugins. Enabled plugins are loaded by ManyBot on startup.
71
+ .SS "disable [plugins...]"
72
+ Disable one or more installed plugins. Disabled plugins remain installed but are not loaded.
73
+ .SS "sync"
74
+ Synchronize the local registry with remote repositories.
75
+ .PP
76
+ Options:
77
+ .RS
78
+ .TP
79
+ .B \-\-no\-add
80
+ Do not add new plugins discovered in the remote registry
81
+ .TP
82
+ .BR \-u ", " \-\-update
83
+ Install or update plugins from the remote registry
84
+ .RE
85
+ .SS "update"
86
+ Update all plugins from remote (equivalent to
87
+ .BR "sync --update" ).
88
+ .SS "validate [path]"
89
+ Validate a plugin's
90
+ .B manyplug.json
91
+ configuration file. Aliased as
92
+ .BR val .
93
+ .PP
94
+ If no path is specified, validates the current directory.
95
+ .SH "PLUGIN STRUCTURE"
96
+ A valid plugin follows this directory structure:
97
+ .PP
98
+ .RS
99
+ .nf
100
+ my-plugin/
101
+ ├── manyplug.json # Plugin metadata (required)
102
+ ├── index.js # Entry point (or as specified in main)
103
+ ├── locale/ # Translations (recommended)
104
+ │ ├── pt.json
105
+ │ ├── en.json
106
+ │ └── es.json
107
+ └── README.md # Documentation
108
+ .fi
109
+ .RE
110
+ .SH "MANYPLUG.JSON"
111
+ The
112
+ .B manyplug.json
113
+ file defines plugin metadata. Required fields:
114
+ .PP
115
+ .RS
116
+ .TP
117
+ .B name
118
+ Plugin name (lowercase letters, numbers, hyphens; 2-50 characters)
119
+ .TP
120
+ .B version
121
+ Semantic version (e.g., "1.0.0")
122
+ .TP
123
+ .B category
124
+ One of: games, media, utility, service, admin, fun
125
+ .RE
126
+ .PP
127
+ Optional fields:
128
+ .PP
129
+ .RS
130
+ .TP
131
+ .B service
132
+ Set to true for background service plugins
133
+ .TP
134
+ .B description
135
+ Plugin description
136
+ .TP
137
+ .B author
138
+ Author name
139
+ .TP
140
+ .B license
141
+ License identifier (e.g., "MIT")
142
+ .TP
143
+ .B main
144
+ Entry point file (default: "index.js")
145
+ .TP
146
+ .B dependencies
147
+ npm dependencies object
148
+ .TP
149
+ .B externalDependencies
150
+ System command dependencies object
151
+ .RE
152
+ .SH "SERVICE PLUGINS"
153
+ Service plugins (marked with
154
+ .B "service: true"
155
+ ) run continuously in the background rather than responding to commands.
156
+ They are useful for tasks like tracking user activity, scheduled jobs, or maintaining state.
157
+ .SH "EXTERNAL DEPENDENCIES"
158
+ Plugins can declare system-level dependencies in
159
+ .BR manyplug.json :
160
+ .PP
161
+ .RS
162
+ .nf
163
+ "externalDependencies": {
164
+ "ffmpeg": "ffmpeg",
165
+ "python3": {
166
+ "command": "python3",
167
+ "optional": true
168
+ }
169
+ }
170
+ .fi
171
+ .RE
172
+ .PP
173
+ The
174
+ .B validate
175
+ command checks if these commands exist in the system PATH.
176
+ .SH "REGISTRY"
177
+ ManyPlug maintains a local
178
+ .B registry.json
179
+ file tracking installed plugins, their versions, categories, and whether they are local or remote.
180
+ .PP
181
+ The remote registry is fetched from configured mirrors (see
182
+ .BR config.json ).
183
+ .SH "PLUGIN ENTRY POINT"
184
+ A plugin's entry file exports an async function receiving
185
+ .B msg
186
+ and
187
+ .B api
188
+ objects:
189
+ .PP
190
+ .RS
191
+ .nf
192
+ export default async function ({ msg, api }) {
193
+ if (!msg.is(CMD_PREFIX + "command")) return;
194
+ await msg.reply("Hello!");
195
+ }
196
+ .fi
197
+ .RE
198
+ .SH "FILES"
199
+ .TP
200
+ .I ./registry.json
201
+ Local plugin registry tracking installed plugins
202
+ .TP
203
+ .I ./config.json
204
+ ManyPlug configuration with mirror URLs
205
+ .TP
206
+ .I ./manyplug.json
207
+ Plugin manifest (in plugin directories)
208
+ .TP
209
+ .I ./manybot.conf
210
+ ManyBot plugin configuration (managed by enable/disable)
211
+ .SH "EXAMPLES"
212
+ Create a new utility plugin:
213
+ .PP
214
+ .RS
215
+ .B manyplug init my-plugin --category utility
216
+ .RE
217
+ .PP
218
+ Install plugins from the registry:
219
+ .PP
220
+ .RS
221
+ .B manyplug install fun games
222
+ .RE
223
+ .PP
224
+ Install from a local path:
225
+ .PP
226
+ .RS
227
+ .B manyplug install --local ./my-custom-plugin
228
+ .RE
229
+ .PP
230
+ Install without confirmation:
231
+ .PP
232
+ .RS
233
+ .B manyplug install plugin-name --yes
234
+ .RE
235
+ .PP
236
+ List all plugins including disabled:
237
+ .PP
238
+ .RS
239
+ .B manyplug list --all
240
+ .RE
241
+ .PP
242
+ Enable and disable plugins:
243
+ .PP
244
+ .RS
245
+ .B manyplug enable plugin-a plugin-b
246
+ .br
247
+ .B manyplug disable plugin-a
248
+ .RE
249
+ .PP
250
+ Sync with remote and update all plugins:
251
+ .PP
252
+ .RS
253
+ .B manyplug sync --update
254
+ .RE
255
+ .PP
256
+ Remove a plugin and its dependencies:
257
+ .PP
258
+ .RS
259
+ .B manyplug remove plugin-name --remove-deps
260
+ .RE
261
+ .PP
262
+ Validate a plugin configuration:
263
+ .PP
264
+ .RS
265
+ .B manyplug validate ./my-plugin
266
+ .RE
267
+ .SH "SEE ALSO"
268
+ .BR manybot (1)
269
+ .SH "AUTHORS"
270
+ Written by freakk.dev.
271
+ .br
272
+ Source available at: https://git.stxerr.dev/manyplug.git
273
+
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "manyplug",
3
+ "version": "1.3.0",
4
+ "description": "CLI plugin manager for ManyBot",
5
+ "man": ["docs/manyplug.1"],
6
+ "keywords": [
7
+ "manybot",
8
+ "plugin",
9
+ "cli",
10
+ "whatsapp"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "freakkdev",
14
+ "type": "module",
15
+ "main": "./bin/manyplug.js",
16
+ "bin": {
17
+ "manyplug": "bin/manyplug.js"
18
+ },
19
+ "scripts": {
20
+ "test": "vitest",
21
+ "test:run": "vitest run",
22
+ "test:coverage": "vitest run --coverage"
23
+ },
24
+ "dependencies": {
25
+ "@freakk.dev/manyplug": "^1.0.1",
26
+ "chalk": "^5.3.0",
27
+ "cli-progress": "^3.12.0",
28
+ "commander": "^11.0.0",
29
+ "fs": "^0.0.1-security",
30
+ "fs-extra": "^11.3.4",
31
+ "gradient-string": "^3.0.0",
32
+ "ora": "^8.0.1",
33
+ "pino": "^9.14.0",
34
+ "pino-pretty": "^11.0.0",
35
+ "sqlite3": "^5.1.7"
36
+ },
37
+ "devDependencies": {
38
+ "@vitest/coverage-v8": "^4.1.4",
39
+ "vitest": "^4.1.4"
40
+ }
41
+ }
package/src/enable.js ADDED
@@ -0,0 +1,86 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+
4
+ const CONF_PATH = path.join(process.cwd(), 'manybot.conf');
5
+ const PLUGINS_DIR = path.join(process.cwd(), 'src', 'plugins');
6
+
7
+ // ------------------------------------------------------------
8
+ // conf file — PLUGINS=[a,b,c]
9
+ // ------------------------------------------------------------
10
+
11
+ async function readEnabled() {
12
+ if (!await fs.pathExists(CONF_PATH)) return [];
13
+ const raw = await fs.readFile(CONF_PATH, 'utf-8');
14
+ const match = raw.match(/PLUGINS=\[\s*([\s\S]*?)\s*\]/);
15
+ if (!match) return [];
16
+ return match[1].split(',').map(s => s.trim()).filter(Boolean);
17
+ }
18
+
19
+ async function writeEnabled(plugins) {
20
+ await fs.writeFile(CONF_PATH, `PLUGINS=[\n${plugins.map(p => p + ',').join('\n')}\n]\n`, 'utf-8');
21
+ }
22
+
23
+ // ------------------------------------------------------------
24
+ // enable / disable commands
25
+ // ------------------------------------------------------------
26
+
27
+ async function toggle(names, action) {
28
+ if (!names.length) {
29
+ console.error(`usage: manyplug ${action} <plugin> [plugin2...]`);
30
+ process.exit(1);
31
+ }
32
+
33
+ const t = Date.now();
34
+ const enabled = await readEnabled();
35
+ const set = new Set(enabled);
36
+ const results = [];
37
+
38
+ for (const name of names) {
39
+ if (action === 'enable' && !await fs.pathExists(path.join(PLUGINS_DIR, name))) {
40
+ console.error(`x ${name}: not installed`);
41
+ results.push({ name, changed: false, notFound: true });
42
+ continue;
43
+ }
44
+ const was = set.has(name);
45
+ if (action === 'enable') set.add(name);
46
+ else set.delete(name);
47
+ const changed = set.has(name) !== was;
48
+ results.push({ name, changed });
49
+ }
50
+
51
+ const changed = results.filter(r => r.changed);
52
+
53
+ if (changed.length) {
54
+ try {
55
+ await writeEnabled([...set]);
56
+ } catch (e) {
57
+ console.error(`error: ${e.message}`);
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ for (const r of results) {
63
+ if (r.notFound) continue; // already printed above
64
+ const symbol = action === 'enable' ? '+' : '-';
65
+ const note = r.changed ? '' : ` (already ${action}d)`;
66
+ console.log(`${symbol} ${r.name}${note}`);
67
+ }
68
+
69
+ if (names.length > 1) {
70
+ const notFound = results.filter(r => r.notFound).length;
71
+ console.log(`${changed.length}/${names.length} changed (${((Date.now() - t) / 1000).toFixed(2)}s)${notFound ? ` ${notFound} not found` : ''}`);
72
+ }
73
+
74
+ if (results.some(r => r.notFound)) process.exit(1);
75
+ }
76
+
77
+ export function enableCommand(input) {
78
+ const names = Array.isArray(input) ? input : (input ? [input] : []);
79
+ return toggle(names, 'enable');
80
+ }
81
+
82
+ export function disableCommand(input) {
83
+ const names = Array.isArray(input) ? input : (input ? [input] : []);
84
+ return toggle(names, 'disable');
85
+ }
86
+
package/src/init.js ADDED
@@ -0,0 +1,111 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { formatSize } from './ui.js';
4
+
5
+ const VALID_CATEGORIES = ['games', 'media', 'utility', 'service', 'admin', 'fun'];
6
+
7
+ // ------------------------------------------------------------
8
+ // templates
9
+ // ------------------------------------------------------------
10
+
11
+ const manyplugJson = (name, category, service) => ({
12
+ name, version: '1.0.0',
13
+ description: `${name} plugin for ManyBot`,
14
+ category, service,
15
+ author: '', license: 'MIT', main: 'index.js',
16
+ dependencies: {}, externalDependencies: {}
17
+ });
18
+
19
+ const indexJs = (name) => `\
20
+ // ${name} — ManyBot plugin
21
+ import { CMD_PREFIX } from "../../config.js";
22
+
23
+ export default async function ({ msg, api }) {
24
+ if (!msg.is(CMD_PREFIX + "hi")) return;
25
+ await msg.reply("Hello!");
26
+ }
27
+ `;
28
+
29
+ const localePt = (name) => ({ plugin: { name, description: `Plugin ${name} para ManyBot` }, commands: {} });
30
+ const localeEn = (name) => ({ plugin: { name, description: `${name} plugin for ManyBot` }, commands: {} });
31
+
32
+ const gitignore = () => `node_modules/\npackage-lock.json\n*.log\n.vscode/\n.DS_Store\ncoverage/\n`;
33
+
34
+ const readme = (name) => `\
35
+ # ${name}
36
+
37
+ Plugin for ManyBot.
38
+
39
+ ## Installation
40
+
41
+ \`\`\`bash
42
+ manyplug install ${name}
43
+ \`\`\`
44
+
45
+ ## Usage
46
+
47
+ Describe how to use your plugin here.
48
+
49
+ ## License
50
+
51
+ MIT
52
+ `;
53
+
54
+ // ------------------------------------------------------------
55
+ // init command
56
+ // ------------------------------------------------------------
57
+
58
+ export async function initCommand(name, options = {}) {
59
+ const t = Date.now();
60
+
61
+ if (!name || !/^[a-z0-9-]+$/.test(name)) {
62
+ console.error('error: name must be lowercase letters, numbers, and hyphens only');
63
+ process.exit(1);
64
+ }
65
+
66
+ const category = VALID_CATEGORIES.includes(options.category) ? options.category : 'utility';
67
+ if (!VALID_CATEGORIES.includes(options.category)) {
68
+ console.warn(`warn: unknown category, using "utility"`);
69
+ }
70
+
71
+ const dir = path.resolve(name);
72
+ const service = options.service || false;
73
+
74
+ if (await fs.pathExists(dir)) {
75
+ process.stdout.write(`"${name}" already exists, overwrite? [y/N] `);
76
+ const answer = await new Promise(res => process.stdin.once('data', d => res(d.toString().trim().toLowerCase())));
77
+ if (answer !== 'y') { console.log('cancelled'); process.exit(0); }
78
+ }
79
+
80
+ const f = (...p) => path.join(dir, ...p);
81
+
82
+ console.log(`creating ${name} category=${category} service=${service}`);
83
+
84
+ try {
85
+ await fs.ensureDir(f('locale'));
86
+ await fs.writeJson(f('manyplug.json'), manyplugJson(name, category, service), { spaces: 2 });
87
+ await fs.writeFile(f('index.js'), indexJs(name));
88
+ await fs.writeJson(f('locale', 'pt.json'), localePt(name), { spaces: 2 });
89
+ await fs.writeJson(f('locale', 'en.json'), localeEn(name), { spaces: 2 });
90
+ await fs.writeFile(f('.gitignore'), gitignore());
91
+ await fs.writeFile(f('README.md'), readme(name));
92
+ } catch (e) {
93
+ console.error(`error: ${e.message}`);
94
+ process.exit(1);
95
+ }
96
+
97
+ const size = await fs.stat(dir).then(() => getDirSize(dir));
98
+ console.log(`done size=${formatSize(size)} time=${((Date.now() - t) / 1000).toFixed(2)}s`);
99
+ console.log(` cd ${name}`);
100
+ console.log(` manyplug validate .`);
101
+ console.log(` manyplug install --local .`);
102
+ }
103
+
104
+ async function getDirSize(dir) {
105
+ let total = 0;
106
+ for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
107
+ const p = path.join(dir, entry.name);
108
+ total += entry.isDirectory() ? await getDirSize(p) : (await fs.stat(p)).size;
109
+ }
110
+ return total;
111
+ }