Share, Embed, and Curate Agent Sessions on DEV [Beta]

AI Summary15 min read

TL;DR

DEV introduces a beta feature allowing authors to upload, curate, and embed Agent Session transcripts into posts. It supports platforms like Gemini CLI and GitHub Copilot, with an example session building a guinea pig food picker app.

Key Takeaways

  • Authors can now upload Agent Session transcripts to DEV for curation, sharing, and embedding in posts.
  • The feature supports multiple platforms including Gemini CLI, Codex, Claude Code, Pi, and GitHub Copilot CLI.
  • It addresses the lack of standardized ways to share coding agent workflows beyond screenshots.
  • Users are encouraged to provide feedback as it's in beta, and can contribute by adding parsers for other tools.
  • An example session demonstrates building a simple web app for a guinea pig food picker using Gemini CLI.

Tags

aiforemdevtogemini

Authors can now upload Agent Session transcripts to their DEV account. Those transcripts can be curated, sliced up, shared publicly, and embedded into posts. This is a beta feature, and we are actively looking for feedback!

Every day, more people are creating software for the first time. Some have long histories as developers, and some have never seen a line of code in their life. In most cases, they're using the same tooling - coding agents. And yet, as these tools become ubiquitous there are surprisingly few ways to share your workflows and results in a standardized way beyond screenshots. Each tool has its own share link generator, each tool outputs logs in a different format. There are a few nascent projects out there to help, but they generally exist as standalone utilities or don't solve the sharing problem explicitly in a way that works on DEV.

We decided to solve that problem by making it possible to upload your transcripts to DEV, automatically parse them into a standardized format, and embed whole or partial sessions into your posts in a way that fits naturally into technical writing.

Supported Platforms

To start with, our parser supports Gemini CLI, Codex, Claude Code, Pi, and GitHub Copilot CLI. If you use a different tool, you're encouraged to open a PR or Issue on Forem to add a new parser yourself or ask us to do so. As an example, you can check out how our Gemini CLI parser works in our GitHub commit:
https://github.com/forem/forem/commit/0230aa56c1b4e59480c1de95822824f62f95aaa4#diff-a096c013f50a6f14bf8c09ab7c693348b9b704adeb99517f6089a511d025daa0

Example Session

Here's a session from an example app I built using Gemini CLI for my pet guinea pig Pearl (obligatory pic in comments).

Wheeky Gemini App
You

build me a simple web app to let my guinea pig tell me what food it wants

Agent

Clarifying Application Needs / Defining Food Selection UI / Mapping Out The App's Flow

I'll build a vibrant, touch-friendly web app called "Pui Pui Picker" designed for guinea pigs (and their humans).

The Plan:

  1. Scaffold: Create a React + TypeScript project using Vite.
  2. Design: A "big button" interface with high-contrast colors and bouncy animations to make it engaging.
  3. Features:
    • Food Grid: Large cards for Hay, Carrots, Romaine, and Bell Peppers.
    • Pui Pui Feedback: Visual and text-based "wheeking" feedback when an item is selected.
    • History: A small "Last Requested" log to track what the piggie "chose."
  4. Styling: Custom Vanilla CSS for a polished, modern look without external dependencies.

Does this sound like a good start for your guinea pig? Or should I add specific snacks they love?

I will start by scaffolding the project.

Show more
Agent
Shell {"command":"npm create vite@latest . -- --template react-ts && npm install","...
Input
{
  "command": "npm create vite@latest . -- --template react-ts && npm install",
  "description": "Scaffolding the React TypeScript project using Vite and installing dependencies."
}
You

let's go simpler than vite and react

Agent

Seeking Simplification for App / Constructing the index.html

I'll create a single-file, vibrant, and touch-friendly HTML application called "The Wheeky Menu". It will use large, colorful buttons and bouncy animations—perfect for a guinea pig's "input" (or a human's finger).

The Plan:

  1. Single File: Everything (HTML, CSS, JS) in index.html for instant loading.
  2. Piggie-Centric UI:
    • Large Interaction Zones: Easy-to-hit cards for Hay, Carrots, Peppers, and Lettuce.
    • Visual Feedback: Cards will "pop" and flash when tapped.
  3. Features:
    • "Wheek!" Notification: A large visual alert showing what was picked.
    • Order History: A simple log at the bottom to see what the piggie "requested" most.

I will now create the index.html file.

Show more
Agent
WriteFile {"content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"...
Input
{"content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\...
Output
Successfully created and wrote to new file: /Users/jonmarkgo/Documents/GitHub/gemini-guinea-pig/index.html. Here is the updated code:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Wheeky Menu - Piggie Food Picker</title>
    <style>
        :root {
            --bg-color: #fdf6e3;
            --card-bg: #ffffff;
            --text-color: #5d4037;
            --accent-color: #ff8a65;
            --shadow: 0 8px 16px rgba(0,0,0,0.1);
            
            --color-hay: #fff176;
            --color-carrot: #ffb74d;
            --color-pepper: #ef5350;
            --color-lettuce: #81c784;
        }

        * {
            box-sizing: border-box;
            -webkit-tap-highlight-color: transparent;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: var(--bg-color);
            color: var(--text-color);
            margin: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            min-height: 100vh;
            padding: 20px;
            overflow-x: hidden;
        }

        header {
            text-align: center;
            margin-bottom: 30px;
        }

        h1 {
            font-size: 2.5rem;
            margin: 0;
            color: var(--accent-color);
            text-shadow: 2px 2px 0px #fff;
        }

        p.subtitle {
            font-style: italic;
            margin-top: 5px;
            opacity: 0.8;
        }

        .grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 20px;
            width: 100%;
            max-width: 600px;
        }

        .food-card {
            background: var(--card-bg);
            border-radius: 24px;
            padding: 30px 10px;
            text-align: center;
            box-shadow: var(--shadow);
            cursor: pointer;
            transition: transform 0.1s, box-shadow 0.1s;
            border: 4px solid transparent;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            user-select: none;
        }

        .food-card:active {
            transform: scale(0.92);
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .food-card .emoji {
            font-size: 4rem;
            margin-bottom: 10px;
        }

        .food-card .label {
            font-weight: bold;
            font-size: 1.2rem;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        /* Food Specific Borders */
        .card-hay { border-color: var(--color-hay); }
        .card-carrot { border-color: var(--color-carrot); }
        .card-pepper { border-color: var(--color-pepper); }
        .card-lettuce { border-color: var(--color-lettuce); }

        /* Announcement Overlay */
        #announcement {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%) scale(0);
            background: white;
            padding: 40px;
            border-radius: 50%;
            box-shadow: 0 0 100px rgba(0,0,0,0.2);
            z-index: 100;
            text-align: center;
            pointer-events: none;
            transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            border: 10px solid var(--accent-color);
        }

        #announcement.show {
            transform: translate(-50%, -50%) scale(1);
        }

        #announcement h2 {
            margin: 0;
            font-size: 3rem;
        }

        /* History Log */
        .history {
            margin-top: 40px;
            width: 100%;
            max-width: 600px;
            background: rgba(255,255,255,0.5);
            padding: 20px;
            border-radius: 16px;
        }

        .history h3 {
            margin-top: 0;
            font-size: 1rem;
            opacity: 0.6;
            text-transform: uppercase;
        }

        #log-list {
            list-style: none;
            padding: 0;
            margin: 0;
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
        }

        .log-item {
            background: white;
            padding: 5px 12px;
            border-radius: 20px;
            font-size: 0.9rem;
            box-shadow: 0 2px 4px rgba(0,0,0,0.05);
            animation: slideIn 0.3s ease-out;
        }

        @keyframes slideIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        @media (max-width: 400px) {
            .grid { grid-template-columns: 1fr; }
            h1 { font-size: 1.8rem; }
        }
    </style>
</head>
<body>

    <header>
        <h1>WHEEKY MENU</h1>
        <p class="subtitle">Let the piggie decide!</p>
    </header>

    <div class="grid">
        <div class="food-card card-hay" onclick="makeRequest('HAY', '🌾')">
            <span class="emoji">🌾</span>
            <span class="label">Hay</span>
        </div>
        <div class="food-card card-carrot" onclick="makeRequest('CARROT', '🥕')">
            <span class="emoji">🥕</span>
            <span class="label">Carrot</span>
        </div>
        <div class="food-card card-pepper" onclick="makeRequest('PEPPER', '🫑')">
            <span class="emoji">🫑</span>
            <span class="label">Pepper</span>
        </div>
        <div class="food-card card-lettuce" onclick="makeRequest('LETTUCE', '🥬')">
            <span class="emoji">🥬</span>
            <span class="label">Lettuce</span>
        </div>
    </div>

    <div id="announcement">
        <h2 id="announcement-text">WHEEK!</h2>
    </div>

    <div class="history">
        <h3>Request History</h3>
        <div id="log-list">
            <!-- History items will appear here -->
        </div>
    </div>

    <script>
        const announcement = document.getElementById('announcement');
        const announcementText = document.getElementById('announcement-text');
        const logList = document.getElementById('log-list');
        let timer;

        function makeRequest(food, emoji) {
            // Show announcement
            announcementText.innerHTML = `${emoji}<br>WHEEK!<br>${food}`;
            announcement.classList.add('show');

            // Add to history
            const item = document.createElement('div');
            item.className = 'log-item';
            const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
            item.textContent = `${time}: ${emoji} ${food}`;
            logList.prepend(item);

            // Keep only last 10
            if (logList.children.length > 10) {
                logList.removeChild(logList.lastChild);
            }

            // Hide announcement after delay
            clearTimeout(timer);
            timer = setTimeout(() => {
                announcement.classList.remove('show');
            }, 1500);

            // Optional: Simple Vibration for mobile
            if (navigator.vibrate) {
                navigator.vibrate(100);
            }
        }
    </script>
</body>
</html>
Agent

Constructing The Interface / Refining The UX Design

The "Wheeky Menu" is ready! It's a single-file web app with large, high-contrast buttons designed for easy "selection" (by snout or finger).

How to use:

  1. Open the file: You can open index.html directly in any web browser.
  2. Piggie Input: Place your tablet or phone on the floor or near the cage. When your guinea pig taps or "boops" a card:
    • A large "WHEEK!" announcement pops up.
    • The request is added to the Request History at the bottom (so you can see what they picked if you step away).
    • On mobile devices, it will provide a small vibration (haptic feedback) when touched.

I've used high-contrast colors and bouncy animations to make it visually stimulating. Enjoy the wheeking!

Show more
You

Find the current Gemini CLI session log file. Look in ~/.gemini/sessions/ for the most recently modified .json or .jsonl file.

Then run the devto command to upload it:

Error: No file specified. Use --auto or provide a file path.\nRun 'devto --help' for usage.
[Shell command 'devto --yes --tool gemini_cli --title "" "$(ls -t ~/.gemini/sessions//*.json ~/.gemini/sessions//*.jsonl 2>/dev/null | head -1)"' exited with code 1]

If devto is not installed, use curl directly:

jq: Bad JSON in --rawfile body : Could not open : No such file or directory
[Shell command 'TMPJSON=$(mktemp) && jq -n --arg title "" --rawfile body "$(ls -t ~/.gemini/sessions//*.json ~/.gemini/sessions//*.jsonl 2>/dev/null | head -1)" '{title: $title, tool_name: "gemini_cli", body: $body}' > "$TMPJSON" && curl -s -X POST "${DEVTO_HOST:-https://dev.to}/api/agent_sessions" -H "Content-Type: application/json" -H "Accept: application/vnd.forem.api-v1+json" -H "api-key: $DEVTO_API_KEY" -d @"$TMPJSON" && rm -f "$TMPJSON"' exited with code 2]

After uploading, display the URL of the created session to the user.

Show more
Agent

Examining Upload Strategies / Implementing Dev Upload Skill

I will activate the dev-upload skill to securely and efficiently upload your session log to DEV.to.

Agent
Activate Skill {"name":"dev-upload"}
Input
{
  "name": "dev-upload"
}
Output
<activated_skill name="dev-upload">
  <instructions>
    # Upload Session to DEV

Upload the current session transcript to [DEV](https://dev.to) as an Agent Session.

## Steps

### 1. Find the transcript

Find the current session's transcript file — the most recently modified `.jsonl` file:
- Claude Code: `~/.claude/projects/` (recursive)
- Codex: `~/.codex/sessions/`
- Gemini CLI: `~/.gemini/sessions/`
- GitHub Copilot: `~/.copilot/`
- Pi: `~/.pi/sessions/`

Use the agent's own session context if available to identify the exact path.

### 2. Load config and check for API key

Load all config in a single bash call:

```bash
if [ -f ~/.config/devto/config ]; then source ~/.config/devto/config; fi
API_KEY="${DEVTO_API_KEY:-${API_KEY:-}}"
HOST="${DEVTO_HOST:-https://dev.to}"
echo "API_KEY=${API_KEY:+SET}"
echo "HOST=$HOST"
```

If no key is found:
1. Tell the user they need a DEV API key and link them to https://dev.to/settings/extensions to generate one.
2. Ask the user to paste their key here in the chat.
3. Once they provide it, save it (along with the host) so they never have to do this again:

```bash
mkdir -p ~/.config/devto
cat > ~/.config/devto/config <<'CONF'
DEVTO_API_KEY="THE_KEY_THEY_GAVE_YOU"
DEVTO_HOST="https://dev.to"
CONF
```

4. Then continue with the upload. Do NOT stop or ask the user to run commands themselves.

### 3. Upload

Do the entire upload in a single bash call. Replace TRANSCRIPT, TITLE, HOST, and API_KEY with actual values:

```bash
set -e
TRANSCRIPT="/path/to/session.jsonl"
TITLE="the session title"
HOST="https://dev.to"
API_KEY="the-key"

TMPJSON=$(mktemp)
trap "rm -f '$TMPJSON'" EXIT

jq -n \
  --arg title "$TITLE" \
  --arg tool_name "auto" \
  --rawfile body "$TRANSCRIPT" \
  '{title: $title, tool_name: $tool_name, body: $body}' > "$TMPJSON"

RESPONSE=$(curl -s -w "\n%{http_code}" \
  -X POST "$HOST/api/agent_sessions" \
  -H "Content-Type: application/json" \
  -H "Accept: application/vnd.forem.api-v1+json" \
  -H "api-key: $API_KEY" \
  -d @"$TMPJSON")

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" = "201" ]; then
  URL=$(echo "$BODY" | jq -r '.url // empty')
  SLUG=$(echo "$BODY" | jq -r '.slug // empty')
  echo "Uploaded successfully!"
  echo "URL: ${URL:-$HOST/agent_sessions/$SLUG}"
else
  echo "Upload failed (HTTP $HTTP_CODE)"
  echo "$BODY" | jq -r '.error // .' 2>/dev/null || echo "$BODY"
fi
```

### 4. Show result

Display the session URL to the user on success. On failure, show the error message and suggest fixes.

## Notes

- If the user provided a title argument, use it. Otherwise generate one from the conversation context.
- The server auto-detects the tool from the transcript content when `tool_name` is `"auto"`.
- Uploads are **private (unlisted) by default**.
- The server applies automatic redaction of common secrets (API keys, tokens, passwords).
- Always substitute actual values into the bash script — do not rely on environment variables being set in the agent's shell.
  </instructions>

  <available_resources>
    Showing up to 200 items (files + folders).

/Users/jonmarkgo/.agents/skills/dev-upload/
├───SKILL.md
└───scripts/
    └───forem-upload
  </available_resources>
</activated_skill>
Agent

Defining the Upload Process / Analyzing the Configuration Steps

I'll locate your most recent session log and check for a DEV.to API key to begin the upload.

Agent
Shell {"description":"Locating the most recent session log and checking for a DEV.t...
Input
{"description":"Locating the most recent session log and checking for a DEV.to API key.","command":"# Find the most recent session log\nTRANSCRIPT=$(ls -t ~/.gemini/sessions/*.jsonl 2>/dev/null | head...
Output
Output: TRANSCRIPT=
API_KEY=SET
HOST=https://dev.to
Process Group PGID: 73065
Agent

Searching the File System / Refining the Search Strategy

I'll look for the session log again, including both .json and .jsonl files across all session subdirectories.

Agent
Shell {"command":"# Find the most recent session log\nTRANSCRIPT=$(ls -t ~/.gemini/...
Input
{"command":"# Find the most recent session log\nTRANSCRIPT=$(ls -t ~/.gemini/sessions/**/*.json ~/.gemini/sessions/**/*.jsonl 2>/dev/null | head -1)\necho \"TRANSCRIPT=$TRANSCRIPT\"","description":"Se...
Output
Output: TRANSCRIPT=
Process Group PGID: 73508
Agent

Exploring the Directory

I'll list the contents of ~/.gemini/sessions/ to find the exact session log files.

Agent
Shell {"command":"ls -R ~/.gemini/sessions/ | head -n 20","description":"Listing th...
Input
{
  "command": "ls -R ~/.gemini/sessions/ | head -n 20",
  "description": "Listing the contents of the Gemini CLI sessions directory."
}
Output
Output: ls: /Users/jonmarkgo/.gemini/sessions/: No such file or directory
Process Group PGID: 73515
Agent

Examining Session Locations / Inspecting Gemini Directory Contents

I'll list the contents of the ~/.gemini/ directory to locate the session logs.

Agent
Shell {"command":"ls -F ~/.gemini/","description":"Listing the contents of the ~/.g...
Input
{
  "command": "ls -F ~/.gemini/",
  "description": "Listing the contents of the ~/.gemini directory."
}
Output
Output: antigravity/                    google_accounts.json            mcp-oauth-tokens-v2.json        settings.json                   tmp/
commands/                       history/                        oauth_creds.json                settings.json.orig              trustedFolders.json
GEMINI.md                       installation_id                 projects.json                   state.json                      user_id
Process Group PGID: 73530
Agent

Checking the History

I'll list the contents of ~/.gemini/history/ to find the session log.

Agent
Shell {"command":"ls -F ~/.gemini/history/ | head -n 20","description":"Listing the...
Input
{
  "command": "ls -F ~/.gemini/history/ | head -n 20",
  "description": "Listing the contents of the ~/.gemini/history directory."
}
Output
Output: cookieparser/
gemini-dog-app/
gemini-guinea-pig/
mlh-core/
Process Group PGID: 73565
Agent

Examining Project Location

I'll list the contents of ~/.gemini/history/gemini-guinea-pig/ to find the current session's log.

Agent
Shell {"command":"ls -t ~/.gemini/history/gemini-guinea-pig/*.jsonl 2>/dev/null | h...
Input
{
  "command": "ls -t ~/.gemini/history/gemini-guinea-pig/*.jsonl 2>/dev/null | head -1",
  "description": "Searching for the most recent session log in the history directory."
}
Output
Output: (empty)
Process Group PGID: 74637
Agent

Beginning File Enumeration

I'll list all files in ~/.gemini/history/gemini-guinea-pig/ to find the session log.

Agent
Shell {"command":"ls -R ~/.gemini/history/gemini-guinea-pig/ | head -n 100","descri...
Input
{
  "command": "ls -R ~/.gemini/history/gemini-guinea-pig/ | head -n 100",
  "description": "Listing all files in the history folder to locate the log file."
}
Output
Output: (empty)
Process Group PGID: 75100
Agent

Pinpointing the Missing Path / Re-evaluating the Scope

I'll list the contents of ~/.gemini/tmp/gemini-guinea-pig/ to find the session log.

Agent
Shell {"command":"ls -F ~/.gemini/tmp/gemini-guinea-pig/ | head -n 20","description...
Input
{
  "command": "ls -F ~/.gemini/tmp/gemini-guinea-pig/ | head -n 20",
  "description": "Listing the contents of the ~/.gemini/tmp/gemini-guinea-pig directory."
}
Output
Output: chats/
logs.json
Process Group PGID: 75368
Agent

Investigating Log Location

I'll read the first 10 lines of logs.json to see if it's the session log.

Agent
Shell {"command":"head -n 10 ~/.gemini/tmp/gemini-guinea-pig/logs.json","descriptio...
Input
{
  "command": "head -n 10 ~/.gemini/tmp/gemini-guinea-pig/logs.json",
  "description": "Reading the beginning of logs.json to confirm its format."
}
Output
Output: [
  {
    "sessionId": "295d2fe2-6276-49c4-9229-db8a61d33f2a",
    "messageId": 0,
    "type": "user",
    "message": "build me a simple web app to let my guinea pig tell me what food it wants",
    "timestamp": "2026-03-04T01:15:24.210Z"
  },
  {
    "sessionId": "295d2fe2-6276-49c4-9229-db8a61d33f2a",
Process Group PGID: 75447

Visit Website