Shim‑CLI Wrapper Clone

Inspect and mask coding‑agent traffic locally

Welcome to another official UVF IT guide! In this quick build you’ll create a minimal command‑line wrapper that captures HTTP requests from a coding agent, displays them, and lets you mask sensitive fields before forwarding.

The result is a tiny Node.js tool inspired by shim‑cli, using only the free‑tier stack: Node, Express, and a local SQLite DB. All steps fit into a 45‑minute session.

Before you start

Shim‑CLI Minimal Architecture
LOCAL RUNTIME HTTP request store/retrieve masks shim-cli (Node) CLI wrapper Proxy Server Express + SQLite SQLite DB Stores masks
1

Phase 1: Scaffold the project

Initialize npm, install Express and better‑sqlite3, and create the entry script. This gives us a tiny Express server that will later accept requests from the shim‑cli wrapper.

setup.sh

#!/usr/bin/env bash
mkdir shim-cli-clone && cd $_
npm init -y > /dev/null
npm install express better-sqlite3 > /dev/null
cat > index.js <<'EOF'
const express = require('express');
const Database = require('better-sqlite3');
const app = express();
app.use(express.json());
const db = new Database('masks.db');
app.get('/', (req, res) => res.send('Shim‑CLI clone running'));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server listening on port ${PORT}`));
EOF
chmod +x setup.sh
./setup.sh
2

Phase 2: Data model – mask storage

Create a simple SQLite table to hold field‑name → replacement mappings. The table will be queried by the server when it needs to replace sensitive values in incoming JSON bodies.

init_db.sh

#!/usr/bin/env bash
sqlite3 masks.db <<'SQL'
CREATE TABLE IF NOT EXISTS masks (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  field TEXT NOT NULL,
  replacement TEXT NOT NULL
);
SQL
chmod +x init_db.sh
./init_db.sh
3

Phase 3: Core API – capture, display, mask

We set up a tiny Express server that will act as the shim proxy. It accepts a JSON payload, logs the raw request for visibility, applies any masks stored in memory, and then forwards the sanitized data to the real target – for our clone we simply echo it back so you can see the masking in action.

The mask store is a plain JavaScript object keyed by the field name you want to hide. When a request arrives we walk the payload, replace matching fields with asterisks, and return the cleaned object. This keeps the example lightweight while demonstrating the core idea.

index.js

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
// In‑memory mask store: { fieldName: true }
const masks = {};
// Helper to mask matching keys recursively
function applyMasks(obj){
  if(Array.isArray(obj)) return obj.map(applyMasks);
  if(obj && typeof obj==='object'){
    return Object.fromEntries(Object.entries(obj).map(([k,v])=>{
      if(masks[k]) return [k,'****'];
      return [k,applyMasks(v)];
    }));
  }
  return obj;
}
app.post('/proxy', (req,res)=>{
  console.log('Incoming payload:', JSON.stringify(req.body,null,2));
  const masked = applyMasks(req.body);
  console.log('Masked payload:', JSON.stringify(masked,null,2));
  // Echo back as the "real" target response
  res.json(masked);
});
app.post('/mask', (req,res)=>{
  const {field}=req.body;
  if(field){ masks[field]=true; res.json({status:'added',field}); }
  else res.status(400).json({error:'field missing'});
});
app.listen(3000,()=>console.log('Proxy listening on 3000'));
4

Phase 4: CLI wrapper to invoke proxy

The CLI uses yargs to give you two handy sub‑commands: mask to add a new field to the mask list, and send to POST a JSON file to the local proxy. This mirrors how the original Shim‑CLI lets you manage masks and test traffic without writing any code yourself.

Each command prints a short confirmation so you can see the effect immediately. The send command reads the file, posts it to http://localhost:3000/proxy, and pretty‑prints the response, showing any masked values replaced with ****.

cli.js

#!/usr/bin/env node
const yargs = require('yargs');
const fs = require('fs');
const axios = require('axios');
const baseURL = 'http://localhost:3000';
const argv = yargs.command('mask <field>', 'Add a field to mask list', y=>{}, async (args)=>{
  try{
    const resp = await axios.post(`${baseURL}/mask`, {field: args.field});
    console.log('Mask added:', resp.data);
  }catch(e){ console.error('Error adding mask:', e.message); }
})
.command('send <file>', 'Send JSON payload to proxy', y=>{}, async (args)=>{
  try{
    const data = JSON.parse(fs.readFileSync(args.file,'utf8'));
    const resp = await axios.post(`${baseURL}/proxy`, data);
    console.log('Response from proxy:');
    console.log(JSON.stringify(resp.data,null,2));
  }catch(e){ console.error('Error sending payload:', e.message); }
})
.demandCommand(1,'Choose a command')
.help().argv;
5

Phase 5: Run & verify locally

Now you can spin up the proxy, add a mask, and test a request—all from your terminal. The steps below start the server, add a mask for a sensitive field, and send a sample JSON file so you can see the masking in action.

If everything works you’ll see the original payload logged, the masked version printed by the CLI, and the server confirming it’s listening. This gives you a concrete foundation to extend later.

# 1. Install dependencies (run once)
npm install express body-parser yargs axios

# 2. Start the proxy server in the background
node index.js &
PROXY_PID=$!
echo "Proxy PID: $PROXY_PID"

# 3. Add a mask for the field "apiKey"
./cli.js mask apiKey

# 4. Create a sample payload (sample.json)
cat > sample.json <<'EOF'
{
  "user":"alice",
  "apiKey":"12345-SECRET",
  "details":{ "token":"abcde" }
}
EOF

# 5. Send the payload through the shim
./cli.js send sample.json

# 6. Clean up
kill $PROXY_PID

Verifying the Stack

CheckCommand / Why it matters
Server starts without errornode index.js → should log ‘Proxy listening on 3000’
Mask command updates DB./cli.js mask apiKey → console confirms insertion with { status: 'added', field: 'apiKey' }
Send command returns masked JSON./cli.js send sample.json → response shows "apiKey": "****"

You did it!

Your minimal shim‑cli clone is now capturing traffic, applying masks, and forwarding data. Extend it by adding auth headers, multiple target URLs, or a richer UI. Happy hacking!