Firost
> Toolbox for command-line scripts
Install / Use
/learn @pixelastic/FirostREADME
> firost
Async glob, read and write files.
I was getting tired of having to write the same helpers and wrappers for
reading/writing files, so I packaged the best libraries and made an API that
works well with async/await.
Filesystem methods
These methods help in finding, reading, moving and writing files on disk.
absolute(filepath)
Converts any filepath to an absolute path, resolving ~/ to home.
absolute('../path/to/file/oops/../.') // /absolute/path/to/file
absolute('~/.npmrc') // /home/tim/.npmrc
copy(source, destination)
Copy file(s)
await copy('index.html', './dist/index.html');
await copy('./foo/*.html', './dist');
download(url, path)
Download a file to specific path on disk
await download('http://www.example.com/file.jpg', './example.jpg');
emptyDir(path)
Empty the content of a directory
await emptyDir('./foo/bar');
exists(path)
Check if a file/directory exists
await exists('./foo/bar/file.ext');
gitRoot()
Returns the path of the closest directory holding a .git folder.
gitRoot(); // /home/tim/projects/firost
gitRoot('~/projects/aberlaas/lib/main.js'); // /home/tim/projects/aberlaas/
glob(pattern, options = {})
Returns an array of filepaths matching the specified glob pattern.
It will return hidden files and directories by default, but you can override it
by passing hiddenFiles: false or directories: false to the options
argument.
const paths = await glob(['./src/**/*.css', '!./src/**/_*.css']);
const noHiddenFiles = await glob(['./lib/**/*.js'], {
hiddenFiles: false,
});
const noDirectories = await glob(['./lib'], { directories: false });
isDirectory(path)
Checks if the given path is a directory
if (await isDirectory('./dist')) {
console.info('Website created');
}
isFile(path)
Checks if the given path is a file
if (await isFile('./package.json')) {
console.info('File exists');
}
mkdirp(path)
Creates a set of nested directories if they don't yet exist.
await mkdirp('./dist/css');
move(source, destination)
Move file(s)
await move('index.html', 'index.back.html');
await move('./*.html', './dist');
packageRoot()
Returns the path of the closest directory holding a package.json file.
packageRoot(); // /home/tim/projects/firost
packageRoot('~/projects/aberlaas/lib/main.js'); // /home/tim/projects/aberlaas/
read(path)
Returns the textual content of a file located at the specified filepath.
const content = await read('./src/css/style.css');
readJson(path)
Returns the content of a JSON file as a JavaScript object.
const data = await readJson('./records.json');
readJsonUrl(url)
Returns the content of a JSON URL as a JavaScript object.
const data = await readJsonUrl('http://www.example.com/data.json');
remove(target)
Remove file(s)
await remove('index.back.html');
await remove('*.back.html');
urlToFilepath(url)
Converts an URL into a filepath suitable for writing the file to disk.
const filepath = urlToFilepath(
'http://www.example.com/path/file.html?foo=bar'
);
// http/www.example.com/path/file_foo-bar.html
// Also works with relative utls
const filepath = urlToFilepath('path/file.html?foo=bar');
// path/file_foo-bar.html
watch(pattern, callback, {watcherName})
Watch for file change, and run specified callback with path to changed files.
function doSomething(filepath, type) {
console.info(`File ${filepath} was just ${type}`);
}
const watcher = await watch(
'./path/to/files/*.jpg',
doSomething,
'my-watcher'
);
// To remove the watcher:
await unwatch('my-watch');
// or await unwatch(watcher);
// To remove all watchers:
await unwatchAll();
// To force wait until all watchers are executed
await waitForWatchers();
write(content, destination)
Write content to a file on disk.
await write('This is my content', './dist/content.txt');
writeJson(data, destination)
Write data to a JSON file on disk. Keys will be ordered alphabetically, for easier diffing of the file.
const records = [
{ name: 'foo', value: 2 },
{ value: 3, name: 'bar' },
];
await writeJson(records, './records/records.json');
Shell methods
These methods help abstracting some common CLI tasks
exit
Stop the current process with specified exitCode. Similar to process.exit.
exit(1);
consoleInfo
Display an information message
consoleInfo('Info');
// • Info
consoleWarn
Display a warning message
consoleWarn('Warning');
// ⚠ Warning
consoleError
Display an error message
consoleInfo('Error');
// ✘ Error
consoleSuccess
Display an success message
consoleSuccess('Success');
// ✔ Success
prompt(question)
Interactively ask user a question
const mood = await prompt('How do you feel?');
run(command)
Run a shell command just like in the terminal, but also allows access to
stdout, stderr and exit code.
Options
| name | default value | description |
| -------- | ------------- | ------------------------------------------------------------------------------------------- |
| shell | false | Set to true to enable shell-specific feature (like &&, >, etc) |
| stdout | true | Set to false to silence stdout |
| stderr | true | Set to false to silence stderr |
| stdin | false | Set to true to allow user to input keyboard keys. This sets stdout and stderr to true |
const { stdout } = await run('echo foo'); // foo
const { stderr } = await run('>&2 echo bar', { shell: true }); // foo
try {
await run('echo foo && exit 42');
} catch (err) {
// err.code = 42
// err.stdout = foo
}
spinner(max)
Creates a spinner with optional max number of elements.
const progress = spinner(10);
progress.tick('Doing task 1');
progress.success('All tasks done');
// or progress.failure('All tasks failed');
shell(command)
Deprecated. Use run instead, it if much more flexible
Run the given command in a shell. Returns stdout, throws with stderr and
exitCode.
try {
const result = await shell('git checkout -b master');
console.info('Created branch master');
} catch (err) {
console.error(err.message);
console.error(err.code);
}
sleep(delay)
Wait for a specific number of milliseconds
await sleep(100); // Wait for 100 ms
tmpDirectory()
Returns a random temporary folder, optionally scoped.
tmpDirectory(); // /tmp/{some-random-uuid}
tmpDirectory('firost/scope/'); // /tmp/firost/scope/{some-random-uuid}
which(command)
Returns the path to an executable on the system. Returns false if none is
found.
if (!(await which('convert'))) {
console.info('You need to install ImageMagick');
}
Utils
cache
Shared singleton to used a proxy cache.
cache.write('foo', 42);
cache.read('foo'); // 42
cache.has('foo'); // true
cache.has('nope'); // false
cache.write('key', { foo: ['one', 'two'], bar: { three: 3 } });
cache.read('key.foo'); // ['one', 'two'];
cache.read('key.bar.three'); // 3
cache.clear('key.foo');
cache.has('key.foo'); // false
cache.clearAll();
cache.has('key'); // false
captureOutput
Silence all output of the specified code, and return it instead
const actual = await captureOutput(async () => {
console.info("This will not get displayed");
await run('echo Test');
await run('./no-existing-script.sh');
});
// actual.stdout: ["This will not get displayed", "Test"]
// actual.stderr: ["Command does not exist"]
error
Returns an Error with both a .code and a .message
throw error('E_ERROR', 'This failed');
normalizeUrl
Normalize a URL
normalizeUrl('http://www.there.com/index.html?sort=asc&name=firost');
// http://www.there.com/?name=firost&sort=asc
pulse
Shared event emitter to listen and emit events
pulse.on('custom', data => {
console.info(data);
});
pulse.emit('custom', 'Hello');
// Hello
firostImpot(id)
Alternative to the default dynamic import(). Pass forceReload: true as an option to
force reloading the latest version on disk, bypassing the singleton cache.
const module = await firostImport('./path/to/module.js');
const updatedModule = await firostImport('./path/to/module.js', {
forceReload: true,
});
uuid()
Returns a unique ID that could be used in URL of filepaths.
console.info(uuid());
// V1StGXR8_Z5jdHi6B-myT
Related Skills
node-connect
342.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
85.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
342.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。