npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pawscript

v0.1.3

Published

PawScript: A command language with token-based suspension for text editors and command-driven applications

Downloads

29

Readme


PawScript: A command language with token-based suspension for text editors and command-driven applications.

## Features

- **Complex Command Syntax**: Support for sequences (`;`), conditionals (`&`), and alternatives (`|`)
- **Token-Based Suspension**: Pause and resume command execution for long-running operations
- **Macro System**: Define and execute reusable command sequences
- **Syntactic Sugar**: Automatic transformation of convenient syntax patterns
- **Type Safety**: Full TypeScript support with comprehensive type definitions
- **Host Agnostic**: Clean interface for integration with any application
- **Command Line Tool**: Execute PawScript files directly from the command line

## Installation

```bash
npm install pawscript

Command Line Usage

PawScript includes a paw command-line tool for executing scripts:

# Execute a script file
paw hello.paw

# Execute with arguments
paw script.paw -- arg1 arg2 arg3

# Execute from stdin
echo "echo 'Hello World'" | paw

# Execute redirected input with arguments
paw -- arg1 arg2 < script.paw

# Auto-adds .paw extension
paw hello  # Executes hello.paw

Standard Library Commands

The CLI provides these built-in commands:

  • argc - Returns the number of script arguments
  • argv [index] - Returns all arguments or a specific argument by index
  • echo/write/print <text> - Output text to stdout
  • read - Read a line from stdin (interactive or redirected)
  • true - Sets success state (exit code 0)
  • false - Sets error state (exit code 1)

Example Scripts

hello.paw:

echo "Hello from PawScript!";
echo "You provided {argc} arguments";

interactive.paw:

echo "What's your name?";
read;
echo "Hello, {get_result}!";

Library Usage

Quick Start

import { PawScript } from 'pawscript';

// Create PawScript interpreter
const pawscript = new PawScript({
  debug: true,
  allowMacros: true
});

// Set up host interface
pawscript.setHost({
  getCurrentContext: () => ({ cursor: { x: 0, y: 0 } }),
  updateStatus: (msg) => console.log(msg),
  requestInput: (prompt) => Promise.resolve('user input'),
  render: () => console.log('render called')
});

// Register commands
pawscript.registerCommands({
  'hello': (ctx) => {
    console.log('Hello from PawScript!');
    return true;
  },
  'echo': (ctx) => {
    console.log('Echo:', ctx.args[0]);
    return true;
  }
});

// Execute commands
pawscript.execute('hello');                    // Simple command
pawscript.execute("echo 'Hello World'");      // Command with arguments
pawscript.execute('hello; echo "chained"');   // Command sequence

Command Syntax

Basic Commands

pawscript.execute('save_file');
pawscript.execute("open_file '/path/to/file'");
pawscript.execute('move_cursor 10, 5');

Command Sequences

// Sequence: Execute all commands
pawscript.execute('save_file; close_buffer; open_file "new.txt"');

// Conditional: Stop on failure
pawscript.execute('save_file & close_buffer & exit');

// Alternative: Stop on success
pawscript.execute('auto_save | prompt_save | cancel');

Syntactic Sugar

// Automatic quote insertion for identifiers
pawscript.execute('macro hello(save_file; exit)');
// Becomes: pawscript.execute("macro 'hello', (save_file; exit)");

Built-in Commands

When allowMacros is enabled (default), PawScript automatically registers these built-in commands:

Macro Commands

  • macro <name>, <commands> - Define a new macro
  • call <name> - Execute a macro by name
  • macro_list - List all defined macros
  • macro_delete <name> - Delete a specific macro
  • macro_clear - Clear all macros

Macros

// Define macro using built-in command with syntactic sugar
pawscript.execute("macro quick_save(save_file; update_status 'Saved')");

// Define macro with arguments
pawscript.execute("macro greet(echo 'Hello $1!')");

// Execute macro using built-in command
pawscript.execute('call quick_save');

// Execute macro with arguments
pawscript.execute("call greet 'World'");

// Or execute macro directly (if it's defined)
pawscript.execute('quick_save');
pawscript.execute("greet 'Alice'");

// Macros in sequences
pawscript.execute('quick_save; close_buffer');

// Programmatic macro management (bypasses syntactic sugar)
pawscript.defineMacro('quick_save', 'save_file; update_status "Saved"');
pawscript.executeMacro('quick_save');

### Usage Examples
```typescript
// Define a macro (using syntactic sugar)
pawscript.execute("macro quick_save(save_file; update_status 'Saved')");

// Execute a macro
pawscript.execute('call quick_save');

// List all macros
pawscript.execute('macro_list');

// Delete a macro
pawscript.execute('macro_delete quick_save');

// Clear all macros
pawscript.execute('macro_clear');

Disabling Built-in Commands

const pawscript = new PawScript({
  allowMacros: false  // Disables macro commands
});

// Or dynamically
pawscript.configure({ allowMacros: false });

Token-Based Suspension (The "Paws" Feature)

For long-running operations, commands can return tokens that pause execution. This is the correct pattern:

pawscript.registerCommand('async_operation', (ctx) => {
  // Request a token to pause execution
  const token = ctx.requestToken((tokenId) => {
    console.log('Operation was interrupted:', tokenId);
  });
  
  // Start async operation using setImmediate
  setImmediate(() => {
    // Simulate async work
    setTimeout(() => {
      console.log('Async operation completed');
      ctx.resumeToken(token, true); // Resume with success
    }, 5000);
  });
  
  return token; // Return token immediately to pause sequence
});

// This will pause at async_operation and resume when it completes
pawscript.execute('async_operation; echo "This runs after async completes"');

Key Points About Tokens:

  1. Immediate Return: Commands must return the token immediately, not wait for async completion
  2. Use setImmediate: Start async work with setImmediate() to avoid blocking
  3. Resume Later: Call ctx.resumeToken() when the async operation completes
  4. Cleanup Support: Provide cleanup callbacks for interruption handling

Result Management

PawScript commands can set formal results that flow through command sequences:

pawscript.registerCommand('calculate', (ctx) => {
  const result = Number(ctx.args[0]) + Number(ctx.args[1]);
  ctx.setResult(result);  // Set formal result
  return true;            // Indicate success
});

// Result flows through sequences
pawscript.execute('calculate 5, 3; print_result');  // Prints 8

Brace Expressions

Use {...} for command evaluation and ${...} for prefixed evaluation:

// Execute command and substitute result
pawscript.execute('echo {calculate 10, 5}');  // Outputs: 15

// Execute command and prefix result with $
pawscript.execute('echo ${get_arg_number}');  // If returns "2", outputs: $2

Host Interface

PawScript integrates with your application through a host interface:

interface IPawScriptHost {
  getCurrentContext(): any;
  updateStatus(message: string): void;
  requestInput(prompt: string, defaultValue?: string): Promise<string>;
  render(): void;
  // Optional methods for advanced features
  createWindow?(options: any): string;
  removeWindow?(id: string): void;
  saveState?(): any;
  restoreState?(snapshot: any): void;
  emit?(event: string, ...args: any[]): void;
  on?(event: string, handler: Function): void;
}

Configuration

const pawscript = new PawScript({
  debug: false,                    // Enable debug logging
  defaultTokenTimeout: 300000,     // Token timeout in ms (5 minutes)
  enableSyntacticSugar: true,      // Enable syntax transformations
  allowMacros: true,               // Enable macro system
  commandSeparators: {
    sequence: ';',                 // Command sequence separator
    conditional: '&',              // Conditional separator
    alternative: '|'               // Alternative separator
  }
});

Integration Example

Here's how to integrate PawScript with an existing application:

// Your existing application
class MyEditor {
  constructor() {
    this.pawscript = new PawScript({ debug: true });
    this.setupPawScript();
  }
  
  setupPawScript() {
    // Set up host interface
    this.pawscript.setHost({
      getCurrentContext: () => ({
        cursor: this.getCursorPosition(),
        selection: this.getSelection(),
        filename: this.getCurrentFilename()
      }),
      updateStatus: (msg) => this.statusBar.show(msg),
      requestInput: (prompt, def) => this.showPrompt(prompt, def),
      render: () => this.redraw()
    });
    
    // Register application-specific commands
    this.pawscript.registerCommands({
      'save_file': (ctx) => this.saveCurrentFile(),
      'open_file': (ctx) => this.openFile(ctx.args[0]),
      'move_cursor': (ctx) => this.moveCursor(ctx.args[0], ctx.args[1]),
      'find_text': (ctx) => this.findText(ctx.args[0])
    });
  }
  
  // Handle user input (key presses, menu clicks, etc.)
  handleCommand(commandString) {
    this.pawscript.execute(commandString);
  }
}

API Reference

PawScript

Constructor

new PawScript(config?: PawScriptConfig)

Methods

  • setHost(host: IPawScriptHost): Set the host application interface
  • registerCommand(name: string, handler: PawScriptHandler): Register a single command
  • registerCommands(commands: Record<string, PawScriptHandler>): Register multiple commands
  • execute(commandString: string, ...args: any[]): Execute a command string
  • requestToken(cleanup?, parent?, timeout?): Request an async token
  • resumeToken(tokenId: string, result: boolean): Resume a suspended command
  • defineMacro(name: string, commands: string): Define a macro
  • executeMacro(name: string): Execute a macro
  • listMacros(): Get list of defined macros
  • deleteMacro(name: string): Delete a macro
  • clearMacros(): Clear all macros
  • getTokenStatus(): Get information about active tokens
  • configure(config: Partial<PawScriptConfig>): Update configuration

PawScriptHandler

Command handlers receive a PawScriptContext object:

interface PawScriptContext {
  host: IPawScriptHost;           // Reference to host application
  args: any[];                    // Parsed command arguments
  state: any;                     // Current application state
  requestToken(cleanup?: Function): string;  // Request async token
  resumeToken(tokenId: string, result: boolean): void;  // Resume token
  // Result management
  setResult(value: any): void;    // Set formal result
  getResult(): any;               // Get current result
  hasResult(): boolean;           // Check if result exists
  clearResult(): void;            // Clear current result
}

Return Values

  • boolean: Synchronous success/failure
  • string (starting with "token_"): Async token for suspension

Command Parsing

PawScript automatically parses command arguments:

// String arguments (quoted)
pawscript.execute("echo 'hello world'");
// → ctx.args = ['hello world']

// Multiple arguments
pawscript.execute("move_cursor 10, 20");
// → ctx.args = [10, 20]

// Mixed types
pawscript.execute("create_window 'MyWindow', 100, 50, true");
// → ctx.args = ['MyWindow', 100, 50, true]

// Parenthetical content (passed as-is)
pawscript.execute("macro hello(save_file; exit)");
// → After syntactic sugar: ctx.args = ['hello', 'save_file; exit']

Error Handling

PawScript provides robust error handling:

pawscript.registerCommand('risky_operation', (ctx) => {
  try {
    // Risky operation
    return performRiskyOperation();
  } catch (error) {
    ctx.host.updateStatus(`Operation failed: ${error.message}`);
    return false;
  }
});

Testing

The library works well with Jest and other testing frameworks:

import { PawScript } from 'pawscript';

describe('My Application Commands', () => {
  let pawscript: PawScript;
  let mockHost: any;

  beforeEach(() => {
    mockHost = {
      getCurrentContext: jest.fn().mockReturnValue({}),
      updateStatus: jest.fn(),
      requestInput: jest.fn(),
      render: jest.fn()
    };

    pawscript = new PawScript({ debug: false });
    pawscript.setHost(mockHost);
  });

  test('should execute my command', () => {
    const myCommand = jest.fn().mockReturnValue(true);
    pawscript.registerCommand('my_command', myCommand);
    
    const result = pawscript.execute('my_command');
    expect(result).toBe(true);
    expect(myCommand).toHaveBeenCalled();
  });

  test('should handle async commands with tokens', () => {
    const asyncCommand = jest.fn().mockImplementation((ctx) => {
      const token = ctx.requestToken();
      setImmediate(() => {
        ctx.resumeToken(token, true);
      });
      return token;
    });
    
    pawscript.registerCommand('async_cmd', asyncCommand);
    
    const result = pawscript.execute('async_cmd');
    expect(typeof result).toBe('string');
    expect(result).toMatch(/^token_/);
  });
});

Advanced Features

Token Chaining

PawScript automatically chains tokens in command sequences:

// If 'async_save' returns a token, 'async_backup' will wait for it
pawscript.execute('async_save; async_backup; notify_complete');

Fallback Handlers

You can register fallback handlers for unknown commands:

pawscript.setFallbackHandler((cmdName, args) => {
  if (cmdName.startsWith('custom_')) {
    return handleCustomCommand(cmdName, args);
  }
  return null; // Let PawScript handle as unknown command
});

Token Status Monitoring

Monitor active tokens for debugging:

const status = pawscript.getTokenStatus();
console.log(`Active tokens: ${status.activeCount}`);
status.tokens.forEach(token => {
  console.log(`${token.id}: age ${token.age}ms, children: ${token.childCount}`);
});

Best Practices

1. Proper Async Pattern

// ✅ CORRECT
pawscript.registerCommand('async_save', (ctx) => {
  const token = ctx.requestToken();
  setImmediate(() => {
    fs.writeFile('file.txt', data, (err) => {
      ctx.resumeToken(token, !err);
    });
  });
  return token;
});

// ❌ WRONG - Don't use Promises directly
pawscript.registerCommand('wrong_async', async (ctx) => {
  await fs.promises.writeFile('file.txt', data);
  return true;
});

2. Error Handling

pawscript.registerCommand('safe_command', (ctx) => {
  try {
    const result = riskyOperation(ctx.args[0]);
    ctx.host.updateStatus('Operation completed');
    return true;
  } catch (error) {
    ctx.host.updateStatus(`Error: ${error.message}`);
    return false;
  }
});

3. State Management

pawscript.registerCommand('context_aware', (ctx) => {
  const { cursor, selection } = ctx.state;
  if (!selection) {
    ctx.host.updateStatus('No selection available');
    return false;
  }
  // Process selection...
  return true;
});

The Name

PawScript gets its name from the token-based suspension system - when a command needs to wait for an async operation to complete, execution "paws" (pauses) until the operation finishes. The name also nods to the language's origins in the mew text editor (which has a cat mascot), while being professional enough for standalone use.

Migration from Other Command Systems

If you're migrating from a different command system:

  1. Wrap existing handlers: Your existing command handlers can be wrapped to match the PawScriptHandler interface
  2. Update async commands: Convert Promise-based async commands to use the token pattern
  3. Configure syntax: Disable syntactic sugar if you need exact command parsing compatibility
  4. Update macros: Migrate existing macros to PawScript's macro system

License

MIT

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass with npm test
  5. Submit a pull request

Changelog

0.1.3

  • Implemented braces for command evaluation (function-like behavior)
  • Implemented substitution for macro arguments $* $# $1 $2
  • Added result management system with formal results, in addition to the success/fail states
  • Added command-line tool (paw) for executing PawScript files
  • Added standard library commands (argc, argv, echo, read, true, false)
  • Fixed syntactic sugar parsing for multi-line content
  • Fixed token suspension and resumption for async operations
  • Improved macro execution with proper state management
  • Enhanced test coverage and documentation

0.1.2

  • Minor fixes

0.1.1

  • Initial release
  • Basic command execution with sequences, conditionals, and alternatives
  • Token-based suspension system ("paws" feature)
  • Macro system with define/execute/list capabilities
  • Syntactic sugar for convenient command syntax
  • Full TypeScript support with comprehensive type definitions
  • Host-agnostic design for easy integration
  • Comprehensive test suite and documentation