appstash-cli

appstash CLI Directory Management

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "appstash-cli" with this command: npx skills add constructive-io/constructive-skills/constructive-io-constructive-skills-appstash-cli

appstash CLI Directory Management

Use appstash for simple, clean application directory resolution in CLI tools. It provides consistent paths for config, cache, data, logs, and temp directories with graceful fallback handling.

When to Apply

Use this skill when:

  • Building a CLI tool that needs to store configuration

  • Implementing persistent caching for CLI operations

  • Managing API keys or auth tokens for CLI tools

  • Adding logging to CLI applications

  • Storing temporary files during CLI operations

Installation

npm install appstash

or

pnpm add appstash

Core Concepts

Directory Structure

appstash creates a clean directory structure under the user's home directory:

~/.<tool>/ ├── config/ # Configuration files (settings, auth profiles) ├── cache/ # Cached data (repos, API responses) ├── data/ # Application data (databases, state) └── logs/ # Log files

/tmp/<tool>/ # Temporary files (ephemeral)

Fallback Chain

appstash never throws errors. If the home directory is unavailable:

  • Falls back to XDG directories (~/.config/<tool> , ~/.cache/<tool> , etc.)

  • Falls back to system temp (/tmp/<tool>/ )

Basic Usage

import { appstash, resolve } from 'appstash';

// Get directories for your CLI tool const dirs = appstash('mycli', { ensure: true });

console.log(dirs.config); // ~/.mycli/config console.log(dirs.cache); // ~/.mycli/cache console.log(dirs.data); // ~/.mycli/data console.log(dirs.logs); // ~/.mycli/logs console.log(dirs.tmp); // /tmp/mycli

Common Patterns

Storing Auth Profiles

Store multiple API endpoints and tokens for CLI tools:

import { appstash, resolve } from 'appstash'; import * as fs from 'fs';

interface AuthProfile { endpoint: string; token?: string; }

interface CliConfig { current?: string; profiles: Record<string, AuthProfile>; }

const dirs = appstash('mycli', { ensure: true }); const configFile = resolve(dirs, 'config', 'auth.json');

function loadConfig(): CliConfig { if (fs.existsSync(configFile)) { return JSON.parse(fs.readFileSync(configFile, 'utf8')); } return { profiles: {} }; }

function saveConfig(config: CliConfig): void { fs.writeFileSync(configFile, JSON.stringify(config, null, 2)); }

function addProfile(name: string, endpoint: string, token?: string): void { const config = loadConfig(); config.profiles[name] = { endpoint, token }; if (!config.current) { config.current = name; } saveConfig(config); }

function useProfile(name: string): void { const config = loadConfig(); if (!config.profiles[name]) { throw new Error(Profile "${name}" not found); } config.current = name; saveConfig(config); }

function getActiveProfile(): AuthProfile | null { const config = loadConfig(); if (config.current && config.profiles[config.current]) { return config.profiles[config.current]; } return null; }

Caching API Responses

import { appstash, resolve } from 'appstash'; import * as fs from 'fs'; import * as path from 'path';

const dirs = appstash('mycli', { ensure: true });

interface CacheEntry<T> { data: T; timestamp: number; }

function getCached<T>(key: string, ttlMs: number): T | null { const cachePath = resolve(dirs, 'cache', ${key}.json);

if (!fs.existsSync(cachePath)) { return null; }

const entry: CacheEntry<T> = JSON.parse(fs.readFileSync(cachePath, 'utf8')); const age = Date.now() - entry.timestamp;

if (age > ttlMs) { fs.unlinkSync(cachePath); return null; }

return entry.data; }

function setCache<T>(key: string, data: T): void { const cachePath = resolve(dirs, 'cache', ${key}.json); fs.mkdirSync(path.dirname(cachePath), { recursive: true });

const entry: CacheEntry<T> = { data, timestamp: Date.now(), };

fs.writeFileSync(cachePath, JSON.stringify(entry)); }

Logging

import { appstash, resolve } from 'appstash'; import * as fs from 'fs';

const dirs = appstash('mycli', { ensure: true }); const logFile = resolve(dirs, 'logs', 'cli.log');

function log(level: 'info' | 'warn' | 'error', message: string): void { const timestamp = new Date().toISOString(); const line = [${timestamp}] [${level.toUpperCase()}] ${message}\n; fs.appendFileSync(logFile, line); }

Update Checking with @inquirerer/utils

Combine appstash with @inquirerer/utils for update checking:

import { appstash, resolve } from 'appstash'; import { checkForUpdates } from '@inquirerer/utils'; import * as fs from 'fs';

const dirs = appstash('mycli', { ensure: true }); const updateCacheFile = resolve(dirs, 'cache', 'update-check.json');

async function checkUpdates(pkgName: string, pkgVersion: string): Promise<void> { // Check if we've checked recently (within 24 hours) if (fs.existsSync(updateCacheFile)) { const cache = JSON.parse(fs.readFileSync(updateCacheFile, 'utf8')); const age = Date.now() - cache.timestamp; if (age < 24 * 60 * 60 * 1000) { return; // Skip check } }

const result = await checkForUpdates({ pkgName, pkgVersion, toolName: 'mycli', });

// Cache the check timestamp fs.writeFileSync(updateCacheFile, JSON.stringify({ timestamp: Date.now() }));

if (result.hasUpdate && result.message) { console.warn(result.message); console.warn('Run npm update -g mycli to upgrade.'); } }

Integration with inquirerer CLI

When building CLIs with inquirerer , use appstash for all persistent storage:

import { CLI, CLIOptions, Inquirerer, ParsedArgs } from 'inquirerer'; import { appstash, resolve } from 'appstash';

const dirs = appstash('mycli', { ensure: true });

// Auth command using appstash const authCommand = async (argv: Partial<ParsedArgs>, prompter: Inquirerer) => { const configFile = resolve(dirs, 'config', 'auth.json');

// ... implement auth management };

// Main CLI setup const commands = async (argv: Partial<ParsedArgs>, prompter: Inquirerer, options: CLIOptions) => { // ... command routing };

const app = new CLI(commands, { minimistOpts: { alias: { v: 'version', h: 'help' } } });

app.run();

Environment Variable Override

Always allow environment variables to override stored config:

import { appstash, resolve } from 'appstash';

function getEndpoint(): string { // Environment variable takes precedence if (process.env.MYCLI_ENDPOINT) { return process.env.MYCLI_ENDPOINT; }

// Fall back to stored profile const profile = getActiveProfile(); return profile?.endpoint || 'http://localhost:3000'; }

function getAuthToken(): string | undefined { // Environment variable takes precedence if (process.env.MYCLI_TOKEN) { return process.env.MYCLI_TOKEN; }

// Fall back to stored profile const profile = getActiveProfile(); return profile?.token; }

Testing

Isolate tests using custom baseDir :

import { appstash } from 'appstash'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path';

describe('CLI config', () => { let testDirs: ReturnType<typeof appstash>;

beforeEach(() => { testDirs = appstash('mycli', { baseDir: fs.mkdtempSync(path.join(os.tmpdir(), 'test-')), ensure: true }); });

afterEach(() => { fs.rmSync(testDirs.root, { recursive: true, force: true }); });

it('should store config', () => { // Test with isolated directories }); });

API Reference

appstash(tool, options?)

Get application directories for a tool.

Parameters:

  • tool (string): Tool name (e.g., 'pgpm', 'mycli')

  • options.baseDir (string): Custom base directory (default: os.homedir() )

  • options.ensure (boolean): Create directories if missing (default: false )

  • options.useXdgFallback (boolean): Use XDG fallback if home fails (default: true )

  • options.tmpRoot (string): Root for temp directory (default: os.tmpdir() )

Returns: AppStashResult with root , config , cache , data , logs , tmp paths

resolve(dirs, kind, ...parts)

Resolve a path within a specific directory.

Parameters:

  • dirs : Result from appstash()

  • kind : 'config' | 'cache' | 'data' | 'logs' | 'tmp'

  • parts : Path segments to join

Returns: Resolved path string

ensure(dirs)

Create directories if they don't exist.

Parameters:

  • dirs : Result from appstash()

Returns: { created: string[], usedFallback: boolean }

References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

constructive-graphql-codegen

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

github-workflows-ollama

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

github-workflows-pgpm

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

inquirerer-cli-building

No summary provided by upstream source.

Repository SourceNeeds Review