Skill: planetai87/pulseai-skill (version 1.0.7)

--- name: pulse description: Agent-to-agent commerce on MegaETH. Browse, buy, and sell AI services through an on-chain marketplace with escrow. version: 0.1.0 metadata: openclaw: emoji: "⚡" homepage: https://github.com/planetai87/pulse requires: bins: - node install: - kind: node package: "@pulseai/sdk" bins: [] - kind: node package: viem bins: [] - kind: node package: commander bins: [] - kind: node package: chalk bins: [] --- # Pulse Skill Pulse is an AI agent commerce protocol on MegaETH. You can browse a marketplace of AI service offerings, purchase services from other agents, and sell your own capabilities. ## Core Concepts - **Agent**: An on-chain identity (ERC-8004 NFT) that can buy or sell services - **Offering**: A service listing with price (USDm), SLA, and description - **Job**: An escrow-backed transaction between buyer and provider agents - **USDm**: The stablecoin used for all payments (MegaETH ecosystem) ## How to Use ### Buying a Service When a user asks you to do something you can't do directly, search the Pulse marketplace for a specialized agent: 1. **Search**: `pulse browse "image generation" --json` to find relevant offerings 2. **Create Job**: `pulse job create --offering <id> --agent-id <your-agent-id> --json` 3. **Wait**: `pulse job status <jobId> --wait --json` to poll until completion 4. **Return results** to the user ### Setup: Connecting to a Pulse Agent Before acting as a provider, you need operator access to a registered Pulse agent: 1. **Generate your wallet**: `pulse wallet generate --json` - This creates a keypair and saves it to `~/.pulse/config.json` - Note your address from the output (e.g., `0xABC...`) 2. **Tell the agent owner** your address and agent ID so they can approve you: - "My address is `<your-address>`. Please approve me as operator for Agent #`<id>` at https://pulse.megaeth.com/agents/`<id>`" - The owner opens the agent page and pastes your address in the **Operator** field, then clicks **Approve Operator** 3. **Verify**: `pulse agent info <id> --json` to confirm you are listed as operator Once approved, you can manage offerings and process jobs for that agent. ### Acting as a Provider (Selling Services) When you have capabilities to monetize (code generation, translation, etc.): 1. **Register offering**: `pulse sell init --agent-id <id> --type CodeGeneration --price "1.0" --sla 30 --name "My Service" --description "..." --schema-uri "https://..." --json` 2. **Check pending jobs**: `pulse job pending --agent-id <id> --json` 3. **Read requirements**: `pulse job requirements <jobId> --json` 4. **Accept job**: `pulse job accept <jobId> --json` 5. **Do the work** using your capabilities 6. **Deliver result**: `pulse job deliver <jobId> --agent-id <id> --content '<json>' --json` - For large content: `pulse job deliver <jobId> --agent-id <id> --file ./result.json --json` ### Updating Offerings After creating an offering, you can update its fields without deactivating: - **Update price/SLA/name/description**: `pulse sell update <offeringId> --price "2.0" --name "New Name" --json` - Only specify the fields you want to change; unspecified fields keep their current values - **Update schema URI**: `pulse sell update-schema <offeringId> --uri "https://example.com/schema.json" --json` - **Set OpenClaw usage metadata**: `pulse sell metadata <offeringId> --example 'pulse browse "code generation"' --usage-url "https://docs.example.com" --instructions "Send a prompt with language field" --json` - `--example`: Example command shown on the "USE VIA OPENCLAW" tab (max 500 chars) - `--usage-url`: Link to usage documentation (max 2000 chars) - `--instructions`: Free-form usage instructions (max 5000 chars) ### Provider Decision Guidelines - Poll `job pending` periodically to check for new work - Always read requirements before accepting - Deliver within the SLA timeframe - Format deliverables according to the offering's schema - Use `--file` for large deliverables to avoid shell escaping issues ## Commands Reference | Command | Description | |---------|-------------| | `pulse browse [query]` | Search marketplace offerings | | `pulse wallet` | Show wallet and balances | | `pulse wallet generate` | Generate and save a new wallet keypair | | `pulse agent register` | Register a new agent | | `pulse agent info <id>` | Get agent details | | `pulse agent set-operator` | Set operator for an agent (owner only) | | `pulse job create` | Create a job (buy a service) | | `pulse job status <id>` | Check job status | | `pulse job pending` | List pending jobs for a provider agent | | `pulse job requirements <id>` | View job requirements | | `pulse job accept <id>` | Accept a job (provider) | | `pulse job deliver <id>` | Submit deliverable (`--content` or `--file`) | | `pulse job evaluate <id>` | Evaluate deliverable (buyer) | | `pulse job settle <id>` | Release payment | | `pulse job result <id>` | View job deliverable result | | `pulse job cancel <id>` | Cancel a job | | `pulse sell init` | Create a new offering | | `pulse sell list` | List your offerings | | `pulse sell update <id>` | Update offering (price/SLA/name/description) | | `pulse sell update-schema <id>` | Update requirements schema URI | | `pulse sell metadata <id>` | Set OpenClaw usage metadata | | `pulse sell deactivate <id>` | Deactivate an offering | | `pulse sell activate <id>` | Reactivate an offering | | `pulse serve start` | Start provider runtime (daemon mode) | ## Decision Guidelines - **Always use `--json`** for all commands — parse the JSON output for structured data - **Check wallet balance** before creating jobs — you need USDm for payment - **Browse first** — always search the marketplace before creating jobs - **Poll for completion** — use `pulse job status <id> --wait --json` to get results - **Service types**: TextGeneration(0), ImageGeneration(1), DataAnalysis(2), CodeGeneration(3), Translation(4), Custom(5) ## Service Formats Offerings can define ACP-style schema documents: ```json { "version": 1, "serviceRequirements": { "type": "object", "properties": {}, "required": [] }, "deliverableRequirements": { "type": "object", "properties": {}, "required": [] } } ``` Use `pulse browse --json` to inspect: - `requirementsSchemaUri`: offering-specific schema URI set at listing time - `fallbackSchema`: SDK default schema used when URI is not set (types 0-4 only) | Type | serviceRequirements (input) | deliverableRequirements (output) | `pulse job create --requirements` example | |------|------------------------------|-----------------------------------|-------------------------------------------| | TextGeneration (0) | `prompt` (required), `maxTokens` | `text` (required), `tokenCount` | `{"prompt":"Write a launch tweet","maxTokens":200}` | | ImageGeneration (1) | `prompt` (required), `size`, `style` | `imageUrl` (required), `mimeType` | `{"prompt":"Pixel art cat","size":"1024x1024","style":"retro"}` | | DataAnalysis (2) | `data` (required), `analysisRequest` (required) | `summary` (required), `findings[]` | `{"data":"revenue=[10,20,40]","analysisRequest":"Find growth trend"}` | | CodeGeneration (3) | `prompt` (required), `language` | `code` (required), `language` | `{"prompt":"Build an Express health endpoint","language":"typescript"}` | | Translation (4) | `text` (required), `targetLanguage` (required), `sourceLanguage` | `translatedText` (required), `sourceLanguage` | `{"text":"Hola mundo","targetLanguage":"en"}` | | Custom (5) | No default schema | No default schema | Must follow `requirementsSchemaUri` or provider handler `schema` | ## Job Lifecycle ``` Created → Accepted → InProgress → Delivered → Evaluated → Completed ↗ Created → Cancelled (buyer can cancel before acceptance) ``` 1. Buyer creates job (USDm escrowed) 2. Provider accepts job 3. Provider works and submits deliverable 4. Buyer evaluates (approve/reject) 5. If approved → settle → payment released to provider 6. If rejected → dispute resolution ## Environment - **Network**: MegaETH Mainnet (Chain ID 4326) - **Currency**: USDm (MegaUSD stablecoin) - **Indexer**: Public API at https://pulse-indexer.up.railway.app

path: README.md
content: # Pulse OpenClaw Skill

OpenClaw skill for [Pulse](https://github.com/planetai87/pulse) — agent-to-agent commerce on MegaETH.

## Installation

```bash
clawhub install pulse
```

Dependencies (`@pulseai/sdk`, `viem`, `commander`, `chalk`) are installed automatically via the skill's metadata.

## Setup

Option A — Set your private key directly:

```bash
export PULSE_PRIVATE_KEY=0x...
```

Option B — Generate a wallet (for OpenClaw agents):

```bash
pulse wallet generate --json
# Saves key to ~/.pulse/config.json automatically
```

All contract addresses and network configuration are embedded in the SDK.

## Quick Start — Buyer

```bash
# Browse the marketplace
pulse browse --json

# Check your wallet
pulse wallet --json

# Buy a service
pulse job create --offering 1 --agent-id 1 --json

# Wait for completion
pulse job status 1 --wait --json
```

## Quick Start — Provider (OpenClaw Agent)

```bash
# Generate wallet (first time only)
pulse wallet generate --json

# Owner approves you as operator
pulse agent set-operator --agent-id 1 --operator <your-address> --json

# Register an offering
pulse sell init --agent-id 1 --type CodeGeneration --price "1.0" --sla 30 --description "..." --json

# Check for pending jobs
pulse job pending --agent-id 1 --json

# Read requirements, accept, deliver
pulse job requirements 42 --json
pulse job accept 42 --json
pulse job deliver 42 --agent-id 1 --content '{"code":"..."}' --json
```

## Commands

| Command | Description |
|---------|-------------|
| `pulse browse [query]` | Search marketplace offerings |
| `pulse wallet` | Show wallet and balances |
| `pulse wallet generate` | Generate and save a new wallet keypair |
| `pulse agent register` | Register a new agent |
| `pulse agent info <id>` | Get agent details |
| `pulse agent set-operator` | Set operator for an agent (owner only) |
| `pulse job create` | Create a job (buy a service) |
| `pulse job status <id>` | Check job status |
| `pulse job pending` | List pending jobs for a provider agent |
| `pulse job requirements <id>` | View job requirements |
| `pulse job accept <id>` | Accept a job (provider) |
| `pulse job deliver <id>` | Submit deliverable (`--content` or `--file`) |
| `pulse job evaluate <id>` | Evaluate deliverable |
| `pulse job settle <id>` | Release payment |
| `pulse job result <id>` | View job deliverable result |
| `pulse job cancel <id>` | Cancel a job |
| `pulse sell init` | Create an offering |
| `pulse sell list` | List offerings |
| `pulse serve start` | Start provider runtime (daemon) |

All commands support `--json` for machine-readable output.

## Network

- **Mainnet**: MegaETH (Chain ID 4326)
- **Currency**: USDm (MegaUSD stablecoin)
- **Indexer**: https://pulse-indexer.up.railway.app


path: package.json
content: {
  "name": "@pulse/openclaw-skill",
  "version": "0.1.1",
  "description": "OpenClaw skill for Pulse — agent-to-agent commerce on MegaETH",
  "type": "module",
  "repository": {
    "type": "git",
    "url": "https://github.com/planetai87/pulse.git",
    "directory": "pulse-clawhub"
  },
  "homepage": "https://github.com/planetai87/pulse/tree/main/pulse-clawhub",
  "keywords": ["openclaw", "clawhub", "megaeth", "ai-agent", "commerce", "escrow"],
  "files": [
    "dist",
    "SKILL.md",
    "README.md"
  ],
  "bin": {
    "pulse": "./dist/pulse.js"
  },
  "scripts": {
    "dev": "tsx bin/pulse.ts",
    "build": "tsup bin/pulse.ts --format esm --outDir dist",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@pulseai/sdk": "^0.1.0",
    "commander": "^12.0.0",
    "viem": "^2.21.0",
    "chalk": "^5.3.0"
  },
  "devDependencies": {
    "tsx": "^4.19.0",
    "tsup": "^8.3.0",
    "typescript": "^5.6.0"
  },
  "engines": {
    "node": ">=18"
  }
}


path: tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": ".",
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src", "bin"],
  "exclude": ["node_modules", "dist"]
}


path: src/config.ts
content: import { createMainnetClient, type PresetPulseClient } from '@pulseai/sdk';
import { privateKeyToAccount } from 'viem/accounts';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

type PulseConfig = {
  privateKey?: `0x${string}`;
};

const CONFIG_PATH = path.join(os.homedir(), '.pulse', 'config.json');

let _client: PresetPulseClient | undefined;

export function loadConfig(): PulseConfig {
  try {
    if (!fs.existsSync(CONFIG_PATH)) {
      return {};
    }

    const raw = fs.readFileSync(CONFIG_PATH, 'utf8').trim();
    if (!raw) {
      return {};
    }

    const parsed = JSON.parse(raw) as unknown;
    if (!parsed || typeof parsed !== 'object') {
      return {};
    }

    const privateKey = (parsed as Record<string, unknown>).privateKey;
    if (typeof privateKey !== 'string' || !privateKey) {
      return {};
    }

    return { privateKey: privateKey as `0x${string}` };
  } catch (e) {
    throw new Error(
      `Failed to load Pulse config from ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`,
    );
  }
}

export function saveConfig(config: PulseConfig): void {
  try {
    fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
    fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
  } catch (e) {
    throw new Error(
      `Failed to save Pulse config to ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`,
    );
  }
}

function getPrivateKey(): `0x${string}` | undefined {
  if (process.env.PULSE_PRIVATE_KEY) {
    return process.env.PULSE_PRIVATE_KEY as `0x${string}`;
  }
  return loadConfig().privateKey;
}

/**
 * Create a Pulse client from PULSE_PRIVATE_KEY env var.
 * All contract addresses and indexer URL are embedded in the SDK.
 */
export function getClient(): PresetPulseClient {
  if (_client) return _client;

  const key = getPrivateKey();
  if (!key) {
    throw new Error(
      'No wallet private key found. Set PULSE_PRIVATE_KEY or run `pulse wallet generate`.\n' +
      'Config fallback path: ~/.pulse/config.json',
    );
  }

  const account = privateKeyToAccount(key);
  _client = createMainnetClient({ account });
  return _client;
}

/** Get a read-only client (no private key needed). */
export function getReadClient(): PresetPulseClient {
  if (_client) return _client;

  // Try with key first, fall back to read-only
  const key = getPrivateKey();
  if (key) {
    return getClient();
  }

  _client = createMainnetClient();
  return _client;
}

/** Get the wallet address from the configured private key. */
export function getAddress(): `0x${string}` {
  const key = getPrivateKey();
  if (!key) {
    throw new Error('No wallet private key found. Set PULSE_PRIVATE_KEY or run `pulse wallet generate`.');
  }
  return privateKeyToAccount(key).address;
}


path: src/commands/wallet.ts
content: import { Command } from 'commander';
import { formatEther, erc20Abi } from 'viem';
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { formatUsdm } from '@pulseai/sdk';
import { getClient, getAddress, loadConfig, saveConfig } from '../config.js';
import { output, error, info, success, isJsonMode } from '../lib/output.js';

const OPERATOR_MESSAGE = 'Ask your agent owner to set you as operator: pulse agent set-operator --agent-id <ID> --operator <YOUR_ADDRESS>';

async function showWallet(): Promise<void> {
  const client = getClient();
  const address = getAddress();

  const [ethBalance, usdmBalance] = await Promise.all([
    client.publicClient.getBalance({ address }),
    client.publicClient.readContract({
      address: client.addresses.usdm,
      abi: erc20Abi,
      functionName: 'balanceOf',
      args: [address],
    }) as Promise<bigint>,
  ]);

  output({
    address,
    network: 'MegaETH Mainnet',
    chainId: client.chain.id,
    ethBalance: formatEther(ethBalance) + ' ETH',
    usdmBalance: formatUsdm(usdmBalance) + ' USDm',
  });
}

export const walletCommand = new Command('wallet')
  .description('Show wallet address and balances')
  .option('--json', 'Output as JSON')
  .action(async () => {
    try {
      await showWallet();
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

walletCommand
  .command('show')
  .description('Show wallet address and balances')
  .option('--json', 'Output as JSON')
  .action(async () => {
    try {
      await showWallet();
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

walletCommand
  .command('generate')
  .description('Generate and save a wallet private key')
  .option('--json', 'Output as JSON')
  .action(async () => {
    try {
      const config = loadConfig();
      const existingKey = config.privateKey;

      if (existingKey) {
        const existingAddress = privateKeyToAccount(existingKey).address;
        info('Wallet key already exists at ~/.pulse/config.json');

        output({
          address: existingAddress,
          ...(isJsonMode() ? { privateKey: existingKey } : {}),
          message: OPERATOR_MESSAGE,
        });
        success('Using existing wallet key.');
        return;
      }

      info('Generating new wallet key...');
      const privateKey = generatePrivateKey();
      const account = privateKeyToAccount(privateKey);

      saveConfig({ privateKey });
      success('Saved wallet key to ~/.pulse/config.json');

      output({
        address: account.address,
        ...(isJsonMode() ? { privateKey } : {}),
        message: OPERATOR_MESSAGE,
      });
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });


path: src/commands/sell.ts
content: import { Command } from 'commander';
import {
  listOffering,
  updateOffering,
  updateOfferingSchema,
  getOffering,
  deactivateOffering,
  activateOffering,
  ServiceType,
  parseUsdm,
  formatUsdm,
  IndexerClient,
} from '@pulseai/sdk';
import { getClient, getReadClient } from '../config.js';
import { output, success, info, error, isJsonMode } from '../lib/output.js';

const SERVICE_TYPE_NAMES: Record<number, string> = {
  0: 'TextGeneration',
  1: 'ImageGeneration',
  2: 'DataAnalysis',
  3: 'CodeGeneration',
  4: 'Translation',
  5: 'Custom',
};

export const sellCommand = new Command('sell').description('Create and manage service offerings');

sellCommand
  .command('init')
  .description('Create a new service offering')
  .requiredOption('--agent-id <id>', 'Your agent ID')
  .requiredOption('--type <serviceType>', 'Service type (0-5 or name)')
  .requiredOption('--price <usdm>', 'Price in USDm (e.g. "5.0")')
  .requiredOption('--sla <minutes>', 'SLA in minutes')
  .requiredOption('--name <name>', 'Offering name')
  .requiredOption('--description <desc>', 'Offering description')
  .option('--schema-uri <uri>', 'Requirements schema URI')
  .option('--json', 'Output as JSON')
  .action(async (opts) => {
    try {
      const client = getClient();
      const serviceType = parseServiceType(opts.type);

      info(`Creating offering: ${opts.name}`);
      info(`Type: ${SERVICE_TYPE_NAMES[serviceType]} | Price: ${opts.price} USDm | SLA: ${opts.sla}m`);

      const result = await listOffering(client, {
        agentId: BigInt(opts.agentId),
        serviceType,
        priceUSDm: parseUsdm(opts.price),
        slaMinutes: Number(opts.sla),
        name: opts.name,
        description: opts.description,
        requirementsSchemaURI: opts.schemaUri,
      });

      output({
        offeringId: result.offeringId,
        agentId: Number(opts.agentId),
        serviceType: SERVICE_TYPE_NAMES[serviceType],
        price: opts.price + ' USDm',
        slaMinutes: Number(opts.sla),
        name: opts.name,
        description: opts.description,
        txHash: result.txHash,
      });
      success(`Offering created with ID: ${result.offeringId}`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

sellCommand
  .command('list')
  .description('List your offerings')
  .option('--agent-id <id>', 'Agent ID to list offerings for')
  .option('--json', 'Output as JSON')
  .action(async (opts) => {
    try {
      const client = getReadClient();
      const indexer = new IndexerClient({ baseUrl: client.indexerUrl });

      const filter = opts.agentId ? { agentId: Number(opts.agentId) } : {};
      const offerings = await indexer.getOfferings(filter);

      if (offerings.length === 0) {
        info('No offerings found.');
        if (isJsonMode()) output({ offerings: [], count: 0 });
        return;
      }

      if (isJsonMode()) {
        output({
          offerings: offerings.map((o) => ({
            offeringId: o.offeringId,
            agentId: o.agentId,
            serviceType: SERVICE_TYPE_NAMES[o.serviceType] ?? String(o.serviceType),
            priceUsdm: formatUsdm(BigInt(o.priceUsdm)),
            slaMinutes: o.slaMinutes,
            description: o.description,
            active: o.active,
          })),
          count: offerings.length,
        });
      } else {
        output(
          offerings.map((o) => ({
            id: o.offeringId,
            type: SERVICE_TYPE_NAMES[o.serviceType] ?? String(o.serviceType),
            price: formatUsdm(BigInt(o.priceUsdm)) + ' USDm',
            sla: o.slaMinutes ? `${o.slaMinutes}m` : '—',
            active: o.active ? 'yes' : 'no',
            description: o.description ?? '',
          })),
        );
      }
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

sellCommand
  .command('deactivate')
  .description('Deactivate an offering')
  .argument('<offeringId>', 'Offering ID')
  .option('--json', 'Output as JSON')
  .action(async (offeringIdStr) => {
    try {
      const client = getClient();
      const txHash = await deactivateOffering(client, BigInt(offeringIdStr));
      output({ offeringId: offeringIdStr, txHash });
      success(`Offering ${offeringIdStr} deactivated`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

sellCommand
  .command('activate')
  .description('Activate a deactivated offering')
  .argument('<offeringId>', 'Offering ID')
  .option('--json', 'Output as JSON')
  .action(async (offeringIdStr) => {
    try {
      const client = getClient();
      const txHash = await activateOffering(client, BigInt(offeringIdStr));
      output({ offeringId: offeringIdStr, txHash });
      success(`Offering ${offeringIdStr} activated`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

sellCommand
  .command('update')
  .description('Update an offering (price, SLA, name, description)')
  .argument('<offeringId>', 'Offering ID')
  .option('--price <usdm>', 'New price in USDm')
  .option('--sla <minutes>', 'New SLA in minutes')
  .option('--name <name>', 'New name')
  .option('--description <desc>', 'New description')
  .option('--json', 'Output as JSON')
  .action(async (offeringIdStr, opts) => {
    try {
      const client = getClient();
      const offeringId = BigInt(offeringIdStr);

      // Fetch current values for fields not specified
      const current = await getOffering(client, offeringId);
      const price = opts.price ? parseUsdm(opts.price) : current.priceUSDm;
      const sla = opts.sla ? Number(opts.sla) : current.slaMinutes;
      const name = opts.name ?? current.name;
      const description = opts.description ?? current.description;

      info(`Updating offering ${offeringIdStr}...`);
      const txHash = await updateOffering(client, offeringId, price, sla, name, description);
      output({ offeringId: offeringIdStr, price: formatUsdm(price) + ' USDm', slaMinutes: sla, name, description, txHash });
      success(`Offering ${offeringIdStr} updated`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

sellCommand
  .command('update-schema')
  .description('Update the requirements schema URI of an offering')
  .argument('<offeringId>', 'Offering ID')
  .requiredOption('--uri <schemaURI>', 'Requirements schema URI')
  .option('--json', 'Output as JSON')
  .action(async (offeringIdStr, opts) => {
    try {
      const client = getClient();
      info(`Updating schema for offering ${offeringIdStr}...`);
      const txHash = await updateOfferingSchema(client, BigInt(offeringIdStr), opts.uri);
      output({ offeringId: offeringIdStr, requirementsSchemaURI: opts.uri, txHash });
      success(`Offering ${offeringIdStr} schema updated`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

sellCommand
  .command('metadata')
  .description('Set OpenClaw usage metadata for an offering')
  .argument('<offeringId>', 'Offering ID')
  .option('--example <cmd>', 'OpenClaw example command (max 500 chars)')
  .option('--usage-url <url>', 'Usage documentation URL (max 2000 chars)')
  .option('--instructions <text>', 'Usage instructions (max 5000 chars)')
  .option('--json', 'Output as JSON')
  .action(async (offeringIdStr, opts) => {
    try {
      const client = getClient();
      if (!client.walletClient?.account) {
        throw new Error('Wallet required to sign metadata update. Set PULSE_PRIVATE_KEY or run `pulse wallet generate`.');
      }

      const offeringId = Number(offeringIdStr);
      const message = `Pulse: Update offering #${offeringId} metadata`;
      const signature = await client.walletClient.signMessage({ message });

      const indexer = new IndexerClient({ baseUrl: client.indexerUrl });
      await indexer.updateOfferingMetadata(offeringId, {
        openclawExample: opts.example,
        usageUrl: opts.usageUrl,
        usageInstructions: opts.instructions,
        signature,
      });

      output({
        offeringId,
        openclawExample: opts.example ?? null,
        usageUrl: opts.usageUrl ?? null,
        usageInstructions: opts.instructions ?? null,
      });
      success(`Metadata updated for offering ${offeringIdStr}`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

function parseServiceType(val: string): ServiceType {
  const num = Number(val);
  if (!isNaN(num) && num >= 0 && num <= 5) return num as ServiceType;
  const key = val as keyof typeof ServiceType;
  if (key in ServiceType) return ServiceType[key];
  throw new Error(
    `Invalid service type: ${val}. Use 0-5 or name (TextGeneration, ImageGeneration, DataAnalysis, CodeGeneration, Translation, Custom)`,
  );
}


path: src/commands/agent.ts
content: import { Command } from 'commander';
import type { Address } from 'viem';
import { registerAgent, initAgent, getAgent, setOperator, IndexerClient } from '@pulseai/sdk';
import { getClient, getReadClient, getAddress } from '../config.js';
import { output, success, error, info } from '../lib/output.js';

export const agentCommand = new Command('agent').description('Agent registration and info');

agentCommand
  .command('register')
  .description('Register a new agent and initialize it in Pulse')
  .option('--name <name>', 'Agent name (used as URI)', 'pulse-agent')
  .option('--json', 'Output as JSON')
  .action(async (opts) => {
    try {
      const client = getClient();
      const address = getAddress();

      info('Registering agent in ERC-8004 Identity Registry...');
      const result = await registerAgent(client, opts.name);

      info(`Agent ID: ${result.agentId}. Initializing in Pulse...`);
      const initHash = await initAgent(client, result.agentId, address);

      output({
        agentId: result.agentId,
        owner: address,
        operator: address,
        registerTx: result.txHash,
        initTx: initHash,
      });
      success(`Agent registered and initialized with ID: ${result.agentId}`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

agentCommand
  .command('info')
  .description('Get agent information')
  .argument('<agentId>', 'Agent ID')
  .option('--json', 'Output as JSON')
  .action(async (agentIdStr) => {
    try {
      const client = getReadClient();

      // Try on-chain first, fall back to indexer for richer data
      try {
        const data = await getAgent(client, BigInt(agentIdStr));
        output({
          agentId: agentIdStr,
          owner: data.owner,
          operator: data.pulseData.operator,
          warrenContract: data.pulseData.warrenMasterContract,
          registeredAt: data.pulseData.registeredAt,
          active: data.active,
        });
      } catch {
        // Fall back to indexer
        const indexer = new IndexerClient({ baseUrl: client.indexerUrl });
        const agent = await indexer.getAgent(Number(agentIdStr));
        output({
          agentId: agent.agentId,
          owner: agent.owner,
          operator: agent.operator,
          warrenContract: agent.warrenContract,
          registeredAt: agent.registeredAt,
          active: agent.active,
        });
      }
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

agentCommand
  .command('set-operator')
  .description('Set operator for an agent (owner only)')
  .requiredOption('--agent-id <id>', 'Agent ID')
  .requiredOption('--operator <address>', 'Operator address')
  .option('--json', 'Output as JSON')
  .action(async (opts) => {
    try {
      const client = getClient();
      info('Setting operator...');
      const txHash = await setOperator(client, BigInt(opts.agentId), opts.operator as Address);
      output({ agentId: opts.agentId, operator: opts.operator, txHash });
      success('Operator updated');
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });


path: src/commands/serve.ts
content: import { Command } from 'commander';
import type { DeliverableParams } from '@pulseai/sdk';
import { HandlerProviderRuntime, ProviderRuntime } from '@pulseai/sdk';
import { getClient } from '../config.js';
import { info, error } from '../lib/output.js';

export const serveCommand = new Command('serve')
  .description('Run provider runtime to handle incoming jobs');

serveCommand
  .command('start')
  .description('Start the provider runtime (poll and handle jobs)')
  .requiredOption('--agent-id <id>', 'Provider agent ID')
  .option('--handler <path>', 'Path to OfferingHandler file (TypeScript/JS)')
  .option('--auto-accept', 'Automatically accept matching jobs', false)
  .option('--auto-deliver', 'Automatically deliver with dummy content', false)
  .option('--poll-interval <ms>', 'Poll interval in milliseconds', '5000')
  .option('--json', 'Output as JSON')
  .action(async (opts) => {
    try {
      const client = getClient();
      const agentId = BigInt(opts.agentId);
      const ts = () => new Date().toISOString();

      if (opts.handler) {
        const { resolve } = await import('node:path');
        const handlerPath = resolve(opts.handler);
        const mod = await import(handlerPath);
        const handler = mod.default ?? mod;

        if (typeof handler.offeringId !== 'number' || typeof handler.executeJob !== 'function') {
          throw new Error(
            `Invalid handler at ${handlerPath}: must export offeringId (number) and executeJob (function)`,
          );
        }

        const runtime = new HandlerProviderRuntime(client, agentId, {
          indexerUrl: client.indexerUrl,
          pollInterval: Number(opts.pollInterval),
        });

        runtime.registerHandler(handler);
        runtime.setErrorHandler((err) => {
          info(`[${ts()}] Error: ${err.message}`);
        });

        info(`Handler loaded for offering #${handler.offeringId}`);
        info('Provider runtime started. Listening for jobs...');
        runtime.start();
        return;
      }

      info(`Provider runtime started for agent ${agentId}`);
      info(`Auto-accept: ${opts.autoAccept} | Auto-deliver: ${opts.autoDeliver}`);
      info('Listening for jobs...\n');

      const runtime = new ProviderRuntime(client, agentId, {
        onJobReceived: async (job) => {
          info(`[${ts()}] New job #${job.jobId} — offering #${job.offeringId}, price: ${job.priceUsdm}`);
          if (opts.autoAccept) {
            info(`[${ts()}] Auto-accepting job #${job.jobId}...`);
            return true;
          }
          info(`[${ts()}] Skipping job #${job.jobId} (auto-accept disabled)`);
          return false;
        },

        onDeliverableRequested: async (job): Promise<DeliverableParams> => {
          info(`[${ts()}] Job #${job.jobId} needs delivery`);
          if (opts.autoDeliver) {
            info(`[${ts()}] Auto-delivering for job #${job.jobId}...`);
            return {
              type: 'inline',
              content: JSON.stringify({ result: `Auto-delivered for job ${job.jobId}`, timestamp: Date.now() }),
              mimeType: 'application/json',
              jobId: BigInt(job.jobId),
            };
          }
          throw new Error('Auto-deliver disabled — provide a --handler file');
        },

        onJobCompleted: (job) => {
          info(`[${ts()}] Job #${job.jobId} completed!`);
        },

        onError: (err) => {
          info(`[${ts()}] Error: ${err.message}`);
        },
      }, {
        indexerUrl: client.indexerUrl,
        pollInterval: Number(opts.pollInterval),
      });

      runtime.start();
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });


path: src/commands/job.ts
content: import { Command } from 'commander';
import type { Hex } from 'viem';
import {
  createJob,
  acceptJob,
  submitDeliverable,
  evaluate,
  settle,
  cancelJob,
  getJob,
  getAgent,
  getOffering,
  createJobTerms,
  deployRequirements,
  deployDeliverable,
  readRequirements,
  readDeliverable,
  IndexerClient,
  JobStatus,
  formatUsdm,
  DEFAULT_SCHEMAS,
  validateAgainstSchema,
  type OfferingSchema,
  type JsonSchema,
} from '@pulseai/sdk';
import type { Address } from 'viem';
import { getClient, getReadClient, getAddress } from '../config.js';
import { output, success, info, error, isJsonMode } from '../lib/output.js';

const STATUS_NAMES: Record<number, string> = {
  0: 'Created',
  1: 'Accepted',
  2: 'InProgress',
  3: 'Delivered',
  4: 'Evaluated',
  5: 'Completed',
  6: 'Disputed',
  7: 'Cancelled',
};

export const jobCommand = new Command('job').description('Job lifecycle commands');

jobCommand
  .command('create')
  .description('Create a new job (auto-approves USDm, deploys WARREN terms)')
  .requiredOption('--offering <id>', 'Offering ID')
  .requiredOption('--agent-id <id>', 'Your (buyer) agent ID')
  .option('--requirements <json>', 'JSON requirements to attach')
  .option('--json', 'Output as JSON')
  .action(async (opts) => {
    try {
      const client = getClient();
      const offeringId = BigInt(opts.offering);
      const buyerAgentId = BigInt(opts.agentId);

      info('Fetching offering details...');
      const offering = await getOffering(client, offeringId);

      let parsedRequirements: Record<string, unknown> | undefined;
      if (opts.requirements) {
        parsedRequirements = parseRequirementsJson(opts.requirements);
        const schema = await resolveRequirementsSchema(
          Number(offering.serviceType),
          offering.requirementsSchemaURI,
        );
        if (schema) {
          const validation = validateAgainstSchema(
            parsedRequirements,
            schema.serviceRequirements,
          );
          if (!validation.valid) {
            throw new Error(
              `Requirements preflight validation failed: ${validation.reason}\nExpected serviceRequirements schema: ${JSON.stringify(schema.serviceRequirements, null, 2)}`,
            );
          }
        }
      }

      info(`Offering: ${offering.description} — ${formatUsdm(offering.priceUSDm)} USDm`);
      info('Creating WARREN job terms hash...');

      const buyerAddress = getAddress();
      const providerData = await getAgent(client, offering.agentId);

      const terms = createJobTerms({
        jobId: 0n, // placeholder — jobId not yet known
        offeringId,
        agreedPrice: offering.priceUSDm,
        slaMinutes: offering.slaMinutes,
        qualityCriteria: offering.description,
        buyerAgent: buyerAddress,
        providerAgent: providerData.owner as Address,
      });

      info('Creating job (includes USDm approval)...');
      const result = await createJob(client, {
        offeringId,
        buyerAgentId,
        warrenTermsHash: terms.hash,
      });

      // Deploy requirements if provided
      if (parsedRequirements) {
        info('Deploying requirements...');
        try {
          await deployRequirements(
            client,
            buyerAgentId,
            result.jobId,
            {
              jobId: result.jobId,
              offeringId,
              requirements: parsedRequirements,
            },
            client.indexerUrl,
          );
        } catch (e) {
          info(`Warning: requirements deployment failed: ${e instanceof Error ? e.message : e}`);
        }
      }

      output({
        jobId: result.jobId,
        offeringId: Number(offeringId),
        price: formatUsdm(offering.priceUSDm) + ' USDm',
        termsHash: terms.hash,
        txHash: result.txHash,
      });
      success(`Job created with ID: ${result.jobId}`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('status')
  .description('Get job status (optionally poll until done)')
  .argument('<jobId>', 'Job ID')
  .option('--wait', 'Poll until job reaches a terminal state', false)
  .option('--poll-interval <ms>', 'Poll interval in milliseconds', '5000')
  .option('--json', 'Output as JSON')
  .action(async (jobIdStr, opts) => {
    try {
      const client = getReadClient();
      const indexer = new IndexerClient({ baseUrl: client.indexerUrl });
      const jobId = Number(jobIdStr);

      if (opts.wait) {
        info(`Polling job #${jobId} until completion...`);
        const interval = Number(opts.pollInterval);

        while (true) {
          const job = await indexer.getJob(jobId);
          const statusName = STATUS_NAMES[job.status] ?? String(job.status);

          info(`Status: ${statusName}`);

          // Terminal states: Completed(5), Disputed(6), Cancelled(7)
          if (job.status >= 5) {
            outputJobDetails(job);
            return;
          }

          await sleep(interval);
        }
      } else {
        const job = await indexer.getJob(jobId);
        outputJobDetails(job);
      }
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('accept')
  .description('Accept a job as provider')
  .argument('<jobId>', 'Job ID')
  .option('--json', 'Output as JSON')
  .action(async (jobIdStr) => {
    try {
      const client = getClient();
      const jobId = BigInt(jobIdStr);

      info('Fetching job details...');
      const job = await getJob(client, jobId);

      const txHash = await acceptJob(client, jobId, job.warrenTermsHash);
      output({ jobId: jobIdStr, txHash });
      success(`Job ${jobIdStr} accepted`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('deliver')
  .description('Submit a deliverable for a job')
  .argument('<jobId>', 'Job ID')
  .option('--hash <hex>', 'Deliverable hash (bytes32) — legacy mode')
  .option('--content <json>', 'Deliverable content as JSON string')
  .option('--file <path>', 'Path to deliverable JSON file')
  .option('--agent-id <id>', 'Provider agent ID')
  .option('--json', 'Output as JSON')
  .action(async (jobIdStr, opts) => {
    try {
      const client = getClient();
      const jobId = BigInt(jobIdStr);

      if (opts.hash && (opts.content || opts.file)) {
        error('Cannot combine --hash with --content/--file');
      }

      // Backward-compatible hash-only mode
      if (opts.hash && !opts.content && !opts.file) {
        const txHash = await submitDeliverable(client, jobId, opts.hash as Hex);
        output({ jobId: jobIdStr, txHash });
        success('Deliverable submitted');
        return;
      }

      if (opts.content && opts.file) {
        error('Cannot use both --content and --file');
      }
      if (!opts.content && !opts.file) {
        error('Must specify --hash, --content, or --file');
      }
      if (!opts.agentId) {
        error('--agent-id is required when using --content or --file');
      }

      const agentId = Number(opts.agentId);
      if (!Number.isFinite(agentId) || !Number.isInteger(agentId) || agentId <= 0) {
        error('Invalid --agent-id (must be a positive integer)');
      }

      let contentStr: string;
      if (opts.content) {
        contentStr = opts.content as string;
      } else {
        const fs = await import('node:fs');
        contentStr = fs.readFileSync(opts.file as string, 'utf8');
      }

      info('Running preflight checks...');
      const indexer = new IndexerClient({ baseUrl: client.indexerUrl });
      const job = await indexer.getJob(Number(jobIdStr));

      if (job.status !== 2) {
        error(
          `Job is not InProgress (status=${STATUS_NAMES[job.status] ?? String(job.status)}). Cannot deliver.`,
        );
      }
      if (job.providerAgentId !== agentId) {
        error(`You are not the provider for this job (provider agent: ${job.providerAgentId})`);
      }

      if (job.acceptedAt && job.slaMinutes) {
        const deadlineMs = (job.acceptedAt + job.slaMinutes * 60) * 1000;
        const remainingMs = deadlineMs - Date.now();
        if (remainingMs < 10 * 60 * 1000) {
          info('Warning: less than 10 minutes remaining on SLA deadline');
        }
      }

      info('Submitting deliverable on-chain...');
      const result = await deployDeliverable(
        client,
        jobId,
        {
          jobId,
          type: 'inline',
          content: contentStr,
          mimeType: 'application/json',
        },
        client.indexerUrl,
      );

      output({
        jobId: jobIdStr,
        hash: result.hash,
        txHash: result.txHash,
      });
      success('Deliverable submitted');
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('evaluate')
  .description('Evaluate a delivered job')
  .argument('<jobId>', 'Job ID')
  .option('--approve', 'Approve the deliverable', false)
  .option('--reject', 'Reject the deliverable', false)
  .option('--feedback <text>', 'Feedback text', '')
  .option('--json', 'Output as JSON')
  .action(async (jobIdStr, opts) => {
    try {
      if (!opts.approve && !opts.reject) {
        error('Must specify --approve or --reject');
      }
      const client = getClient();
      const txHash = await evaluate(client, BigInt(jobIdStr), !!opts.approve, opts.feedback);
      output({ jobId: jobIdStr, approved: !!opts.approve, txHash });
      success(`Job ${jobIdStr} evaluated: ${opts.approve ? 'approved' : 'rejected'}`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('settle')
  .description('Settle an evaluated job (release payment)')
  .argument('<jobId>', 'Job ID')
  .option('--json', 'Output as JSON')
  .action(async (jobIdStr) => {
    try {
      const client = getClient();
      const txHash = await settle(client, BigInt(jobIdStr));
      output({ jobId: jobIdStr, txHash });
      success(`Job ${jobIdStr} settled`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('result')
  .description('View job deliverable result')
  .argument('<jobId>', 'Job ID')
  .option('--json', 'Output as JSON')
  .action(async (jobIdStr) => {
    try {
      const client = getReadClient();
      const indexer = new IndexerClient({ baseUrl: client.indexerUrl });
      const jobId = Number(jobIdStr);

      info('Fetching job details...');
      const job = await indexer.getJob(jobId);
      const statusName = STATUS_NAMES[job.status] ?? String(job.status);

      if (job.status < 3) {
        error(`Job #${jobId} has not been delivered yet (status: ${statusName})`);
        return;
      }

      info(`Job #${jobId} — status: ${statusName}, deliverableHash: ${job.deliverableHash}`);
      info('Reading deliverable from indexer...');

      try {
        const deliverable = await readDeliverable(BigInt(jobId), client.indexerUrl);
        output({
          jobId,
          status: statusName,
          deliverableHash: job.deliverableHash,
          type: deliverable.type,
          content: deliverable.content ?? null,
          url: deliverable.url ?? null,
          mimeType: deliverable.mimeType ?? 'application/json',
          size: deliverable.size,
          timestamp: new Date(deliverable.timestamp).toISOString(),
        });
        success(`Deliverable retrieved for job #${jobId}`);
      } catch {
        info('Deliverable content not available in indexer. Showing on-chain data only.');
        output({
          jobId,
          status: statusName,
          deliverableHash: job.deliverableHash,
          deliveredAt: job.deliveredAt ? new Date(job.deliveredAt * 1000).toISOString() : null,
          note: 'Deliverable hash exists on-chain but content was not stored in indexer.',
        });
      }
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('cancel')
  .description('Cancel a job')
  .argument('<jobId>', 'Job ID')
  .option('--json', 'Output as JSON')
  .action(async (jobIdStr) => {
    try {
      const client = getClient();
      const txHash = await cancelJob(client, BigInt(jobIdStr));
      output({ jobId: jobIdStr, txHash });
      success(`Job ${jobIdStr} cancelled`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('pending')
  .description('List pending jobs for a provider agent')
  .requiredOption('--agent-id <id>', 'Provider agent ID')
  .option('--json', 'Output as JSON')
  .action(async (opts) => {
    try {
      const client = getReadClient();
      const indexer = new IndexerClient({ baseUrl: client.indexerUrl });
      const agentId = Number(opts.agentId);

      if (!Number.isFinite(agentId) || !Number.isInteger(agentId) || agentId <= 0) {
        error('Invalid --agent-id (must be a positive integer)');
      }

      info('Fetching pending jobs...');
      const jobs = await indexer.getJobs({ status: 0, agentId });
      const pendingJobs = jobs.filter((job) => job.providerAgentId === agentId);

      if (pendingJobs.length === 0) {
        info('No pending jobs found.');
        output([]);
        return;
      }

      output(
        pendingJobs.map((job) => ({
          jobId: job.jobId,
          offeringId: job.offeringId,
          buyerAgentId: job.buyerAgentId,
          price: formatUsdm(BigInt(job.priceUsdm)) + ' USDm',
          slaMinutes: job.slaMinutes,
          createdAt: job.createdAt ? new Date(job.createdAt * 1000).toISOString() : null,
        })),
      );
      success(`${pendingJobs.length} pending job(s) found`);
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

jobCommand
  .command('requirements')
  .description('View job requirements')
  .argument('<jobId>', 'Job ID')
  .option('--json', 'Output as JSON')
  .action(async (jobIdStr) => {
    try {
      const client = getReadClient();
      const jobId = Number(jobIdStr);
      if (!Number.isFinite(jobId) || !Number.isInteger(jobId) || jobId < 0) {
        error('Invalid <jobId>');
      }

      info('Fetching job requirements...');

      const warrenRequirements = await readRequirements(client, BigInt(jobId), client.indexerUrl);
      if (warrenRequirements) {
        output(warrenRequirements);
        success('Requirements retrieved from WARREN (verified)');
        return;
      }

      const baseUrl = client.indexerUrl.replace(/\/$/, '');
      const response = await fetch(`${baseUrl}/jobs/${jobId}`);
      if (!response.ok) {
        error(`Failed to fetch job from indexer: ${response.status}`);
      }

      const payload = await response.json() as { data?: Record<string, unknown> };
      const job = payload.data;
      if (!job) {
        error('Invalid indexer response: missing job payload');
      }

      const requirementsContent = job.requirements_content;
      if (typeof requirementsContent === 'string' && requirementsContent.length > 0) {
        try {
          const parsed = JSON.parse(requirementsContent) as unknown;
          output(parsed);
          success('Requirements retrieved from indexer');
          return;
        } catch {
          output({ raw: requirementsContent });
          return;
        }
      }

      info('No requirements found for this job.');
      output({ jobId, requirements: null });
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

function outputJobDetails(job: { jobId: number; offeringId: number; buyerAgentId: number; providerAgentId: number; priceUsdm: string; status: number; slaMinutes: number | null; createdAt: number | null; acceptedAt: number | null; deliveredAt: number | null; evaluatedAt: number | null; settledAt: number | null }) {
  output({
    jobId: job.jobId,
    offeringId: job.offeringId,
    buyerAgentId: job.buyerAgentId,
    providerAgentId: job.providerAgentId,
    price: formatUsdm(BigInt(job.priceUsdm)) + ' USDm',
    status: STATUS_NAMES[job.status] ?? String(job.status),
    slaMinutes: job.slaMinutes,
    createdAt: job.createdAt ? new Date(job.createdAt * 1000).toISOString() : null,
    acceptedAt: job.acceptedAt ? new Date(job.acceptedAt * 1000).toISOString() : null,
    deliveredAt: job.deliveredAt ? new Date(job.deliveredAt * 1000).toISOString() : null,
    evaluatedAt: job.evaluatedAt ? new Date(job.evaluatedAt * 1000).toISOString() : null,
    settledAt: job.settledAt ? new Date(job.settledAt * 1000).toISOString() : null,
  });
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function parseRequirementsJson(rawRequirements: string): Record<string, unknown> {
  let parsed: unknown;
  try {
    parsed = JSON.parse(rawRequirements);
  } catch {
    throw new Error('--requirements must be valid JSON');
  }

  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
    throw new Error('--requirements must be a JSON object');
  }

  return parsed as Record<string, unknown>;
}

async function resolveRequirementsSchema(
  serviceType: number,
  requirementsSchemaUri?: string,
): Promise<OfferingSchema | undefined> {
  if (requirementsSchemaUri && requirementsSchemaUri.trim().length > 0) {
    const fromUri = await loadOfferingSchemaFromUri(requirementsSchemaUri.trim());
    if (!fromUri) {
      throw new Error(
        `Unsupported requirementsSchemaURI format: ${requirementsSchemaUri}. Use http(s), data: URI, or inline JSON.`,
      );
    }
    return fromUri;
  }

  return DEFAULT_SCHEMAS[serviceType] as (typeof DEFAULT_SCHEMAS)[number] | undefined;
}

async function loadOfferingSchemaFromUri(schemaUri: string): Promise<OfferingSchema | null> {
  let schemaPayload: unknown;
  if (schemaUri.startsWith('data:')) {
    schemaPayload = parseDataUriJson(schemaUri);
  } else if (schemaUri.startsWith('http://') || schemaUri.startsWith('https://')) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 30_000);
    try {
      const response = await fetch(schemaUri, { signal: controller.signal });
      if (!response.ok) {
        throw new Error(`Failed to fetch requirements schema URI (${response.status} ${response.statusText})`);
      }
      const buf = await response.arrayBuffer();
      if (buf.byteLength > 1_048_576) {
        throw new Error(`Schema too large (${buf.byteLength} bytes). Limit is 1MB`);
      }
      schemaPayload = JSON.parse(new TextDecoder().decode(buf));
    } finally {
      clearTimeout(timeoutId);
    }
  } else if (schemaUri.trim().startsWith('{')) {
    schemaPayload = JSON.parse(schemaUri);
  } else {
    return null;
  }

  if (!isOfferingSchema(schemaPayload)) {
    throw new Error('requirementsSchemaURI did not resolve to a valid OfferingSchema document');
  }
  return schemaPayload;
}

function parseDataUriJson(dataUri: string): unknown {
  const commaIndex = dataUri.indexOf(',');
  if (commaIndex < 0) {
    throw new Error('Invalid data URI schema format');
  }

  const metadata = dataUri.slice(5, commaIndex);
  const payload = dataUri.slice(commaIndex + 1);
  const decoded = metadata.includes(';base64')
    ? new TextDecoder().decode(Uint8Array.from(atob(payload), (char) => char.charCodeAt(0)))
    : decodeURIComponent(payload);

  return JSON.parse(decoded);
}

function isOfferingSchema(value: unknown): value is OfferingSchema {
  if (typeof value !== 'object' || value === null) return false;
  const candidate = value as Partial<OfferingSchema>;
  return (
    typeof candidate.version === 'number' &&
    isJsonSchema(candidate.serviceRequirements) &&
    isJsonSchema(candidate.deliverableRequirements)
  );
}

function isJsonSchema(value: unknown): value is JsonSchema {
  if (typeof value !== 'object' || value === null) return false;
  const candidate = value as Partial<JsonSchema>;
  return (
    candidate.type === 'object' &&
    typeof candidate.properties === 'object' &&
    candidate.properties !== null
  );
}


path: src/commands/browse.ts
content: import { Command } from 'commander';
import { DEFAULT_SCHEMAS, IndexerClient, ServiceType, formatUsdm } from '@pulseai/sdk';
import { getReadClient } from '../config.js';
import { output, info, error, isJsonMode } from '../lib/output.js';

const SERVICE_TYPE_NAMES: Record<number, string> = {
  0: 'TextGeneration',
  1: 'ImageGeneration',
  2: 'DataAnalysis',
  3: 'CodeGeneration',
  4: 'Translation',
  5: 'Custom',
};

export const browseCommand = new Command('browse')
  .description('Browse available offerings on the Pulse marketplace')
  .argument('[query]', 'Search query (filters by description)')
  .option('--type <serviceType>', 'Filter by service type (0-5 or name)')
  .option('--max-price <usdm>', 'Maximum price in USDm (e.g. "10.0")')
  .option('--agent-id <id>', 'Filter by provider agent ID')
  .option('--json', 'Output as JSON')
  .action(async (query, opts) => {
    try {
      const client = getReadClient();
      const indexer = new IndexerClient({ baseUrl: client.indexerUrl });

      const filter: { serviceType?: number; active?: boolean; agentId?: number } = {
        active: true,
      };

      if (opts.type !== undefined) {
        filter.serviceType = parseServiceType(opts.type);
      }

      if (opts.agentId !== undefined) {
        filter.agentId = Number(opts.agentId);
      }

      info('Searching Pulse marketplace...');

      let offerings = await indexer.getOfferings(filter);

      // Filter by max price
      if (opts.maxPrice) {
        const maxPrice = parseFloat(opts.maxPrice);
        offerings = offerings.filter((o) => {
          const price = parseFloat(formatUsdm(BigInt(o.priceUsdm)));
          return price <= maxPrice;
        });
      }

      // Filter by query (description match)
      if (query) {
        const q = query.toLowerCase();
        offerings = offerings.filter(
          (o) => o.description?.toLowerCase().includes(q),
        );
      }

      if (offerings.length === 0) {
        if (isJsonMode()) {
          output({ offerings: [], count: 0 });
        } else {
          info('No offerings found matching your criteria.');
        }
        return;
      }

      if (isJsonMode()) {
        output({
          offerings: offerings.map((o) => ({
            offeringId: o.offeringId,
            agentId: o.agentId,
            serviceType: SERVICE_TYPE_NAMES[o.serviceType] ?? String(o.serviceType),
            priceUsdm: formatUsdm(BigInt(o.priceUsdm)),
            slaMinutes: o.slaMinutes,
            description: o.description,
            requirementsSchemaUri: o.requirementsSchemaUri ?? null,
            fallbackSchema: o.requirementsSchemaUri
              ? null
              : (DEFAULT_SCHEMAS[o.serviceType] as (typeof DEFAULT_SCHEMAS)[number] | undefined) ?? null,
          })),
          count: offerings.length,
        });
      } else {
        output(
          offerings.map((o) => ({
            id: o.offeringId,
            agent: o.agentId,
            type: SERVICE_TYPE_NAMES[o.serviceType] ?? String(o.serviceType),
            price: formatUsdm(BigInt(o.priceUsdm)) + ' USDm',
            sla: o.slaMinutes ? `${o.slaMinutes}m` : '—',
            description: truncate(o.description ?? '', 50),
          })),
        );
      }
    } catch (e) {
      error(e instanceof Error ? e.message : String(e));
    }
  });

function truncate(s: string, max: number): string {
  return s.length > max ? s.slice(0, max - 1) + '…' : s;
}

function parseServiceType(val: string): number {
  const num = Number(val);
  if (!isNaN(num) && num >= 0 && num <= 5) return num;
  const key = val as keyof typeof ServiceType;
  if (key in ServiceType) return ServiceType[key];
  throw new Error(
    `Invalid service type: ${val}. Use 0-5 or name (TextGeneration, ImageGeneration, DataAnalysis, CodeGeneration, Translation, Custom)`,
  );
}


path: src/lib/output.ts
content: import chalk from 'chalk';

let jsonMode = false;

export function setJsonMode(enabled: boolean) {
  jsonMode = enabled;
}

export function isJsonMode(): boolean {
  return jsonMode;
}

/** Print data as JSON or formatted table/key-value. */
export function output(data: unknown) {
  if (jsonMode) {
    console.log(JSON.stringify(data, bigintReplacer, 2));
  } else if (Array.isArray(data)) {
    printTable(data);
  } else if (typeof data === 'object' && data !== null) {
    printKeyValue(data as Record<string, unknown>);
  } else {
    console.log(data);
  }
}

export function success(msg: string) {
  if (!jsonMode) {
    console.log(chalk.green('✓') + ' ' + msg);
  }
}

export function error(msg: string): never {
  if (jsonMode) {
    console.error(JSON.stringify({ error: msg }));
  } else {
    console.error(chalk.red('✗') + ' ' + msg);
  }
  process.exit(1);
}

export function info(msg: string) {
  if (!jsonMode) {
    console.log(chalk.blue('ℹ') + ' ' + msg);
  }
}

function printKeyValue(obj: Record<string, unknown>) {
  const maxKey = Math.max(...Object.keys(obj).map((k) => k.length));
  for (const [key, val] of Object.entries(obj)) {
    const label = chalk.bold(key.padEnd(maxKey));
    console.log(`  ${label}  ${formatValue(val)}`);
  }
}

function printTable(rows: Record<string, unknown>[]) {
  if (rows.length === 0) {
    console.log('  (no data)');
    return;
  }
  const keys = Object.keys(rows[0]);
  const widths = keys.map((k) =>
    Math.max(k.length, ...rows.map((r) => String(r[k] ?? '').length)),
  );

  const header = keys.map((k, i) => chalk.bold(k.padEnd(widths[i]))).join('  ');
  console.log('  ' + header);
  console.log('  ' + widths.map((w) => '─'.repeat(w)).join('  '));

  for (const row of rows) {
    const line = keys.map((k, i) => String(row[k] ?? '').padEnd(widths[i])).join('  ');
    console.log('  ' + line);
  }
}

function formatValue(val: unknown): string {
  if (typeof val === 'bigint') return val.toString();
  if (val === undefined || val === null) return chalk.dim('—');
  return String(val);
}

function bigintReplacer(_key: string, value: unknown): unknown {
  return typeof value === 'bigint' ? value.toString() : value;
}


path: dist/pulse.js
content: #!/usr/bin/env node

// bin/pulse.ts
import { Command as Command7 } from "commander";

// src/lib/output.ts
import chalk from "chalk";
var jsonMode = false;
function setJsonMode(enabled) {
  jsonMode = enabled;
}
function isJsonMode() {
  return jsonMode;
}
function output(data) {
  if (jsonMode) {
    console.log(JSON.stringify(data, bigintReplacer, 2));
  } else if (Array.isArray(data)) {
    printTable(data);
  } else if (typeof data === "object" && data !== null) {
    printKeyValue(data);
  } else {
    console.log(data);
  }
}
function success(msg) {
  if (!jsonMode) {
    console.log(chalk.green("\u2713") + " " + msg);
  }
}
function error(msg) {
  if (jsonMode) {
    console.error(JSON.stringify({ error: msg }));
  } else {
    console.error(chalk.red("\u2717") + " " + msg);
  }
  process.exit(1);
}
function info(msg) {
  if (!jsonMode) {
    console.log(chalk.blue("\u2139") + " " + msg);
  }
}
function printKeyValue(obj) {
  const maxKey = Math.max(...Object.keys(obj).map((k) => k.length));
  for (const [key, val] of Object.entries(obj)) {
    const label = chalk.bold(key.padEnd(maxKey));
    console.log(`  ${label}  ${formatValue(val)}`);
  }
}
function printTable(rows) {
  if (rows.length === 0) {
    console.log("  (no data)");
    return;
  }
  const keys = Object.keys(rows[0]);
  const widths = keys.map(
    (k) => Math.max(k.length, ...rows.map((r) => String(r[k] ?? "").length))
  );
  const header = keys.map((k, i) => chalk.bold(k.padEnd(widths[i]))).join("  ");
  console.log("  " + header);
  console.log("  " + widths.map((w) => "\u2500".repeat(w)).join("  "));
  for (const row of rows) {
    const line = keys.map((k, i) => String(row[k] ?? "").padEnd(widths[i])).join("  ");
    console.log("  " + line);
  }
}
function formatValue(val) {
  if (typeof val === "bigint") return val.toString();
  if (val === void 0 || val === null) return chalk.dim("\u2014");
  return String(val);
}
function bigintReplacer(_key, value) {
  return typeof value === "bigint" ? value.toString() : value;
}

// src/commands/browse.ts
import { Command } from "commander";
import { DEFAULT_SCHEMAS, IndexerClient, ServiceType, formatUsdm } from "@pulseai/sdk";

// src/config.ts
import { createMainnetClient } from "@pulseai/sdk";
import { privateKeyToAccount } from "viem/accounts";
import fs from "fs";
import os from "os";
import path from "path";
var CONFIG_PATH = path.join(os.homedir(), ".pulse", "config.json");
var _client;
function loadConfig() {
  try {
    if (!fs.existsSync(CONFIG_PATH)) {
      return {};
    }
    const raw = fs.readFileSync(CONFIG_PATH, "utf8").trim();
    if (!raw) {
      return {};
    }
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== "object") {
      return {};
    }
    const privateKey = parsed.privateKey;
    if (typeof privateKey !== "string" || !privateKey) {
      return {};
    }
    return { privateKey };
  } catch (e) {
    throw new Error(
      `Failed to load Pulse config from ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`
    );
  }
}
function saveConfig(config) {
  try {
    fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
    fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
  } catch (e) {
    throw new Error(
      `Failed to save Pulse config to ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`
    );
  }
}
function getPrivateKey() {
  if (process.env.PULSE_PRIVATE_KEY) {
    return process.env.PULSE_PRIVATE_KEY;
  }
  return loadConfig().privateKey;
}
function getClient() {
  if (_client) return _client;
  const key = getPrivateKey();
  if (!key) {
    throw new Error(
      "No wallet private key found. Set PULSE_PRIVATE_KEY or run `pulse wallet generate`.\nConfig fallback path: ~/.pulse/config.json"
    );
  }
  const account = privateKeyToAccount(key);
  _client = createMainnetClient({ account });
  return _client;
}
function getReadClient() {
  if (_client) return _client;
  const key = getPrivateKey();
  if (key) {
    return getClient();
  }
  _client = createMainnetClient();
  return _client;
}
function getAddress() {
  const key = getPrivateKey();
  if (!key) {
    throw new Error("No wallet private key found. Set PULSE_PRIVATE_KEY or run `pulse wallet generate`.");
  }
  return privateKeyToAccount(key).address;
}

// src/commands/browse.ts
var SERVICE_TYPE_NAMES = {
  0: "TextGeneration",
  1: "ImageGeneration",
  2: "DataAnalysis",
  3: "CodeGeneration",
  4: "Translation",
  5: "Custom"
};
var browseCommand = new Command("browse").description("Browse available offerings on the Pulse marketplace").argument("[query]", "Search query (filters by description)").option("--type <serviceType>", "Filter by service type (0-5 or name)").option("--max-price <usdm>", 'Maximum price in USDm (e.g. "10.0")').option("--agent-id <id>", "Filter by provider agent ID").option("--json", "Output as JSON").action(async (query, opts) => {
  try {
    const client = getReadClient();
    const indexer = new IndexerClient({ baseUrl: client.indexerUrl });
    const filter = {
      active: true
    };
    if (opts.type !== void 0) {
      filter.serviceType = parseServiceType(opts.type);
    }
    if (opts.agentId !== void 0) {
      filter.agentId = Number(opts.agentId);
    }
    info("Searching Pulse marketplace...");
    let offerings = await indexer.getOfferings(filter);
    if (opts.maxPrice) {
      const maxPrice = parseFloat(opts.maxPrice);
      offerings = offerings.filter((o) => {
        const price = parseFloat(formatUsdm(BigInt(o.priceUsdm)));
        return price <= maxPrice;
      });
    }
    if (query) {
      const q = query.toLowerCase();
      offerings = offerings.filter(
        (o) => o.description?.toLowerCase().includes(q)
      );
    }
    if (offerings.length === 0) {
      if (isJsonMode()) {
        output({ offerings: [], count: 0 });
      } else {
        info("No offerings found matching your criteria.");
      }
      return;
    }
    if (isJsonMode()) {
      output({
        offerings: offerings.map((o) => ({
          offeringId: o.offeringId,
          agentId: o.agentId,
          serviceType: SERVICE_TYPE_NAMES[o.serviceType] ?? String(o.serviceType),
          priceUsdm: formatUsdm(BigInt(o.priceUsdm)),
          slaMinutes: o.slaMinutes,
          description: o.description,
          requirementsSchemaUri: o.requirementsSchemaUri ?? null,
          fallbackSchema: o.requirementsSchemaUri ? null : DEFAULT_SCHEMAS[o.serviceType] ?? null
        })),
        count: offerings.length
      });
    } else {
      output(
        offerings.map((o) => ({
          id: o.offeringId,
          agent: o.agentId,
          type: SERVICE_TYPE_NAMES[o.serviceType] ?? String(o.serviceType),
          price: formatUsdm(BigInt(o.priceUsdm)) + " USDm",
          sla: o.slaMinutes ? `${o.slaMinutes}m` : "\u2014",
          description: truncate(o.description ?? "", 50)
        }))
      );
    }
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
function truncate(s, max) {
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
}
function parseServiceType(val) {
  const num = Number(val);
  if (!isNaN(num) && num >= 0 && num <= 5) return num;
  const key = val;
  if (key in ServiceType) return ServiceType[key];
  throw new Error(
    `Invalid service type: ${val}. Use 0-5 or name (TextGeneration, ImageGeneration, DataAnalysis, CodeGeneration, Translation, Custom)`
  );
}

// src/commands/agent.ts
import { Command as Command2 } from "commander";
import { registerAgent, initAgent, getAgent, setOperator, IndexerClient as IndexerClient2 } from "@pulseai/sdk";
var agentCommand = new Command2("agent").description("Agent registration and info");
agentCommand.command("register").description("Register a new agent and initialize it in Pulse").option("--name <name>", "Agent name (used as URI)", "pulse-agent").option("--json", "Output as JSON").action(async (opts) => {
  try {
    const client = getClient();
    const address = getAddress();
    info("Registering agent in ERC-8004 Identity Registry...");
    const result = await registerAgent(client, opts.name);
    info(`Agent ID: ${result.agentId}. Initializing in Pulse...`);
    const initHash = await initAgent(client, result.agentId, address);
    output({
      agentId: result.agentId,
      owner: address,
      operator: address,
      registerTx: result.txHash,
      initTx: initHash
    });
    success(`Agent registered and initialized with ID: ${result.agentId}`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
agentCommand.command("info").description("Get agent information").argument("<agentId>", "Agent ID").option("--json", "Output as JSON").action(async (agentIdStr) => {
  try {
    const client = getReadClient();
    try {
      const data = await getAgent(client, BigInt(agentIdStr));
      output({
        agentId: agentIdStr,
        owner: data.owner,
        operator: data.pulseData.operator,
        warrenContract: data.pulseData.warrenMasterContract,
        registeredAt: data.pulseData.registeredAt,
        active: data.active
      });
    } catch {
      const indexer = new IndexerClient2({ baseUrl: client.indexerUrl });
      const agent = await indexer.getAgent(Number(agentIdStr));
      output({
        agentId: agent.agentId,
        owner: agent.owner,
        operator: agent.operator,
        warrenContract: agent.warrenContract,
        registeredAt: agent.registeredAt,
        active: agent.active
      });
    }
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
agentCommand.command("set-operator").description("Set operator for an agent (owner only)").requiredOption("--agent-id <id>", "Agent ID").requiredOption("--operator <address>", "Operator address").option("--json", "Output as JSON").action(async (opts) => {
  try {
    const client = getClient();
    info("Setting operator...");
    const txHash = await setOperator(client, BigInt(opts.agentId), opts.operator);
    output({ agentId: opts.agentId, operator: opts.operator, txHash });
    success("Operator updated");
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});

// src/commands/job.ts
import { Command as Command3 } from "commander";
import {
  createJob,
  acceptJob,
  submitDeliverable,
  evaluate,
  settle,
  cancelJob,
  getJob,
  getAgent as getAgent2,
  getOffering,
  createJobTerms,
  deployRequirements,
  deployDeliverable,
  readRequirements,
  readDeliverable,
  IndexerClient as IndexerClient3,
  formatUsdm as formatUsdm2,
  DEFAULT_SCHEMAS as DEFAULT_SCHEMAS2,
  validateAgainstSchema
} from "@pulseai/sdk";
var STATUS_NAMES = {
  0: "Created",
  1: "Accepted",
  2: "InProgress",
  3: "Delivered",
  4: "Evaluated",
  5: "Completed",
  6: "Disputed",
  7: "Cancelled"
};
var jobCommand = new Command3("job").description("Job lifecycle commands");
jobCommand.command("create").description("Create a new job (auto-approves USDm, deploys WARREN terms)").requiredOption("--offering <id>", "Offering ID").requiredOption("--agent-id <id>", "Your (buyer) agent ID").option("--requirements <json>", "JSON requirements to attach").option("--json", "Output as JSON").action(async (opts) => {
  try {
    const client = getClient();
    const offeringId = BigInt(opts.offering);
    const buyerAgentId = BigInt(opts.agentId);
    info("Fetching offering details...");
    const offering = await getOffering(client, offeringId);
    let parsedRequirements;
    if (opts.requirements) {
      parsedRequirements = parseRequirementsJson(opts.requirements);
      const schema = await resolveRequirementsSchema(
        Number(offering.serviceType),
        offering.requirementsSchemaURI
      );
      if (schema) {
        const validation = validateAgainstSchema(
          parsedRequirements,
          schema.serviceRequirements
        );
        if (!validation.valid) {
          throw new Error(
            `Requirements preflight validation failed: ${validation.reason}
Expected serviceRequirements schema: ${JSON.stringify(schema.serviceRequirements, null, 2)}`
          );
        }
      }
    }
    info(`Offering: ${offering.description} \u2014 ${formatUsdm2(offering.priceUSDm)} USDm`);
    info("Creating WARREN job terms hash...");
    const buyerAddress = getAddress();
    const providerData = await getAgent2(client, offering.agentId);
    const terms = createJobTerms({
      jobId: 0n,
      // placeholder — jobId not yet known
      offeringId,
      agreedPrice: offering.priceUSDm,
      slaMinutes: offering.slaMinutes,
      qualityCriteria: offering.description,
      buyerAgent: buyerAddress,
      providerAgent: providerData.owner
    });
    info("Creating job (includes USDm approval)...");
    const result = await createJob(client, {
      offeringId,
      buyerAgentId,
      warrenTermsHash: terms.hash
    });
    if (parsedRequirements) {
      info("Deploying requirements...");
      try {
        await deployRequirements(
          client,
          buyerAgentId,
          result.jobId,
          {
            jobId: result.jobId,
            offeringId,
            requirements: parsedRequirements
          },
          client.indexerUrl
        );
      } catch (e) {
        info(`Warning: requirements deployment failed: ${e instanceof Error ? e.message : e}`);
      }
    }
    output({
      jobId: result.jobId,
      offeringId: Number(offeringId),
      price: formatUsdm2(offering.priceUSDm) + " USDm",
      termsHash: terms.hash,
      txHash: result.txHash
    });
    success(`Job created with ID: ${result.jobId}`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("status").description("Get job status (optionally poll until done)").argument("<jobId>", "Job ID").option("--wait", "Poll until job reaches a terminal state", false).option("--poll-interval <ms>", "Poll interval in milliseconds", "5000").option("--json", "Output as JSON").action(async (jobIdStr, opts) => {
  try {
    const client = getReadClient();
    const indexer = new IndexerClient3({ baseUrl: client.indexerUrl });
    const jobId = Number(jobIdStr);
    if (opts.wait) {
      info(`Polling job #${jobId} until completion...`);
      const interval = Number(opts.pollInterval);
      while (true) {
        const job = await indexer.getJob(jobId);
        const statusName = STATUS_NAMES[job.status] ?? String(job.status);
        info(`Status: ${statusName}`);
        if (job.status >= 5) {
          outputJobDetails(job);
          return;
        }
        await sleep(interval);
      }
    } else {
      const job = await indexer.getJob(jobId);
      outputJobDetails(job);
    }
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("accept").description("Accept a job as provider").argument("<jobId>", "Job ID").option("--json", "Output as JSON").action(async (jobIdStr) => {
  try {
    const client = getClient();
    const jobId = BigInt(jobIdStr);
    info("Fetching job details...");
    const job = await getJob(client, jobId);
    const txHash = await acceptJob(client, jobId, job.warrenTermsHash);
    output({ jobId: jobIdStr, txHash });
    success(`Job ${jobIdStr} accepted`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("deliver").description("Submit a deliverable for a job").argument("<jobId>", "Job ID").option("--hash <hex>", "Deliverable hash (bytes32) \u2014 legacy mode").option("--content <json>", "Deliverable content as JSON string").option("--file <path>", "Path to deliverable JSON file").option("--agent-id <id>", "Provider agent ID").option("--json", "Output as JSON").action(async (jobIdStr, opts) => {
  try {
    const client = getClient();
    const jobId = BigInt(jobIdStr);
    if (opts.hash && (opts.content || opts.file)) {
      error("Cannot combine --hash with --content/--file");
    }
    if (opts.hash && !opts.content && !opts.file) {
      const txHash = await submitDeliverable(client, jobId, opts.hash);
      output({ jobId: jobIdStr, txHash });
      success("Deliverable submitted");
      return;
    }
    if (opts.content && opts.file) {
      error("Cannot use both --content and --file");
    }
    if (!opts.content && !opts.file) {
      error("Must specify --hash, --content, or --file");
    }
    if (!opts.agentId) {
      error("--agent-id is required when using --content or --file");
    }
    const agentId = Number(opts.agentId);
    if (!Number.isFinite(agentId) || !Number.isInteger(agentId) || agentId <= 0) {
      error("Invalid --agent-id (must be a positive integer)");
    }
    let contentStr;
    if (opts.content) {
      contentStr = opts.content;
    } else {
      const fs2 = await import("fs");
      contentStr = fs2.readFileSync(opts.file, "utf8");
    }
    info("Running preflight checks...");
    const indexer = new IndexerClient3({ baseUrl: client.indexerUrl });
    const job = await indexer.getJob(Number(jobIdStr));
    if (job.status !== 2) {
      error(
        `Job is not InProgress (status=${STATUS_NAMES[job.status] ?? String(job.status)}). Cannot deliver.`
      );
    }
    if (job.providerAgentId !== agentId) {
      error(`You are not the provider for this job (provider agent: ${job.providerAgentId})`);
    }
    if (job.acceptedAt && job.slaMinutes) {
      const deadlineMs = (job.acceptedAt + job.slaMinutes * 60) * 1e3;
      const remainingMs = deadlineMs - Date.now();
      if (remainingMs < 10 * 60 * 1e3) {
        info("Warning: less than 10 minutes remaining on SLA deadline");
      }
    }
    info("Deploying deliverable to WARREN + on-chain...");
    const result = await deployDeliverable(
      client,
      BigInt(agentId),
      jobId,
      {
        jobId,
        type: "inline",
        content: contentStr,
        mimeType: "application/json"
      },
      client.indexerUrl
    );
    output({
      jobId: jobIdStr,
      hash: result.hash,
      masterAddress: result.masterAddress,
      txHash: result.txHash
    });
    success("Deliverable deployed and submitted");
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("evaluate").description("Evaluate a delivered job").argument("<jobId>", "Job ID").option("--approve", "Approve the deliverable", false).option("--reject", "Reject the deliverable", false).option("--feedback <text>", "Feedback text", "").option("--json", "Output as JSON").action(async (jobIdStr, opts) => {
  try {
    if (!opts.approve && !opts.reject) {
      error("Must specify --approve or --reject");
    }
    const client = getClient();
    const txHash = await evaluate(client, BigInt(jobIdStr), !!opts.approve, opts.feedback);
    output({ jobId: jobIdStr, approved: !!opts.approve, txHash });
    success(`Job ${jobIdStr} evaluated: ${opts.approve ? "approved" : "rejected"}`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("settle").description("Settle an evaluated job (release payment)").argument("<jobId>", "Job ID").option("--json", "Output as JSON").action(async (jobIdStr) => {
  try {
    const client = getClient();
    const txHash = await settle(client, BigInt(jobIdStr));
    output({ jobId: jobIdStr, txHash });
    success(`Job ${jobIdStr} settled`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("result").description("View job deliverable result").argument("<jobId>", "Job ID").option("--json", "Output as JSON").action(async (jobIdStr) => {
  try {
    const client = getReadClient();
    const indexer = new IndexerClient3({ baseUrl: client.indexerUrl });
    const jobId = Number(jobIdStr);
    info("Fetching job details...");
    const job = await indexer.getJob(jobId);
    const statusName = STATUS_NAMES[job.status] ?? String(job.status);
    if (job.status < 3) {
      error(`Job #${jobId} has not been delivered yet (status: ${statusName})`);
      return;
    }
    info(`Job #${jobId} \u2014 status: ${statusName}, deliverableHash: ${job.deliverableHash}`);
    info("Reading deliverable from WARREN...");
    try {
      const deliverable = await readDeliverable(client, BigInt(jobId), client.indexerUrl);
      output({
        jobId,
        status: statusName,
        deliverableHash: job.deliverableHash,
        type: deliverable.type,
        content: deliverable.content ?? null,
        url: deliverable.url ?? null,
        mimeType: deliverable.mimeType ?? "application/json",
        size: deliverable.size,
        timestamp: new Date(deliverable.timestamp).toISOString()
      });
      success(`Deliverable retrieved for job #${jobId}`);
    } catch {
      info("WARREN content not available. Showing on-chain data only.");
      output({
        jobId,
        status: statusName,
        deliverableHash: job.deliverableHash,
        deliveredAt: job.deliveredAt ? new Date(job.deliveredAt * 1e3).toISOString() : null,
        warren: false,
        note: "Deliverable hash exists on-chain but content was not deployed to WARREN."
      });
    }
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("cancel").description("Cancel a job").argument("<jobId>", "Job ID").option("--json", "Output as JSON").action(async (jobIdStr) => {
  try {
    const client = getClient();
    const txHash = await cancelJob(client, BigInt(jobIdStr));
    output({ jobId: jobIdStr, txHash });
    success(`Job ${jobIdStr} cancelled`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("pending").description("List pending jobs for a provider agent").requiredOption("--agent-id <id>", "Provider agent ID").option("--json", "Output as JSON").action(async (opts) => {
  try {
    const client = getReadClient();
    const indexer = new IndexerClient3({ baseUrl: client.indexerUrl });
    const agentId = Number(opts.agentId);
    if (!Number.isFinite(agentId) || !Number.isInteger(agentId) || agentId <= 0) {
      error("Invalid --agent-id (must be a positive integer)");
    }
    info("Fetching pending jobs...");
    const jobs = await indexer.getJobs({ status: 0, agentId });
    const pendingJobs = jobs.filter((job) => job.providerAgentId === agentId);
    if (pendingJobs.length === 0) {
      info("No pending jobs found.");
      output([]);
      return;
    }
    output(
      pendingJobs.map((job) => ({
        jobId: job.jobId,
        offeringId: job.offeringId,
        buyerAgentId: job.buyerAgentId,
        price: formatUsdm2(BigInt(job.priceUsdm)) + " USDm",
        slaMinutes: job.slaMinutes,
        createdAt: job.createdAt ? new Date(job.createdAt * 1e3).toISOString() : null
      }))
    );
    success(`${pendingJobs.length} pending job(s) found`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
jobCommand.command("requirements").description("View job requirements").argument("<jobId>", "Job ID").option("--json", "Output as JSON").action(async (jobIdStr) => {
  try {
    const client = getReadClient();
    const jobId = Number(jobIdStr);
    if (!Number.isFinite(jobId) || !Number.isInteger(jobId) || jobId < 0) {
      error("Invalid <jobId>");
    }
    info("Fetching job requirements...");
    const warrenRequirements = await readRequirements(client, BigInt(jobId), client.indexerUrl);
    if (warrenRequirements) {
      output(warrenRequirements);
      success("Requirements retrieved from WARREN (verified)");
      return;
    }
    const baseUrl = client.indexerUrl.replace(/\/$/, "");
    const response = await fetch(`${baseUrl}/jobs/${jobId}`);
    if (!response.ok) {
      error(`Failed to fetch job from indexer: ${response.status}`);
    }
    const payload = await response.json();
    const job = payload.data;
    if (!job) {
      error("Invalid indexer response: missing job payload");
    }
    const requirementsContent = job.requirements_content;
    if (typeof requirementsContent === "string" && requirementsContent.length > 0) {
      try {
        const parsed = JSON.parse(requirementsContent);
        output(parsed);
        success("Requirements retrieved from indexer");
        return;
      } catch {
        output({ raw: requirementsContent });
        return;
      }
    }
    info("No requirements found for this job.");
    output({ jobId, requirements: null });
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
function outputJobDetails(job) {
  output({
    jobId: job.jobId,
    offeringId: job.offeringId,
    buyerAgentId: job.buyerAgentId,
    providerAgentId: job.providerAgentId,
    price: formatUsdm2(BigInt(job.priceUsdm)) + " USDm",
    status: STATUS_NAMES[job.status] ?? String(job.status),
    slaMinutes: job.slaMinutes,
    createdAt: job.createdAt ? new Date(job.createdAt * 1e3).toISOString() : null,
    acceptedAt: job.acceptedAt ? new Date(job.acceptedAt * 1e3).toISOString() : null,
    deliveredAt: job.deliveredAt ? new Date(job.deliveredAt * 1e3).toISOString() : null,
    evaluatedAt: job.evaluatedAt ? new Date(job.evaluatedAt * 1e3).toISOString() : null,
    settledAt: job.settledAt ? new Date(job.settledAt * 1e3).toISOString() : null
  });
}
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function parseRequirementsJson(rawRequirements) {
  let parsed;
  try {
    parsed = JSON.parse(rawRequirements);
  } catch {
    throw new Error("--requirements must be valid JSON");
  }
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
    throw new Error("--requirements must be a JSON object");
  }
  return parsed;
}
async function resolveRequirementsSchema(serviceType, requirementsSchemaUri) {
  if (requirementsSchemaUri && requirementsSchemaUri.trim().length > 0) {
    const fromUri = await loadOfferingSchemaFromUri(requirementsSchemaUri.trim());
    if (!fromUri) {
      throw new Error(
        `Unsupported requirementsSchemaURI format: ${requirementsSchemaUri}. Use http(s), data: URI, or inline JSON.`
      );
    }
    return fromUri;
  }
  return DEFAULT_SCHEMAS2[serviceType];
}
async function loadOfferingSchemaFromUri(schemaUri) {
  let schemaPayload;
  if (schemaUri.startsWith("data:")) {
    schemaPayload = parseDataUriJson(schemaUri);
  } else if (schemaUri.startsWith("http://") || schemaUri.startsWith("https://")) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 3e4);
    try {
      const response = await fetch(schemaUri, { signal: controller.signal });
      if (!response.ok) {
        throw new Error(`Failed to fetch requirements schema URI (${response.status} ${response.statusText})`);
      }
      const buf = await response.arrayBuffer();
      if (buf.byteLength > 1048576) {
        throw new Error(`Schema too large (${buf.byteLength} bytes). Limit is 1MB`);
      }
      schemaPayload = JSON.parse(new TextDecoder().decode(buf));
    } finally {
      clearTimeout(timeoutId);
    }
  } else if (schemaUri.trim().startsWith("{")) {
    schemaPayload = JSON.parse(schemaUri);
  } else {
    return null;
  }
  if (!isOfferingSchema(schemaPayload)) {
    throw new Error("requirementsSchemaURI did not resolve to a valid OfferingSchema document");
  }
  return schemaPayload;
}
function parseDataUriJson(dataUri) {
  const commaIndex = dataUri.indexOf(",");
  if (commaIndex < 0) {
    throw new Error("Invalid data URI schema format");
  }
  const metadata = dataUri.slice(5, commaIndex);
  const payload = dataUri.slice(commaIndex + 1);
  const decoded = metadata.includes(";base64") ? new TextDecoder().decode(Uint8Array.from(atob(payload), (char) => char.charCodeAt(0))) : decodeURIComponent(payload);
  return JSON.parse(decoded);
}
function isOfferingSchema(value) {
  if (typeof value !== "object" || value === null) return false;
  const candidate = value;
  return typeof candidate.version === "number" && isJsonSchema(candidate.serviceRequirements) && isJsonSchema(candidate.deliverableRequirements);
}
function isJsonSchema(value) {
  if (typeof value !== "object" || value === null) return false;
  const candidate = value;
  return candidate.type === "object" && typeof candidate.properties === "object" && candidate.properties !== null;
}

// src/commands/sell.ts
import { Command as Command4 } from "commander";
import {
  listOffering,
  deactivateOffering,
  activateOffering,
  ServiceType as ServiceType2,
  parseUsdm,
  formatUsdm as formatUsdm3,
  IndexerClient as IndexerClient4
} from "@pulseai/sdk";
var SERVICE_TYPE_NAMES2 = {
  0: "TextGeneration",
  1: "ImageGeneration",
  2: "DataAnalysis",
  3: "CodeGeneration",
  4: "Translation",
  5: "Custom"
};
var sellCommand = new Command4("sell").description("Create and manage service offerings");
sellCommand.command("init").description("Create a new service offering").requiredOption("--agent-id <id>", "Your agent ID").requiredOption("--type <serviceType>", "Service type (0-5 or name)").requiredOption("--price <usdm>", 'Price in USDm (e.g. "5.0")').requiredOption("--sla <minutes>", "SLA in minutes").requiredOption("--description <desc>", "Offering description").option("--schema-uri <uri>", "Requirements schema URI").option("--json", "Output as JSON").action(async (opts) => {
  try {
    const client = getClient();
    const serviceType = parseServiceType2(opts.type);
    info(`Creating offering: ${opts.description}`);
    info(`Type: ${SERVICE_TYPE_NAMES2[serviceType]} | Price: ${opts.price} USDm | SLA: ${opts.sla}m`);
    const result = await listOffering(client, {
      agentId: BigInt(opts.agentId),
      serviceType,
      priceUSDm: parseUsdm(opts.price),
      slaMinutes: Number(opts.sla),
      description: opts.description,
      requirementsSchemaURI: opts.schemaUri
    });
    output({
      offeringId: result.offeringId,
      agentId: Number(opts.agentId),
      serviceType: SERVICE_TYPE_NAMES2[serviceType],
      price: opts.price + " USDm",
      slaMinutes: Number(opts.sla),
      description: opts.description,
      txHash: result.txHash
    });
    success(`Offering created with ID: ${result.offeringId}`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
sellCommand.command("list").description("List your offerings").option("--agent-id <id>", "Agent ID to list offerings for").option("--json", "Output as JSON").action(async (opts) => {
  try {
    const client = getReadClient();
    const indexer = new IndexerClient4({ baseUrl: client.indexerUrl });
    const filter = opts.agentId ? { agentId: Number(opts.agentId) } : {};
    const offerings = await indexer.getOfferings(filter);
    if (offerings.length === 0) {
      info("No offerings found.");
      if (isJsonMode()) output({ offerings: [], count: 0 });
      return;
    }
    if (isJsonMode()) {
      output({
        offerings: offerings.map((o) => ({
          offeringId: o.offeringId,
          agentId: o.agentId,
          serviceType: SERVICE_TYPE_NAMES2[o.serviceType] ?? String(o.serviceType),
          priceUsdm: formatUsdm3(BigInt(o.priceUsdm)),
          slaMinutes: o.slaMinutes,
          description: o.description,
          active: o.active
        })),
        count: offerings.length
      });
    } else {
      output(
        offerings.map((o) => ({
          id: o.offeringId,
          type: SERVICE_TYPE_NAMES2[o.serviceType] ?? String(o.serviceType),
          price: formatUsdm3(BigInt(o.priceUsdm)) + " USDm",
          sla: o.slaMinutes ? `${o.slaMinutes}m` : "\u2014",
          active: o.active ? "yes" : "no",
          description: o.description ?? ""
        }))
      );
    }
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
sellCommand.command("deactivate").description("Deactivate an offering").argument("<offeringId>", "Offering ID").option("--json", "Output as JSON").action(async (offeringIdStr) => {
  try {
    const client = getClient();
    const txHash = await deactivateOffering(client, BigInt(offeringIdStr));
    output({ offeringId: offeringIdStr, txHash });
    success(`Offering ${offeringIdStr} deactivated`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
sellCommand.command("activate").description("Activate a deactivated offering").argument("<offeringId>", "Offering ID").option("--json", "Output as JSON").action(async (offeringIdStr) => {
  try {
    const client = getClient();
    const txHash = await activateOffering(client, BigInt(offeringIdStr));
    output({ offeringId: offeringIdStr, txHash });
    success(`Offering ${offeringIdStr} activated`);
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
function parseServiceType2(val) {
  const num = Number(val);
  if (!isNaN(num) && num >= 0 && num <= 5) return num;
  const key = val;
  if (key in ServiceType2) return ServiceType2[key];
  throw new Error(
    `Invalid service type: ${val}. Use 0-5 or name (TextGeneration, ImageGeneration, DataAnalysis, CodeGeneration, Translation, Custom)`
  );
}

// src/commands/wallet.ts
import { Command as Command5 } from "commander";
import { formatEther, erc20Abi } from "viem";
import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
import { formatUsdm as formatUsdm4 } from "@pulseai/sdk";
var OPERATOR_MESSAGE = "Ask your agent owner to set you as operator: pulse agent set-operator --agent-id <ID> --operator <YOUR_ADDRESS>";
async function showWallet() {
  const client = getClient();
  const address = getAddress();
  const [ethBalance, usdmBalance] = await Promise.all([
    client.publicClient.getBalance({ address }),
    client.publicClient.readContract({
      address: client.addresses.usdm,
      abi: erc20Abi,
      functionName: "balanceOf",
      args: [address]
    })
  ]);
  output({
    address,
    network: "MegaETH Mainnet",
    chainId: client.chain.id,
    ethBalance: formatEther(ethBalance) + " ETH",
    usdmBalance: formatUsdm4(usdmBalance) + " USDm"
  });
}
var walletCommand = new Command5("wallet").description("Show wallet address and balances").option("--json", "Output as JSON").action(async () => {
  try {
    await showWallet();
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
walletCommand.command("show").description("Show wallet address and balances").option("--json", "Output as JSON").action(async () => {
  try {
    await showWallet();
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});
walletCommand.command("generate").description("Generate and save a wallet private key").option("--json", "Output as JSON").action(async () => {
  try {
    const config = loadConfig();
    const existingKey = config.privateKey;
    if (existingKey) {
      const existingAddress = privateKeyToAccount2(existingKey).address;
      info("Wallet key already exists at ~/.pulse/config.json");
      output({
        address: existingAddress,
        ...isJsonMode() ? { privateKey: existingKey } : {},
        message: OPERATOR_MESSAGE
      });
      success("Using existing wallet key.");
      return;
    }
    info("Generating new wallet key...");
    const privateKey = generatePrivateKey();
    const account = privateKeyToAccount2(privateKey);
    saveConfig({ privateKey });
    success("Saved wallet key to ~/.pulse/config.json");
    output({
      address: account.address,
      ...isJsonMode() ? { privateKey } : {},
      message: OPERATOR_MESSAGE
    });
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});

// src/commands/serve.ts
import { Command as Command6 } from "commander";
import { HandlerProviderRuntime, ProviderRuntime } from "@pulseai/sdk";
var serveCommand = new Command6("serve").description("Run provider runtime to handle incoming jobs");
serveCommand.command("start").description("Start the provider runtime (poll and handle jobs)").requiredOption("--agent-id <id>", "Provider agent ID").option("--handler <path>", "Path to OfferingHandler file (TypeScript/JS)").option("--auto-accept", "Automatically accept matching jobs", false).option("--auto-deliver", "Automatically deliver with dummy content", false).option("--poll-interval <ms>", "Poll interval in milliseconds", "5000").option("--json", "Output as JSON").action(async (opts) => {
  try {
    const client = getClient();
    const agentId = BigInt(opts.agentId);
    const ts = () => (/* @__PURE__ */ new Date()).toISOString();
    if (opts.handler) {
      const { resolve } = await import("path");
      const handlerPath = resolve(opts.handler);
      const mod = await import(handlerPath);
      const handler = mod.default ?? mod;
      if (typeof handler.offeringId !== "number" || typeof handler.executeJob !== "function") {
        throw new Error(
          `Invalid handler at ${handlerPath}: must export offeringId (number) and executeJob (function)`
        );
      }
      const runtime2 = new HandlerProviderRuntime(client, agentId, {
        indexerUrl: client.indexerUrl,
        pollInterval: Number(opts.pollInterval)
      });
      runtime2.registerHandler(handler);
      runtime2.setErrorHandler((err) => {
        info(`[${ts()}] Error: ${err.message}`);
      });
      info(`Handler loaded for offering #${handler.offeringId}`);
      info("Provider runtime started. Listening for jobs...");
      runtime2.start();
      return;
    }
    info(`Provider runtime started for agent ${agentId}`);
    info(`Auto-accept: ${opts.autoAccept} | Auto-deliver: ${opts.autoDeliver}`);
    info("Listening for jobs...\n");
    const runtime = new ProviderRuntime(client, agentId, {
      onJobReceived: async (job) => {
        info(`[${ts()}] New job #${job.jobId} \u2014 offering #${job.offeringId}, price: ${job.priceUsdm}`);
        if (opts.autoAccept) {
          info(`[${ts()}] Auto-accepting job #${job.jobId}...`);
          return true;
        }
        info(`[${ts()}] Skipping job #${job.jobId} (auto-accept disabled)`);
        return false;
      },
      onDeliverableRequested: async (job) => {
        info(`[${ts()}] Job #${job.jobId} needs delivery`);
        if (opts.autoDeliver) {
          info(`[${ts()}] Auto-delivering for job #${job.jobId}...`);
          return {
            type: "inline",
            content: JSON.stringify({ result: `Auto-delivered for job ${job.jobId}`, timestamp: Date.now() }),
            mimeType: "application/json",
            jobId: BigInt(job.jobId)
          };
        }
        throw new Error("Auto-deliver disabled \u2014 provide a --handler file");
      },
      onJobCompleted: (job) => {
        info(`[${ts()}] Job #${job.jobId} completed!`);
      },
      onError: (err) => {
        info(`[${ts()}] Error: ${err.message}`);
      }
    }, {
      indexerUrl: client.indexerUrl,
      pollInterval: Number(opts.pollInterval)
    });
    runtime.start();
  } catch (e) {
    error(e instanceof Error ? e.message : String(e));
  }
});

// bin/pulse.ts
var program = new Command7().name("pulse").description("Pulse \u2014 Agent-to-agent commerce on MegaETH").version("0.1.0");
program.addCommand(browseCommand);
program.addCommand(agentCommand);
program.addCommand(jobCommand);
program.addCommand(sellCommand);
program.addCommand(walletCommand);
program.addCommand(serveCommand);
function installJsonHook(cmd) {
  cmd.hook("preAction", (thisCommand) => {
    if (thisCommand.opts().json) setJsonMode(true);
  });
  for (const sub of cmd.commands) {
    installJsonHook(sub);
  }
}
installJsonHook(program);
program.parse();


path: bin/pulse.ts
content: #!/usr/bin/env node
import { Command } from 'commander';
import { setJsonMode } from '../src/lib/output.js';
import { browseCommand } from '../src/commands/browse.js';
import { agentCommand } from '../src/commands/agent.js';
import { jobCommand } from '../src/commands/job.js';
import { sellCommand } from '../src/commands/sell.js';
import { walletCommand } from '../src/commands/wallet.js';
import { serveCommand } from '../src/commands/serve.js';

const program = new Command()
  .name('pulse')
  .description('Pulse — Agent-to-agent commerce on MegaETH')
  .version('0.1.0');

// Register commands
program.addCommand(browseCommand);
program.addCommand(agentCommand);
program.addCommand(jobCommand);
program.addCommand(sellCommand);
program.addCommand(walletCommand);
program.addCommand(serveCommand);

// Set JSON mode from --json option on any command/subcommand
function installJsonHook(cmd: Command) {
  cmd.hook('preAction', (thisCommand) => {
    if (thisCommand.opts().json) setJsonMode(true);
  });
  for (const sub of cmd.commands) {
    installJsonHook(sub);
  }
}
installJsonHook(program);

program.parse();


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)