/nuxt-terminal

Shell-like Terminal + OS simulator for Vue/Nuxt.js, powered by xtermjs

Primary LanguageTypeScriptMIT LicenseMIT

nuxt-terminal

A client-side, shell-like terminal based on xtermjs, featuring a simple filesystem and an application layer. Wrapped in a Vue Component and pre-configured for Nuxt.js.

This library allows fast building of terminal interfaces for games, educational projects and applications where a simple yet robust web terminal / OS simulator is required.

nuxt-terminal.gif

Features

  • Shell-like typing: arrows, backspace/delete, home/end and buffer handling are already covered
  • Filesystem: Folders and files defined through a simple schema
  • FS Navigation: cd and ls commands with unix-like path syntax
  • File reading: cat command for printing file content to terminal
  • Stdout: A handler for printing text to terminal wrapped by word
  • Colors: Color helper for beautiful UIs
  • Applications: Unix-like approach for applications, where every command calls a single application with arguments.
  • Responsive: Uses xterm-addon-fit to resize the console at runtime.
  • Typescript: Type interfaces available for TS projects

Setup

The package is distributed via npm and yarn, so it should be pretty simple to install. Go to your Nuxt/Vue project root folder and run one of the commands below:

npm

$ npm i nuxt-terminal

yarn

$ yarn add nuxt-terminal

If you're using Nuxt.js, you should also list the plugin file at nuxt.config.js:

// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
    '~node_modules/nuxt-terminal/plugin.client.js'
],

If you're using Vue without Nuxt.js you can also use it, just make sure to import both the component and the xterm.css file.

import Vue from 'vue'
import NuxtTerminal from 'nuxt-terminal'
import 'node_modules/xterm/css/xterm.css'

Vue.use(NuxtTerminal)

Hello World

After setting up you should be able to use the NuxtTerminal component.

<NuxtTerminal 
    :user="user"
    :domain="domain"
    :welcome="welcome"
    :filesystem="filesystem"
    :apps="apps"
/>

It should grow to fit the container it's in. If this doesn't happen, make sure the parent's dimensions are set (not auto), or else it shouldn't work well.

Then you should create the filesystem on the <script> section. Either props or data should be fine. You should not use computed for the filesystem or the apps parameter.

<script lang="ts">
import Vue from 'vue'
import { Filesystem, Folder, File, NuxtTerminalApp } from 'nuxt-terminal'

export default Vue.extend({
  data: () => ({
    
    user: 'root',
    domain: 'test',
    welcome: 'Hello world!',

    filesystem: new Filesystem([
      new Folder('home', [
        new Folder('user', [
          new File('passwords.txt', 'user:123456')
        ])
      ]),
      new File('README', 'This is a client-side bash-like terminal! Enjoy!')
    ]),
    
    apps: []
  })
})

</script>

nuxt-terminal-filesystem.gif

Apps

Every input sent to the shell is split (at whitespaces) into arguments, where the first argument is the app name.

This way, ls and cat are also apps (just like Unix!). When you type:

ls /home/user

The ls app should receive the arguments: ['ls', '/home/user'].

In order to create an app you should extend the NuxtTerminalApp class. The class name is the application name, called by the shell.

import NuxtTerminalApp from "nuxt-terminal";

class example extends NuxtTerminalApp {
  async main(_args: string[]): Promise<number> {
    this.stdout.print('The app has access to the shell stdout.\n');
    return 0;
  }
}

Then, you simply list the type on the apps property:

    ...
    filesystem: {...},
    apps: [example]
    ...

nuxt-terminal-example

In order to keep the Single File Component structure of Vue, you can define the App class right above the export default on the <script> tag of the component.

Apps also have access to the Filesystem, and a pointer to the INode from where the app was launched.

import { NuxtTerminalApp, Folder } from 'nuxt-terminal'
class test_fs extends NuxtTerminalApp {
    async main(args: string[]): Promise<number> {
        
        this.stdout.print('The app was launched from: ' + this.node.path);

        let relative_node = this.fs.node(this.node, 'test/');
        let absolute_node = this.fs.node(null, '/test/');
        let also_absolute_node = this.fs.node(this.node, '/test/');

        return 0;
    }
}

Development Notes

I've built this library for an educational game/prank I wanted to play on some teens. It turns out it felt useful for other projects, so I've decided to wrap it and publish.

It's still pretty raw, so feel free to open Issues and Pull Requests.

In order to run the library standalone for development purposes you should:

git clone https://github.com/hugoaboud/nuxt-terminal.git
cd nuxt-terminal
npm run serve