• Stars
    star
    199
  • Rank 196,105 (Top 4 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 8 years ago
  • Updated almost 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A collection of useful primitives for creating interactive chat bots.

chatter

A collection of useful primitives for creating interactive chat bots.

NPM

Build Status Built with Grunt

Usage

npm install --save chatter

Tested in Node 4.x and 6.x.

Note: If the following code example syntax looks unfamiliar, don't worry, it's just JavaScript! Read a detailed overview of ECMAScript 2015 features to learn more.

What is a bot?

For the purposes of this documentation, a bot is an automated system that selectively responds to text messages with text responses. The bot may simply respond to messages, statelessly, or the bot may do more complex things, like keep track of ongoing conversations with users.

The most basic bot looks something like this (note, this is pseudocode):

// Import the chat service's library.
const ChatService = require('chat-service');

// Create a chat service instance with the relevant options.
const myChatService = new ChatService(options);

// When the chat service receives a message that it cares about, respond to it.
myChatService.on('message', message => {
  // This is the code you'll spend most of your time writing. Ie, the bot:
  if (doICareAbout(message)) {
    myChatService.send(response);
  }
});

// Actually connect to the chat service.
myChatService.connect();

Usually, all the behind-the-scenes work of connecting the bot to the remote service, handling reconnections, keeping track of state (what users the bot is currently talking to, what channels the bot is currently in) is done by a service-specific library.

So, what's left to do? Well, as shown in the previous example, you'll need to write the code that determines if a given message warrants a response, and to then deliver that response to the user.

This project aims to help with all that.

Message handlers

Since most bot code is centered around these two steps:

  1. Testing an incoming message to see if it should be handled
  2. Sending a response based on the incoming message

It makes sense to introduce a primitive that does these things. That primitive is called a "message handler."

The simplest message handler is a function that accepts a message argument and returns a response if it should respond, or false if it doesn't care about that message:

const lolHandler = message => {
  if (/lol/i.test(message)) {
    const newMessage = message.replace(/lol/ig, 'laugh out loud');
    return `More like "${newMessage}" amirite`;
  }
  return false;
};

lolHandler('what')     // false
lolHandler('lol what') // 'More like "laugh out loud what" amirite'

But what if a message handler needs to yield a response asynchronously, instead of returning a value immediately? It can, by returning a Promise:

const stuffHandler = message => {
  if (message === 'get stuff') {
    return db.query('SELECT * FROM STUFF').then(results => {
      const stuff = results.join(', ');
      return `Look at all the stuff: ${stuff}`;
    });
  }
  return false;
};

stuffHandler('huh')       // false
stuffHandler('get stuff') // Promise -> 'Look at all the stuff! ...'

But what do you do if you want to pass all messages through both lolHandler and stuffHandler? What if your bot needs to be able to respond to a dozen types of message in a dozen different ways?

Just create an array of message handlers:

const messageHandlers = [
  lolHandler,
  stuffHandler,
];

Now, you've undoubtedly realized that functions that return values or promises and arrays behave quite differently. Without a helper function that knows how to iterate over that array, or wait for a promise to resolve, or both of those things, this won't make your job any easier.

Fortunately, chatter gives you that helper function.

Processing messages

The processMessage function (that chatter exports) takes two arguments:

  1. A message handler
  2. A message

This function understands that a message handler might be a function or an array of functions like the ones described above (or a few other possible things, which will be explained later).

It takes the message handler and message you give it and intelligently processes them to produce a response (if any), and it returns a Promise that will be resolved with that response:

const processMessage = require('chatter').processMessage;

// (See the previous examples for the definition of "messageHandlers")
processMessage(messageHandlers, message).then(response => {
  // do something with response
});

// An example:
function simulate(message) {
  processMessage(messageHandlers, message).then(response => {
    if (response === false) {
      response = `Sorry, I don't understand "${message}".`;
    }
    console.log(response);
  });
}

simulate('lol what')  // Logs: More like "laugh out loud what" amirite
simulate('get stuff') // Logs: Look at all the stuff! ...
simulate('huh')       // Logs: Sorry, I don't understand "huh".

Of course, instead of logging the response, your bot would be sending it back to the user who said the message or the channel it was said in, using the chat service library. But you get the idea.

Message handlers, more specifically

As far as the processMessage function is concerned, a message handler is a function, an object with a handleMessage method, or an array of any combination of those things. That array may contain other arrays.

const functionMessageHandler = message => {
  if (condition) {
    return response;
  }
  return false;
};

const objectMessageHandler = {
  handleMessage(message) {
    if (condition) {
      return response;
    }
    return false;
  },
};

const arrayMessageHandler = [
  functionMessageHandler,
  objectMessageHandler,
];

A message handler function (or method) may return false or return a Promise that yields false to indicate that the message handler doesn't care about the message, and the next message handler (if any) should process the message.

const returnsFalseMessageHandler = function(message) {
  return false;
};

const yieldsFalseMessageHandler = function(message) {
  return Promise.resolve(false);
};

A message handler may return or yield any other value, and if so, iteration will be stopped immediately and that value will be yielded.

const returnsValueMessageHandler = function(message) {
  return 'hello';
};

const yieldsValueMessageHandler = function(message) {
  return Promise.resolve('world');
};

Also, a message handler may return another message handler (function, object or array) and those new message handlers will be processed in-line.

As you can see, message handlers may be very simple, but may be composed in very creative ways.

(See message-handlers for more examples)

A naive bot using message handlers and processMessage

Like the earlier What is a bot? example, this bot is pseudocode, but this time it uses message handlers and the processMessage helper function:

// Import the chat service's library.
const ChatService = require('chat-service');

// Import the chatter processMessage helper function.
const processMessage = require('chatter').processMessage;

// Define your message handlers.
const messageHandlers = [...];

// Create a chat service instance with the relevant options.
const myChatService = new ChatService(options);

// When the chat service receives a message that it cares about, respond to it.
myChatService.on('message', message => {
  // The bot just became a whole lot more flexible:
  processMessage(messageHandlers, message).then(response => {
    if (response === false) {
      response = `Sorry, I don't understand "${message}".`;
    }
    myChatService.send(response);
  });
});

// Actually connect to the chat service.
myChatService.connect();

Additional included message handlers

Because there are a number of common things message handlers need to do, a few message handler "creator" functions have been included to make creating common message handlers easier.

createMatcher

The createMatcher function creates a new message handler that only calls the specified message handler if the message matches. It accepts a match option, which is a string, regex or function, to match against the message. If a string is specified, it matches the beginning of the message. If a message is matched, the remainder of the message will be passed into the specified message handler:

const createMatcher = require('chatter').createMatcher;

// Matches "add" prefix, then splits the message into an array and adds the
// array items into a sum.
const addMatcher = createMatcher({match: 'add'}, message => {
  const numbers = message.split(' ');
  const result = numbers.reduce((sum, n) => sum + Number(n), 0);
  return `${numbers.join(' + ')} = ${result}`;
});

// Matches "multiply" prefix, then splits the message into an array and
// multiplies the array items into a product.
const multiplyMatcher = createMatcher({match: 'mult'}, message => {
  const numbers = message.split(' ');
  const result = numbers.reduce((product, n) => product * Number(n), 1);
  return `${numbers.join(' x ')} = ${result}`;
});

// Parent message handler that "namespaces" its sub-handlers and provides a
// fallback message if a sub-handler isn't matched.
const mathMatcher = createMatcher({match: 'math'}, [
  addMatcher,
  multiplyMatcher,
  message => `Sorry, I don't understand "${message}".`,
]);

processMessage(mathMatcher, 'add 3 4 5')       // Promise -> false
processMessage(mathMatcher, 'math add 3 4 5')  // Promise -> 3 + 4 + 5 = 12
processMessage(mathMatcher, 'math mult 3 4 5') // Promise -> 3 x 4 x 5 = 60
processMessage(mathMatcher, 'math sub 3 4 5')  // Promise -> Sorry, I don't understand "sub 3 4 5".

See the create-matcher example.

createParser

The createParser function creates a new message handler that calls the specified message handler, not with a message string, but with an object representing the "parsed" message. This is especially useful if you want to work with an array of words from the message, instead of just a string message.

const createParser = require('chatter').createParser;

// Reduce the array of parsed args into a sum.
const addHandler = createParser(parsed => {
  const args = parsed.args;
  const result = args.reduce((sum, num) => sum + Number(num), 0);
  return `${args.join(' + ')} = ${result}`;
});

processMessage(addHandler, '1 2 3')    // Promise -> 1 + 2 + 3 = 6
processMessage(addHandler, '4 five 6') // Promise -> 4 + five + 6 = NaN

When parseOptions is defined, any options in the message specified like option=value will be parsed and processed via the defined function, and made available in parsed.options (any non-options args will still be available in parsed.args). As you can see, options may be abbreviated!

const parsingHandler = createParser({
  parseOptions: {
    alpha: String,
    beta: Number,
  },
}, parsed => JSON.stringify(parsed.options));

processMessage(parsingHandler, 'a=1 b=2') // Promise -> {"alpha":"1","beta":2}

See the create-parser example.

createCommand

The createCommand function is meant to be used to create a nested tree of message handlers that each have a name, description and usage information, with an automatically-created help command and a fallback message handler.

Like with the createMatcher match option, the name will be used to match the message, with the remainder of the message being passed into the specified message handler. The name, description and usage options will be used to display contextual help and usage information.

Note that because the response from message handlers created with createCommand may return arrays, they should be normalized into a newline-joined string with the included normalizeMessage helper function.

const createCommand = require('chatter').createCommand;

// Command that adds args into a sum.
const addCommand = createCommand({
  name: 'add',
  description: 'Adds some numbers.',
  usage: 'number [ number [ number ... ] ]',
}, createParser(parsed => {
  const args = parsed.args;
  const result = args.reduce((sum, n) => sum + Number(n), 0);
  return `${args.join(' + ')} = ${result}`;
}));

// Command that multiplies args into a product.
const multiplyCommand = createCommand({
  name: 'multiply',
  description: 'Multiplies some numbers.',
  usage: 'number [ number [ number ... ] ]',
}, createParser(parsed => {
  const args = parsed.args;
  const result = args.reduce((product, n) => product * Number(n), 1);
  return `${args.join(' x ')} = ${result}`;
}));

// Parent command that provides a "help" command and fallback usage information.
const rootCommand = createCommand({
  isParent: true,
  description: 'Some example math commands.',
}, [
  addCommand,
  multiplyCommand,
]);

processMessage(rootCommand, 'hello').then(normalizeMessage);
// Unknown command *hello*.
// Try *help* for more information.

processMessage(rootCommand, 'help').then(normalizeMessage);
// Some example math commands.
// *Commands:*
// > *add* - Adds some numbers.
// > *multiply* - Multiplies some numbers.
// > *help* - Get help for the specified command.

processMessage(rootCommand, 'help add').then(normalizeMessage);
// Adds some numbers.
// Usage: `add number [ number [ number ... ] ]`

processMessage(rootCommand, 'add 3 4 5').then(normalizeMessage);
// 3 + 4 + 5 = 12

processMessage(rootCommand, 'multiply').then(normalizeMessage);
// Usage: `multiply number [ number [ number ... ] ]`
// Or try *help multiply* for more information.

processMessage(rootCommand, 'multiply 3 4 5').then(normalizeMessage);
// 3 x 4 x 5 = 60

See the create-command and create-command-namespaced examples.

createConversation

The createConversation function creates a new message handler that calls the specified message handler, doing nothing of note until that message handler returns an object with a dialog property, which should be a new message handler. At that point, the new message handler is stored and used instead of the originally-specified message handler to handle the next message. After that message, the message handler is reverted to the original, unless another dialog is specified, in which case that is used instead.

Conversations can be used to create an interactive sequence of message handlers, and must be be cached on a per-conversation basis (usually per-channel or per-direct message), because of the need to keep track of the current dialog.

const createConversation = require('chatter').createConversation;

const helloHandler = message => {
  return message.indexOf('hello') !== -1 ? 'Hello to you too!' : false;
};

const askHandler = createMatcher({match: 'ask'}, () => {
  return {
    message: 'Why do you want me to ask you a question?',
    dialog(message) {
      return `I'm not sure "${message}" is a good reason.`;
    },
  };
});

const chooseHandler = createMatcher({match: 'choose'}, () => {
  return {
    message: `Choose one of the following: a, b or c.`,
    dialog: handleChoices,
  };
});

const handleChoices = choice => {
  if (choice === 'a' || choice === 'b' || choice === 'c') {
    return `Thank you for choosing "${choice}".`;
  }
  return {
    message: `I'm sorry, but "${choice}" is not a valid choice. Try again.`,
    dialog: handleChoices,
  };
};

const conversationHandler = createConversation([
  helloHandler,
  askHandler,
  chooseHandler,
]);

function handleResponse(response) {
  if (response !== false) {
    console.log(response.message || response);
  }
}

processMessage(conversationHandler, 'ask').then(handleResponse);
// Why do you want me to ask you a question?

processMessage(conversationHandler, 'hello').then(handleResponse);
// I'm not sure "hello" is a good reason.

processMessage(conversationHandler, 'hello').then(handleResponse);
// Hello to you too!

processMessage(conversationHandler, 'choose').then(handleResponse);
// Choose one of the following: a, b or c.

processMessage(conversationHandler, 'hello').then(handleResponse);
// I'm sorry, but "hello" is not a valid choice. Try again.

processMessage(conversationHandler, 'b').then(handleResponse);
// Thank you for choosing "b".

processMessage(conversationHandler, 'hello').then(handleResponse);
// Hello to you too!

See the bot-conversation example.

createArgsAdjuster

The createArgsAdjuster function creates a new message handler that calls the specified message handler with a different set of arguments than the message handler received. This is especially useful when you need to pass state from where a parent message handler is created into a child message handler.

const createArgsAdjuster = require('chatter').createArgsAdjuster;

// Increments the counter and returns a string decribing the new state.
const incrementCommand = createCommand({
  name: 'increment',
  description: 'Increment the counter and show it.',
}, (message, state) => {
  state.counter++;
  return `The counter is now at ${state.counter}.`;
});

// Returns a message handler that encapsualates some state, and passes that
// state into child commands as an argument.
function getStatefulMessageHandler() {
  const state = {counter: 0};
  return createArgsAdjuster({
    adjustArgs(message) {
      return [message, state];
    },
  }, createCommand({
    isParent: true,
    description: 'An exciting command, for sure.',
  }, [
    incrementCommand,
  ]));
}

const firstStatefulHandler = getStatefulMessageHandler();

processMessage(firstStatefulHandler, 'increment')  // Promise -> The counter is now at 1.
processMessage(firstStatefulHandler, 'increment')  // Promise -> The counter is now at 2.

const secondStatefulHandler = getStatefulMessageHandler();

processMessage(secondStatefulHandler, 'increment') // Promise -> The counter is now at 1.
processMessage(firstStatefulHandler, 'increment')  // Promise -> The counter is now at 3.
processMessage(secondStatefulHandler, 'increment') // Promise -> The counter is now at 2.

See the create-args-adjuster and bot-stateful examples.

Creating a more robust bot

As with message handlers, bot behaviors can get a little complex. As shown in the preceding createConversation and createArgsAdjuster examples, message handlers may have state. Additionally, the previous bot examples don't handle errors in a useful way or do anything to normalize responses, which means your message handlers may need extra code to help format multi-line responses.

To that end, this pseudocode example bot is implemented using createBot:

// Import the chat service's library.
const ChatService = require('chat-service');

// Import the chatter createBot function.
const createBot = require('chatter').createBot;

// Create a chat service instance with the relevant options.
const myChatService = new ChatService(options);

// Create the chatter bot.
const myBot = createBot({
  // Return message handler for this message. See the "createArgsAdjuster"
  // example for the definition of getStatefulMessageHandler.
  createMessageHandler(id) {
    const messageHandler = getStatefulMessageHandler();
    // Let the bot know that the message handler has state, so it'll be cached.
    messageHandler.hasState = true;
    return messageHandler;
  },
  // Get a cache id from the "message" object passed into onMessage. In this
  // example, each user gets their own stateful message handler, with its own
  // unique counter.
  getMessageHandlerCacheId(message) {
    return message.user;
  },
  // If a message handler responded to a message, send the normalized text
  // response back to the user.
  sendResponse(message, text) {
    myChatService.getUser(message.user).send(text);
  },
});

// Whenever a chat service message is received, pass its user and text values
// into the chatter bot.
myChatService.on('message', message => {
  const user = message.userName;
  const text = message.messageText;
  return myBot.onMessage({user, text});
});

// Actually connect to the chat service.
myChatService.connect();

// Simulated chat log:
// <cowboy> test
//          (nothing happens)
// <cowboy> help
// <bot>    An exciting command, for sure.
//          *Commands:*
//          > *increment* - Increment the counter and show it.
// <cowboy> increment
// <bot>    The counter is now at 1.
// <cowboy> increment
// <bot>    The counter is now at 2.
// <tyler>  increment
// <bot>    The counter is now at 1.
// <cowboy> increment
// <bot>    The counter is now at 3.
// <tyler>  increment
// <bot>    The counter is now at 2.

See the bot-stateful and bot-conversation examples.

Creating a Slack bot

While the aforementioned Bot is generally useful, it's really meant to be a starting point from which other more specific bots may be derived. Which brings us to SlackBot.

SlackBot contains all of the functionality of Bot, with some useful additional features like passing channel, user and slack information into message handlers, alowing message handlers to be chosen dynamically based on channel, group or dm, and automatically connecting the bot to the slack rtm & web clients.

When you create a SlackBot, you pass in an instance of the Slack RtmClient and WebClient, and the connections between the bot and service are handled for you automatically. All you have to do is specify your message handlers (which can be done programmatically, as in the example below) and tell the bot to login:

// Import the official Slack client.
const slack = require('@slack/client');
const RtmClient = slack.RtmClient;
const WebClient = slack.WebClient;
const MemoryDataStore = slack.MemoryDataStore;

// Import the chatter createBot function.
const createSlackBot = require('chatter').createSlackBot;

const bot = createSlackBot({
  // The bot name.
  name: 'Chatter Bot',
  // The getSlack function should return instances of the slack rtm and web
  // clients, like so. See https://github.com/slackhq/node-slack-sdk
  getSlack() {
    return {
      rtmClient: new RtmClient(process.env.SLACK_API_TOKEN, {
        dataStore: new MemoryDataStore(),
        autoReconnect: true,
        logLevel: 'error',
      }),
      webClient: new WebClient(process.env.SLACK_API_TOKEN),
    };
  },
  // Return message handler for this message. See the "createArgsAdjuster"
  // example for the definition of getStatefulMessageHandler.
  createMessageHandler(id, meta) {
    const channel = meta.channel;
    // In this example, the bot will only handle DMs and ignore public channels.
    if (channel.is_im) {
      const messageHandler = getStatefulMessageHandler();
      // Let the bot know that the message handler has state, so it'll be cached.
      messageHandler.hasState = true;
      return messageHandler;
    }
  },
});

// Connect!
bot.login();

See the slack-bot example.

API

Bot

  • Bot - The base bot class.
  • createBot - function that returns an instance of Bot.
  • SlackBot - Subclass of Bot that contains Slack-specific functionality.
  • createSlackBot - function that returns an instance of SlackBot.

Message handlers

  • DelegatingMessageHandler -
  • createDelegate - function that returns an instance of DelegatingMessageHandler.
  • MatchingMessageHandler -
  • createMatcher - function that returns an instance of MatchingMessageHandler.
  • ArgsAdjustingMessageHandler -
  • createArgsAdjuster - function that returns an instance of ArgsAdjustingMessageHandler.
  • ParsingMessageHandler -
  • createParser - function that returns an instance of ParsingMessageHandler.
  • ConversingMessageHandler -
  • createConversation - function that returns an instance of ConversingMessageHandler.
  • CommandMessageHandler -
  • createCommand - function that returns an instance of CommandMessageHandler.

Util

  • handleMessage - Pass specified arguments through a message handler or array of message handlers.
  • isMessageHandlerOrHandlers - Facilitate message handler result parsing.
  • parseArgs - Parse args from an array or string. Suitable for use with lines of chat.
  • isMessage - Is the argument a message? It's a message if it's an Array, nested Arrays, or a value comprised solely of String, Number, null, undefined or false values.
  • normalizeMessage - Flatten message array and remove null, undefined or false items, then join on newline.
  • composeCreators - Compose creators that accept a signature like getHandlers() into a single creator. All creators receive the same options object.

Developing and Contributing

npm scripts

This project and all examples are written for nodejs in ES2015, using babel. Ensure you have Node 4.x or 6.x and Npm installed and run npm install before running any of the following commands:

  • npm test - Lints project code and runs tests.
  • npm run build - Builds project code from src into dist for publishing.
  • npm run start - Watches project files for changes, linting, testing and building as-necessary.
  • npm run babel - Run ES2015 javascript via the babel cli.
  • npm run prepublish - Automatically runs npm run build before publishing.

Contributing

TBD

More Repositories

1

jqfundamentals.com

jQuery Fundamentals
HTML
184
star
2

StartupDataTrends

JavaScript
105
star
3

deployment-workflow

Modern Web Deployment Workflow
HTML
90
star
4

critical-css-boilerplate

PHP
57
star
5

JSARToolkit-Wrapper

A simple JavaScript wrapper for allowing you to easily track Augmented Reality Markers in a video source and replace them with custom PNGs.
JavaScript
52
star
6

d3-reuse-examples

Building reusable charts with d3.chart
39
star
7

django_gmapsfield

A Google Maps field type for the django web framework
JavaScript
39
star
8

ovc-2016-videos

Code for visualizing videos from OpenVis Conf 2016
CSS
33
star
9

privacy-stack

A remix.run stack focused on privacy.
TypeScript
31
star
10

mobilevis

MobileVis - a gallery of mobile data visualizations
JavaScript
30
star
11

roost-sandiego-2014-app

Roostagram!
JavaScript
24
star
12

open-web-fundamentals

a celebration and exploration of the open web, for and by those interested in becoming responsible and informed web- makers and consumers
CSS
22
star
13

grunt-init-chromeapp

grunt-init template for creating Chrome Apps
JavaScript
21
star
14

roost-boston-2012

JavaScript
16
star
15

learn-ssh

Explore the fundamentals of SSH (secure shell), a cryptographic protocol used to communicate with and control servers on the internet.
Python
16
star
16

roost-chicago-2014-app

Roostagram!
JavaScript
15
star
17

learn-ansible

Demystify server configuration and application deployments using the IT automation tool Ansible.
JavaScript
13
star
18

web-platform-contribution-guide

Source code for the Web Platform Contribution Guide, a guidebook to becoming a web platform contributor.
Shell
13
star
19

stereotropes-analysis

Python
12
star
20

gaia-notes

Information on Contributing to Mozilla's Gaia project
Shell
12
star
21

stereotropes-client

JavaScript
12
star
22

test262-report-issue-tracker

Public issue tracker for the Test262 Report:
11
star
23

stereotropes-data-public

Stereotropes public data repository
11
star
24

mobilevis-patterns

MobileVis Pattern Static Application (the counterpart to the gallery at github.com/bocoup/mobilevis)
CSS
11
star
25

winter-holiday-icons

Holiday icons from Bocoup
10
star
26

react-router-oauth

Basic oauth support for react-router
JavaScript
10
star
27

test262-stream

A Node.js API for traversing the Test262 test suite
JavaScript
10
star
28

reconbot

https://bocoup.com/services/web-connected-devices
JavaScript
10
star
29

bots

Our bots.
JavaScript
8
star
30

roostagram-boilerplate

Roostagram!!! Boilerplate files
JavaScript
8
star
31

html5-building-blocks

7
star
32

sb-util

sb-util, a library that can query Scratch projects via *.sb3 and project.json files
TypeScript
6
star
33

webpack-workshop

JavaScript
6
star
34

learn-terraform

Completely automate your infrastructure with Terraform, a cutting edge tool that can provision entire datacenters with a few keystrokes.
HCL
6
star
35

ccpa-authorized-agent

CCPA Authorized Agent Onboarding tools
JavaScript
5
star
36

j5ik-reconbot-tessel-edition

Reconbot: Tessel 2 Edition
JavaScript
5
star
37

coral-ask-election-2016

Bocoup
JavaScript
5
star
38

Cor-

JavaScript
5
star
39

people

Bocouper directory.
JavaScript
5
star
40

gp-conflict-1

This is the working repository for the first Interactive piece to the Global Post focusing on the Syrian conflict.
JavaScript
4
star
41

test262-regexp-generator

Generete tests for RegExp based on unicode data
JavaScript
4
star
42

popcorn-chain-plugin

Popcorn Chain Plugin can be used to load and execute additional Popcorn scripts and their accompanying videos, at time specified by the user.
JavaScript
3
star
43

bob-cli

Draw Bob in the CLI
JavaScript
3
star
44

blocks-capacity-planner

An airtable block for matching supply to demand in an airtable base.
JavaScript
3
star
45

skillsbot

Skill Tracking Bot
JavaScript
2
star
46

todomvc-vanillajs

JavaScript
2
star
47

dontlookatme

JavaScript
2
star
48

infrastructure-foundation

Bocoup Foundation Inc's Infrastructure
HCL
2
star
49

buildbot-sample

A simple Buildbot configuration intended for demonstration purposes only.
Shell
2
star
50

p5js-site-proto

MDX
2
star
51

deploy

Do-It-Yourself secure server provisioning and deployment.
Shell
2
star
52

bocoup_revamp_spike

TypeScript
1
star
53

standards-testing

JavaScript
1
star
54

maintainable-web-apps-app

Roostagram for Maintainable Web Apps
JavaScript
1
star
55

test262-v8-machinery

JavaScript
1
star
56

web-browser-request-mechanisms

1
star
57

PMC-Webpack-Workshop

Webpack workshop for June 23, 2017
JavaScript
1
star
58

github-licenses

Ruby
1
star
59

wpt-error-report

A report on uncompleted tests in Web Platform Tests
JavaScript
1
star
60

wpt-cdp-experiment

Experiments in running the web-platform-tests via the Chrome Debugger Protocol
Python
1
star
61

test262-automation

Automation scripts for exporting implementor tests into test262
JavaScript
1
star
62

rockbreaker

A system to mitigate the perf damage caused by third party scripts of dubious quality and their many, many dependencies.
JavaScript
1
star
63

airtable-github-actions-example

JavaScript
1
star
64

wpt-dev

Set up your Debian-based system to contribute to the Web Platform Test suite!
Shell
1
star
65

wpt-docs

A clone of the web-platform-tests project to collaborate on documentation reorganization
HTML
1
star
66

infrastructure-web-platform

Web Platform Tests Infrastructure
HCL
1
star
67

scripts

Example scripts for the Airtable scripting block
JavaScript
1
star
68

aria-at-automation-experiment-ci

Shell
1
star
69

nest-weekly-review

An application for managing billing data for consulting projects
JavaScript
1
star