Skip to content
Back to skills

Parsh Files

ASecurity

How to use @parshjs/files for typed JSON file storage in a parsh CLI. Use when adding persistent config or state to a CLI (credentials, user prefs, cached state) — anything written as JSON on disk. Pairs with the parsh skill — read that first if you don't know how parsh CLIs are structured.

  • 4 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added September 12, 2026
businessnodeapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 12, 2026

npx -y skills add aibot88/sec_skill_store --skill parsh-files --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Parsh Files?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Parsh Files
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aibot88-parsh-files/badge)](https://www.skillsdirectory.com/skills/aibot88-parsh-files)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: parsh-files
description: How to use @parshjs/files for typed JSON file storage in a parsh CLI. Use when adding persistent config or state to a CLI (credentials, user prefs, cached state) — anything written as JSON on disk. Pairs with the parsh skill — read that first if you don't know how parsh CLIs are structured.
---

# parsh-files

[`@parshjs/files`](https://www.npmjs.com/package/@parshjs/files) gives a parsh CLI a typed `ctx.context.files` for persistent JSON storage. Each file is declared with a [Standard Schema v1](https://standardschema.dev) schema (Zod, Valibot, ArkType, …); reads and writes are validated and atomic.

For the broader parsh workflow (commands, codegen, `Register`), see [`../parsh/SKILL.md`](../parsh/SKILL.md).

## When to use

Persistent CLI state — config, credentials, tokens, small caches. Not for large data, binary blobs, or hot-path I/O.

## Setup

Inject `createFilesContext` into `createCli`'s `context` and register the CLI so the types propagate.

```ts
// src/main.ts
import { join } from 'node:path';
import { createCli } from '@parshjs/core';
import { createFilesContext, osHomeConfigDir } from '@parshjs/files';
import { z } from 'zod';
import { commandTree } from './commandTree.gen.ts';

const cli = createCli({
  programName: 'mycli',
  tree: commandTree,
  context: {
    files: createFilesContext({
      basePath: join(osHomeConfigDir(), 'mycli'),
      files: {
        credentials: {
          filename: 'credentials.json',
          schema: z.object({ accessKey: z.string().min(1), secretKey: z.string().min(1) }),
        },
        prefs: {
          filename: 'prefs.json',
          schema: z.object({ region: z.string(), color: z.boolean() }),
          defaults: { region: 'us-east-1', color: true },
        },
      },
    }),
  },
});

declare module '@parshjs/core' {
  interface Register {
    cli: typeof cli;
  }
}

await cli.main();
```

`osHomeConfigDir()` resolves per-OS (`~/.config/<x>`, `~/Library/Application Support/<x>`, `%APPDATA%\<x>`). `osHomeDir()` is `~`.

## Patterns

### Defaults — drop the `?? DEFAULTS` boilerplate

Declaring `defaults` on a spec makes `read()` return them when the file is missing (instead of throwing). Defaults live in memory only — nothing is written until an explicit write. Type-checked against the schema's inferred output.

### Gate a subcommand on a file existing

`ensureExists()` in `beforeHandler` produces a friendly user-facing error and lets the handler use `read()` (not `maybeRead()`).

```ts
defineCommand('s3 buckets list', {
  options: {},
  beforeHandler: async ({ files }) => {
    await files.credentials.ensureExists({ message: 'Run `mycli configure` first.' });
  },
  handler: async ({ files }) => {
    const creds = await files.credentials.read();
    /* … */
  },
});
```

### Sync access with `load()`

For call sites that need synchronous field access (constructing a client at startup, reads inside a TUI loop), `await load()` to get a stateful handle with a sync `.value`. `load()` is idempotent — call it from any number of `beforeHandler`s, disk is read once.

```ts
defineCommand('serve', {
  options: {},
  beforeHandler: async ({ files }) => {
    await files.prefs.load();
  },
  handler: async ({ files }) => {
    const prefs = await files.prefs.load();
    const client = createApiClient(prefs.value.region);   // sync
    await prefs.set({ region: 'eu-west-2' });             // partial write, updates .value
  },
});
```

`set()` and `replace()` keep `.value` in sync with disk. `reload()` is only for the case where something *outside* this handle modified the file (another process, a hand-edit). Single-process ownership is assumed.

## Common mistakes

- **Forgetting the `Register` augmentation.** Without it, `ctx.context.files` has no type. See the [parsh skill](../parsh/SKILL.md#shared-context).
- **`read()` without `defaults` or `ensureExists()`.** It throws on missing. Either declare `defaults` on the spec, gate via `ensureExists()` in `beforeHandler`, or use `maybeRead()` and handle `null`.
- **Hand-rolling JSON next to `@parshjs/files`.** Use the typed handle so writes are atomic and schema-checked.
- **Calling `load()` again to refresh.** It's idempotent — returns the cached handle. Use `reload()` on the loaded handle to re-read from disk.

Files in this skill

  • SKILL.md4.2 KB
  • source.json794 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.