ZyVOP Logo
Content That Connects
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZyVOP Logo
Content That Connects

The Developer Publishing Hub. Write once, cross-post to Dev.to, Medium, Hashnode, WordPress & Bluesky with automated canonical source tags and zero paywalls.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • Developer API & CLI
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Developer Publishing Hub.

Zero paywalls · Full content ownership
All systems operational
HomeAutomating AI Context: How I Built a Custom Extension for IBM Bob IDE to Inject Project Rules

Automating AI Context: How I Built a Custom Extension for IBM Bob IDE to Inject Project Rules

Alain Airom (Ayrom)
Alain Airom (Ayrom)
Build Engineer
August 31, 2026
10 min read
Automating AI Context: How I Built a Custom Extension for IBM Bob IDE to Inject Project Rules
#vsix#sdlc#IDE#bob
👍1

Building my own “vsix” extension to automate my own projects

Introduction

Setting up consistent workspace instructions across projects can quickly become a tedious chore. In IBM Bob IDE, system prompt behaviors and coding standards are guided by a project-level AGENTS.md file located at the root of your repository. While effective, manually copying a master rules file into every newly created or cloned workspace breaks flow and easily leads to configuration drift.

Note: While AGENTS.md can be configured globally across all your projects, I prefer maintaining project-level files. This allows me to start from a shared foundational rule set while giving each workspace the freedom to include custom, project-specific tweaks.


TL;DR-What is AGENTS.MD?

In a modern Software Development Lifecycle (SDLC), an AGENTS.md file is a machine-readable context and configuration specification checked directly into a project repository to dictate how autonomous AI coding agents (such as Cursor, IBM Bob, Claude Code, or GitHub Copilot) interact with the codebase. Functioning as an authoritative set of runtime instructions loaded into the AI’s prompt context, it explicitly defines executable CLI commands, coding standards, project structure maps, architectural constraints, and operational boundaries—such as distinguishing actions the AI can perform autonomously versus those requiring human approval. By checking AGENTS.md into version control alongside application code, software engineering teams standardize AI behavior across developer environments, prevent AI configuration drift, reduce model-generated bugs, and ensure seamless alignment with project-specific development guidelines.

  • Example;

    # Project Development Rule
     - rule 1
     - rule 2
     ...
    

To eliminate this manual step, I built bob-agents-injector—a custom extension designed specifically for IBM Bob IDE. Developed directly within Bob using its native capabilities, the extension automatically injects a canonical AGENTS.md file from a central source path (defaulting to ~/Devs/AGENTS.MD) into any workspace as soon as it is opened.

~/Devs/AGENTS.MD            ← Master rules (single source of truth)
        │
        └──► <project>/AGENTS.md   ← Injected automatically when project opens

Implementation

Hooking into the IDE Event Loop

image-20260831140002801

The key to seamless injection is to align the extension with VS Code's exact activation lifecycle. So I asked Bob to build the required extension to automate copying the AGENTS.MD from my global "Devs" folder each time I create a new folder for a new project using Bob IDE!

Bob IDE is a fork of VS Code (Microsoft's open-source Code - OSS).

VS Code

Bob IDE

Source

Microsoft (closed binary, open source core)

IBM fork of Code - OSS

Extension marketplace

VS Code Marketplace

IBM's own marketplace + Open VSX

Telemetry

Microsoft

IBM

Binary name

code

bobide / bobide-insiders

Extensions dir

~/.vscode/extensions

~/.bobide/extensions


scripts/
├── bob-agents-injector/
│   ├── package.json
│   ├── out/extension.js
│   └── build.js
└── install-bob-agents-injector.sh   ← optional convenience wrapper
  • onStartupFinished: Fires when Bob completes initial loading.

  • onDidChangeWorkspaceFolders: Fires dynamically whenever a new project folder is opened or swapped.

// =============================================================================
// Bob AGENTS.md Auto-Injector — extension.ts (source, compile with build.js)
//
// This extension mirrors the EXACT trigger chain used by the bob-marketplace
// extension to inject .bob/mcp.json, but instead copies ~/Devs/AGENTS.MD to
// <workspaceRoot>/AGENTS.md at the same moment:
//
//   Trigger 1: onStartupFinished  → fires when Bob IDE is ready at launch
//   Trigger 2: onDidChangeWorkspaceFolders → fires when a NEW project is opened
//
// Both map directly to the two events in bob-marketplace/out/extension.js:
//   void mcpServer.start()                          (at activate time)
//   vscode.workspace.onDidChangeWorkspaceFolders(   (at workspace change)
//       () => void mcpServer.reregister())
//
// Idempotency: if AGENTS.md already exists in the workspace root and its
// content matches the source file, nothing is written. If it exists but
// differs, it is left untouched (user may have customised it).
// =============================================================================

'use strict';

const vscode = require('vscode');
const fs = require('fs');
const path = require('path');
const os = require('os');

// ── Configuration helpers ────────────────────────────────────────────────────
function getConfig() {
    const cfg = vscode.workspace.getConfiguration('bob.agentsInjector');
    const rawSource = cfg.get('sourcePath', '').trim()
        || path.join(os.homedir(), 'Devs', 'AGENTS.MD');
    // Expand leading ~ (vscode settings may contain literal ~)
    const sourcePath = rawSource.startsWith('~')
        ? path.join(os.homedir(), rawSource.slice(1))
        : rawSource;
    return {
        enabled: cfg.get('enabled', true),
        sourcePath,
        targetFileName: cfg.get('targetFileName', 'AGENTS.md').trim() || 'AGENTS.md',
    };
}

// ── Core inject function ─────────────────────────────────────────────────────
/**
 * Copies the master AGENTS.MD into the workspace root as AGENTS.md,
 * following the same logic as MarketplaceMcpServer.registerIntoWorkspace():
 *   - reads existing target (non-fatal if missing)
 *   - skips if content is identical (idempotent)
 *   - skips if content differs (user may have customised it)
 *   - creates the target file only when it does not exist
 */
async function injectAgentsMd() {
    const cfg = getConfig();

    if (!cfg.enabled) {
        return;
    }

    // ── Validate source ───────────────────────────────────────────────────────
    if (!fs.existsSync(cfg.sourcePath) || !fs.statSync(cfg.sourcePath).isFile()) {
        // Non-fatal: source missing means the user hasn't set up the master file.
        // Log quietly — don't pop an annoying dialog on every project open.
        console.warn(`[bob-agents-injector] Source not found: ${cfg.sourcePath}. ` +
            `Set 'bob.agentsInjector.sourcePath' in settings.`);
        return;
    }

    // ── Resolve workspace root ────────────────────────────────────────────────
    const folders = vscode.workspace.workspaceFolders;
    if (!folders || folders.length === 0) {
        return; // No workspace open yet — identical to bob-marketplace's behaviour
    }

    // Mirror bob-marketplace: use first workspace folder (workspaceFolders[0])
    const workspaceRoot = folders[0].uri.fsPath;
    const targetPath = path.join(workspaceRoot, cfg.targetFileName);

    // ── Read source content ────────────────────────────────────────────────────
    let sourceContent;
    try {
        sourceContent = fs.readFileSync(cfg.sourcePath);
    } catch (err) {
        console.warn(`[bob-agents-injector] Could not read source: ${err.message}`);
        return;
    }

    // ── Check existing target ─────────────────────────────────────────────────
    if (fs.existsSync(targetPath)) {
        try {
            const existing = fs.readFileSync(targetPath);
            if (existing.equals(sourceContent)) {
                // Already up-to-date — silent no-op (same as bob-marketplace
                // behaviour when mcp.json already has the right URL)
                return;
            } else {
                // Target exists and differs — user may have customised it.
                // Do NOT overwrite. Log for visibility.
                console.log(`[bob-agents-injector] ${cfg.targetFileName} exists and ` +
                    `differs from master — skipping to preserve local changes.`);
                return;
            }
        } catch {
            // Can't read existing file — try writing anyway
        }
    }

    // ── Write target ──────────────────────────────────────────────────────────
    try {
        fs.writeFileSync(targetPath, sourceContent);
        console.log(`[bob-agents-injector] ✅ Injected ${cfg.targetFileName} → ${targetPath}`);
    } catch (err) {
        console.warn(`[bob-agents-injector] Could not write target: ${err.message}`);
    }
}

// ── Extension entry points ───────────────────────────────────────────────────
function activate(context) {
    console.log('[bob-agents-injector] Activating (onStartupFinished)');

    // ── Trigger 1: at IDE startup — mirror mcpServer.start() call ────────────
    void injectAgentsMd();

    // ── Trigger 2: when workspace changes — mirror onDidChangeWorkspaceFolders
    //    This is the KEY trigger: fires when the user opens a NEW project folder
    //    in Bob, which is the exact same moment .bob/mcp.json is (re-)injected.
    context.subscriptions.push(
        vscode.workspace.onDidChangeWorkspaceFolders(() => {
            void injectAgentsMd();
        })
    );

    // ── Trigger 3: settings change — mirror bob.marketplace.installLocation ──
    context.subscriptions.push(
        vscode.workspace.onDidChangeConfiguration((e) => {
            if (e.affectsConfiguration('bob.agentsInjector')) {
                void injectAgentsMd();
            }
        })
    );
}

function deactivate() {
    /* nothing to clean up */
}

module.exports = { activate, deactivate };

Idempotent Injections & Safety Checks

Automatically modifying workspace files requires strict safeguards to avoid overwriting intentionally customized local rules. The injectAgentsMd() execution loop enforces idempotency through a simple decision flow:

  1. Verify Source: Ensures ~/Devs/AGENTS.MD exists before taking action.

  2. Missing Target: If no AGENTS.md is present at the target workspace root, it creates one from the master file.

  3. Identical Content: If the target matches the master file byte-for-byte, execution skips silently.

  4. Modified Target: If the target exists but differs from the master file (indicating local edits), execution skips to preserve project-specific overrides.

Local AGENTS.md State

Extension Action

Does not exist

Copy master AGENTS.MD to workspace root MD+ 2

Exists (identical)

Skip silently (up-to-date) MD+ 2

Exists (modified)

Skip execution (preserve local customizations) MD+ 2

Dependency-Free Packaging and Installation

To keep the development footprint minimal, the project packages its VSIX installer using a zero-dependency build.js script reliant solely on Node.js built-ins (fs, path, and zlib). It constructs a valid .vsix archive directly from raw buffers, complete with proper CRC-32 checksums and ZIP structure formatting.

#!/usr/bin/env node
// =============================================================================
// build.js — packages bob-agents-injector into a .vsix file
//
// Usage (run from scripts/bob-agents-injector/):
//   node build.js
//
// Then install into Bob:
//   /Applications/IBM\ Bob\ -\ Insiders.app/Contents/Resources/app/bin/bobide-insiders \
//     --install-extension bob-agents-injector-1.0.0.vsix
//
// No npm install needed — uses only Node.js built-ins.
// =============================================================================

'use strict';

const fs   = require('fs');
const path = require('path');
const zlib = require('zlib');

const DIR  = __dirname;
const PKG  = JSON.parse(fs.readFileSync(path.join(DIR, 'package.json'), 'utf8'));
const VSIX = path.join(DIR, `${PKG.name}-${PKG.version}.vsix`);

// ── VSIX manifest ─────────────────────────────────────────────────────────────
const VSIX_MANIFEST = `<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0"
  xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011"
  xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
  <Metadata>
    <Identity Language="en-US"
      Id="${PKG.name}"
      Version="${PKG.version}"
      Publisher="${PKG.publisher}"/>
    <DisplayName>${PKG.displayName}</DisplayName>
    <Description>${PKG.description}</Description>
    <Tags>bob,agents,automation</Tags>
    <GalleryFlags>Public</GalleryFlags>
    <License>MIT</License>
    <Categories>Other</Categories>
  </Metadata>
  <Installation>
    <InstallationTarget Id="Microsoft.VisualStudio.Code" Version="[1.85,)"/>
  </Installation>
  <Dependencies/>
  <Assets>
    <Asset Type="Microsoft.VisualStudio.Code.Manifest"
           Path="extension/package.json"
           Addressable="true"/>
  </Assets>
</PackageManifest>`;

// ── [Content_Types].xml ───────────────────────────────────────────────────────
const CONTENT_TYPES = `<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="json"        ContentType="application/json"/>
  <Default Extension="js"          ContentType="application/javascript"/>
  <Default Extension="vsixmanifest" ContentType="text/xml"/>
</Types>`;

// ── CRC-32 (standard ZIP polynomial) ─────────────────────────────────────────
const CRC_TABLE = (() => {
  const t = new Uint32Array(256);
  for (let n = 0; n < 256; n++) {
    let c = n;
    for (let k = 0; k < 8; k++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
    t[n] = c;
  }
  return t;
})();

function crc32(buf) {
  let crc = 0xffffffff;
  for (let i = 0; i < buf.length; i++) crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
  return (crc ^ 0xffffffff) >>> 0;
}

// ── DOS date/time for ZIP headers ─────────────────────────────────────────────
function dosDateTime(d) {
  const date = ((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate();
  const time = (d.getHours() << 11) | (d.getMinutes() << 5) | Math.floor(d.getSeconds() / 2);
  return { date, time };
}

// ── Build one ZIP entry (local header + data + central-dir record) ────────────
function makeEntry(name, rawData, now) {
  const nameBytes = Buffer.from(name, 'utf8');
  const compressed = zlib.deflateRawSync(rawData, { level: 9 });
  const crc  = crc32(rawData);
  const { date, time } = dosDateTime(now);

  // Local file header (signature 0x04034b50)
  const local = Buffer.alloc(30 + nameBytes.length);
  local.writeUInt32LE(0x04034b50, 0);   // signature
  local.writeUInt16LE(20, 4);           // version needed to extract (2.0)
  local.writeUInt16LE(0, 6);            // general purpose bit flag
  local.writeUInt16LE(8, 8);            // compression method: deflate
  local.writeUInt16LE(time, 10);        // last mod file time
  local.writeUInt16LE(date, 12);        // last mod file date
  local.writeUInt32LE(crc, 14);         // crc-32
  local.writeUInt32LE(compressed.length, 18); // compressed size
  local.writeUInt32LE(rawData.length, 22);    // uncompressed size
  local.writeUInt16LE(nameBytes.length, 26);  // file name length
  local.writeUInt16LE(0, 28);           // extra field length
  nameBytes.copy(local, 30);

  return {
    localBlock: Buffer.concat([local, compressed]),
    crc, date, time,
    compressedSize: compressed.length,
    uncompressedSize: rawData.length,
    nameBytes,
  };
}

// ── Central directory record for one entry ────────────────────────────────────
function centralRecord(entry, localOffset) {
  const rec = Buffer.alloc(46 + entry.nameBytes.length);
  rec.writeUInt32LE(0x02014b50, 0);   // central dir signature
  rec.writeUInt16LE(20, 4);           // version made by
  rec.writeUInt16LE(20, 6);           // version needed
  rec.writeUInt16LE(0, 8);            // general purpose bit flag
  rec.writeUInt16LE(8, 10);           // compression method: deflate
  rec.writeUInt16LE(entry.time, 12);
  rec.writeUInt16LE(entry.date, 14);
  rec.writeUInt32LE(entry.crc, 16);
  rec.writeUInt32LE(entry.compressedSize, 20);
  rec.writeUInt32LE(entry.uncompressedSize, 24);
  rec.writeUInt16LE(entry.nameBytes.length, 28); // file name length
  rec.writeUInt16LE(0, 30);           // extra field length
  rec.writeUInt16LE(0, 32);           // file comment length
  rec.writeUInt16LE(0, 34);           // disk number start
  rec.writeUInt16LE(0, 36);           // internal file attributes
  rec.writeUInt32LE(0, 38);           // external file attributes
  rec.writeUInt32LE(localOffset, 42); // relative offset of local header
  entry.nameBytes.copy(rec, 46);
  return rec;
}

// ── End of central directory record ──────────────────────────────────────────
function eocdRecord(entryCount, centralSize, centralOffset) {
  const eocd = Buffer.alloc(22);
  eocd.writeUInt32LE(0x06054b50, 0);   // end of central dir signature
  eocd.writeUInt16LE(0, 4);            // disk number
  eocd.writeUInt16LE(0, 6);            // disk with central dir
  eocd.writeUInt16LE(entryCount, 8);   // entries on this disk
  eocd.writeUInt16LE(entryCount, 10);  // total entries
  eocd.writeUInt32LE(centralSize, 12);
  eocd.writeUInt32LE(centralOffset, 16);
  eocd.writeUInt16LE(0, 20);           // comment length
  return eocd;
}

// ── Assemble VSIX (= ZIP) ─────────────────────────────────────────────────────
function buildVsix() {
  const now = new Date();

  // Files to pack — ORDER matters: [Content_Types].xml and .vsixmanifest FIRST
  const files = [
    { name: '[Content_Types].xml',        data: Buffer.from(CONTENT_TYPES, 'utf8') },
    { name: '.vsixmanifest',              data: Buffer.from(VSIX_MANIFEST, 'utf8') },
    { name: 'extension/package.json',     data: fs.readFileSync(path.join(DIR, 'package.json')) },
    { name: 'extension/out/extension.js', data: fs.readFileSync(path.join(DIR, 'out', 'extension.js')) },
  ];

  const localBlocks  = [];
  const centralRecs  = [];
  let   offset       = 0;

  for (const { name, data } of files) {
    const entry = makeEntry(name, data, now);
    centralRecs.push(centralRecord(entry, offset));
    localBlocks.push(entry.localBlock);
    offset += entry.localBlock.length;
  }

  const centralBuf = Buffer.concat(centralRecs);
  const eocd = eocdRecord(files.length, centralBuf.length, offset);

  fs.writeFileSync(VSIX, Buffer.concat([...localBlocks, centralBuf, eocd]));

  const kb = (fs.statSync(VSIX).size / 1024).toFixed(1);
  console.log(`✅  Built: ${VSIX}  (${kb} KB)`);
  console.log('');
  console.log('Or install directly with (use the correct variant for your machine):');
  console.log(`  # Standard IBM Bob:`);
  console.log(`  /Applications/IBM\\ Bob.app/Contents/Resources/app/bin/bobide --install-extension "${VSIX}"`);
  console.log(`  # IBM Bob - Insiders:`);
  console.log(`  /Applications/IBM\\ Bob\\ -\\ Insiders.app/Contents/Resources/app/bin/bobide-insiders --install-extension "${VSIX}"`);
  console.log('');
  console.log('Or just run:  bash scripts/install-bob-agents-injector.sh');
}

buildVsix();
  • The dependencies of the code;

    {
      "name": "bob-agents-injector",
      "displayName": "Bob AGENTS.md Auto-Injector",
      "description": "Automatically copies ~/Devs/AGENTS.MD into AGENTS.md at the root of every workspace opened in IBM Bob — using the same trigger timing as the bob-marketplace mcp.json injection.",
      "version": "1.0.0",
      "publisher": "local",
      "license": "MIT",
      "engines": { "vscode": "^1.85.0" },
      "categories": ["Other"],
      "activationEvents": ["onStartupFinished"],
      "main": "./out/extension.js",
      "contributes": {
        "configuration": {
          "title": "Bob AGENTS.md Injector",
          "properties": {
            "bob.agentsInjector.sourcePath": {
              "type": "string",
              "default": "",
              "description": "Absolute path to the master AGENTS.MD. Defaults to ~/Devs/AGENTS.MD. Supports ~ expansion."
            },
            "bob.agentsInjector.targetFileName": {
              "type": "string",
              "default": "AGENTS.md",
              "description": "File name to write in the workspace root. Bob reads 'AGENTS.md' (case-insensitive on macOS)."
            },
            "bob.agentsInjector.enabled": {
              "type": "boolean",
              "default": true,
              "description": "Enable or disable automatic AGENTS.md injection."
            }
          }
        }
      },
      "scripts": {
        "compile": "node build.js",
        "package": "node build.js && bob --install-extension bob-agents-injector-1.0.0.vsix"
      },
      "devDependencies": {}
    }
    
  • The next step is to build the ".vsix" file.

    image-20260831121417071
# Package the VSIX without npm dependencies
node build.js

# Install automatically into IBM Bob or IBM Bob Insiders
bash scripts/install-bob-agents-injector.sh
  • Once the bash runs, the extension is in place.

    ╔══════════════════════════════════════════════════════════════════════╗
    ║  ✅  bob-agents-injector is now installed!                           ║
    ╠══════════════════════════════════════════════════════════════════════╣
    ║                                                                      ║
    ║  How it works:                                                       ║
    ║  • On every project open, Bob fires onDidChangeWorkspaceFolders     ║
    ║  • The injector catches that event (same as bob-marketplace does)   ║
    ║  • It copies ~/Devs/AGENTS.MD → <project>/AGENTS.md                ║
    ║  • If AGENTS.md already exists and is unmodified, it skips it       ║
    ║  • If AGENTS.md was customised locally, it leaves it untouched      ║
    ║                                                                      ║
    ║  Bob's RuleLoader reads:  <workspaceRoot>/AGENTS.md                 ║
    ║  (NOT .bob/AGENTS.MD — that path is not read by Bob)                ║
    ║                                                                      ║
    ║  Optional settings (Bob Settings → search 'agentsInjector'):        ║
    ║    bob.agentsInjector.sourcePath    custom source path               ║
    ║    bob.agentsInjector.targetFileName  target file name               ║
    ║    bob.agentsInjector.enabled         toggle on/off                  ║
    ║                                                                      ║
    ║  To trigger immediately: reload the Bob window                      ║
    ║    Menu → Developer → Reload Window  (or Cmd+Shift+P → Reload)      ║
    ╚══════════════════════════════════════════════════════════════════════╝
    

Once installed, user preferences can be adjusted anytime via Bob Settings (Cmd+,) under the bob.agentsInjector namespace to customize target file naming or update source paths.

image-20260831133533096

Conclusion

Automating workspace preparation ensures that every AI interaction starts with consistent context, constraints, and project rules without adding repetitive setup steps. By mirroring IBM Bob’s internal event models, bob-agents-injector bridges the gap between global master rules and individual repository management.

Whether working across stable or Insiders builds of IBM Bob, spending a few minutes to build local IDE utilities pays back continuous micro-dividends in productivity and rule enforcement across every project you touch.

Thanks for reading 🏗️

Links

  • IBM Bob: https://bob.ibm.com/

Comments (0)

Login to post a comment.

Alain Airom (Ayrom)
Alain Airom (Ayrom)

Build Engineer

IT guy, IBMer... sharing my hands-on experiences and technical subjects of my interest (IBM or not). A bit "touche à tout"!

Subscribe to Alain Airom (Ayrom)'s Newsletter

More from Alain Airom (Ayrom)

View profile

Deep Dive: Testing Radar UI for Kubernetes using MCP, a Go GUI, and an Autonomous Agent

Hand-on test of radarhq.io K8S UI and dashboard on a macOS with Minikube and Podman Introduction Kubernetes dashboards are often either overloaded with unnecess...

5 minAug 31

Embedding Docling-NLP in Ad-Hoc UI Applications: A Lightweight Blueprint

Implementing a “Graph Language Model” with Docling-NLP Image from Docling-AI Building a local, privacy-focused Document AI pipeline often feels like balancing h...

3 minAug 31

Loop Engineering 101

From Curiosity to Creation: Building a Loop Engineering Application with IBM’s Bob Introduction For some time now, “Loop Engineering” has been generating signif...

9 minAug 31

Building an OpenTelemetry Instrumentation Wizard

Accelerating observability adoption by automating OpenTelemetry instrumentation across heterogeneous codebases Introduction Years ago, I was tasked with buildin...

14 minAug 31

Benchmarking Local AI: Building a llama.cpp vs. Ollama Comparison & Benchmarking App

Pros and cons of both tools provided by Bob! Introduction I’ve been reading and seeing blog posts regarding the virtues of llama.cpp versus Ollama. Myself, I'm ...

5 minAug 31