First commit

main
Nick Dumas 3 weeks ago
commit d7934fe348

@ -0,0 +1,5 @@
# CHANGELOG
## 1.2.0
- Add support for Foundry v10

@ -0,0 +1,30 @@
MIT License
Copyright (c) 2020 Asacolips Projects / Foundry Mods
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
This license does not apply to the compendium content listed in this software's
"packs" directory. See the README for licensing information for the compendium
packs.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
IMAGE LICENSES
anvil-impact.png by Lorc under CC BY 3.0 from game-icons.net

@ -0,0 +1,82 @@
# Wanderhome System
![Foundry v11](https://img.shields.io/badge/foundry-v11-green)
This system is a wanderhome system that you can use as a starting point for building your own custom systems. It's similar to Simple World-building, but has examples of creating attributes in code rather than dynamically through the UI.
## Usage
There are two ways to get started: using the Wanderhome system generator command or manually renaming and updating files.
Regardless of which method you choose, think carefully about your system's name. Your system's package name when submitted to Foundry must be formatted like `alphanumeric-lowercase`, and it must be unique. Check the Foundry systems package list for conflicts before committing to a name!
> **Data Models**
>
> If you would like to use DataModel classes instead of the older template.json configuration, you'll need to use the `npm run generate` command described below and choose to enable them when asked. DataModels are currently an optional feature, and are only availabe in the generator CLI due to that.
### Generator
This system includes a generator CLI in `package.json`. To use it, you must have [node.js](https://nodejs.org) installed, and it's recommended that you install node 20 or later.
> **Python Generator**
>
> If you would rather use Python than node, theres an excellent Python-based generator created by Cussa at https://github.com/Cussa/fvtt-wanderhome-initializator. Give it a shot!
Once you have npm installed, you can run the following in your terminal or command prompt:
```bash
npm install
npm run generate
```
Your terminal should prompt you to name your system. Read the instructions carefully, the letter case and special characters in each question matter for correct system generation.
Once the generator completes, it will output your system to `build/<your-system-name>`, where `<your-system-name>` is the package name you supplied during the prompt.
Copy this directory over to your Foundry systems directory and start coding!
### Manual Replacement
Before installing this system, you should rename any files that have `wanderhome` in their filename to use whatever machine-safe name your system needs, such as `adnd2e` if you were building a system for 2nd edition Advanced Dungeons & Dragons. In addition, you should search through the files for `wanderhome` and `Wanderhome` and do the same for those, replacing them with appropriate names for your system.
The `name` property in your `system.json` file is your system's package name. This need to be formatted `alphanumeric-lowercase`, and it must also match the foldername you use for your system.
### Vue 3 Wanderhome
**NOTE: The Vue 3 version is currently outdated and considered an advanced usage of Foundry due to it being a custom renderer. Only try it out if you _really_ like Vue and are feeling dangerous!**
Alternatively, there's another build of this system that supports using Vue 3 components (ES module build target) for character sheet templates.
Head over to the [Vue3Wanderhome System](https://gitlab.com/asacolips-projects/foundry-mods/vue3wanderhome) repo if you're interested in using Vue!
### Getting Help
Check out the [Official Foundry VTT Discord](https://discord.gg/foundryvtt)! The #system-development channel has helpful pins and is a good place to ask questions about any part of the foundry application.
For more static references, the [Knowledge Base](https://foundryvtt.com/kb/) and [API Documentation](https://foundryvtt.com/api/) provide different levels of detail. For the most detail, you can find the client side code in your foundry installation location. Classes are documented in individual files under `resources/app/client` and `resources/app/common`, and the code is collated into a single file at `resources/app/public/scripts/foundry.js`.
#### Tutorial
For much more information on how to use this system as a starting point for making your own, see the [full tutorial on the Foundry Wiki](https://foundryvtt.wiki/en/development/guides/SD-tutorial)!
Note: Tutorial may be out of date, so look out for the Foundry compatibility badge at the top of each page.
## Sheet Layout
This system includes a handful of helper CSS classes to help you lay out your sheets if you're not comfortable diving into CSS fully. Those are:
- `flexcol`: Included by Foundry itself, this lays out the child elements of whatever element you place this on vertically.
- `flexrow`: Included by Foundry itself, this lays out the child elements of whatever element you place this on horizontally.
- `flex-center`: When used on something that's using flexrow or flexcol, this will center the items and text.
- `flex-between`: When used on something that's using flexrow or flexcol, this will attempt to place space between the items. Similar to "justify" in word processors.
- `flex-group-center`: Add a border, padding, and center all items.
- `flex-group-left`: Add a border, padding, and left align all items.
- `flex-group-right`: Add a border, padding, and right align all items.
- `grid`: When combined with the `grid-Ncol` classes, this will lay out child elements in a grid.
- `grid-Ncol`: Replace `N` with any number from 1-12, such as `grid-3col`. When combined with `grid`, this will layout child elements in a grid with a number of columns equal to the number specified.
## Compiling the CSS
This repo includes both CSS for the theme and SCSS source files. If you're new to CSS, it's probably easier to just work in those files directly and delete the SCSS directory. If you're interested in using a CSS preprocessor to add support for nesting, variables, and more, you can run `npm install` in this directory to install the dependencies for the scss compiler. After that, just run `npm run build` to compile the SCSS and start a process that watches for new changes.
![image](http://mattsmith.in/images/wanderhome.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@ -0,0 +1,366 @@
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
/* Global styles */
.window-app {
font-family: "Roboto", sans-serif;
}
.rollable:hover, .rollable:focus {
color: #000;
text-shadow: 0 0 10px red;
cursor: pointer;
}
.grid,
.grid-2col {
display: grid;
grid-column: span 2/span 2;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin: 10px 0;
padding: 0;
}
.grid-3col {
grid-column: span 3/span 3;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-4col {
grid-column: span 4/span 4;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-5col {
grid-column: span 5/span 5;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-6col {
grid-column: span 6/span 6;
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.grid-7col {
grid-column: span 7/span 7;
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.grid-8col {
grid-column: span 8/span 8;
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.grid-9col {
grid-column: span 9/span 9;
grid-template-columns: repeat(9, minmax(0, 1fr));
}
.grid-10col {
grid-column: span 10/span 10;
grid-template-columns: repeat(10, minmax(0, 1fr));
}
.grid-11col {
grid-column: span 11/span 11;
grid-template-columns: repeat(11, minmax(0, 1fr));
}
.grid-12col {
grid-column: span 12/span 12;
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.grid-start-2 {
grid-column-start: 2;
}
.grid-start-3 {
grid-column-start: 3;
}
.grid-start-4 {
grid-column-start: 4;
}
.grid-start-5 {
grid-column-start: 5;
}
.grid-start-6 {
grid-column-start: 6;
}
.grid-start-7 {
grid-column-start: 7;
}
.grid-start-8 {
grid-column-start: 8;
}
.grid-start-9 {
grid-column-start: 9;
}
.grid-start-10 {
grid-column-start: 10;
}
.grid-start-11 {
grid-column-start: 11;
}
.grid-start-12 {
grid-column-start: 12;
}
.grid-span-2 {
grid-column-end: span 2;
}
.grid-span-3 {
grid-column-end: span 3;
}
.grid-span-4 {
grid-column-end: span 4;
}
.grid-span-5 {
grid-column-end: span 5;
}
.grid-span-6 {
grid-column-end: span 6;
}
.grid-span-7 {
grid-column-end: span 7;
}
.grid-span-8 {
grid-column-end: span 8;
}
.grid-span-9 {
grid-column-end: span 9;
}
.grid-span-10 {
grid-column-end: span 10;
}
.grid-span-11 {
grid-column-end: span 11;
}
.grid-span-12 {
grid-column-end: span 12;
}
.flex-group-center,
.flex-group-left,
.flex-group-right {
justify-content: center;
align-items: center;
text-align: center;
}
.flex-group-left {
justify-content: flex-start;
text-align: left;
}
.flex-group-right {
justify-content: flex-end;
text-align: right;
}
.flexshrink {
flex: 0;
}
.flex-between {
justify-content: space-between;
}
.flexlarge {
flex: 2;
}
.align-left {
justify-content: flex-start;
text-align: left;
}
.align-right {
justify-content: flex-end;
text-align: right;
}
.align-center {
justify-content: center;
text-align: center;
}
/* Styles limited to wanderhome sheets */
.wanderhome .item-form {
font-family: "Roboto", sans-serif;
}
.wanderhome .sheet-header {
flex: 0 auto;
overflow: hidden;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
margin-bottom: 10px;
}
.wanderhome .sheet-header .profile-img {
flex: 0 0 100px;
height: 100px;
margin-right: 10px;
}
.wanderhome .sheet-header .header-fields {
flex: 1;
}
.wanderhome .sheet-header h1.charname {
height: 50px;
padding: 0px;
margin: 5px 0;
border-bottom: 0;
}
.wanderhome .sheet-header h1.charname input {
width: 100%;
height: 100%;
margin: 0;
}
.wanderhome .sheet-tabs {
flex: 0;
}
.wanderhome .sheet-body,
.wanderhome .sheet-body .tab,
.wanderhome .sheet-body .tab .editor {
height: 100%;
}
.wanderhome .tox .tox-editor-container {
background: #fff;
}
.wanderhome .tox .tox-edit-area {
padding: 0 8px;
}
.wanderhome .resource-label {
font-weight: bold;
}
.wanderhome .items-header {
height: 28px;
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: 2px groove #eeede0;
font-weight: bold;
}
.wanderhome .items-header > * {
font-size: 14px;
text-align: center;
}
.wanderhome .items-header .item-name {
font-weight: bold;
padding-left: 5px;
text-align: left;
display: flex;
}
.wanderhome .items-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
scrollbar-width: thin;
color: #444;
}
.wanderhome .items-list .item-list {
list-style: none;
margin: 0;
padding: 0;
}
.wanderhome .items-list .item-name {
flex: 2;
margin: 0;
overflow: hidden;
font-size: 13px;
text-align: left;
align-items: center;
display: flex;
}
.wanderhome .items-list .item-name h3, .wanderhome .items-list .item-name h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
.wanderhome .items-list .item-controls {
display: flex;
flex: 0 0 100px;
justify-content: flex-end;
}
.wanderhome .items-list .item-controls a {
font-size: 12px;
text-align: center;
margin: 0 6px;
}
.wanderhome .items-list .item {
align-items: center;
padding: 0 2px;
border-bottom: 1px solid #c9c7b8;
}
.wanderhome .items-list .item:last-child {
border-bottom: none;
}
.wanderhome .items-list .item .item-name {
color: #191813;
}
.wanderhome .items-list .item .item-name .item-image {
flex: 0 0 30px;
height: 30px;
background-size: 30px;
border: none;
margin-right: 5px;
}
.wanderhome .items-list .item-prop {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.wanderhome .items-list .items-header {
height: 28px;
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: 2px groove #eeede0;
font-weight: bold;
}
.wanderhome .items-list .items-header > * {
font-size: 12px;
text-align: center;
}
.wanderhome .items-list .items-header .item-name {
padding-left: 5px;
text-align: left;
}
.wanderhome .item-formula {
flex: 0 0 200px;
padding: 0 8px;
}
.wanderhome .effects .item .effect-source,
.wanderhome .effects .item .effect-duration,
.wanderhome .effects .item .effect-controls {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.wanderhome .effects .item .effect-controls {
border: none;
}

@ -0,0 +1,41 @@
{
"WANDERHOME": {
"Ability": {
"Str": { "long": "Strength", "abbr": "str" },
"Con": { "long": "Constitution", "abbr": "con" },
"Dex": { "long": "Dexterity", "abbr": "dex" },
"Int": { "long": "Intelligence", "abbr": "int" },
"Wis": { "long": "Wisdom", "abbr": "wis" },
"Cha": { "long": "Charisma", "abbr": "cha" }
},
"SheetLabels": {
"Actor": "Wanderhome Actor Sheet",
"Item": "Wanderhome Item Sheet"
},
"Item": {
"Spell": {
"SpellLVL": "Level {level} Spells",
"AddLVL": "Add LVL {level}"
}
},
"Effect": {
"Source": "Source",
"Toggle": "Toggle Effect",
"Temporary": "Temporary Effects",
"Passive": "Passive Effects",
"Inactive": "Inactive Effects"
}
},
"TYPES": {
"Actor": {
"character": "Character",
"npc": "NPC"
},
"Item": {
"item": "Item",
"feature": "Feature",
"spell": "Spell"
}
}
}

@ -0,0 +1,10 @@
// Export Actors
export {default as WanderhomeActorBase} from "./base-actor.mjs";
export {default as WanderhomeCharacter} from "./actor-character.mjs";
export {default as WanderhomeNPC} from "./actor-npc.mjs";
// Export Items
export {default as WanderhomeItemBase} from "./base-item.mjs";
export {default as WanderhomeItem} from "./item-item.mjs";
export {default as WanderhomeFeature} from "./item-feature.mjs";
export {default as WanderhomeSpell} from "./item-spell.mjs";

@ -0,0 +1,52 @@
import WanderhomeActorBase from "./base-actor.mjs";
export default class WanderhomeCharacter extends WanderhomeActorBase {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const schema = super.defineSchema();
schema.attributes = new fields.SchemaField({
level: new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1 })
}),
});
// Iterate over ability names and create a new SchemaField for each.
schema.abilities = new fields.SchemaField(Object.keys(CONFIG.WANDERHOME.abilities).reduce((obj, ability) => {
obj[ability] = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }),
});
return obj;
}, {}));
return schema;
}
prepareDerivedData() {
// Loop through ability scores, and add their modifiers to our sheet output.
for (const key in this.abilities) {
// Calculate the modifier using d20 rules.
this.abilities[key].mod = Math.floor((this.abilities[key].value - 10) / 2);
// Handle ability label localization.
this.abilities[key].label = game.i18n.localize(CONFIG.WANDERHOME.abilities[key]) ?? key;
}
}
getRollData() {
const data = {};
// Copy the ability scores to the top level, so that rolls can use
// formulas like `@str.mod + 4`.
if (this.abilities) {
for (let [k,v] of Object.entries(this.abilities)) {
data[k] = foundry.utils.deepClone(v);
}
}
data.lvl = this.attributes.level.value;
return data
}
}

@ -0,0 +1,19 @@
import WanderhomeActorBase from "./base-actor.mjs";
export default class WanderhomeNPC extends WanderhomeActorBase {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const schema = super.defineSchema();
schema.cr = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 });
schema.xp = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 });
return schema
}
prepareDerivedData() {
this.xp = this.cr * this.cr * 100;
}
}

@ -0,0 +1,23 @@
import WanderhomeDataModel from "./base-model.mjs";
export default class WanderhomeActorBase extends WanderhomeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const schema = {};
schema.health = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 10 })
});
schema.power = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 5, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
});
schema.biography = new fields.StringField({ required: true, blank: true }); // equivalent to passing ({initial: ""}) for StringFields
return schema;
}
}

@ -0,0 +1,14 @@
import WanderhomeDataModel from "./base-model.mjs";
export default class WanderhomeItemBase extends WanderhomeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
schema.description = new fields.StringField({ required: true, blank: true });
return schema;
}
}

@ -0,0 +1,14 @@
export default class WanderhomeDataModel extends foundry.abstract.TypeDataModel {
/**
* Convert the schema to a plain object.
*
* The built in `toObject()` method will ignore derived data when using Data Models.
* This additional method will instead use the spread operator to return a simplified
* version of the data.
*
* @returns {object} Plain object either via deepClone or the spread operator.
*/
toPlainObject() {
return {...this};
}
}

@ -0,0 +1,3 @@
import WanderhomeItemBase from "./base-item.mjs";
export default class WanderhomeFeature extends WanderhomeItemBase {}

@ -0,0 +1,31 @@
import WanderhomeItemBase from "./base-item.mjs";
export default class WanderhomeItem extends WanderhomeItemBase {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const schema = super.defineSchema();
schema.quantity = new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 });
schema.weight = new fields.NumberField({ required: true, nullable: false, initial: 0, min: 0 });
// Break down roll formula into three independent fields
schema.roll = new fields.SchemaField({
diceNum: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
diceSize: new fields.StringField({ initial: "d20" }),
diceBonus: new fields.StringField({ initial: "+@str.mod+ceil(@lvl / 2)" })
})
schema.formula = new fields.StringField({ blank: true });
return schema;
}
prepareDerivedData() {
// Build the formula dynamically using string interpolation
const roll = this.roll;
this.formula = `${roll.diceNum}${roll.diceSize}${roll.diceBonus}`
}
}

@ -0,0 +1,13 @@
import WanderhomeItemBase from "./base-item.mjs";
export default class WanderhomeSpell extends WanderhomeItemBase {
static defineSchema() {
const fields = foundry.data.fields;
const schema = super.defineSchema();
schema.spellLevel = new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 1, max: 9 });
return schema;
}
}

@ -0,0 +1,69 @@
/**
* Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class WanderhomeActor extends Actor {
/** @override */
prepareData() {
// Prepare data for the actor. Calling the super version of this executes
// the following, in order: data reset (to clear active effects),
// prepareBaseData(), prepareEmbeddedDocuments() (including active effects),
// prepareDerivedData().
super.prepareData();
}
/** @override */
prepareBaseData() {
// Data modifications in this step occur before processing embedded
// documents or derived data.
}
/**
* @override
* Augment the actor source data with additional dynamic data that isn't
* handled by the actor's DataModel. Data calculated in this step should be
* available both inside and outside of character sheets (such as if an actor
* is queried and has a roll executed directly from it).
*/
prepareDerivedData() {
const actorData = this;
const flags = actorData.flags.wanderhome || {};
}
/**
*
* @override
* Augment the actor's default getRollData() method by appending the data object
* generated by the its DataModel's getRollData(), or null. This polymorphic
* approach is useful when you have actors & items that share a parent Document,
* but have slightly different data preparation needs.
*/
getRollData() {
return { ...super.getRollData(), ...this.system.getRollData?.() ?? null };
}
/**
* Convert the actor document to a plain object.
*
* The built in `toObject()` method will ignore derived data when using Data Models.
* This additional method will instead use the spread operator to return a simplified
* version of the data.
*
* @returns {object} Plain object either via deepClone or the spread operator.
*/
toPlainObject() {
const result = {...this};
// Simplify system data.
result.system = this.system.toPlainObject();
// Add items.
result.items = this.items?.size > 0 ? this.items.contents : [];
// Add effects.
result.effects = this.effects?.size > 0 ? this.effects.contents : [];
return result;
}
}

@ -0,0 +1,92 @@
/**
* Extend the basic Item with some very simple modifications.
* @extends {Item}
*/
export class WanderhomeItem extends Item {
/**
* Augment the basic Item data model with additional dynamic data.
*/
prepareData() {
// As with the actor class, items are documents that can have their data
// preparation methods overridden (such as prepareBaseData()).
super.prepareData();
}
/**
* Prepare a data object which defines the data schema used by dice roll commands against this Item
* @override
*/
getRollData() {
// Starts off by populating the roll data with a shallow copy of `this.system`
const rollData = { ...this.system };
// Quit early if there's no parent actor
if (!this.actor) return rollData;
// If present, add the actor's roll data
rollData.actor = this.actor.getRollData();
return rollData;
}
/**
* Convert the actor document to a plain object.
*
* The built in `toObject()` method will ignore derived data when using Data Models.
* This additional method will instead use the spread operator to return a simplified
* version of the data.
*
* @returns {object} Plain object either via deepClone or the spread operator.
*/
toPlainObject() {
const result = { ...this };
// Simplify system data.
result.system = this.system.toPlainObject();
// Add effects.
result.effects = this.effects?.size > 0 ? this.effects.contents : [];
return result;
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
async roll() {
const item = this;
// Initialize chat data.
const speaker = ChatMessage.getSpeaker({ actor: this.actor });
const rollMode = game.settings.get('core', 'rollMode');
const label = `[${item.type}] ${item.name}`;
// If there's no roll data, send a chat message.
if (!this.system.formula) {
ChatMessage.create({
speaker: speaker,
rollMode: rollMode,
flavor: label,
content: item.system.description ?? '',
});
}
// Otherwise, create a roll and send a chat message from it.
else {
// Retrieve roll data.
const rollData = this.getRollData();
// Invoke the roll and submit it to chat.
const roll = new Roll(rollData.formula, rollData.actor);
// If you need to store the value first, uncomment the next line.
// const result = await roll.evaluate();
roll.toMessage({
speaker: speaker,
rollMode: rollMode,
flavor: label,
});
return roll;
}
}
}

@ -0,0 +1,23 @@
export const WANDERHOME = {};
/**
* The set of Ability Scores used within the system.
* @type {Object}
*/
WANDERHOME.abilities = {
str: 'WANDERHOME.Ability.Str.long',
dex: 'WANDERHOME.Ability.Dex.long',
con: 'WANDERHOME.Ability.Con.long',
int: 'WANDERHOME.Ability.Int.long',
wis: 'WANDERHOME.Ability.Wis.long',
cha: 'WANDERHOME.Ability.Cha.long',
};
WANDERHOME.abilityAbbreviations = {
str: 'WANDERHOME.Ability.Str.abbr',
dex: 'WANDERHOME.Ability.Dex.abbr',
con: 'WANDERHOME.Ability.Con.abbr',
int: 'WANDERHOME.Ability.Int.abbr',
wis: 'WANDERHOME.Ability.Wis.abbr',
cha: 'WANDERHOME.Ability.Cha.abbr',
};

@ -0,0 +1,68 @@
/**
* Manage Active Effect instances through an Actor or Item Sheet via effect control buttons.
* @param {MouseEvent} event The left-click event on the effect control
* @param {Actor|Item} owner The owning document which manages this effect
*/
export function onManageActiveEffect(event, owner) {
event.preventDefault();
const a = event.currentTarget;
const li = a.closest('li');
const effect = li.dataset.effectId
? owner.effects.get(li.dataset.effectId)
: null;
switch (a.dataset.action) {
case 'create':
return owner.createEmbeddedDocuments('ActiveEffect', [
{
name: game.i18n.format('DOCUMENT.New', {
type: game.i18n.localize('DOCUMENT.ActiveEffect'),
}),
icon: 'icons/svg/aura.svg',
origin: owner.uuid,
'duration.rounds':
li.dataset.effectType === 'temporary' ? 1 : undefined,
disabled: li.dataset.effectType === 'inactive',
},
]);
case 'edit':
return effect.sheet.render(true);
case 'delete':
return effect.delete();
case 'toggle':
return effect.update({ disabled: !effect.disabled });
}
}
/**
* Prepare the data structure for Active Effects which are currently embedded in an Actor or Item.
* @param {ActiveEffect[]} effects A collection or generator of Active Effect documents to prepare sheet data for
* @return {object} Data for rendering
*/
export function prepareActiveEffectCategories(effects) {
// Define effect header categories
const categories = {
temporary: {
type: 'temporary',
label: game.i18n.localize('WANDERHOME.Effect.Temporary'),
effects: [],
},
passive: {
type: 'passive',
label: game.i18n.localize('WANDERHOME.Effect.Passive'),
effects: [],
},
inactive: {
type: 'inactive',
label: game.i18n.localize('WANDERHOME.Effect.Inactive'),
effects: [],
},
};
// Iterate over active effects, classifying them into categories
for (let e of effects) {
if (e.disabled) categories.inactive.effects.push(e);
else if (e.isTemporary) categories.temporary.effects.push(e);
else categories.passive.effects.push(e);
}
return categories;
}

@ -0,0 +1,16 @@
/**
* Define a set of template paths to pre-load
* Pre-loaded templates are compiled and cached for fast access when rendering
* @return {Promise}
*/
export const preloadHandlebarsTemplates = async function () {
return loadTemplates([
// Actor partials.
'systems/wanderhome/templates/actor/parts/actor-features.hbs',
'systems/wanderhome/templates/actor/parts/actor-items.hbs',
'systems/wanderhome/templates/actor/parts/actor-spells.hbs',
'systems/wanderhome/templates/actor/parts/actor-effects.hbs',
// Item partials
'systems/wanderhome/templates/item/parts/item-effects.hbs',
]);
};

@ -0,0 +1,256 @@
import {
onManageActiveEffect,
prepareActiveEffectCategories,
} from '../helpers/effects.mjs';
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class WanderhomeActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['wanderhome', 'sheet', 'actor'],
width: 600,
height: 600,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'features',
},
],
});
}
/** @override */
get template() {
return `systems/wanderhome/templates/actor/actor-${this.actor.type}-sheet.hbs`;
}
/* -------------------------------------------- */
/** @override */
async getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const actorData = this.document.toPlainObject();
// Add the actor's data to context.data for easier access, as well as flags.
context.system = actorData.system;
context.flags = actorData.flags;
// Adding a pointer to CONFIG.WANDERHOME
context.config = CONFIG.WANDERHOME;
// Prepare character data and items.
if (actorData.type == 'character') {
this._prepareItems(context);
this._prepareCharacterData(context);
}
// Prepare NPC data and items.
if (actorData.type == 'npc') {
this._prepareItems(context);
}
// Enrich biography info for display
// Enrichment turns text like `[[/r 1d20]]` into buttons
context.enrichedBiography = await TextEditor.enrichHTML(
this.actor.system.biography,
{
// Whether to show secret blocks in the finished html
secrets: this.document.isOwner,
// Necessary in v11, can be removed in v12
async: true,
// Data to fill in for inline rolls
rollData: this.actor.getRollData(),
// Relative UUID resolution
relativeTo: this.actor,
}
);
// Prepare active effects
context.effects = prepareActiveEffectCategories(
// A generator that returns all effects stored on the actor
// as well as any items
this.actor.allApplicableEffects()
);
return context;
}
/**
* Character-specific context modifications
*
* @param {object} context The context object to mutate
*/
_prepareCharacterData(context) {
// This is where you can enrich character-specific editor fields
// or setup anything else that's specific to this type
}
/**
* Organize and classify Items for Actor sheets.
*
* @param {object} context The context object to mutate
*/
_prepareItems(context) {
// Initialize containers.
const gear = [];
const features = [];
const spells = {
0: [],
1: [],
2: [],
3: [],
4: [],
5: [],
6: [],
7: [],
8: [],
9: [],
};
// Iterate through items, allocating to containers
for (let i of context.items) {
i.img = i.img || Item.DEFAULT_ICON;
// Append to gear.
if (i.type === 'item') {
gear.push(i);
}
// Append to features.
else if (i.type === 'feature') {
features.push(i);
}
// Append to spells.
else if (i.type === 'spell') {
if (i.system.spellLevel != undefined) {
spells[i.system.spellLevel].push(i);
}
}
}
// Assign and return
context.gear = gear;
context.features = features;
context.spells = spells;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Render the item sheet for viewing/editing prior to the editable check.
html.on('click', '.item-edit', (ev) => {
const li = $(ev.currentTarget).parents('.item');
const item = this.actor.items.get(li.data('itemId'));
item.sheet.render(true);
});
// -------------------------------------------------------------
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
// Add Inventory Item
html.on('click', '.item-create', this._onItemCreate.bind(this));
// Delete Inventory Item
html.on('click', '.item-delete', (ev) => {
const li = $(ev.currentTarget).parents('.item');
const item = this.actor.items.get(li.data('itemId'));
item.delete();
li.slideUp(200, () => this.render(false));
});
// Active Effect management
html.on('click', '.effect-control', (ev) => {
const row = ev.currentTarget.closest('li');
const document =
row.dataset.parentId === this.actor.id
? this.actor
: this.actor.items.get(row.dataset.parentId);
onManageActiveEffect(ev, document);
});
// Rollable abilities.
html.on('click', '.rollable', this._onRoll.bind(this));
// Drag events for macros.
if (this.actor.isOwner) {
let handler = (ev) => this._onDragStart(ev);
html.find('li.item').each((i, li) => {
if (li.classList.contains('inventory-header')) return;
li.setAttribute('draggable', true);
li.addEventListener('dragstart', handler, false);
});
}
}
/**
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
* @param {Event} event The originating click event
* @private
*/
async _onItemCreate(event) {
event.preventDefault();
const header = event.currentTarget;
// Get the type of item to create.
const type = header.dataset.type;
// Grab any data associated with this control.
const data = duplicate(header.dataset);
// Initialize a default name.
const name = `New ${type.capitalize()}`;
// Prepare the item object.
const itemData = {
name: name,
type: type,
system: data,
};
// Remove the type from the dataset since it's in the itemData.type prop.
delete itemData.system['type'];
// Finally, create the item!
return await Item.create(itemData, { parent: this.actor });
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
_onRoll(event) {
event.preventDefault();
const element = event.currentTarget;
const dataset = element.dataset;
// Handle item rolls.
if (dataset.rollType) {
if (dataset.rollType == 'item') {
const itemId = element.closest('.item').dataset.itemId;
const item = this.actor.items.get(itemId);
if (item) return item.roll();
}
}
// Handle rolls that supply the formula directly.
if (dataset.roll) {
let label = dataset.label ? `[ability] ${dataset.label}` : '';
let roll = new Roll(dataset.roll, this.actor.getRollData());
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
});
return roll;
}
}
}

@ -0,0 +1,93 @@
import {
onManageActiveEffect,
prepareActiveEffectCategories,
} from '../helpers/effects.mjs';
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class WanderhomeItemSheet extends ItemSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['wanderhome', 'sheet', 'item'],
width: 520,
height: 480,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
],
});
}
/** @override */
get template() {
const path = 'systems/wanderhome/templates/item';
// Return a single sheet for all item types.
// return `${path}/item-sheet.hbs`;
// Alternatively, you could use the following return statement to do a
// unique item sheet by type, like `weapon-sheet.hbs`.
return `${path}/item-${this.item.type}-sheet.hbs`;
}
/* -------------------------------------------- */
/** @override */
async getData() {
// Retrieve base data structure.
const context = super.getData();
// Use a safe clone of the item data for further operations.
const itemData = this.document.toPlainObject();
// Enrich description info for display
// Enrichment turns text like `[[/r 1d20]]` into buttons
context.enrichedDescription = await TextEditor.enrichHTML(
this.item.system.description,
{
// Whether to show secret blocks in the finished html
secrets: this.document.isOwner,
// Necessary in v11, can be removed in v12
async: true,
// Data to fill in for inline rolls
rollData: this.item.getRollData(),
// Relative UUID resolution
relativeTo: this.item,
}
);
// Add the item's data to context.data for easier access, as well as flags.
context.system = itemData.system;
context.flags = itemData.flags;
// Adding a pointer to CONFIG.WANDERHOME
context.config = CONFIG.WANDERHOME;
// Prepare active effects for easier access
context.effects = prepareActiveEffectCategories(this.item.effects);
return context;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
// Roll handlers, click handlers, etc. would go here.
// Active Effect management
html.on('click', '.effect-control', (ev) =>
onManageActiveEffect(ev, this.item)
);
}
}

@ -0,0 +1,158 @@
// Import document classes.
import { WanderhomeActor } from './documents/actor.mjs';
import { WanderhomeItem } from './documents/item.mjs';
// Import sheet classes.
import { WanderhomeActorSheet } from './sheets/actor-sheet.mjs';
import { WanderhomeItemSheet } from './sheets/item-sheet.mjs';
// Import helper/utility classes and constants.
import { preloadHandlebarsTemplates } from './helpers/templates.mjs';
import { WANDERHOME } from './helpers/config.mjs';
// Import DataModel classes
import * as models from './data/_module.mjs';
/* -------------------------------------------- */
/* Init Hook */
/* -------------------------------------------- */
Hooks.once('init', function () {
// Add utility classes to the global game object so that they're more easily
// accessible in global contexts.
game.wanderhome = {
WanderhomeActor,
WanderhomeItem,
rollItemMacro,
};
// Add custom constants for configuration.
CONFIG.WANDERHOME = WANDERHOME;
/**
* Set an initiative formula for the system
* @type {String}
*/
CONFIG.Combat.initiative = {
formula: '1d20 + @abilities.dex.mod',
decimals: 2,
};
// Define custom Document and DataModel classes
CONFIG.Actor.documentClass = WanderhomeActor;
// Note that you don't need to declare a DataModel
// for the base actor/item classes - they are included
// with the Character/NPC as part of super.defineSchema()
CONFIG.Actor.dataModels = {
character: models.WanderhomeCharacter,
npc: models.WanderhomeNPC
}
CONFIG.Item.documentClass = WanderhomeItem;
CONFIG.Item.dataModels = {
item: models.WanderhomeItem,
feature: models.WanderhomeFeature,
spell: models.WanderhomeSpell
}
// Active Effects are never copied to the Actor,
// but will still apply to the Actor from within the Item
// if the transfer property on the Active Effect is true.
CONFIG.ActiveEffect.legacyTransferral = false;
// Register sheet application classes
Actors.unregisterSheet('core', ActorSheet);
Actors.registerSheet('wanderhome', WanderhomeActorSheet, {
makeDefault: true,
label: 'WANDERHOME.SheetLabels.Actor',
});
Items.unregisterSheet('core', ItemSheet);
Items.registerSheet('wanderhome', WanderhomeItemSheet, {
makeDefault: true,
label: 'WANDERHOME.SheetLabels.Item',
});
// Preload Handlebars templates.
return preloadHandlebarsTemplates();
});
/* -------------------------------------------- */
/* Handlebars Helpers */
/* -------------------------------------------- */
// If you need to add Handlebars helpers, here is a useful example:
Handlebars.registerHelper('toLowerCase', function (str) {
return str.toLowerCase();
});
/* -------------------------------------------- */
/* Ready Hook */
/* -------------------------------------------- */
Hooks.once('ready', function () {
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on('hotbarDrop', (bar, data, slot) => createItemMacro(data, slot));
});
/* -------------------------------------------- */
/* Hotbar Macros */
/* -------------------------------------------- */
/**
* Create a Macro from an Item drop.
* Get an existing item macro if one exists, otherwise create a new one.
* @param {Object} data The dropped data
* @param {number} slot The hotbar slot to use
* @returns {Promise}
*/
async function createItemMacro(data, slot) {
// First, determine if this is a valid owned item.
if (data.type !== 'Item') return;
if (!data.uuid.includes('Actor.') && !data.uuid.includes('Token.')) {
return ui.notifications.warn(
'You can only create macro buttons for owned Items'
);
}
// If it is, retrieve it based on the uuid.
const item = await Item.fromDropData(data);
// Create the macro command using the uuid.
const command = `game.wanderhome.rollItemMacro("${data.uuid}");`;
let macro = game.macros.find(
(m) => m.name === item.name && m.command === command
);
if (!macro) {
macro = await Macro.create({
name: item.name,
type: 'script',
img: item.img,
command: command,
flags: { 'wanderhome.itemMacro': true },
});
}
game.user.assignHotbarMacro(macro, slot);
return false;
}
/**
* Create a Macro from an Item drop.
* Get an existing item macro if one exists, otherwise create a new one.
* @param {string} itemUuid
*/
function rollItemMacro(itemUuid) {
// Reconstruct the drop data so that we can load the item.
const dropData = {
type: 'Item',
uuid: itemUuid,
};
// Load the item from the uuid.
Item.fromDropData(dropData).then((item) => {
// Determine if the item loaded and if it's an owned item.
if (!item || !item.parent) {
const itemName = item?.name ?? itemUuid;
return ui.notifications.warn(
`Could not find item ${itemName}. You may need to delete and recreate this macro.`
);
}
// Trigger the item roll
item.roll();
});
}

1
node_modules/.bin/detect-libc generated vendored

@ -0,0 +1 @@
../detect-libc/bin/detect-libc.js

1
node_modules/.bin/sass generated vendored

@ -0,0 +1 @@
../sass/sass.js

804
node_modules/.package-lock.json generated vendored

@ -0,0 +1,804 @@
{
"name": "wanderhome",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@inquirer/checkbox": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz",
"integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/figures": "^1.0.13",
"@inquirer/type": "^3.0.8",
"ansi-escapes": "^4.3.2",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/confirm": {
"version": "5.1.14",
"resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz",
"integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/type": "^3.0.8"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/core": {
"version": "10.1.15",
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz",
"integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==",
"dependencies": {
"@inquirer/figures": "^1.0.13",
"@inquirer/type": "^3.0.8",
"ansi-escapes": "^4.3.2",
"cli-width": "^4.1.0",
"mute-stream": "^2.0.0",
"signal-exit": "^4.1.0",
"wrap-ansi": "^6.2.0",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/editor": {
"version": "4.2.16",
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.16.tgz",
"integrity": "sha512-iSzLjT4C6YKp2DU0fr8T7a97FnRRxMO6CushJnW5ktxLNM2iNeuyUuUA5255eOLPORoGYCrVnuDOEBdGkHGkpw==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/external-editor": "^1.0.0",
"@inquirer/type": "^3.0.8"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/expand": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz",
"integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/type": "^3.0.8",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/external-editor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.0.tgz",
"integrity": "sha512-5v3YXc5ZMfL6OJqXPrX9csb4l7NlQA2doO1yynUjpUChT9hg4JcuBVP0RbsEJ/3SL/sxWEyFjT2W69ZhtoBWqg==",
"dependencies": {
"chardet": "^2.1.0",
"iconv-lite": "^0.6.3"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
}
},
"node_modules/@inquirer/figures": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz",
"integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==",
"engines": {
"node": ">=18"
}
},
"node_modules/@inquirer/input": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz",
"integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/type": "^3.0.8"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/number": {
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz",
"integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/type": "^3.0.8"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/password": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz",
"integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/type": "^3.0.8",
"ansi-escapes": "^4.3.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/prompts": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.1.tgz",
"integrity": "sha512-LpBPeIpyCF1H3C7SK/QxJQG4iV1/SRmJdymfcul8PuwtVhD0JI1CSwqmd83VgRgt1QEsDojQYFSXJSgo81PVMw==",
"dependencies": {
"@inquirer/checkbox": "^4.2.0",
"@inquirer/confirm": "^5.1.14",
"@inquirer/editor": "^4.2.16",
"@inquirer/expand": "^4.0.17",
"@inquirer/input": "^4.2.1",
"@inquirer/number": "^3.0.17",
"@inquirer/password": "^4.0.17",
"@inquirer/rawlist": "^4.1.5",
"@inquirer/search": "^3.1.0",
"@inquirer/select": "^4.3.1"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/rawlist": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz",
"integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/type": "^3.0.8",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/search": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.0.tgz",
"integrity": "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/figures": "^1.0.13",
"@inquirer/type": "^3.0.8",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/select": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz",
"integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/figures": "^1.0.13",
"@inquirer/type": "^3.0.8",
"ansi-escapes": "^4.3.2",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/type": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz",
"integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"dependencies": {
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
"micromatch": "^4.0.5",
"node-addon-api": "^7.0.0"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@types/node": {
"version": "24.2.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
"integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
"peer": true,
"dependencies": {
"undici-types": "~7.10.0"
}
},
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
"dependencies": {
"type-fest": "^0.21.3"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"optional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chardet": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz",
"integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA=="
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/cli-width": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
"integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
"engines": {
"node": ">= 12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"optional": true,
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"optional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/immutable": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
"dev": true
},
"node_modules/inquirer": {
"version": "12.9.1",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.1.tgz",
"integrity": "sha512-G7uXAb9OiLcd+9jmA/7KKrItvFF00kKk/jb6CtG+Tm2zSPWfzzhyJwDhVCy+mBmE32o2zJnB5JnknIIv2Ft+AA==",
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/prompts": "^7.8.1",
"@inquirer/type": "^3.0.8",
"ansi-escapes": "^4.3.2",
"mute-stream": "^2.0.0",
"run-async": "^4.0.5",
"rxjs": "^7.8.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"optional": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"optional": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"optional": true,
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/mute-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
"integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"optional": true
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"optional": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/run-async": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz",
"integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sass": {
"version": "1.90.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"optional": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/type-fest": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undici-types": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"peer": true
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yoctocolors-cjs": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz",
"integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}
}
}

@ -0,0 +1,22 @@
Copyright (c) 2025 Simon Boudrias
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,182 @@
# `@inquirer/checkbox`
Simple interactive command line prompt to display a list of checkboxes (multi select).
![Checkbox prompt](https://cdn.rawgit.com/SBoudrias/Inquirer.js/28ae8337ba51d93e359ef4f7ee24e79b69898962/assets/screenshots/checkbox.svg)
# Special Thanks
<div align="center" markdown="1">
[![Graphite](https://github.com/user-attachments/assets/53db40ca-2254-481a-a094-6597f8716e29)](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs)<br>
### [Graphite is the AI developer productivity platform helping teams on GitHub ship higher quality software, faster](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs)
</div>
# Installation
<table>
<tr>
<th>npm</th>
<th>yarn</th>
</tr>
<tr>
<td>
```sh
npm install @inquirer/prompts
```
</td>
<td>
```sh
yarn add @inquirer/prompts
```
</td>
</tr>
<tr>
<td colSpan="2" align="center">Or</td>
</tr>
<tr>
<td>
```sh
npm install @inquirer/checkbox
```
</td>
<td>
```sh
yarn add @inquirer/checkbox
```
</td>
</tr>
</table>
# Usage
```js
import { checkbox, Separator } from '@inquirer/prompts';
// Or
// import checkbox, { Separator } from '@inquirer/checkbox';
const answer = await checkbox({
message: 'Select a package manager',
choices: [
{ name: 'npm', value: 'npm' },
{ name: 'yarn', value: 'yarn' },
new Separator(),
{ name: 'pnpm', value: 'pnpm', disabled: true },
{
name: 'pnpm',
value: 'pnpm',
disabled: '(pnpm is not available)',
},
],
});
```
## Options
| Property | Type | Required | Description |
| --------- | --------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| message | `string` | yes | The question to ask |
| choices | `Choice[]` | yes | List of the available choices. |
| pageSize | `number` | no | By default, lists of choice longer than 7 will be paginated. Use this option to control how many choices will appear on the screen at once. |
| loop | `boolean` | no | Defaults to `true`. When set to `false`, the cursor will be constrained to the top and bottom of the choice list without looping. |
| required | `boolean` | no | When set to `true`, ensures at least one choice must be selected. |
| validate | `async (Choice[]) => boolean \| string` | no | On submit, validate the choices. When returning a string, it'll be used as the error message displayed to the user. Note: returning a rejected promise, we'll assume a code error happened and crash. |
| shortcuts | [See Shortcuts](#Shortcuts) | no | Customize shortcut keys for `all` and `invert`. |
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
`Separator` objects can be used in the `choices` array to render non-selectable lines in the choice list. By default it'll render a line, but you can provide the text as argument (`new Separator('-- Dependencies --')`). This option is often used to add labels to groups within long list of options.
### `Choice` object
The `Choice` object is typed as
```ts
type Choice<Value> = {
value: Value;
name?: string;
description?: string;
short?: string;
checked?: boolean;
disabled?: boolean | string;
};
```
Here's each property:
- `value`: The value is what will be returned by `await checkbox()`.
- `name`: This is the string displayed in the choice list.
- `description`: Option for a longer description string that'll appear under the list when the cursor highlight a given choice.
- `short`: Once the prompt is done (press enter), we'll use `short` if defined to render next to the question. By default we'll use `name`.
- `checked`: If `true`, the option will be checked by default.
- `disabled`: Disallow the option from being selected. If `disabled` is a string, it'll be used as a help tip explaining why the choice isn't available.
Also note the `choices` array can contain `Separator`s to help organize long lists.
`choices` can also be an array of string, in which case the string will be used both as the `value` and the `name`.
## Shortcuts
You can customize the shortcut keys for `all` and `invert` or disable them by setting them to `null`.
```ts
type Shortcuts = {
all?: string | null; // default: 'a'
invert?: string | null; // default: 'i'
};
```
## Theming
You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.
```ts
type Theme = {
prefix: string | { idle: string; done: string };
spinner: {
interval: number;
frames: string[];
};
style: {
answer: (text: string) => string;
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
error: (text: string) => string;
defaultAnswer: (text: string) => string;
help: (text: string) => string;
highlight: (text: string) => string;
key: (text: string) => string;
disabledChoice: (text: string) => string;
description: (text: string) => string;
renderSelectedChoices: <T>(
selectedChoices: ReadonlyArray<Choice<T>>,
allChoices: ReadonlyArray<Choice<T> | Separator>,
) => string;
};
icon: {
checked: string;
unchecked: string;
cursor: string;
};
helpMode: 'always' | 'never' | 'auto';
};
```
### `theme.helpMode`
- `auto` (default): Hide the help tips after an interaction occurs. The scroll tip will hide after any interactions, the selection tip will hide as soon as a first selection is done.
- `always`: The help tips will always show and never hide.
- `never`: The help tips will never show.
# License
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Licensed under the MIT license.

@ -0,0 +1,52 @@
import { Separator, type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type CheckboxTheme = {
icon: {
checked: string;
unchecked: string;
cursor: string;
};
style: {
disabledChoice: (text: string) => string;
renderSelectedChoices: <T>(selectedChoices: ReadonlyArray<NormalizedChoice<T>>, allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>) => string;
description: (text: string) => string;
};
helpMode: 'always' | 'never' | 'auto';
};
type CheckboxShortcuts = {
all?: string | null;
invert?: string | null;
};
type Choice<Value> = {
value: Value;
name?: string;
description?: string;
short?: string;
disabled?: boolean | string;
checked?: boolean;
type?: never;
};
type NormalizedChoice<Value> = {
value: Value;
name: string;
description?: string;
short: string;
disabled: boolean | string;
checked: boolean;
};
declare const _default: <Value>(config: {
message: string;
prefix?: string | undefined;
pageSize?: number | undefined;
instructions?: string | boolean | undefined;
choices: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[];
loop?: boolean | undefined;
required?: boolean | undefined;
validate?: ((choices: readonly Choice<Value>[]) => boolean | string | Promise<string | boolean>) | undefined;
theme?: PartialDeep<Theme<CheckboxTheme>> | undefined;
shortcuts?: CheckboxShortcuts | undefined;
}, context?: import("@inquirer/type").Context) => Promise<Value[]> & {
cancel: () => void;
};
export default _default;
export { Separator } from '@inquirer/core';

@ -0,0 +1,207 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Separator = void 0;
const core_1 = require("@inquirer/core");
const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs"));
const figures_1 = __importDefault(require("@inquirer/figures"));
const ansi_escapes_1 = __importDefault(require("ansi-escapes"));
const checkboxTheme = {
icon: {
checked: yoctocolors_cjs_1.default.green(figures_1.default.circleFilled),
unchecked: figures_1.default.circle,
cursor: figures_1.default.pointer,
},
style: {
disabledChoice: (text) => yoctocolors_cjs_1.default.dim(`- ${text}`),
renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(', '),
description: (text) => yoctocolors_cjs_1.default.cyan(text),
},
helpMode: 'auto',
};
function isSelectable(item) {
return !core_1.Separator.isSeparator(item) && !item.disabled;
}
function isChecked(item) {
return isSelectable(item) && Boolean(item.checked);
}
function toggle(item) {
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
}
function check(checked) {
return function (item) {
return isSelectable(item) ? { ...item, checked } : item;
};
}
function normalizeChoices(choices) {
return choices.map((choice) => {
if (core_1.Separator.isSeparator(choice))
return choice;
if (typeof choice === 'string') {
return {
value: choice,
name: choice,
short: choice,
disabled: false,
checked: false,
};
}
const name = choice.name ?? String(choice.value);
const normalizedChoice = {
value: choice.value,
name,
short: choice.short ?? name,
disabled: choice.disabled ?? false,
checked: choice.checked ?? false,
};
if (choice.description) {
normalizedChoice.description = choice.description;
}
return normalizedChoice;
});
}
exports.default = (0, core_1.createPrompt)((config, done) => {
const { instructions, pageSize = 7, loop = true, required, validate = () => true, } = config;
const shortcuts = { all: 'a', invert: 'i', ...config.shortcuts };
const theme = (0, core_1.makeTheme)(checkboxTheme, config.theme);
const firstRender = (0, core_1.useRef)(true);
const [status, setStatus] = (0, core_1.useState)('idle');
const prefix = (0, core_1.usePrefix)({ status, theme });
const [items, setItems] = (0, core_1.useState)(normalizeChoices(config.choices));
const bounds = (0, core_1.useMemo)(() => {
const first = items.findIndex(isSelectable);
const last = items.findLastIndex(isSelectable);
if (first === -1) {
throw new core_1.ValidationError('[checkbox prompt] No selectable choices. All choices are disabled.');
}
return { first, last };
}, [items]);
const [active, setActive] = (0, core_1.useState)(bounds.first);
const [showHelpTip, setShowHelpTip] = (0, core_1.useState)(true);
const [errorMsg, setError] = (0, core_1.useState)();
(0, core_1.useKeypress)(async (key) => {
if ((0, core_1.isEnterKey)(key)) {
const selection = items.filter(isChecked);
const isValid = await validate([...selection]);
if (required && !items.some(isChecked)) {
setError('At least one choice must be selected');
}
else if (isValid === true) {
setStatus('done');
done(selection.map((choice) => choice.value));
}
else {
setError(isValid || 'You must select a valid value');
}
}
else if ((0, core_1.isUpKey)(key) || (0, core_1.isDownKey)(key)) {
if (loop ||
((0, core_1.isUpKey)(key) && active !== bounds.first) ||
((0, core_1.isDownKey)(key) && active !== bounds.last)) {
const offset = (0, core_1.isUpKey)(key) ? -1 : 1;
let next = active;
do {
next = (next + offset + items.length) % items.length;
} while (!isSelectable(items[next]));
setActive(next);
}
}
else if ((0, core_1.isSpaceKey)(key)) {
setError(undefined);
setShowHelpTip(false);
setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice)));
}
else if (key.name === shortcuts.all) {
const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked);
setItems(items.map(check(selectAll)));
}
else if (key.name === shortcuts.invert) {
setItems(items.map(toggle));
}
else if ((0, core_1.isNumberKey)(key)) {
const selectedIndex = Number(key.name) - 1;
// Find the nth item (ignoring separators)
let selectableIndex = -1;
const position = items.findIndex((item) => {
if (core_1.Separator.isSeparator(item))
return false;
selectableIndex++;
return selectableIndex === selectedIndex;
});
const selectedItem = items[position];
if (selectedItem && isSelectable(selectedItem)) {
setActive(position);
setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice)));
}
}
});
const message = theme.style.message(config.message, status);
let description;
const page = (0, core_1.usePagination)({
items,
active,
renderItem({ item, isActive }) {
if (core_1.Separator.isSeparator(item)) {
return ` ${item.separator}`;
}
if (item.disabled) {
const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)';
return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
}
if (isActive) {
description = item.description;
}
const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
const color = isActive ? theme.style.highlight : (x) => x;
const cursor = isActive ? theme.icon.cursor : ' ';
return color(`${cursor}${checkbox} ${item.name}`);
},
pageSize,
loop,
});
if (status === 'done') {
const selection = items.filter(isChecked);
const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
return `${prefix} ${message} ${answer}`;
}
let helpTipTop = '';
let helpTipBottom = '';
if (theme.helpMode === 'always' ||
(theme.helpMode === 'auto' &&
showHelpTip &&
(instructions === undefined || instructions))) {
if (typeof instructions === 'string') {
helpTipTop = instructions;
}
else {
const keys = [
`${theme.style.key('space')} to select`,
shortcuts.all ? `${theme.style.key(shortcuts.all)} to toggle all` : '',
shortcuts.invert
? `${theme.style.key(shortcuts.invert)} to invert selection`
: '',
`and ${theme.style.key('enter')} to proceed`,
];
helpTipTop = ` (Press ${keys.filter((key) => key !== '').join(', ')})`;
}
if (items.length > pageSize &&
(theme.helpMode === 'always' ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(theme.helpMode === 'auto' && firstRender.current))) {
helpTipBottom = `\n${theme.style.help('(Use arrow keys to reveal more choices)')}`;
firstRender.current = false;
}
}
const choiceDescription = description
? `\n${theme.style.description(description)}`
: ``;
let error = '';
if (errorMsg) {
error = `\n${theme.style.error(errorMsg)}`;
}
return `${prefix} ${message}${helpTipTop}\n${page}${helpTipBottom}${choiceDescription}${error}${ansi_escapes_1.default.cursorHide}`;
});
var core_2 = require("@inquirer/core");
Object.defineProperty(exports, "Separator", { enumerable: true, get: function () { return core_2.Separator; } });

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

@ -0,0 +1,52 @@
import { Separator, type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type CheckboxTheme = {
icon: {
checked: string;
unchecked: string;
cursor: string;
};
style: {
disabledChoice: (text: string) => string;
renderSelectedChoices: <T>(selectedChoices: ReadonlyArray<NormalizedChoice<T>>, allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>) => string;
description: (text: string) => string;
};
helpMode: 'always' | 'never' | 'auto';
};
type CheckboxShortcuts = {
all?: string | null;
invert?: string | null;
};
type Choice<Value> = {
value: Value;
name?: string;
description?: string;
short?: string;
disabled?: boolean | string;
checked?: boolean;
type?: never;
};
type NormalizedChoice<Value> = {
value: Value;
name: string;
description?: string;
short: string;
disabled: boolean | string;
checked: boolean;
};
declare const _default: <Value>(config: {
message: string;
prefix?: string | undefined;
pageSize?: number | undefined;
instructions?: string | boolean | undefined;
choices: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[];
loop?: boolean | undefined;
required?: boolean | undefined;
validate?: ((choices: readonly Choice<Value>[]) => boolean | string | Promise<string | boolean>) | undefined;
theme?: PartialDeep<Theme<CheckboxTheme>> | undefined;
shortcuts?: CheckboxShortcuts | undefined;
}, context?: import("@inquirer/type").Context) => Promise<Value[]> & {
cancel: () => void;
};
export default _default;
export { Separator } from '@inquirer/core';

@ -0,0 +1,200 @@
import { createPrompt, useState, useKeypress, usePrefix, usePagination, useRef, useMemo, makeTheme, isUpKey, isDownKey, isSpaceKey, isNumberKey, isEnterKey, ValidationError, Separator, } from '@inquirer/core';
import colors from 'yoctocolors-cjs';
import figures from '@inquirer/figures';
import ansiEscapes from 'ansi-escapes';
const checkboxTheme = {
icon: {
checked: colors.green(figures.circleFilled),
unchecked: figures.circle,
cursor: figures.pointer,
},
style: {
disabledChoice: (text) => colors.dim(`- ${text}`),
renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(', '),
description: (text) => colors.cyan(text),
},
helpMode: 'auto',
};
function isSelectable(item) {
return !Separator.isSeparator(item) && !item.disabled;
}
function isChecked(item) {
return isSelectable(item) && Boolean(item.checked);
}
function toggle(item) {
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
}
function check(checked) {
return function (item) {
return isSelectable(item) ? { ...item, checked } : item;
};
}
function normalizeChoices(choices) {
return choices.map((choice) => {
if (Separator.isSeparator(choice))
return choice;
if (typeof choice === 'string') {
return {
value: choice,
name: choice,
short: choice,
disabled: false,
checked: false,
};
}
const name = choice.name ?? String(choice.value);
const normalizedChoice = {
value: choice.value,
name,
short: choice.short ?? name,
disabled: choice.disabled ?? false,
checked: choice.checked ?? false,
};
if (choice.description) {
normalizedChoice.description = choice.description;
}
return normalizedChoice;
});
}
export default createPrompt((config, done) => {
const { instructions, pageSize = 7, loop = true, required, validate = () => true, } = config;
const shortcuts = { all: 'a', invert: 'i', ...config.shortcuts };
const theme = makeTheme(checkboxTheme, config.theme);
const firstRender = useRef(true);
const [status, setStatus] = useState('idle');
const prefix = usePrefix({ status, theme });
const [items, setItems] = useState(normalizeChoices(config.choices));
const bounds = useMemo(() => {
const first = items.findIndex(isSelectable);
const last = items.findLastIndex(isSelectable);
if (first === -1) {
throw new ValidationError('[checkbox prompt] No selectable choices. All choices are disabled.');
}
return { first, last };
}, [items]);
const [active, setActive] = useState(bounds.first);
const [showHelpTip, setShowHelpTip] = useState(true);
const [errorMsg, setError] = useState();
useKeypress(async (key) => {
if (isEnterKey(key)) {
const selection = items.filter(isChecked);
const isValid = await validate([...selection]);
if (required && !items.some(isChecked)) {
setError('At least one choice must be selected');
}
else if (isValid === true) {
setStatus('done');
done(selection.map((choice) => choice.value));
}
else {
setError(isValid || 'You must select a valid value');
}
}
else if (isUpKey(key) || isDownKey(key)) {
if (loop ||
(isUpKey(key) && active !== bounds.first) ||
(isDownKey(key) && active !== bounds.last)) {
const offset = isUpKey(key) ? -1 : 1;
let next = active;
do {
next = (next + offset + items.length) % items.length;
} while (!isSelectable(items[next]));
setActive(next);
}
}
else if (isSpaceKey(key)) {
setError(undefined);
setShowHelpTip(false);
setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice)));
}
else if (key.name === shortcuts.all) {
const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked);
setItems(items.map(check(selectAll)));
}
else if (key.name === shortcuts.invert) {
setItems(items.map(toggle));
}
else if (isNumberKey(key)) {
const selectedIndex = Number(key.name) - 1;
// Find the nth item (ignoring separators)
let selectableIndex = -1;
const position = items.findIndex((item) => {
if (Separator.isSeparator(item))
return false;
selectableIndex++;
return selectableIndex === selectedIndex;
});
const selectedItem = items[position];
if (selectedItem && isSelectable(selectedItem)) {
setActive(position);
setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice)));
}
}
});
const message = theme.style.message(config.message, status);
let description;
const page = usePagination({
items,
active,
renderItem({ item, isActive }) {
if (Separator.isSeparator(item)) {
return ` ${item.separator}`;
}
if (item.disabled) {
const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)';
return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
}
if (isActive) {
description = item.description;
}
const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
const color = isActive ? theme.style.highlight : (x) => x;
const cursor = isActive ? theme.icon.cursor : ' ';
return color(`${cursor}${checkbox} ${item.name}`);
},
pageSize,
loop,
});
if (status === 'done') {
const selection = items.filter(isChecked);
const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
return `${prefix} ${message} ${answer}`;
}
let helpTipTop = '';
let helpTipBottom = '';
if (theme.helpMode === 'always' ||
(theme.helpMode === 'auto' &&
showHelpTip &&
(instructions === undefined || instructions))) {
if (typeof instructions === 'string') {
helpTipTop = instructions;
}
else {
const keys = [
`${theme.style.key('space')} to select`,
shortcuts.all ? `${theme.style.key(shortcuts.all)} to toggle all` : '',
shortcuts.invert
? `${theme.style.key(shortcuts.invert)} to invert selection`
: '',
`and ${theme.style.key('enter')} to proceed`,
];
helpTipTop = ` (Press ${keys.filter((key) => key !== '').join(', ')})`;
}
if (items.length > pageSize &&
(theme.helpMode === 'always' ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(theme.helpMode === 'auto' && firstRender.current))) {
helpTipBottom = `\n${theme.style.help('(Use arrow keys to reveal more choices)')}`;
firstRender.current = false;
}
}
const choiceDescription = description
? `\n${theme.style.description(description)}`
: ``;
let error = '';
if (errorMsg) {
error = `\n${theme.style.error(errorMsg)}`;
}
return `${prefix} ${message}${helpTipTop}\n${page}${helpTipBottom}${choiceDescription}${error}${ansiEscapes.cursorHide}`;
});
export { Separator } from '@inquirer/core';

@ -0,0 +1,3 @@
{
"type": "module"
}

@ -0,0 +1,112 @@
{
"name": "@inquirer/checkbox",
"version": "4.2.0",
"description": "Inquirer checkbox prompt",
"keywords": [
"answer",
"answers",
"ask",
"base",
"cli",
"command",
"command-line",
"confirm",
"enquirer",
"generate",
"generator",
"hyper",
"input",
"inquire",
"inquirer",
"interface",
"iterm",
"javascript",
"menu",
"node",
"nodejs",
"prompt",
"promptly",
"prompts",
"question",
"readline",
"scaffold",
"scaffolder",
"scaffolding",
"stdin",
"stdout",
"terminal",
"tty",
"ui",
"yeoman",
"yo",
"zsh"
],
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/README.md",
"repository": {
"type": "git",
"url": "https://github.com/SBoudrias/Inquirer.js.git"
},
"license": "MIT",
"author": "Simon Boudrias <admin@simonboudrias.com>",
"sideEffects": false,
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
},
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/commonjs/index.d.ts",
"files": [
"dist"
],
"scripts": {
"attw": "attw --pack",
"tsc": "tshy"
},
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/figures": "^1.0.13",
"@inquirer/type": "^3.0.8",
"ansi-escapes": "^4.3.2",
"yoctocolors-cjs": "^2.1.2"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@inquirer/testing": "^2.1.49",
"tshy": "^3.0.2"
},
"engines": {
"node": ">=18"
},
"publishConfig": {
"access": "public"
},
"tshy": {
"exclude": [
"src/**/*.test.ts"
],
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
},
"gitHead": "c1a755fe8b50377b685ea5951e0794985ce8d356"
}

@ -0,0 +1,22 @@
Copyright (c) 2025 Simon Boudrias
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,102 @@
# `@inquirer/confirm`
Simple interactive command line prompt to gather boolean input from users.
![Confirm prompt](https://cdn.rawgit.com/SBoudrias/Inquirer.js/28ae8337ba51d93e359ef4f7ee24e79b69898962/assets/screenshots/confirm.svg)
# Special Thanks
<div align="center" markdown="1">
[![Graphite](https://github.com/user-attachments/assets/53db40ca-2254-481a-a094-6597f8716e29)](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs)<br>
### [Graphite is the AI developer productivity platform helping teams on GitHub ship higher quality software, faster](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs)
</div>
# Installation
<table>
<tr>
<th>npm</th>
<th>yarn</th>
</tr>
<tr>
<td>
```sh
npm install @inquirer/prompts
```
</td>
<td>
```sh
yarn add @inquirer/prompts
```
</td>
</tr>
<tr>
<td colSpan="2" align="center">Or</td>
</tr>
<tr>
<td>
```sh
npm install @inquirer/confirm
```
</td>
<td>
```sh
yarn add @inquirer/confirm
```
</td>
</tr>
</table>
# Usage
```js
import { confirm } from '@inquirer/prompts';
// Or
// import confirm from '@inquirer/confirm';
const answer = await confirm({ message: 'Continue?' });
```
## Options
| Property | Type | Required | Description |
| ----------- | ----------------------- | -------- | ------------------------------------------------------- |
| message | `string` | yes | The question to ask |
| default | `boolean` | no | Default answer (true or false) |
| transformer | `(boolean) => string` | no | Transform the prompt printed message to a custom string |
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
## Theming
You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.
```ts
type Theme = {
prefix: string | { idle: string; done: string };
spinner: {
interval: number;
frames: string[];
};
style: {
answer: (text: string) => string;
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
defaultAnswer: (text: string) => string;
};
};
```
# License
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Licensed under the MIT license.

@ -0,0 +1,10 @@
import { type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type ConfirmConfig = {
message: string;
default?: boolean;
transformer?: (value: boolean) => string;
theme?: PartialDeep<Theme>;
};
declare const _default: import("@inquirer/type").Prompt<boolean, ConfirmConfig>;
export default _default;

@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@inquirer/core");
function getBooleanValue(value, defaultValue) {
let answer = defaultValue !== false;
if (/^(y|yes)/i.test(value))
answer = true;
else if (/^(n|no)/i.test(value))
answer = false;
return answer;
}
function boolToString(value) {
return value ? 'Yes' : 'No';
}
exports.default = (0, core_1.createPrompt)((config, done) => {
const { transformer = boolToString } = config;
const [status, setStatus] = (0, core_1.useState)('idle');
const [value, setValue] = (0, core_1.useState)('');
const theme = (0, core_1.makeTheme)(config.theme);
const prefix = (0, core_1.usePrefix)({ status, theme });
(0, core_1.useKeypress)((key, rl) => {
if ((0, core_1.isEnterKey)(key)) {
const answer = getBooleanValue(value, config.default);
setValue(transformer(answer));
setStatus('done');
done(answer);
}
else if (key.name === 'tab') {
const answer = boolToString(!getBooleanValue(value, config.default));
rl.clearLine(0); // Remove the tab character.
rl.write(answer);
setValue(answer);
}
else {
setValue(rl.line);
}
});
let formattedValue = value;
let defaultValue = '';
if (status === 'done') {
formattedValue = theme.style.answer(value);
}
else {
defaultValue = ` ${theme.style.defaultAnswer(config.default === false ? 'y/N' : 'Y/n')}`;
}
const message = theme.style.message(config.message, status);
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
});

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

@ -0,0 +1,10 @@
import { type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type ConfirmConfig = {
message: string;
default?: boolean;
transformer?: (value: boolean) => string;
theme?: PartialDeep<Theme>;
};
declare const _default: import("@inquirer/type").Prompt<boolean, ConfirmConfig>;
export default _default;

@ -0,0 +1,46 @@
import { createPrompt, useState, useKeypress, isEnterKey, usePrefix, makeTheme, } from '@inquirer/core';
function getBooleanValue(value, defaultValue) {
let answer = defaultValue !== false;
if (/^(y|yes)/i.test(value))
answer = true;
else if (/^(n|no)/i.test(value))
answer = false;
return answer;
}
function boolToString(value) {
return value ? 'Yes' : 'No';
}
export default createPrompt((config, done) => {
const { transformer = boolToString } = config;
const [status, setStatus] = useState('idle');
const [value, setValue] = useState('');
const theme = makeTheme(config.theme);
const prefix = usePrefix({ status, theme });
useKeypress((key, rl) => {
if (isEnterKey(key)) {
const answer = getBooleanValue(value, config.default);
setValue(transformer(answer));
setStatus('done');
done(answer);
}
else if (key.name === 'tab') {
const answer = boolToString(!getBooleanValue(value, config.default));
rl.clearLine(0); // Remove the tab character.
rl.write(answer);
setValue(answer);
}
else {
setValue(rl.line);
}
});
let formattedValue = value;
let defaultValue = '';
if (status === 'done') {
formattedValue = theme.style.answer(value);
}
else {
defaultValue = ` ${theme.style.defaultAnswer(config.default === false ? 'y/N' : 'Y/n')}`;
}
const message = theme.style.message(config.message, status);
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
});

@ -0,0 +1,3 @@
{
"type": "module"
}

@ -0,0 +1,109 @@
{
"name": "@inquirer/confirm",
"version": "5.1.14",
"description": "Inquirer confirm prompt",
"keywords": [
"answer",
"answers",
"ask",
"base",
"cli",
"command",
"command-line",
"confirm",
"enquirer",
"generate",
"generator",
"hyper",
"input",
"inquire",
"inquirer",
"interface",
"iterm",
"javascript",
"menu",
"node",
"nodejs",
"prompt",
"promptly",
"prompts",
"question",
"readline",
"scaffold",
"scaffolder",
"scaffolding",
"stdin",
"stdout",
"terminal",
"tty",
"ui",
"yeoman",
"yo",
"zsh"
],
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/confirm/README.md",
"repository": {
"type": "git",
"url": "https://github.com/SBoudrias/Inquirer.js.git"
},
"license": "MIT",
"author": "Simon Boudrias <admin@simonboudrias.com>",
"sideEffects": false,
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
},
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/commonjs/index.d.ts",
"files": [
"dist"
],
"scripts": {
"attw": "attw --pack",
"tsc": "tshy"
},
"dependencies": {
"@inquirer/core": "^10.1.15",
"@inquirer/type": "^3.0.8"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@inquirer/testing": "^2.1.49",
"tshy": "^3.0.2"
},
"engines": {
"node": ">=18"
},
"publishConfig": {
"access": "public"
},
"tshy": {
"exclude": [
"src/**/*.test.ts"
],
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
},
"gitHead": "c1a755fe8b50377b685ea5951e0794985ce8d356"
}

@ -0,0 +1,22 @@
Copyright (c) 2025 Simon Boudrias
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,393 @@
# `@inquirer/core`
The `@inquirer/core` package is the library enabling the creation of Inquirer prompts.
It aims to implements a lightweight API similar to React hooks - but without JSX.
# Special Thanks
<div align="center" markdown="1">
[![Graphite](https://github.com/user-attachments/assets/53db40ca-2254-481a-a094-6597f8716e29)](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs)<br>
### [Graphite is the AI developer productivity platform helping teams on GitHub ship higher quality software, faster](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs)
</div>
# Installation
<table>
<tr>
<th>npm</th>
<th>yarn</th>
</tr>
<tr>
<td>
```sh
npm install @inquirer/core
```
</td>
<td>
```sh
yarn add @inquirer/core
```
</td>
</tr>
</table>
# Usage
## Basic concept
Visual terminal apps are at their core strings rendered onto the terminal.
The most basic prompt is a function returning a string that'll be rendered in the terminal. This function will run every time the prompt state change, and the new returned string will replace the previously rendered one. The prompt cursor appears after the string.
Wrapping the rendering function with `createPrompt()` will setup the rendering layer, inject the state management utilities, and wait until the `done` callback is called.
```ts
import { createPrompt } from '@inquirer/core';
const input = createPrompt((config, done) => {
// Implement logic
return '? My question';
});
// And it is then called as
const answer = await input({
/* config */
});
```
## Hooks
State management and user interactions are handled through hooks. Hooks are common [within the React ecosystem](https://react.dev/reference/react/hooks), and Inquirer reimplement the common ones.
### State hook
State lets a component “remember” information like user input. For example, an input prompt can use state to store the input value, while a list prompt can use state to track the cursor index.
`useState` declares a state variable that you can update directly.
```ts
import { createPrompt, useState } from '@inquirer/core';
const input = createPrompt((config, done) => {
const [index, setIndex] = useState(0);
// ...
```
### Keypress hook
Almost all prompts need to react to user actions. In a terminal, this is done through typing.
`useKeypress` allows you to react to keypress events, and access the prompt line.
```ts
const input = createPrompt((config, done) => {
useKeypress((key) => {
if (key.name === 'enter') {
done(answer);
}
});
// ...
```
Behind the scenes, Inquirer prompts are wrappers around [readlines](https://nodejs.org/api/readline.html). Aside the keypress event object, the hook also pass the active readline instance to the event handler.
```ts
const input = createPrompt((config, done) => {
useKeypress((key, readline) => {
setValue(readline.line);
});
// ...
```
### Ref hook
Refs let a prompt hold some information that isnt used for rendering, like a class instance or a timeout ID. Unlike with state, updating a ref does not re-render your prompt. Refs are an “escape hatch” from the rendering paradigm.
`useRef` declares a ref. You can hold any value in it, but most often its used to hold a timeout ID.
```ts
const input = createPrompt((config, done) => {
const timeout = useRef(null);
// ...
```
### Effect Hook
Effects let a prompt connect to and synchronize with external systems. This includes dealing with network or animations.
`useEffect` connects a component to an external system.
```ts
const chat = createPrompt((config, done) => {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
// ...
```
### Performance hook
A common way to optimize re-rendering performance is to skip unnecessary work. For example, you can tell Inquirer to reuse a cached calculation or to skip a re-render if the data has not changed since the previous render.
`useMemo` lets you cache the result of an expensive calculation.
```ts
const todoSelect = createPrompt((config, done) => {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
```
### Rendering hooks
#### Prefix / loading
All default prompts, and most custom ones, uses a prefix at the beginning of the prompt line. This helps visually delineate different questions, and provides a convenient area to render a loading spinner.
`usePrefix` is a built-in hook to do this.
```ts
const input = createPrompt((config, done) => {
const prefix = usePrefix({ status });
return `${prefix} My question`;
});
```
#### Pagination
When looping through a long list of options (like in the `select` prompt), paginating the results appearing on the screen at once can be necessary. The `usePagination` hook is the utility used within the `select` and `checkbox` prompts to cycle through the list of options.
Pagination works by taking in the list of options and returning a subset of the rendered items that fit within the page. The hook takes in a few options. It needs a list of options (`items`), and a `pageSize` which is the number of lines to be rendered. The `active` index is the index of the currently selected/selectable item. The `loop` option is a boolean that indicates if the list should loop around when reaching the end: this is the default behavior. The pagination hook renders items only as necessary, so it takes a function that can render an item at an index, including an `active` state, called `renderItem`.
```js
export default createPrompt((config, done) => {
const [active, setActive] = useState(0);
const allChoices = config.choices.map((choice) => choice.name);
const page = usePagination({
items: allChoices,
active: active,
renderItem: ({ item, index, isActive }) => `${isActive ? ">" : " "}${index}. ${item.toString()}`
pageSize: config.pageSize,
loop: config.loop,
});
return `... ${page}`;
});
```
## `createPrompt()` API
As we saw earlier, the rendering function should return a string, and eventually call `done` to close the prompt and return the answer.
```ts
const input = createPrompt((config, done) => {
const [value, setValue] = useState();
useKeypress((key, readline) => {
if (key.name === 'enter') {
done(answer);
} else {
setValue(readline.line);
}
});
return `? ${config.message} ${value}`;
});
```
The rendering function can also return a tuple of 2 string (`[string, string]`.) The first string represents the prompt. The second one is content to render under the prompt, like an error message. The text input cursor will appear after the first string.
```ts
const number = createPrompt((config, done) => {
// Add some logic here
return [`? My question ${input}`, `! The input must be a number`];
});
```
### Typescript
If using typescript, `createPrompt` takes 2 generic arguments.
```ts
// createPrompt<Value, Config>
const input = createPrompt<string, { message: string }>(// ...
```
The first one is the type of the resolved value
```ts
const answer: string = await input();
```
The second one is the type of the prompt config; in other words the interface the created prompt will provide to users.
```ts
const answer = await input({
message: 'My question',
});
```
## Key utilities
Listening for keypress events inside an inquirer prompt is a very common pattern. To ease this, we export a few utility functions taking in the keypress event object and return a boolean:
- `isEnterKey()`
- `isBackspaceKey()`
- `isSpaceKey()`
- `isUpKey()` - Note: this utility will handle vim and emacs keybindings (up, `k`, and `ctrl+p`)
- `isDownKey()` - Note: this utility will handle vim and emacs keybindings (down, `j`, and `ctrl+n`)
- `isNumberKey()` one of 1, 2, 3, 4, 5, 6, 7, 8, 9, 0
## Theming
Theming utilities will allow you to expose customization of the prompt style. Inquirer also has a few standard theme values shared across all the official prompts.
To allow standard customization:
```ts
import { createPrompt, usePrefix, makeTheme, type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type PromptConfig = {
theme?: PartialDeep<Theme>;
};
export default createPrompt<string, PromptConfig>((config, done) => {
const theme = makeTheme(config.theme);
const prefix = usePrefix({ status, theme });
return `${prefix} ${theme.style.highlight('hello')}`;
});
```
To setup a custom theme:
```ts
import { createPrompt, makeTheme, type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type PromptTheme = {};
const promptTheme: PromptTheme = {
icon: '!',
};
type PromptConfig = {
theme?: PartialDeep<Theme<PromptTheme>>;
};
export default createPrompt<string, PromptConfig>((config, done) => {
const theme = makeTheme(promptTheme, config.theme);
const prefix = usePrefix({ status, theme });
return `${prefix} ${theme.icon}`;
});
```
The [default theme keys cover](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/core/src/lib/theme.ts):
```ts
type DefaultTheme = {
prefix: string | { idle: string; done: string };
spinner: {
interval: number;
frames: string[];
};
style: {
answer: (text: string) => string;
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
error: (text: string) => string;
defaultAnswer: (text: string) => string;
help: (text: string) => string;
highlight: (text: string) => string;
key: (text: string) => string;
};
};
```
# Examples
You can refer to any `@inquirer/prompts` prompts for real examples:
- [Confirm Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/confirm/src/index.ts)
- [Input Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/input/src/index.ts)
- [Password Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/password/src/index.ts)
- [Editor Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/editor/src/index.ts)
- [Select Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/select/src/index.ts)
- [Checkbox Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/src/index.ts)
- [Rawlist Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/rawlist/src/index.ts)
- [Expand Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/expand/src/index.ts)
```ts
import colors from 'yoctocolors';
import {
createPrompt,
useState,
useKeypress,
isEnterKey,
usePrefix,
type Status,
} from '@inquirer/core';
const confirm = createPrompt<boolean, { message: string; default?: boolean }>(
(config, done) => {
const [status, setStatus] = useState<Status>('idle');
const [value, setValue] = useState('');
const prefix = usePrefix({});
useKeypress((key, rl) => {
if (isEnterKey(key)) {
const answer = value ? /^y(es)?/i.test(value) : config.default !== false;
setValue(answer ? 'yes' : 'no');
setStatus('done');
done(answer);
} else {
setValue(rl.line);
}
});
let formattedValue = value;
let defaultValue = '';
if (status === 'done') {
formattedValue = colors.cyan(value);
} else {
defaultValue = colors.dim(config.default === false ? ' (y/N)' : ' (Y/n)');
}
const message = colors.bold(config.message);
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
},
);
/**
* Which then can be used like this:
*/
const answer = await confirm({ message: 'Do you want to continue?' });
```
# License
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Licensed under the MIT license.

@ -0,0 +1,13 @@
export * from './lib/key.ts';
export * from './lib/errors.ts';
export { usePrefix } from './lib/use-prefix.ts';
export { useState } from './lib/use-state.ts';
export { useEffect } from './lib/use-effect.ts';
export { useMemo } from './lib/use-memo.ts';
export { useRef } from './lib/use-ref.ts';
export { useKeypress } from './lib/use-keypress.ts';
export { makeTheme } from './lib/make-theme.ts';
export type { Theme, Status } from './lib/theme.ts';
export { usePagination } from './lib/pagination/use-pagination.ts';
export { createPrompt } from './lib/create-prompt.ts';
export { Separator } from './lib/Separator.ts';

@ -0,0 +1,39 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Separator = exports.createPrompt = exports.usePagination = exports.makeTheme = exports.useKeypress = exports.useRef = exports.useMemo = exports.useEffect = exports.useState = exports.usePrefix = void 0;
__exportStar(require("./lib/key.js"), exports);
__exportStar(require("./lib/errors.js"), exports);
var use_prefix_ts_1 = require("./lib/use-prefix.js");
Object.defineProperty(exports, "usePrefix", { enumerable: true, get: function () { return use_prefix_ts_1.usePrefix; } });
var use_state_ts_1 = require("./lib/use-state.js");
Object.defineProperty(exports, "useState", { enumerable: true, get: function () { return use_state_ts_1.useState; } });
var use_effect_ts_1 = require("./lib/use-effect.js");
Object.defineProperty(exports, "useEffect", { enumerable: true, get: function () { return use_effect_ts_1.useEffect; } });
var use_memo_ts_1 = require("./lib/use-memo.js");
Object.defineProperty(exports, "useMemo", { enumerable: true, get: function () { return use_memo_ts_1.useMemo; } });
var use_ref_ts_1 = require("./lib/use-ref.js");
Object.defineProperty(exports, "useRef", { enumerable: true, get: function () { return use_ref_ts_1.useRef; } });
var use_keypress_ts_1 = require("./lib/use-keypress.js");
Object.defineProperty(exports, "useKeypress", { enumerable: true, get: function () { return use_keypress_ts_1.useKeypress; } });
var make_theme_ts_1 = require("./lib/make-theme.js");
Object.defineProperty(exports, "makeTheme", { enumerable: true, get: function () { return make_theme_ts_1.makeTheme; } });
var use_pagination_ts_1 = require("./lib/pagination/use-pagination.js");
Object.defineProperty(exports, "usePagination", { enumerable: true, get: function () { return use_pagination_ts_1.usePagination; } });
var create_prompt_ts_1 = require("./lib/create-prompt.js");
Object.defineProperty(exports, "createPrompt", { enumerable: true, get: function () { return create_prompt_ts_1.createPrompt; } });
var Separator_ts_1 = require("./lib/Separator.js");
Object.defineProperty(exports, "Separator", { enumerable: true, get: function () { return Separator_ts_1.Separator; } });

@ -0,0 +1,10 @@
/**
* Separator object
* Used to space/separate choices group
*/
export declare class Separator {
readonly separator: string;
readonly type: string;
constructor(separator?: string);
static isSeparator(choice: unknown): choice is Separator;
}

@ -0,0 +1,28 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Separator = void 0;
const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs"));
const figures_1 = __importDefault(require("@inquirer/figures"));
/**
* Separator object
* Used to space/separate choices group
*/
class Separator {
separator = yoctocolors_cjs_1.default.dim(Array.from({ length: 15 }).join(figures_1.default.line));
type = 'separator';
constructor(separator) {
if (separator) {
this.separator = separator;
}
}
static isSeparator(choice) {
return Boolean(choice &&
typeof choice === 'object' &&
'type' in choice &&
choice.type === 'separator');
}
}
exports.Separator = Separator;

@ -0,0 +1,4 @@
import { type Prompt, type Prettify } from '@inquirer/type';
type ViewFunction<Value, Config> = (config: Prettify<Config>, done: (value: Value) => void) => string | [string, string | undefined];
export declare function createPrompt<Value, Config>(view: ViewFunction<Value, Config>): Prompt<Value, Config>;
export {};

@ -0,0 +1,156 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPrompt = createPrompt;
const readline = __importStar(require("node:readline"));
const node_async_hooks_1 = require("node:async_hooks");
const mute_stream_1 = __importDefault(require("mute-stream"));
const signal_exit_1 = require("signal-exit");
const screen_manager_ts_1 = __importDefault(require("./screen-manager.js"));
const promise_polyfill_ts_1 = require("./promise-polyfill.js");
const hook_engine_ts_1 = require("./hook-engine.js");
const errors_ts_1 = require("./errors.js");
function getCallSites() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const _prepareStackTrace = Error.prepareStackTrace;
let result = [];
try {
Error.prepareStackTrace = (_, callSites) => {
const callSitesWithoutCurrent = callSites.slice(1);
result = callSitesWithoutCurrent;
return callSitesWithoutCurrent;
};
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
new Error().stack;
}
catch {
// An error will occur if the Node flag --frozen-intrinsics is used.
// https://nodejs.org/api/cli.html#--frozen-intrinsics
return result;
}
Error.prepareStackTrace = _prepareStackTrace;
return result;
}
function createPrompt(view) {
const callSites = getCallSites();
const prompt = (config, context = {}) => {
// Default `input` to stdin
const { input = process.stdin, signal } = context;
const cleanups = new Set();
// Add mute capabilities to the output
const output = new mute_stream_1.default();
output.pipe(context.output ?? process.stdout);
const rl = readline.createInterface({
terminal: true,
input,
output,
});
const screen = new screen_manager_ts_1.default(rl);
const { promise, resolve, reject } = promise_polyfill_ts_1.PromisePolyfill.withResolver();
const cancel = () => reject(new errors_ts_1.CancelPromptError());
if (signal) {
const abort = () => reject(new errors_ts_1.AbortPromptError({ cause: signal.reason }));
if (signal.aborted) {
abort();
return Object.assign(promise, { cancel });
}
signal.addEventListener('abort', abort);
cleanups.add(() => signal.removeEventListener('abort', abort));
}
cleanups.add((0, signal_exit_1.onExit)((code, signal) => {
reject(new errors_ts_1.ExitPromptError(`User force closed the prompt with ${code} ${signal}`));
}));
// SIGINT must be explicitly handled by the prompt so the ExitPromptError can be handled.
// Otherwise, the prompt will stop and in some scenarios never resolve.
// Ref issue #1741
const sigint = () => reject(new errors_ts_1.ExitPromptError(`User force closed the prompt with SIGINT`));
rl.on('SIGINT', sigint);
cleanups.add(() => rl.removeListener('SIGINT', sigint));
// Re-renders only happen when the state change; but the readline cursor could change position
// and that also requires a re-render (and a manual one because we mute the streams).
// We set the listener after the initial workLoop to avoid a double render if render triggered
// by a state change sets the cursor to the right position.
const checkCursorPos = () => screen.checkCursorPos();
rl.input.on('keypress', checkCursorPos);
cleanups.add(() => rl.input.removeListener('keypress', checkCursorPos));
return (0, hook_engine_ts_1.withHooks)(rl, (cycle) => {
// The close event triggers immediately when the user press ctrl+c. SignalExit on the other hand
// triggers after the process is done (which happens after timeouts are done triggering.)
// We triggers the hooks cleanup phase on rl `close` so active timeouts can be cleared.
const hooksCleanup = node_async_hooks_1.AsyncResource.bind(() => hook_engine_ts_1.effectScheduler.clearAll());
rl.on('close', hooksCleanup);
cleanups.add(() => rl.removeListener('close', hooksCleanup));
cycle(() => {
try {
const nextView = view(config, (value) => {
setImmediate(() => resolve(value));
});
// Typescript won't allow this, but not all users rely on typescript.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (nextView === undefined) {
const callerFilename = callSites[1]?.getFileName();
throw new Error(`Prompt functions must return a string.\n at ${callerFilename}`);
}
const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView;
screen.render(content, bottomContent);
hook_engine_ts_1.effectScheduler.run();
}
catch (error) {
reject(error);
}
});
return Object.assign(promise
.then((answer) => {
hook_engine_ts_1.effectScheduler.clearAll();
return answer;
}, (error) => {
hook_engine_ts_1.effectScheduler.clearAll();
throw error;
})
// Wait for the promise to settle, then cleanup.
.finally(() => {
cleanups.forEach((cleanup) => cleanup());
screen.done({ clearContent: Boolean(context.clearPromptOnDone) });
output.end();
})
// Once cleanup is done, let the expose promise resolve/reject to the internal one.
.then(() => promise), { cancel });
});
};
return prompt;
}

@ -0,0 +1,20 @@
export declare class AbortPromptError extends Error {
name: string;
message: string;
constructor(options?: {
cause?: unknown;
});
}
export declare class CancelPromptError extends Error {
name: string;
message: string;
}
export declare class ExitPromptError extends Error {
name: string;
}
export declare class HookError extends Error {
name: string;
}
export declare class ValidationError extends Error {
name: string;
}

@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationError = exports.HookError = exports.ExitPromptError = exports.CancelPromptError = exports.AbortPromptError = void 0;
class AbortPromptError extends Error {
name = 'AbortPromptError';
message = 'Prompt was aborted';
constructor(options) {
super();
this.cause = options?.cause;
}
}
exports.AbortPromptError = AbortPromptError;
class CancelPromptError extends Error {
name = 'CancelPromptError';
message = 'Prompt was canceled';
}
exports.CancelPromptError = CancelPromptError;
class ExitPromptError extends Error {
name = 'ExitPromptError';
}
exports.ExitPromptError = ExitPromptError;
class HookError extends Error {
name = 'HookError';
}
exports.HookError = HookError;
class ValidationError extends Error {
name = 'ValidationError';
}
exports.ValidationError = ValidationError;

@ -0,0 +1,23 @@
import type { InquirerReadline } from '@inquirer/type';
export declare function withHooks<T>(rl: InquirerReadline, cb: (cycle: (render: () => void) => void) => T): T;
export declare function readline(): InquirerReadline;
export declare function withUpdates<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R;
type SetPointer<Value> = {
get(): Value;
set(value: Value): void;
initialized: true;
};
type UnsetPointer<Value> = {
get(): void;
set(value: Value): void;
initialized: false;
};
type Pointer<Value> = SetPointer<Value> | UnsetPointer<Value>;
export declare function withPointer<Value, ReturnValue>(cb: (pointer: Pointer<Value>) => ReturnValue): ReturnValue;
export declare function handleChange(): void;
export declare const effectScheduler: {
queue(cb: (readline: InquirerReadline) => void | (() => void)): void;
run(): void;
clearAll(): void;
};
export {};

@ -0,0 +1,118 @@
"use strict";
/* eslint @typescript-eslint/no-explicit-any: ["off"] */
Object.defineProperty(exports, "__esModule", { value: true });
exports.effectScheduler = void 0;
exports.withHooks = withHooks;
exports.readline = readline;
exports.withUpdates = withUpdates;
exports.withPointer = withPointer;
exports.handleChange = handleChange;
const node_async_hooks_1 = require("node:async_hooks");
const errors_ts_1 = require("./errors.js");
const hookStorage = new node_async_hooks_1.AsyncLocalStorage();
function createStore(rl) {
const store = {
rl,
hooks: [],
hooksCleanup: [],
hooksEffect: [],
index: 0,
handleChange() { },
};
return store;
}
// Run callback in with the hook engine setup.
function withHooks(rl, cb) {
const store = createStore(rl);
return hookStorage.run(store, () => {
function cycle(render) {
store.handleChange = () => {
store.index = 0;
render();
};
store.handleChange();
}
return cb(cycle);
});
}
// Safe getStore utility that'll return the store or throw if undefined.
function getStore() {
const store = hookStorage.getStore();
if (!store) {
throw new errors_ts_1.HookError('[Inquirer] Hook functions can only be called from within a prompt');
}
return store;
}
function readline() {
return getStore().rl;
}
// Merge state updates happening within the callback function to avoid multiple renders.
function withUpdates(fn) {
const wrapped = (...args) => {
const store = getStore();
let shouldUpdate = false;
const oldHandleChange = store.handleChange;
store.handleChange = () => {
shouldUpdate = true;
};
const returnValue = fn(...args);
if (shouldUpdate) {
oldHandleChange();
}
store.handleChange = oldHandleChange;
return returnValue;
};
return node_async_hooks_1.AsyncResource.bind(wrapped);
}
function withPointer(cb) {
const store = getStore();
const { index } = store;
const pointer = {
get() {
return store.hooks[index];
},
set(value) {
store.hooks[index] = value;
},
initialized: index in store.hooks,
};
const returnValue = cb(pointer);
store.index++;
return returnValue;
}
function handleChange() {
getStore().handleChange();
}
exports.effectScheduler = {
queue(cb) {
const store = getStore();
const { index } = store;
store.hooksEffect.push(() => {
store.hooksCleanup[index]?.();
const cleanFn = cb(readline());
if (cleanFn != null && typeof cleanFn !== 'function') {
throw new errors_ts_1.ValidationError('useEffect return value must be a cleanup function or nothing.');
}
store.hooksCleanup[index] = cleanFn;
});
},
run() {
const store = getStore();
withUpdates(() => {
store.hooksEffect.forEach((effect) => {
effect();
});
// Warning: Clean the hooks before exiting the `withUpdates` block.
// Failure to do so means an updates would hit the same effects again.
store.hooksEffect.length = 0;
})();
},
clearAll() {
const store = getStore();
store.hooksCleanup.forEach((cleanFn) => {
cleanFn?.();
});
store.hooksEffect.length = 0;
store.hooksCleanup.length = 0;
},
};

@ -0,0 +1,10 @@
export type KeypressEvent = {
name: string;
ctrl: boolean;
};
export declare const isUpKey: (key: KeypressEvent) => boolean;
export declare const isDownKey: (key: KeypressEvent) => boolean;
export declare const isSpaceKey: (key: KeypressEvent) => boolean;
export declare const isBackspaceKey: (key: KeypressEvent) => boolean;
export declare const isNumberKey: (key: KeypressEvent) => boolean;
export declare const isEnterKey: (key: KeypressEvent) => boolean;

@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEnterKey = exports.isNumberKey = exports.isBackspaceKey = exports.isSpaceKey = exports.isDownKey = exports.isUpKey = void 0;
const isUpKey = (key) =>
// The up key
key.name === 'up' ||
// Vim keybinding
key.name === 'k' ||
// Emacs keybinding
(key.ctrl && key.name === 'p');
exports.isUpKey = isUpKey;
const isDownKey = (key) =>
// The down key
key.name === 'down' ||
// Vim keybinding
key.name === 'j' ||
// Emacs keybinding
(key.ctrl && key.name === 'n');
exports.isDownKey = isDownKey;
const isSpaceKey = (key) => key.name === 'space';
exports.isSpaceKey = isSpaceKey;
const isBackspaceKey = (key) => key.name === 'backspace';
exports.isBackspaceKey = isBackspaceKey;
const isNumberKey = (key) => '1234567890'.includes(key.name);
exports.isNumberKey = isNumberKey;
const isEnterKey = (key) => key.name === 'enter' || key.name === 'return';
exports.isEnterKey = isEnterKey;

@ -0,0 +1,3 @@
import type { Prettify, PartialDeep } from '@inquirer/type';
import { type Theme } from './theme.ts';
export declare function makeTheme<SpecificTheme extends object>(...themes: ReadonlyArray<undefined | PartialDeep<Theme<SpecificTheme>>>): Prettify<Theme<SpecificTheme>>;

@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeTheme = makeTheme;
const theme_ts_1 = require("./theme.js");
function isPlainObject(value) {
if (typeof value !== 'object' || value === null)
return false;
let proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(value) === proto;
}
function deepMerge(...objects) {
const output = {};
for (const obj of objects) {
for (const [key, value] of Object.entries(obj)) {
const prevValue = output[key];
output[key] =
isPlainObject(prevValue) && isPlainObject(value)
? deepMerge(prevValue, value)
: value;
}
}
return output;
}
function makeTheme(...themes) {
const themesToMerge = [
theme_ts_1.defaultTheme,
...themes.filter((theme) => theme != null),
];
return deepMerge(...themesToMerge);
}

@ -0,0 +1,16 @@
import type { Prettify } from '@inquirer/type';
export declare function usePagination<T>({ items, active, renderItem, pageSize, loop, }: {
items: ReadonlyArray<T>;
/** The index of the active item. */
active: number;
/** Renders an item as part of a page. */
renderItem: (layout: Prettify<{
item: T;
index: number;
isActive: boolean;
}>) => string;
/** The size of the page. */
pageSize: number;
/** Allows creating an infinitely looping list. `true` if unspecified. */
loop?: boolean;
}): string;

@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.usePagination = usePagination;
const use_ref_ts_1 = require("../use-ref.js");
const utils_ts_1 = require("../utils.js");
function usePointerPosition({ active, renderedItems, pageSize, loop, }) {
const state = (0, use_ref_ts_1.useRef)({
lastPointer: active,
lastActive: undefined,
});
const { lastPointer, lastActive } = state.current;
const middle = Math.floor(pageSize / 2);
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
const defaultPointerPosition = renderedItems
.slice(0, active)
.reduce((acc, item) => acc + item.length, 0);
let pointer = defaultPointerPosition;
if (renderedLength > pageSize) {
if (loop) {
/**
* Creates the next position for the pointer considering an infinitely
* looping list of items to be rendered on the page.
*
* The goal is to progressively move the cursor to the middle position as the user move down, and then keep
* the cursor there. When the user move up, maintain the cursor position.
*/
// By default, keep the cursor position as-is.
pointer = lastPointer;
if (
// First render, skip this logic.
lastActive != null &&
// Only move the pointer down when the user moves down.
lastActive < active &&
// Check user didn't move up across page boundary.
active - lastActive < pageSize) {
pointer = Math.min(
// Furthest allowed position for the pointer is the middle of the list
middle, Math.abs(active - lastActive) === 1
? Math.min(
// Move the pointer at most the height of the last active item.
lastPointer + (renderedItems[lastActive]?.length ?? 0),
// If the user moved by one item, move the pointer to the natural position of the active item as
// long as it doesn't move the cursor up.
Math.max(defaultPointerPosition, lastPointer))
: // Otherwise, move the pointer down by the difference between the active and last active item.
lastPointer + active - lastActive);
}
}
else {
/**
* Creates the next position for the pointer considering a finite list of
* items to be rendered on a page.
*
* The goal is to keep the pointer in the middle of the page whenever possible, until
* we reach the bounds of the list (top or bottom). In which case, the cursor moves progressively
* to the bottom or top of the list.
*/
const spaceUnderActive = renderedItems
.slice(active)
.reduce((acc, item) => acc + item.length, 0);
pointer =
spaceUnderActive < pageSize - middle
? // If the active item is near the end of the list, progressively move the cursor towards the end.
pageSize - spaceUnderActive
: // Otherwise, progressively move the pointer to the middle of the list.
Math.min(defaultPointerPosition, middle);
}
}
// Save state for the next render
state.current.lastPointer = pointer;
state.current.lastActive = active;
return pointer;
}
function usePagination({ items, active, renderItem, pageSize, loop = true, }) {
const width = (0, utils_ts_1.readlineWidth)();
const bound = (num) => ((num % items.length) + items.length) % items.length;
const renderedItems = items.map((item, index) => {
if (item == null)
return [];
return (0, utils_ts_1.breakLines)(renderItem({ item, index, isActive: index === active }), width).split('\n');
});
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
const renderItemAtIndex = (index) => renderedItems[index] ?? [];
const pointer = usePointerPosition({ active, renderedItems, pageSize, loop });
// Render the active item to decide the position.
// If the active item fits under the pointer, we render it there.
// Otherwise, we need to render it to fit at the bottom of the page; moving the pointer up.
const activeItem = renderItemAtIndex(active).slice(0, pageSize);
const activeItemPosition = pointer + activeItem.length <= pageSize ? pointer : pageSize - activeItem.length;
// Create an array of lines for the page, and add the lines of the active item into the page
const pageBuffer = Array.from({ length: pageSize });
pageBuffer.splice(activeItemPosition, activeItem.length, ...activeItem);
// Store to prevent rendering the same item twice
const itemVisited = new Set([active]);
// Fill the page under the active item
let bufferPointer = activeItemPosition + activeItem.length;
let itemPointer = bound(active + 1);
while (bufferPointer < pageSize &&
!itemVisited.has(itemPointer) &&
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer > active)) {
const lines = renderItemAtIndex(itemPointer);
const linesToAdd = lines.slice(0, pageSize - bufferPointer);
pageBuffer.splice(bufferPointer, linesToAdd.length, ...linesToAdd);
// Move pointers for next iteration
itemVisited.add(itemPointer);
bufferPointer += linesToAdd.length;
itemPointer = bound(itemPointer + 1);
}
// Fill the page over the active item
bufferPointer = activeItemPosition - 1;
itemPointer = bound(active - 1);
while (bufferPointer >= 0 &&
!itemVisited.has(itemPointer) &&
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer < active)) {
const lines = renderItemAtIndex(itemPointer);
const linesToAdd = lines.slice(Math.max(0, lines.length - bufferPointer - 1));
pageBuffer.splice(bufferPointer - linesToAdd.length + 1, linesToAdd.length, ...linesToAdd);
// Move pointers for next iteration
itemVisited.add(itemPointer);
bufferPointer -= linesToAdd.length;
itemPointer = bound(itemPointer - 1);
}
return pageBuffer.filter((line) => typeof line === 'string').join('\n');
}

@ -0,0 +1,7 @@
export declare class PromisePolyfill<T> extends Promise<T> {
static withResolver<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: unknown) => void;
};
}

@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PromisePolyfill = void 0;
// TODO: Remove this class once Node 22 becomes the minimum supported version.
class PromisePolyfill extends Promise {
// Available starting from Node 22
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
static withResolver() {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve: resolve, reject: reject };
}
}
exports.PromisePolyfill = PromisePolyfill;

@ -0,0 +1,14 @@
import type { InquirerReadline } from '@inquirer/type';
export default class ScreenManager {
private height;
private extraLinesUnderPrompt;
private cursorPos;
private readonly rl;
constructor(rl: InquirerReadline);
write(content: string): void;
render(content: string, bottomContent?: string): void;
checkCursorPos(): void;
done({ clearContent }: {
clearContent: boolean;
}): void;
}

@ -0,0 +1,90 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_util_1 = require("node:util");
const ansi_escapes_1 = __importDefault(require("ansi-escapes"));
const utils_ts_1 = require("./utils.js");
const height = (content) => content.split('\n').length;
const lastLine = (content) => content.split('\n').pop() ?? '';
function cursorDown(n) {
return n > 0 ? ansi_escapes_1.default.cursorDown(n) : '';
}
class ScreenManager {
// These variables are keeping information to allow correct prompt re-rendering
height = 0;
extraLinesUnderPrompt = 0;
cursorPos;
rl;
constructor(rl) {
this.rl = rl;
this.cursorPos = rl.getCursorPos();
}
write(content) {
this.rl.output.unmute();
this.rl.output.write(content);
this.rl.output.mute();
}
render(content, bottomContent = '') {
// Write message to screen and setPrompt to control backspace
const promptLine = lastLine(content);
const rawPromptLine = (0, node_util_1.stripVTControlCharacters)(promptLine);
// Remove the rl.line from our prompt. We can't rely on the content of
// rl.line (mainly because of the password prompt), so just rely on it's
// length.
let prompt = rawPromptLine;
if (this.rl.line.length > 0) {
prompt = prompt.slice(0, -this.rl.line.length);
}
this.rl.setPrompt(prompt);
// SetPrompt will change cursor position, now we can get correct value
this.cursorPos = this.rl.getCursorPos();
const width = (0, utils_ts_1.readlineWidth)();
content = (0, utils_ts_1.breakLines)(content, width);
bottomContent = (0, utils_ts_1.breakLines)(bottomContent, width);
// Manually insert an extra line if we're at the end of the line.
// This prevent the cursor from appearing at the beginning of the
// current line.
if (rawPromptLine.length % width === 0) {
content += '\n';
}
let output = content + (bottomContent ? '\n' + bottomContent : '');
/**
* Re-adjust the cursor at the correct position.
*/
// We need to consider parts of the prompt under the cursor as part of the bottom
// content in order to correctly cleanup and re-render.
const promptLineUpDiff = Math.floor(rawPromptLine.length / width) - this.cursorPos.rows;
const bottomContentHeight = promptLineUpDiff + (bottomContent ? height(bottomContent) : 0);
// Return cursor to the input position (on top of the bottomContent)
if (bottomContentHeight > 0)
output += ansi_escapes_1.default.cursorUp(bottomContentHeight);
// Return cursor to the initial left offset.
output += ansi_escapes_1.default.cursorTo(this.cursorPos.cols);
/**
* Render and store state for future re-rendering
*/
this.write(cursorDown(this.extraLinesUnderPrompt) +
ansi_escapes_1.default.eraseLines(this.height) +
output);
this.extraLinesUnderPrompt = bottomContentHeight;
this.height = height(output);
}
checkCursorPos() {
const cursorPos = this.rl.getCursorPos();
if (cursorPos.cols !== this.cursorPos.cols) {
this.write(ansi_escapes_1.default.cursorTo(cursorPos.cols));
this.cursorPos = cursorPos;
}
}
done({ clearContent }) {
this.rl.setPrompt('');
let output = cursorDown(this.extraLinesUnderPrompt);
output += clearContent ? ansi_escapes_1.default.eraseLines(this.height) : '\n';
output += ansi_escapes_1.default.cursorShow;
this.write(output);
this.rl.close();
}
}
exports.default = ScreenManager;

@ -0,0 +1,155 @@
import type { Prettify } from '@inquirer/type';
/**
* Union type representing the possible statuses of a prompt.
*
* - `'loading'`: The prompt is currently loading.
* - `'idle'`: The prompt is loaded and currently waiting for the user to
* submit an answer.
* - `'done'`: The user has submitted an answer and the prompt is finished.
* - `string`: Any other string: The prompt is in a custom state.
*/
export type Status = 'loading' | 'idle' | 'done' | (string & {});
type DefaultTheme = {
/**
* Prefix to prepend to the message. If a function is provided, it will be
* called with the current status of the prompt, and the return value will be
* used as the prefix.
*
* @remarks
* If `status === 'loading'`, this property is ignored and the spinner (styled
* by the `spinner` property) will be displayed instead.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (status) => status === 'done' ? colors.green('✔') : colors.blue('?')
* ```
*/
prefix: string | Prettify<Omit<Record<Status, string>, 'loading'>>;
/**
* Configuration for the spinner that is displayed when the prompt is in the
* `'loading'` state.
*
* We recommend the use of {@link https://github.com/sindresorhus/cli-spinners|cli-spinners} for a list of available spinners.
*/
spinner: {
/**
* The time interval between frames, in milliseconds.
*
* @defaultValue
* ```ts
* 80
* ```
*/
interval: number;
/**
* A list of frames to show for the spinner.
*
* @defaultValue
* ```ts
* ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
* ```
*/
frames: string[];
};
/**
* Object containing functions to style different parts of the prompt.
*/
style: {
/**
* Style to apply to the user's answer once it has been submitted.
*
* @param text - The user's answer.
* @returns The styled answer.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(text)
* ```
*/
answer: (text: string) => string;
/**
* Style to apply to the message displayed to the user.
*
* @param text - The message to style.
* @param status - The current status of the prompt.
* @returns The styled message.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text, status) => colors.bold(text)
* ```
*/
message: (text: string, status: Status) => string;
/**
* Style to apply to error messages.
*
* @param text - The error message.
* @returns The styled error message.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.red(`> ${text}`)
* ```
*/
error: (text: string) => string;
/**
* Style to apply to the default answer when one is provided.
*
* @param text - The default answer.
* @returns The styled default answer.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.dim(`(${text})`)
* ```
*/
defaultAnswer: (text: string) => string;
/**
* Style to apply to help text.
*
* @param text - The help text.
* @returns The styled help text.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.dim(text)
* ```
*/
help: (text: string) => string;
/**
* Style to apply to highlighted text.
*
* @param text - The text to highlight.
* @returns The highlighted text.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(text)
* ```
*/
highlight: (text: string) => string;
/**
* Style to apply to keyboard keys referred to in help texts.
*
* @param text - The key to style.
* @returns The styled key.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(colors.bold(`<${text}>`))
* ```
*/
key: (text: string) => string;
};
};
export type Theme<Extension extends object = object> = Prettify<Extension & DefaultTheme>;
export declare const defaultTheme: DefaultTheme;
export {};

@ -0,0 +1,28 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultTheme = void 0;
const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs"));
const figures_1 = __importDefault(require("@inquirer/figures"));
exports.defaultTheme = {
prefix: {
idle: yoctocolors_cjs_1.default.blue('?'),
// TODO: use figure
done: yoctocolors_cjs_1.default.green(figures_1.default.tick),
},
spinner: {
interval: 80,
frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].map((frame) => yoctocolors_cjs_1.default.yellow(frame)),
},
style: {
answer: yoctocolors_cjs_1.default.cyan,
message: yoctocolors_cjs_1.default.bold,
error: (text) => yoctocolors_cjs_1.default.red(`> ${text}`),
defaultAnswer: (text) => yoctocolors_cjs_1.default.dim(`(${text})`),
help: yoctocolors_cjs_1.default.dim,
highlight: yoctocolors_cjs_1.default.cyan,
key: (text) => yoctocolors_cjs_1.default.cyan(yoctocolors_cjs_1.default.bold(`<${text}>`)),
},
};

@ -0,0 +1,2 @@
import type { InquirerReadline } from '@inquirer/type';
export declare function useEffect(cb: (rl: InquirerReadline) => void | (() => void), depArray: ReadonlyArray<unknown>): void;

@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useEffect = useEffect;
const hook_engine_ts_1 = require("./hook-engine.js");
function useEffect(cb, depArray) {
(0, hook_engine_ts_1.withPointer)((pointer) => {
const oldDeps = pointer.get();
const hasChanged = !Array.isArray(oldDeps) || depArray.some((dep, i) => !Object.is(dep, oldDeps[i]));
if (hasChanged) {
hook_engine_ts_1.effectScheduler.queue(cb);
}
pointer.set(depArray);
});
}

@ -0,0 +1,3 @@
import { type InquirerReadline } from '@inquirer/type';
import { type KeypressEvent } from './key.ts';
export declare function useKeypress(userHandler: (event: KeypressEvent, rl: InquirerReadline) => void | Promise<void>): void;

@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useKeypress = useKeypress;
const use_ref_ts_1 = require("./use-ref.js");
const use_effect_ts_1 = require("./use-effect.js");
const hook_engine_ts_1 = require("./hook-engine.js");
function useKeypress(userHandler) {
const signal = (0, use_ref_ts_1.useRef)(userHandler);
signal.current = userHandler;
(0, use_effect_ts_1.useEffect)((rl) => {
let ignore = false;
const handler = (0, hook_engine_ts_1.withUpdates)((_input, event) => {
if (ignore)
return;
void signal.current(event, rl);
});
rl.input.on('keypress', handler);
return () => {
ignore = true;
rl.input.removeListener('keypress', handler);
};
}, []);
}

@ -0,0 +1 @@
export declare function useMemo<Value>(fn: () => Value, dependencies: ReadonlyArray<unknown>): Value;

@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useMemo = useMemo;
const hook_engine_ts_1 = require("./hook-engine.js");
function useMemo(fn, dependencies) {
return (0, hook_engine_ts_1.withPointer)((pointer) => {
const prev = pointer.get();
if (!prev ||
prev.dependencies.length !== dependencies.length ||
prev.dependencies.some((dep, i) => dep !== dependencies[i])) {
const value = fn();
pointer.set({ value, dependencies });
return value;
}
return prev.value;
});
}

@ -0,0 +1,5 @@
import type { Theme, Status } from './theme.ts';
export declare function usePrefix({ status, theme, }: {
status?: Status;
theme?: Theme;
}): string;

@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.usePrefix = usePrefix;
const use_state_ts_1 = require("./use-state.js");
const use_effect_ts_1 = require("./use-effect.js");
const make_theme_ts_1 = require("./make-theme.js");
function usePrefix({ status = 'idle', theme, }) {
const [showLoader, setShowLoader] = (0, use_state_ts_1.useState)(false);
const [tick, setTick] = (0, use_state_ts_1.useState)(0);
const { prefix, spinner } = (0, make_theme_ts_1.makeTheme)(theme);
(0, use_effect_ts_1.useEffect)(() => {
if (status === 'loading') {
let tickInterval;
let inc = -1;
// Delay displaying spinner by 300ms, to avoid flickering
const delayTimeout = setTimeout(() => {
setShowLoader(true);
tickInterval = setInterval(() => {
inc = inc + 1;
setTick(inc % spinner.frames.length);
}, spinner.interval);
}, 300);
return () => {
clearTimeout(delayTimeout);
clearInterval(tickInterval);
};
}
else {
setShowLoader(false);
}
}, [status]);
if (showLoader) {
return spinner.frames[tick];
}
// There's a delay before we show the loader. So we want to ignore `loading` here, and pass idle instead.
const iconName = status === 'loading' ? 'idle' : status;
return typeof prefix === 'string' ? prefix : (prefix[iconName] ?? prefix['idle']);
}

@ -0,0 +1,6 @@
export declare function useRef<Value>(val: Value): {
current: Value;
};
export declare function useRef<Value>(val?: Value): {
current: Value | undefined;
};

@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useRef = useRef;
const use_state_ts_1 = require("./use-state.js");
function useRef(val) {
return (0, use_state_ts_1.useState)({ current: val })[0];
}

@ -0,0 +1,4 @@
type NotFunction<T> = T extends (...args: never) => unknown ? never : T;
export declare function useState<Value>(defaultValue: NotFunction<Value> | (() => Value)): [Value, (newValue: Value) => void];
export declare function useState<Value>(defaultValue?: NotFunction<Value> | (() => Value)): [Value | undefined, (newValue?: Value) => void];
export {};

@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useState = useState;
const node_async_hooks_1 = require("node:async_hooks");
const hook_engine_ts_1 = require("./hook-engine.js");
function useState(defaultValue) {
return (0, hook_engine_ts_1.withPointer)((pointer) => {
const setState = node_async_hooks_1.AsyncResource.bind(function setState(newValue) {
// Noop if the value is still the same.
if (pointer.get() !== newValue) {
pointer.set(newValue);
// Trigger re-render
(0, hook_engine_ts_1.handleChange)();
}
});
if (pointer.initialized) {
return [pointer.get(), setState];
}
const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
pointer.set(value);
return [value, setState];
});
}

@ -0,0 +1,13 @@
/**
* Force line returns at specific width. This function is ANSI code friendly and it'll
* ignore invisible codes during width calculation.
* @param {string} content
* @param {number} width
* @return {string}
*/
export declare function breakLines(content: string, width: number): string;
/**
* Returns the width of the active readline, or 80 as default value.
* @returns {number}
*/
export declare function readlineWidth(): number;

@ -0,0 +1,32 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.breakLines = breakLines;
exports.readlineWidth = readlineWidth;
const cli_width_1 = __importDefault(require("cli-width"));
const wrap_ansi_1 = __importDefault(require("wrap-ansi"));
const hook_engine_ts_1 = require("./hook-engine.js");
/**
* Force line returns at specific width. This function is ANSI code friendly and it'll
* ignore invisible codes during width calculation.
* @param {string} content
* @param {number} width
* @return {string}
*/
function breakLines(content, width) {
return content
.split('\n')
.flatMap((line) => (0, wrap_ansi_1.default)(line, width, { trim: false, hard: true })
.split('\n')
.map((str) => str.trimEnd()))
.join('\n');
}
/**
* Returns the width of the active readline, or 80 as default value.
* @returns {number}
*/
function readlineWidth() {
return (0, cli_width_1.default)({ defaultWidth: 80, output: (0, hook_engine_ts_1.readline)().output });
}

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

@ -0,0 +1,13 @@
export * from './lib/key.ts';
export * from './lib/errors.ts';
export { usePrefix } from './lib/use-prefix.ts';
export { useState } from './lib/use-state.ts';
export { useEffect } from './lib/use-effect.ts';
export { useMemo } from './lib/use-memo.ts';
export { useRef } from './lib/use-ref.ts';
export { useKeypress } from './lib/use-keypress.ts';
export { makeTheme } from './lib/make-theme.ts';
export type { Theme, Status } from './lib/theme.ts';
export { usePagination } from './lib/pagination/use-pagination.ts';
export { createPrompt } from './lib/create-prompt.ts';
export { Separator } from './lib/Separator.ts';

@ -0,0 +1,12 @@
export * from "./lib/key.js";
export * from "./lib/errors.js";
export { usePrefix } from "./lib/use-prefix.js";
export { useState } from "./lib/use-state.js";
export { useEffect } from "./lib/use-effect.js";
export { useMemo } from "./lib/use-memo.js";
export { useRef } from "./lib/use-ref.js";
export { useKeypress } from "./lib/use-keypress.js";
export { makeTheme } from "./lib/make-theme.js";
export { usePagination } from "./lib/pagination/use-pagination.js";
export { createPrompt } from "./lib/create-prompt.js";
export { Separator } from "./lib/Separator.js";

@ -0,0 +1,10 @@
/**
* Separator object
* Used to space/separate choices group
*/
export declare class Separator {
readonly separator: string;
readonly type: string;
constructor(separator?: string);
static isSeparator(choice: unknown): choice is Separator;
}

@ -0,0 +1,21 @@
import colors from 'yoctocolors-cjs';
import figures from '@inquirer/figures';
/**
* Separator object
* Used to space/separate choices group
*/
export class Separator {
separator = colors.dim(Array.from({ length: 15 }).join(figures.line));
type = 'separator';
constructor(separator) {
if (separator) {
this.separator = separator;
}
}
static isSeparator(choice) {
return Boolean(choice &&
typeof choice === 'object' &&
'type' in choice &&
choice.type === 'separator');
}
}

@ -0,0 +1,4 @@
import { type Prompt, type Prettify } from '@inquirer/type';
type ViewFunction<Value, Config> = (config: Prettify<Config>, done: (value: Value) => void) => string | [string, string | undefined];
export declare function createPrompt<Value, Config>(view: ViewFunction<Value, Config>): Prompt<Value, Config>;
export {};

@ -0,0 +1,117 @@
import * as readline from 'node:readline';
import { AsyncResource } from 'node:async_hooks';
import MuteStream from 'mute-stream';
import { onExit as onSignalExit } from 'signal-exit';
import ScreenManager from "./screen-manager.js";
import { PromisePolyfill } from "./promise-polyfill.js";
import { withHooks, effectScheduler } from "./hook-engine.js";
import { AbortPromptError, CancelPromptError, ExitPromptError } from "./errors.js";
function getCallSites() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const _prepareStackTrace = Error.prepareStackTrace;
let result = [];
try {
Error.prepareStackTrace = (_, callSites) => {
const callSitesWithoutCurrent = callSites.slice(1);
result = callSitesWithoutCurrent;
return callSitesWithoutCurrent;
};
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
new Error().stack;
}
catch {
// An error will occur if the Node flag --frozen-intrinsics is used.
// https://nodejs.org/api/cli.html#--frozen-intrinsics
return result;
}
Error.prepareStackTrace = _prepareStackTrace;
return result;
}
export function createPrompt(view) {
const callSites = getCallSites();
const prompt = (config, context = {}) => {
// Default `input` to stdin
const { input = process.stdin, signal } = context;
const cleanups = new Set();
// Add mute capabilities to the output
const output = new MuteStream();
output.pipe(context.output ?? process.stdout);
const rl = readline.createInterface({
terminal: true,
input,
output,
});
const screen = new ScreenManager(rl);
const { promise, resolve, reject } = PromisePolyfill.withResolver();
const cancel = () => reject(new CancelPromptError());
if (signal) {
const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
if (signal.aborted) {
abort();
return Object.assign(promise, { cancel });
}
signal.addEventListener('abort', abort);
cleanups.add(() => signal.removeEventListener('abort', abort));
}
cleanups.add(onSignalExit((code, signal) => {
reject(new ExitPromptError(`User force closed the prompt with ${code} ${signal}`));
}));
// SIGINT must be explicitly handled by the prompt so the ExitPromptError can be handled.
// Otherwise, the prompt will stop and in some scenarios never resolve.
// Ref issue #1741
const sigint = () => reject(new ExitPromptError(`User force closed the prompt with SIGINT`));
rl.on('SIGINT', sigint);
cleanups.add(() => rl.removeListener('SIGINT', sigint));
// Re-renders only happen when the state change; but the readline cursor could change position
// and that also requires a re-render (and a manual one because we mute the streams).
// We set the listener after the initial workLoop to avoid a double render if render triggered
// by a state change sets the cursor to the right position.
const checkCursorPos = () => screen.checkCursorPos();
rl.input.on('keypress', checkCursorPos);
cleanups.add(() => rl.input.removeListener('keypress', checkCursorPos));
return withHooks(rl, (cycle) => {
// The close event triggers immediately when the user press ctrl+c. SignalExit on the other hand
// triggers after the process is done (which happens after timeouts are done triggering.)
// We triggers the hooks cleanup phase on rl `close` so active timeouts can be cleared.
const hooksCleanup = AsyncResource.bind(() => effectScheduler.clearAll());
rl.on('close', hooksCleanup);
cleanups.add(() => rl.removeListener('close', hooksCleanup));
cycle(() => {
try {
const nextView = view(config, (value) => {
setImmediate(() => resolve(value));
});
// Typescript won't allow this, but not all users rely on typescript.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (nextView === undefined) {
const callerFilename = callSites[1]?.getFileName();
throw new Error(`Prompt functions must return a string.\n at ${callerFilename}`);
}
const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView;
screen.render(content, bottomContent);
effectScheduler.run();
}
catch (error) {
reject(error);
}
});
return Object.assign(promise
.then((answer) => {
effectScheduler.clearAll();
return answer;
}, (error) => {
effectScheduler.clearAll();
throw error;
})
// Wait for the promise to settle, then cleanup.
.finally(() => {
cleanups.forEach((cleanup) => cleanup());
screen.done({ clearContent: Boolean(context.clearPromptOnDone) });
output.end();
})
// Once cleanup is done, let the expose promise resolve/reject to the internal one.
.then(() => promise), { cancel });
});
};
return prompt;
}

@ -0,0 +1,20 @@
export declare class AbortPromptError extends Error {
name: string;
message: string;
constructor(options?: {
cause?: unknown;
});
}
export declare class CancelPromptError extends Error {
name: string;
message: string;
}
export declare class ExitPromptError extends Error {
name: string;
}
export declare class HookError extends Error {
name: string;
}
export declare class ValidationError extends Error {
name: string;
}

@ -0,0 +1,21 @@
export class AbortPromptError extends Error {
name = 'AbortPromptError';
message = 'Prompt was aborted';
constructor(options) {
super();
this.cause = options?.cause;
}
}
export class CancelPromptError extends Error {
name = 'CancelPromptError';
message = 'Prompt was canceled';
}
export class ExitPromptError extends Error {
name = 'ExitPromptError';
}
export class HookError extends Error {
name = 'HookError';
}
export class ValidationError extends Error {
name = 'ValidationError';
}

@ -0,0 +1,23 @@
import type { InquirerReadline } from '@inquirer/type';
export declare function withHooks<T>(rl: InquirerReadline, cb: (cycle: (render: () => void) => void) => T): T;
export declare function readline(): InquirerReadline;
export declare function withUpdates<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R;
type SetPointer<Value> = {
get(): Value;
set(value: Value): void;
initialized: true;
};
type UnsetPointer<Value> = {
get(): void;
set(value: Value): void;
initialized: false;
};
type Pointer<Value> = SetPointer<Value> | UnsetPointer<Value>;
export declare function withPointer<Value, ReturnValue>(cb: (pointer: Pointer<Value>) => ReturnValue): ReturnValue;
export declare function handleChange(): void;
export declare const effectScheduler: {
queue(cb: (readline: InquirerReadline) => void | (() => void)): void;
run(): void;
clearAll(): void;
};
export {};

@ -0,0 +1,110 @@
/* eslint @typescript-eslint/no-explicit-any: ["off"] */
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks';
import { HookError, ValidationError } from "./errors.js";
const hookStorage = new AsyncLocalStorage();
function createStore(rl) {
const store = {
rl,
hooks: [],
hooksCleanup: [],
hooksEffect: [],
index: 0,
handleChange() { },
};
return store;
}
// Run callback in with the hook engine setup.
export function withHooks(rl, cb) {
const store = createStore(rl);
return hookStorage.run(store, () => {
function cycle(render) {
store.handleChange = () => {
store.index = 0;
render();
};
store.handleChange();
}
return cb(cycle);
});
}
// Safe getStore utility that'll return the store or throw if undefined.
function getStore() {
const store = hookStorage.getStore();
if (!store) {
throw new HookError('[Inquirer] Hook functions can only be called from within a prompt');
}
return store;
}
export function readline() {
return getStore().rl;
}
// Merge state updates happening within the callback function to avoid multiple renders.
export function withUpdates(fn) {
const wrapped = (...args) => {
const store = getStore();
let shouldUpdate = false;
const oldHandleChange = store.handleChange;
store.handleChange = () => {
shouldUpdate = true;
};
const returnValue = fn(...args);
if (shouldUpdate) {
oldHandleChange();
}
store.handleChange = oldHandleChange;
return returnValue;
};
return AsyncResource.bind(wrapped);
}
export function withPointer(cb) {
const store = getStore();
const { index } = store;
const pointer = {
get() {
return store.hooks[index];
},
set(value) {
store.hooks[index] = value;
},
initialized: index in store.hooks,
};
const returnValue = cb(pointer);
store.index++;
return returnValue;
}
export function handleChange() {
getStore().handleChange();
}
export const effectScheduler = {
queue(cb) {
const store = getStore();
const { index } = store;
store.hooksEffect.push(() => {
store.hooksCleanup[index]?.();
const cleanFn = cb(readline());
if (cleanFn != null && typeof cleanFn !== 'function') {
throw new ValidationError('useEffect return value must be a cleanup function or nothing.');
}
store.hooksCleanup[index] = cleanFn;
});
},
run() {
const store = getStore();
withUpdates(() => {
store.hooksEffect.forEach((effect) => {
effect();
});
// Warning: Clean the hooks before exiting the `withUpdates` block.
// Failure to do so means an updates would hit the same effects again.
store.hooksEffect.length = 0;
})();
},
clearAll() {
const store = getStore();
store.hooksCleanup.forEach((cleanFn) => {
cleanFn?.();
});
store.hooksEffect.length = 0;
store.hooksCleanup.length = 0;
},
};

@ -0,0 +1,10 @@
export type KeypressEvent = {
name: string;
ctrl: boolean;
};
export declare const isUpKey: (key: KeypressEvent) => boolean;
export declare const isDownKey: (key: KeypressEvent) => boolean;
export declare const isSpaceKey: (key: KeypressEvent) => boolean;
export declare const isBackspaceKey: (key: KeypressEvent) => boolean;
export declare const isNumberKey: (key: KeypressEvent) => boolean;
export declare const isEnterKey: (key: KeypressEvent) => boolean;

@ -0,0 +1,18 @@
export const isUpKey = (key) =>
// The up key
key.name === 'up' ||
// Vim keybinding
key.name === 'k' ||
// Emacs keybinding
(key.ctrl && key.name === 'p');
export const isDownKey = (key) =>
// The down key
key.name === 'down' ||
// Vim keybinding
key.name === 'j' ||
// Emacs keybinding
(key.ctrl && key.name === 'n');
export const isSpaceKey = (key) => key.name === 'space';
export const isBackspaceKey = (key) => key.name === 'backspace';
export const isNumberKey = (key) => '1234567890'.includes(key.name);
export const isEnterKey = (key) => key.name === 'enter' || key.name === 'return';

@ -0,0 +1,3 @@
import type { Prettify, PartialDeep } from '@inquirer/type';
import { type Theme } from './theme.ts';
export declare function makeTheme<SpecificTheme extends object>(...themes: ReadonlyArray<undefined | PartialDeep<Theme<SpecificTheme>>>): Prettify<Theme<SpecificTheme>>;

@ -0,0 +1,30 @@
import { defaultTheme } from "./theme.js";
function isPlainObject(value) {
if (typeof value !== 'object' || value === null)
return false;
let proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(value) === proto;
}
function deepMerge(...objects) {
const output = {};
for (const obj of objects) {
for (const [key, value] of Object.entries(obj)) {
const prevValue = output[key];
output[key] =
isPlainObject(prevValue) && isPlainObject(value)
? deepMerge(prevValue, value)
: value;
}
}
return output;
}
export function makeTheme(...themes) {
const themesToMerge = [
defaultTheme,
...themes.filter((theme) => theme != null),
];
return deepMerge(...themesToMerge);
}

@ -0,0 +1,16 @@
import type { Prettify } from '@inquirer/type';
export declare function usePagination<T>({ items, active, renderItem, pageSize, loop, }: {
items: ReadonlyArray<T>;
/** The index of the active item. */
active: number;
/** Renders an item as part of a page. */
renderItem: (layout: Prettify<{
item: T;
index: number;
isActive: boolean;
}>) => string;
/** The size of the page. */
pageSize: number;
/** Allows creating an infinitely looping list. `true` if unspecified. */
loop?: boolean;
}): string;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save