npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gl-sdk4-plugin-kit

v0.9.0

Published

Unofficial toolkit for building native GL.iNet router admin panel plugins

Readme

gl-sdk4-plugin-kit

Unofficial toolkit for building native SDK4 admin panel plugins for GL.iNet routers.

GL.iNet Beryl AX (GL-MT3000)

Reference hardware used for live validation: GL.iNet Beryl AX (GL-MT3000). Product photo source: GL.iNet.

GL.iNet routers run an SDK4 Vue.js admin UI on top of OpenWrt. GL.iNet publishes firmware build tooling and prebuilt SDK4 packages, but no complete extension SDK for adding native admin pages. This project documents the package contract and automates building, testing, and deploying compatible plugins.

How It Works

GL.iNet's admin panel loads plugins dynamically:

  1. Menu entries are JSON files in /usr/share/oui/menu.d/ on the router
  2. Views are webpack-bundled Vue 2 components in /www/views/gl-sdk4-ui-{name}.common.js.gz
  3. The app fetches the JS bundle, runs eval(), and mounts the Vue component

This toolkit automates the entire workflow: scaffold, validate, build, inspect, deploy, and install.

The supported runtime starts at GL.iNet firmware 4.8. Router-changing commands fingerprint the exact admin bundle and reject unknown firmware by default. Support requires a verified model, normalized firmware version, and bundle SHA-256 tuple; it is not inferred from a model name or a 4.x version string.

Quick Start

# Install the CLI once
npm install --global gl-sdk4-plugin-kit

# Create a self-contained UI-only plugin and install its dependencies
glplugin init my-plugin --install

# Or create a working Vue + session-protected CGI + UCI package
glplugin init my-router-tool --profile full-stack --install

# Prepare it
cd my-plugin

# Add a second page under the plugin's main menu item
glplugin view add details --title "Details"

# Save a project-local router alias (no password is stored)
glplugin target add beryl [email protected] --use

# Validate, build, package, upload, and install
glplugin check
glplugin install

Open your router's admin panel and refresh — your plugin appears in the sidebar.

Development loop

glplugin deploy --build
glplugin dev

# Or inspect/install an existing package artifact
glplugin build
glplugin package
glplugin inspect dist/gl-sdk4-ui-my-plugin_1.0.0_all.ipk
glplugin install --no-build
glplugin uninstall

CLI Commands

| Command | Description | |---------|-------------| | glplugin init <name> [--profile ui-only\|full-stack] [--install] | Scaffold a self-contained UI-only or full-stack plugin project | | glplugin view add\|list\|remove | Manage the plugin's Vue pages and menu entries | | glplugin compatibility capture\|verify | Capture redacted metadata and verify it against the matching admin bundle | | glplugin check [--strict] | Validate project files, hooks, and toolchain | | glplugin capabilities | List valid manifest capability IDs and read-only RPC probes | | glplugin build | Build plugin (webpack + gzip) | | glplugin package | Create .ipk package (installable via opkg, survives reboots) | | glplugin inspect <package.ipk> | Inspect package metadata and file layout safely | | glplugin target <action> | Add, select, list, show, or remove router aliases | | glplugin deploy [target\|host] [--build] | Preflight the router, then optionally build and deploy UI assets via SCP | | glplugin install [target\|host] | Check, build, package, preflight, upload, and opkg install | | glplugin uninstall [target\|host] | Remove the project package from a router | | glplugin dev [target\|host] | Watch and sync UI-only or full-stack project changes | | glplugin test [target\|host] | Test plugin and capability connectivity against a router | | glplugin doctor [target\|host] | Detect model, firmware, auth algorithm, and read-only capabilities | | glplugin extract [target\|host] [--insecure-host-key] | Extract firmware evidence via SSH | | glplugin extract <ip> --rpc [--password-stdin] [--include-sensitive] | Discover API endpoints via RPC (no SSH needed) | | glplugin extract <host> --full | Both SSH + RPC extraction | | glplugin <command> --help | Show command help without executing it |

All commands support --cwd; finite commands support --json, --quiet, and --verbose. See the CLI workflow reference for targets, precedence, machine output, and exit codes.

Generated projects pin the exact gl-sdk4-plugin-kit version that created them, so their npm scripts use a project-local CLI instead of depending on a later global upgrade. Omit --install when dependency installation must be performed separately.

Router authentication and doctor

This authentication is only for developer-side CLI commands such as doctor, test, and RPC extraction. A plugin page loaded inside the GL.iNet admin UI continues to use the existing admin session and does not show a second login. Standard $rpcRequest calls are authorized by the firmware; plugin-owned CGI endpoints must validate that browser session explicitly.

Router passwords are never accepted as positional CLI arguments. Interactive commands use a hidden TTY prompt; automation can provide one password line on stdin:

glplugin doctor 192.168.8.1
printf '%s\n' "$ROUTER_PASSWORD" | glplugin doctor 192.168.8.1 --password-stdin --json

The CLI negotiates challenge.alg instead of assuming MD5 crypt. Algorithms 1 (MD5 crypt), 5 (SHA-256 crypt), and 6 (SHA-512 crypt) are supported because all three are implemented by the official 4.8.1 and 4.9.0 UI bundles. Unknown algorithms fail explicitly.

HTTP remains the default for local router access. For HTTPS, certificate verification is enabled by default:

glplugin doctor router.local --https
glplugin doctor router.local --https --insecure # explicit opt-out for a self-signed certificate

doctor fingerprints the exact admin bundle, enforces the firmware 4.8 minimum, and calls only read methods. Inside a plugin project it also enforces compatibility.requiredCapabilities. Missing optional modules remain informational; missing required modules fail the project check. An unknown model, firmware, and bundle tuple does fail compatibility unless --allow-unverified is supplied explicitly.

test uses the same feature-gated capability catalog as doctor, verifies the project view through ui.get_menu_list, downloads the deployed bundle, and checks its Vue export. It does not print any part of the session ID.

SSH commands use argument arrays without a local shell. New host keys are accepted once and then checked on later connections. --insecure-host-key disables this verification explicitly; use it only for disposable development routers whose host key cannot be persisted. Multi-step commands reuse one temporary OpenSSH connection; dev keeps it for the watcher lifetime, so rebuilds do not prompt again.

Plugin Structure

my-plugin/
├── gl-plugin.json        # Plugin/package manifest
├── src/
│   └── index.vue          # Your Vue component
├── i18n/                  # Plugin translations
├── menu.json              # Menu entry definition
├── webpack.config.js      # Build config (pre-configured)
├── package.json
└── dist/                  # Build output
    └── gl-sdk4-ui-my-plugin.common.js.gz

Writing Plugins

Your plugin is a standard Vue 2 single-file component. RPC failures reject normally, so handle them explicitly:

<template>
  <div>
    <gl-title :title="'My Plugin'" />
    <gl-card class="gl-sdk4-card">
      <p>{{ model }} - {{ uptime }}</p>
      <p v-if="error">{{ error }}</p>
      <gl-button type="primary" @click="fetchData">Refresh</gl-button>
    </gl-card>
  </div>
</template>

<script>
export default {
  name: 'my-plugin',
  data() {
    return { model: '', uptime: '', error: '' };
  },
  created() {
    this.fetchData();
  },
  methods: {
    async fetchData() {
      this.error = '';
      try {
        const info = await this.$rpcRequest('call', ['sid', 'system', 'get_info', {}]);
        const status = await this.$rpcRequest('call', ['sid', 'system', 'get_status', {}]);
        this.model = info.board_info.model;
        const s = status.system.uptime;
        this.uptime = Math.floor(s/3600) + 'h ' + Math.floor(s%3600/60) + 'm';
      } catch (error) {
        this.error = error.message || 'RPC request failed';
      }
    },
  },
};
</script>

<style src="@gl-sdk4-plugin-kit/gl-card.css"></style>

<style scoped>
p { color: var(--text-color); }
</style>

Native charts

The kit includes GlStableLineChart, a small adapter around the firmware-provided gl-line-chart. It preserves observed array/object identities, prevents Y-axis shrinkage during polling, and can render stable timeline bands without bundling a second chart engine.

const { GlStableLineChart } = require('@gl-sdk4-plugin-kit/chart');

Import @gl-sdk4-plugin-kit/gl-line-chart.css, register the component locally, and declare gl-line-chart in compatibility.requiredComponents. See the native chart guide for the full contract and examples.

Node.js API Client

Control your router from scripts without SSH:

npm install gl-sdk4-plugin-kit
const { api } = require('gl-sdk4-plugin-kit');

const client = await api.createClient('192.168.8.1', 'your-password');

try {
  // Read
  const info = await client.system.getInfo();
  const clients = await client.clients.getList();
  const vpn = await client.vpnClient.getStatus();

  // Write
  await client.wifi.setConfig({ ... });
  await client.firewall.addPortForward({ name: 'SSH', proto: 'tcp', dest_ip: '192.168.8.100', dest_port: '22', src_dport: '2222' });

  // VPN control (use set_tunnel, not stop)
  const tunnels = await client.vpnClient.getTunnel();
  tunnels.tunnels[0].enabled = false;
  await client.rpc('vpn-client', 'set_tunnel', tunnels.tunnels[0]); // disable
  tunnels.tunnels[0].enabled = true;
  await client.rpc('vpn-client', 'set_tunnel', tunnels.tunnels[0]); // re-enable
} finally {
  await client.close();
}

Vue API Mixin

Install the toolkit as a build-time dependency when importing its API mixin. Webpack includes the API factory and catalog in the plugin bundle, so the router does not need the Node package at runtime.

For reference, the full mixin provides 49 namespaces with 326 methods. Calls preserve RPC rejections instead of converting failures to null:

const { glApiMixin } = require('gl-sdk4-plugin-kit/browser');

export default {
  mixins: [glApiMixin],
  async created() {
    const info = await this.glApi.system.getInfo();
    const clients = await this.glApi.clients.getList();
  }
};

Package Configuration

gl-plugin.json is the single source for the plugin ID, profile, OpenWrt package metadata, filesystem overlay, and lifecycle hooks. package.json remains the Node/build manifest.

{
  "schemaVersion": 1,
  "id": "my-plugin",
  "profile": "full-stack",
  "views": [
    { "id": "my-plugin", "entry": "src/index.vue", "menu": "menu.json" },
    { "id": "my-plugin-tools", "entry": "src/tools.vue", "menu": "menus/tools.json" }
  ],
  "compatibility": {
    "minimumFirmware": "4.8.0",
    "requiredComponents": ["gl-card", "gl-title"],
    "requiredCapabilities": ["wifi", "repeater"]
  },
  "package": {
    "name": "gl-sdk4-ui-my-plugin",
    "architecture": "all",
    "depends": ["libc", "gl-sdk4-ui-core"],
    "section": "base",
    "conffiles": ["/etc/config/my-plugin"]
  },
  "overlay": "overlay",
  "lifecycle": {
    "postinst": "hooks/postinst",
    "prerm": "hooks/prerm"
  }
}

The default ui-only profile packages the admin view, menu, and translations. full-stack additionally copies overlay/ into the router root filesystem, supports package dependencies and conffiles, and validates POSIX shell lifecycle hooks. The optional views array packages multiple native pages; omitting it keeps the single src/index.vue plus menu.json convention. Existing pre-manifest projects using package.json.pluginName/glPlugin remain readable through a legacy adapter.

Use glplugin view add <id> to create a child page under the primary menu item, or pass --top-level to create an independent top-level page. glplugin view list shows the declared pages. glplugin view remove <id> updates the manifest but keeps source and menu files unless --delete-files is explicitly supplied. Short IDs are automatically namespaced: in my-plugin, view add details declares the globally safe view ID my-plugin-details while keeping src/details.vue.

glplugin package also writes Installed-Size, SourceName, X-GL-Firmware-Min, X-GL-UI-Contract, and the standard OpenWrt lifecycle wrappers. Menu files are package-owned and are not marked as conffiles. See Package Manifest and Profiles for the full contract.

Documentation

Examples

  • hello-world — Minimal plugin showing device info
  • network-info — Plugin with tables and multiple cards
  • full-stack — Vue page calling a packaged session-protected shell CGI backend

Extract Components from Any Firmware

If you're running a different firmware version, extract fresh component data:

glplugin extract [email protected]
glplugin extract 192.168.8.1 --rpc

This SSHs into the router, downloads app.js, computes its SHA-256 fingerprint, and resolves its component registry against runtime-verified firmware catalogs. It also extracts CSS variables, icons, and RPC signatures into extracted-components.json. Unknown bundle fingerprints are reported as unknown; literal registration signals are diagnostic only and are never presented as a complete global component list.

RPC extraction also records successful method response shapes. Fields that can hold passwords, tokens, or private keys are replaced with <redacted> by default. Use --include-sensitive only when raw local output is required, and never commit or share that output.

Compatibility Status

  • GL-MT3000 4.8.1 release: live supported.
  • GL-AXT1800 4.8.3 release: official artifact verified.
  • GL-SFT1200 4.8.3 release: official artifact verified.
  • GL-MT6000 4.9.1 release: official artifact verified.

GitHub Actions downloads these exact official images and verifies their published SHA-256, SquashFS package layout, auth/runtime contract, static component signals, runtime registry snapshots where available, opkg, menu layout, and lifecycle dispatch. New bundle fingerprints remain blocked until added to the catalog. See the compatibility notes for the exact matrix and validation boundary.

To inspect a modern but unknown tuple without editing the catalog:

glplugin doctor 192.168.8.1 --allow-unverified --json > doctor.json
glplugin compatibility capture doctor.json
glplugin compatibility verify compatibility-candidate.json --bundle admin-app.js.gz

The candidate contains only normalized router/runtime evidence and omits the target, hostname, authentication data, capabilities, and raw RPC responses. A successful verification means the candidate and supplied bundle agree and are ready-for-review, not supported; runtime registry capture, catalog changes, and firmware artifact verification remain manual.

Disclaimer

This is an unofficial, community-driven project. It is not affiliated with, endorsed by, or supported by GL.iNet. Component documentation was obtained through reverse engineering of the publicly accessible admin panel JavaScript. No proprietary source code is included in this repository.

Contributing

See CONTRIBUTING.md before opening a pull request. A new component catalog must include the decompressed bundle SHA-256 and a runtime inspection of Vue.options.components. Do not infer the global registry from bundle strings. Submitted extraction output must be manually reviewed and redacted.

License

MIT