Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

cac

Package Overview
Dependencies
Maintainers
3
Versions
120
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cac - npm Package Compare versions

Comparing version 5.0.16 to 6.0.0

dist/cac.cjs.js

79

dist/Command.d.ts

@@ -1,22 +0,65 @@

import Options, { IOptionsInput } from './Options';
export interface ICommandOptions {
desc: string;
alias?: string | string[];
examples?: string[];
[k: string]: any;
import Option, { OptionConfig } from './Option';
interface CommandArg {
required: boolean;
value: string;
variadic: boolean;
}
export interface ICommand extends ICommandOptions {
name: string;
desc: string;
names: string[];
interface HelpConfig {
bin: string;
subCommands?: Command[];
versionNumber?: string;
globalOptions?: Option[];
}
export declare type CommandHandler = (input: string[], flags: {
[k: string]: any;
}) => any | Promise<any>;
interface HelpSection {
title?: string;
body: string;
}
interface CommandConfig {
allowUnknownOptions?: boolean;
}
declare type HelpCallback = (sections: HelpSection[]) => void;
export default class Command {
command: ICommand;
options: Options;
handler?: CommandHandler;
constructor(name: string, opt: ICommandOptions | string, handler?: CommandHandler);
option(name: string, opt: string | IOptionsInput): this;
rawName: string;
description: string;
options: Option[];
aliasNames: string[];
name: string;
args: CommandArg[];
commandAction?: (...args: any[]) => any;
usageText?: string;
versionNumber?: string;
examples: string[];
config: CommandConfig;
helpCallback?: HelpCallback;
constructor(rawName: string, description: string);
usage(text: string): this;
allowUnknownOptions(): this;
version(version: string): this;
example(text: string): this;
/**
* Add a option for this command
* @param rawName Raw option name(s)
* @param description Option description
* @param config Option config
*/
option(rawName: string, description: string, config?: OptionConfig): this;
alias(name: string): this;
action(callback: (...args: any[]) => any): this;
/**
* Check if a command name is matched by this command
* @param name Command name
*/
isMatched(name: string): boolean;
readonly isDefaultCommand: boolean;
/**
* Check if an option is registered in this command
* @param name Option name
*/
hasOption(name: string): Option | undefined;
outputHelp(config: HelpConfig): void;
outputVersion(bin: string): this;
checkUnknownOptions(options: {
[k: string]: any;
}, globalCommand: Command): boolean;
}
export { HelpCallback };

@@ -1,15 +0,55 @@

/// <reference path="../declarations.d.ts" />
/// <reference types="minimist" />
import { Opts } from 'minimost';
import Cac, { ICacOptions } from './Cac';
declare function cac(opts?: ICacOptions): Cac;
declare namespace cac {
function parse(args: string[], opts: Opts): {
input: string[];
flags: {
/// <reference types="node" />
import { EventEmitter } from 'events';
import { Opts as MinimostOpts } from 'minimost';
import Command, { HelpCallback } from './Command';
import { OptionConfig } from './Option';
interface ParsedArgv {
args: string[];
options: {
[k: string]: any;
};
}
declare class CAC extends EventEmitter {
bin: string;
commands: Command[];
globalCommand: Command;
matchedCommand: Command;
/**
* Raw CLI arguments
*/
rawArgs: string[];
/**
* Parsed CLI arguments
*/
args: string[];
/**
* Parsed CLI options
*/
options: {
[k: string]: any;
};
constructor();
usage(text: string): this;
command(rawName: string, description: string): Command;
option(rawName: string, description: string, config?: OptionConfig): this;
help(callback?: HelpCallback): this;
version(version: string): this;
/**
* Add a global example
* @param example
*/
example(example: string): this;
outputHelp(): this;
outputVersion(): this;
parse(argv?: string[]): ParsedArgv;
minimost(argv: string[], minimostOptions: MinimostOpts): {
args: string[];
options: {
[k: string]: any;
};
};
runCommandAction(command: Command, globalCommand: Command, { args, options }: ParsedArgv): any;
}
declare const cac: () => CAC;
export default cac;
export { Cac };

@@ -1,5 +0,14 @@

import { TabelData } from 'text-table';
export declare function orderNames(names: string[]): string[];
export declare function textTable(data: TabelData): string;
export declare function prefixOption(option: string): string;
export declare function invariant(exp: any, message: string): void;
import Option from './Option';
export declare const removeBrackets: (v: string) => string;
export declare const findAllBrackets: (v: string) => {
required: boolean;
value: string;
variadic: boolean;
}[];
export declare const getMinimostOptions: (options: Option[]) => {
default: {};
boolean: string[];
alias: {};
};
export declare const findLongest: (arr: string[]) => string;
export declare const padRight: (str: string, length: number) => string;
{
"name": "cac",
"version": "5.0.16",
"description": "Command-line queen.",
"version": "6.0.0",
"description": "Simple yet powerful framework for building command-line apps.",
"repository": {

@@ -9,54 +9,60 @@ "url": "egoist/cac",

},
"main": "dist/cac.js",
"types": "dist/index.d.ts",
"main": "dist/cac.cjs.js",
"files": [
"dist",
"declarations.d.ts"
"!**/__test__/**"
],
"keywords": [
"cli",
"framework",
"parse",
"argv",
"app",
"simple",
"cac"
],
"engines": {
"node": ">=6"
},
"scripts": {
"test": "yarn lint && yarn integration",
"integration": "yarn build && jest --env node",
"test": "jest",
"build": "bili",
"postbuild": "rm -rf dist/__test__",
"prepublishOnly": "yarn build",
"toc": "markdown-toc -i README.md",
"lint": "echo lint"
"prepublishOnly": "npm run build"
},
"author": "egoist <0x142857@gmail.com>",
"license": "MIT",
"dependencies": {
"chalk": "^2.4.1",
"joycon": "^2.1.2",
"minimost": "^1.2.0",
"redent": "^2.0.0",
"string-width": "^2.1.1",
"text-table": "^0.2.0"
},
"devDependencies": {
"@types/execa": "^0.9.0",
"@types/jest": "^22.2.3",
"@types/node": "^9.6.5",
"@types/strip-ansi": "^3.0.0",
"bili": "^3.3.0",
"@types/jest": "^23.3.9",
"bili": "^3.4.2",
"cz-conventional-changelog": "^2.1.0",
"eslint-config-rem": "^3.0.0",
"execa": "^0.9.0",
"jest": "^22.4.3",
"markdown-toc": "^1.1.0",
"rollup-plugin-typescript2": "^0.17.2",
"strip-ansi": "^4.0.0",
"ts-jest": "^23.10.4",
"execa": "^1.0.0",
"husky": "^1.2.0",
"jest": "^23.6.0",
"lint-staged": "^8.1.0",
"markdown-toc": "^1.2.0",
"prettier": "^1.15.2",
"rollup-plugin-typescript2": "^0.18.0",
"semantic-release": "^15.12.1",
"ts-jest": "^23.10.5",
"ts-node": "^7.0.1",
"typescript": "^3.1.6"
},
"dependencies": {
"minimost": "^1.2.0"
},
"release": {
"branch": "master"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"lint-staged": {
"*.{js,json,ts}": [
"prettier --write",
"git add"
],
"*.md": [
"yarn toc",
"prettier --write",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "npm t && lint-staged"
}
}
}
<img width="945" alt="2017-07-26 9 27 05" src="https://user-images.githubusercontent.com/8784712/28623641-373450f4-7249-11e7-854d-1b076dab274d.png">
[![NPM version](https://img.shields.io/npm/v/cac.svg?style=flat)](https://npmjs.com/package/cac) [![NPM downloads](https://img.shields.io/npm/dm/cac.svg?style=flat)](https://npmjs.com/package/cac) [![CircleCI](https://circleci.com/gh/cacjs/cac/tree/master.svg?style=shield)](https://circleci.com/gh/cacjs/cac/tree/master) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/egoist/donate) [![chat](https://img.shields.io/badge/chat-on%20discord-7289DA.svg?style=flat)](https://chat.egoist.moe) [![install size](https://badgen.net/packagephobia/install/cac)](https://packagephobia.now.sh/result?p=cac)
[![NPM version](https://img.shields.io/npm/v/cac.svg?style=flat)](https://npmjs.com/package/cac) [![NPM downloads](https://img.shields.io/npm/dm/cac.svg?style=flat)](https://npmjs.com/package/cac) [![CircleCI](https://circleci.com/gh/cacjs/cac/tree/master.svg?style=shield)](https://circleci.com/gh/cacjs/cac/tree/master) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/egoist/donate) [![chat](https://img.shields.io/badge/chat-on%20discord-7289DA.svg?style=flat)](https://chat.egoist.moe) [![install size](https://badgen.net/packagephobia/install/cac)](https://packagephobia.now.sh/result?p=cac)
## Introduction
**C**ommand **A**nd **C**onquer, the queen living in your command line, is a minimalistic but pluggable CLI framework.
**C**ommand **A**nd **C**onquer is a JavaScript library for building CLI apps.
## Install
## Table of Contents
```bash
yarn add cac
```
## Table of contents
<!-- toc -->
- [Install](#install)
- [Usage](#usage)
- [Friends](#friends)
- [Documentation](#documentation)
- [CLI instance](#cli-instance)
* [cli.option(name, [option])](#clioptionname-option)
* [cli.command(name, [option], [handler])](#clicommandname-option-handler)
+ [command](#command)
- [command.option(name, [option])](#commandoptionname-option)
* [cli.parse([argv], [option])](#cliparseargv-option)
* [cli.showHelp()](#clishowhelp)
* [cli.use(plugin)](#cliuseplugin)
* [cli.bin](#clibin)
* [cli.argv](#cliargv)
* [cli.extraHelp(help)](#cliextrahelphelp)
+ [help](#help)
* [Simple Parsing](#simple-parsing)
* [Display Help Message and Version](#display-help-message-and-version)
* [Command-specific Options](#command-specific-options)
* [Brackets](#brackets)
* [Variadic Arguments](#variadic-arguments)
* [Dot-nested Options](#dot-nested-options)
* [Default Command](#default-command)
* [With TypeScript](#with-typescript)
- [References](#references)
* [CLI Instance](#cli-instance)
+ [cli.command(name, description)](#clicommandname-description)
+ [cli.option(name, description, config?)](#clioptionname-description-config)
+ [cli.parse(argv?)](#cliparseargv)
+ [cli.version(version)](#cliversionversion)
+ [cli.help(callback?)](#clihelpcallback)
+ [cli.outputHelp()](#clioutputhelp)
* [Command Instance](#command-instance)
+ [command.option()](#commandoption)
+ [command.action(callback)](#commandactioncallback)
+ [command.alias(name)](#commandaliasname)
+ [command.allowUnknownOptions()](#commandallowunknownoptions)
+ [command.example(example)](#commandexampleexample)
* [Events](#events)
+ [error](#error)
+ [parsed](#parsed)
+ [executed](#executed)
- [FAQ](#faq)
* [Why not `commander.js` `yargs` `caporal.js` or `meow` ?](#why-not-commanderjs-yargs-caporaljs-or-meow-)
* [How is the name written and pronounced?](#how-is-the-name-written-and-pronounced)

@@ -47,137 +45,214 @@ - [Contributing](#contributing)

## Install
```bash
yarn add cac
```
## Usage
Use `./examples/simple.js` as example:
### Simple Parsing
Use CAC as simple argument parser:
```js
const cac = require('cac')
// examples/basic-usage.js
const cli = require('cac')()
const cli = cac()
// Add a default command
const defaultCommand = cli.command('*', {
desc: 'The default command'
}, (input, flags) => {
if (flags.age) {
console.log(`${input[0]} is ${flags.age} years old`)
}
cli.option('--type [type]', 'Choose a project type', {
default: 'node'
})
defaultCommand.option('age', {
desc: 'tell me the age'
const parsed = cli.parse()
console.log(JSON.stringify(parsed, null, 2))
```
<img width="500" alt="2018-11-25 3 17 56" src="https://user-images.githubusercontent.com/8784712/48976608-57610600-f0c5-11e8-924c-8da6a5fbfbb0.png">
### Display Help Message and Version
```js
// examples/help.js
const cli = require('cac')()
cli.option('--type [type]', 'Choose a project type', {
default: 'node'
})
cli.option('--name <name>', 'Provide your name')
// Add a sub command
cli.command('bob', {
desc: 'Command for bob'
}, () => {
console.log('This is a command dedicated to bob!')
cli.command('lint [...files]', 'Lint files').action((files, options) => {
console.log(files, options)
})
// Bootstrap the CLI app
// Display help message when `-h` or `--help` appears
cli.help()
// Display version number when `-h` or `--help` appears
cli.version('0.0.0')
cli.parse()
```
Then run it:
<img width="500" alt="2018-11-25 8 21 14" src="https://user-images.githubusercontent.com/8784712/48979012-acb20d00-f0ef-11e8-9cc6-8ffca00ab78a.png">
<img width="303" alt="2017-07-26 2 29 46" src="https://i.loli.net/2017/07/26/5978a1a7c72f5.png">
### Command-specific Options
And the **Help Documentation** is ready out of the box:
You can attach options to a command.
<img width="600" alt="cli help" src="https://i.loli.net/2018/09/07/5b92344112d40.png">
```js
const cli = require('cac')()
The Help Documentation will be command-specific when you are using `--help` with a sub command, like below:
cli
.command('rm <dir>')
.option('-r, --recursive', 'Remove recursively')
.action((dir, options) => {
console.log('remove ' + dir + (options.recursive ? ' recursively' : ''))
})
<img width="600" alt="sub command help" src="https://i.loli.net/2018/09/07/5b92344116b8c.png">
cli.parse()
```
## Friends
A command's options are validated when the command is used. Any unknown options will be reported as an error. However, if an action-based command does not define an action, then the options are not validated. If you really want to use unknown options, use [`command.allowUnknownOptions`](#commandallowunknownoptions).
Projects that use **CAC**:
### Brackets
- [SAO](https://github.com/egoist/sao): ⚔️ Futuristic scaffolding tool.
- [DocPad](https://github.com/docpad/docpad): 🏹 Powerful Static Site Generator.
- [Poi](https://github.com/egoist/poi): ⚡️ Delightful web development.
- [bili](https://github.com/egoist/bili): 🥂 Schweizer Armeemesser for bundling JavaScript libraries.
- [lass](https://github.com/lassjs/lass): 💁🏻 Scaffold a modern package boilerplate for Node.js.
- Feel free to add yours here...
When using brackets in command name, angled brackets indicate required command arguments, while sqaure bracket indicate optional arguments.
## Documentation
When using brackets in option name, angled brackets indicate that the option value is required, while sqaure bracket indicate that the value is optional.
## CLI instance
```js
const cli = require('cac')()
You can create a CLI instance as follows:
cli
.command('deploy <folder>', 'Deploy a folder to AWS')
.option('--scale [level]', 'Scaling level')
.action((folder, options) => {
console.log(folder)
console.log(options)
})
cli.parse()
```
### Variadic Arguments
The last argument of a command can be variadic, and only the last argument. To make an argument variadic you have to add `...` to the start of argument name, just like the rest operator in JavaScript. Here is an example:
```js
const cli = cac(options)
const cli = require('cac')()
cli
.command('build <entry> [...otherFiles]', 'Build your app')
.option('--foo', 'Foo option')
.action((entry, otherFiles, options) => {
console.log(entry)
console.log(otherFiles)
console.log(options)
})
cli.help()
cli.parse()
```
`options` argument is optional here:
<img width="500" alt="2018-11-25 8 25 30" src="https://user-images.githubusercontent.com/8784712/48979056-47125080-f0f0-11e8-9d8f-3219e0beb0ed.png">
- `options.bin`: The CLI bin name to show in help message when `--help` flag is used.
- `options.defaultOpts`: By default we add serveral global command options like `--help` and `--version`, however you can disable them all by setting it to `false` or configure them individually as follow:
- `options.defaultOpts.help`: `true` Show help message when `--help` flag is used, alias flag `-h`.
- `options.defaultOpts.version`: `true` Show version number when `--version` flag is used, alias flag `-v`.
### Dot-nested Options
### cli.option(name, [option])
Dot-nested options will be merged into a single option.
Register an option globally, i.e. for all commands
```js
const cli = require('cac')()
- name: `string` option name
- option: `object` `string`
- desc: `string` description
- alias: `string` `Array<string>` option name alias
- type: `string` option type, valid values: `boolean` `string`
- default: `any` option default value
- required: `boolean` mark option as required
- choices: `Array<any>` limit valid values for the option
cli
.command('build', 'desc')
.option('--env <env>', 'Set envs')
.example('--env.API_SECRET xxx')
.action(options => {
console.log(options)
})
### cli.command(name, [option], [handler])
cli.help()
- name: `string`
- option: `object` `string` (`string` is used as `desc`)
- desc: `string` description
- alias: `string` `Array<string>` command name alias
- examples: `Array<string>` command examples
- match: `(name: string) => boolean` A custom command matcher
- handler: `function` command handler
- input: `Array<string>` cli arguments
- flags: `object` cli flags
cli.parse()
```
<img width="500" alt="2018-11-25 9 37 53" src="https://user-images.githubusercontent.com/8784712/48979771-6ada9400-f0fa-11e8-8192-e541b2cfd9da.png">
### Default Command
Register a command that will be used when no other command is matched.
```js
const command = cli.command('init', 'init a new project', (input, flags) => {
const folderName = input[0]
console.log(`init project in folder ${folderName}`)
})
const cli = require('cac')()
cli
// Simply omit the command name, just brackets
.command('[...files]', 'Build files')
.option('--minimize', 'Minimize output')
.action((files, options) => {
console.log(files)
console.log(options.minimize)
})
cli.parse()
```
`cli.command` returns a [command](#command) instance.
### With TypeScript
#### command
First you need `@types/node` to be installed as a dev dependency in your project:
##### command.option(name, [option])
```bash
yarn add @types/node --dev
```
Same as [cli.option](#clioptionname-option) but it adds options for specified command.
To make our own type definitions work with commonjs modules, use the default export:
### cli.parse([argv], [option])
```js
const cac = require('cac').default
- argv: `Array<string>` Defaults to `process.argv.slice(2)`
- option
- run: `boolean` Defaults to `true` Run command after parsed argv.
const cli = cac()
### cli.showHelp()
//...
```
Display cli helps, must be called after `cli.parse()`
For ES modules, it works out of the box.
### cli.use(plugin)
## References
- plugin: `Plugin` `Array<Plugin>`
### CLI Instance
Apply a plugin to cli instance:
CLI instance is created by invoking the `cac` function:
```js
cli.use(plugin(options))
const cac = require('cac')
const cli = cac()
```
function plugin(options) {
return cli => {
// do something...
#### cli.command(name, description)
- Type: `(name: string, description: string) => Command`
Create a command instance.
#### cli.option(name, description, config?)
- Type: `(name: string, description: string, config?: OptionConfig) => CLI`
Add a global option.
The option also accepts a third argument `config` for addtional config:
- `config.default`: Default value for the option.
- `config.coerce`: `(value: any) => newValue` A function to process the option value.
#### cli.parse(argv?)
- Type: `(argv = process.argv) => ParsedArgv`
```ts
interface ParsedArgv {
args: string[]
options: {
[k: string]: any
}

@@ -187,82 +262,112 @@ }

### cli.bin
When this method is called, `cli.rawArgs` `cli.args` `cli.options` `cli.matchedCommand` will also be available.
Type: `string`
#### cli.version(version)
The filename of executed file.
- Type: `(version: string) => CLI`
e.g. It's `cli.js` when you run `node ./cli.js`.
Output version number when `-v, --version` flag appears.
### cli.argv
#### cli.help(callback?)
A getter which simply returns `cli.parse(null, { run: false })`
- Type: `(callback?: HelpCallback) => CLI`
### cli.extraHelp(help)
Output help message when `-h, --help` flag appears.
Add extra help messages to the bottom of help.
Optional `callback` allows post-processing of help text before it is displayed:
#### help
```ts
type HelpCallback = (sections: HelpSection[]) => void
Type: `string` `object`
interface HelpSection {
title?: string
body: string
}
```
The `help` could be a `string` or in `{ title, body }` format.
#### cli.outputHelp()
### Events
- Type: `() => CLI`
#### error
Output help message.
Error handler for errors in your command handler:
### Command Instance
Command instance is created by invoking the `cli.command` method:
```js
cli.on('error', err => {
console.error('command failed:', err)
process.exit(1)
})
const command = cli.command('build [...files]', 'Build given files')
```
#### parsed
#### command.option()
Emit after CAC parsed cli arguments:
Basically the same as `cli.option` but this adds the option to specific command.
```js
cli.on('parsed', (command, input, flags) => {
// command might be undefined
})
```
#### command.action(callback)
#### executed
- Type: `(callback: ActionCallback) => Command`
Emit after CAC executed commands or outputed help / version number:
Use a callback function as the command action when the command matches user inputs.
```js
cli.on('executed', (command, input, flags) => {
// command might be undefined
})
```ts
type ActionCallback = (
// Parsed CLI args
// The last arg will be an array if it's an varadic argument
...args: string | string[] | number | number[]
// Parsed CLI options
options: Options
) => any
interface Options {
[k: string]: any
}
```
## FAQ
#### command.alias(name)
### Why not `commander.js` `yargs` `caporal.js` or `meow` ?
- Type: `(name: string) => Command`
`CAC` is simpler and less opinionated comparing to `commander.js` `yargs` `caporal.js`.
Add an alias name to this command, the `name` here can't contain brackets.
Commander.js does not support [chaining option](https://github.com/tj/commander.js/issues/606) which is a feature I like a lot. It's not really actively maintained at the time of writing either.
#### command.allowUnknownOptions()
Yargs has a powerful API, but it's so massive that my brain trembles. Meow is simple and elegant but I have to manully construct the *help* message, which will be annoying. And I want it to support *sub-command* too.
- Type: `() => Command`
And none of them are pluggable.
Allow unknown options in this command, by default CAC will log an error when unknown options are used.
**So why creating a new thing instead of pull request?**
#### command.example(example)
I would ask me myself why there's `preact` instead of PR to `react`, and why `yarn` instead of PR to `npm`? It's obvious.
- Type: `(example: string) => Command`
**CAC** is kind of like a combination of the simplicity of Meow and the powerful features of the rest. And our *help* log is inspired by Caporal.js, I guess it might be the most elegant one out there?
Add an example which will be displayed at the end of help message.
<img alt="preview" src="https://i.loli.net/2017/07/26/59789ed2112f6.png" width="500">
### Events
Listen to commands:
```js
// Listen to the `foo` command
cli.on('command:foo', () => {
// Do something
})
// Listen to the default command
cli.on('command:!', () => {
// Do something
})
// Listen to unknown commands
cli.on('command:*', () => {
console.error('Invalid command: %', cli.args.join(' '))
process.exit(1)
})
```
## FAQ
### How is the name written and pronounced?
CAC, not Cac or cac, pronounced `C-A-C`.
CAC, or cac, pronounced `C-A-C`.
And this project is dedicated to our lovely C.C. sama. Maybe CAC stands for C&C as well :P
This project is dedicated to our lovely C.C. sama. Maybe CAC stands for C&C as well :P

@@ -279,8 +384,7 @@ <img src="http://i.giphy.com/v3FeH4swox9mg.gif" width="400"/>

## Author
**cac** © [egoist](https://github.com/egoist), Released under the [MIT](./LICENSE) License.<br>
**CAC** © [EGOIST](https://github.com/egoist), Released under the [MIT](./LICENSE) License.<br>
Authored and maintained by egoist with help from contributors ([list](https://github.com/cacjs/cac/contributors)).
> [egoist.moe](https://egoist.moe) · GitHub [@egoist](https://github.com/egoist) · Twitter [@_egoistlily](https://twitter.com/_egoistlily)
> [Website](https://egoist.sh) · GitHub [@egoist](https://github.com/egoist) · Twitter [@\_egoistlily](https://twitter.com/_egoistlily)

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc