Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .bot/skills/video-read/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: video-read
description: Analyze video files (screen recordings, demos, walkthroughs) using AI vision models. Use when you need to understand, describe, or extract information from video content. Supports local files and Slack-hosted videos.
allowed-tools: Bash
model: haiku
---

# Video Read

Analyze video files using Gemini 2.5 Flash (native video understanding) or GPT-4o (frame extraction fallback).

## Usage

**Local video file:**

```bash
bun ../bot/cli.ts video read -f <video_path> [-m gemini|gpt4o] [-p "custom prompt"]
```

**Slack file by ID:**

```bash
bun ../bot/cli.ts video read --slack-file <FILE_ID> [-m gemini|gpt4o] [-p "custom prompt"]
```

**Slack file by URL:**

```bash
bun ../bot/cli.ts video read --slack-url "<SLACK_FILE_URL>" [-m gemini|gpt4o] [-p "custom prompt"]
```

## Parameters

- `-f, --file`: Local video file path (.mp4, .webm, .mov, .avi, .mkv)
- `--slack-file`: Slack file ID (auto-downloads then analyzes)
- `--slack-url`: Slack file URL (auto-downloads then analyzes)
- `-m, --model`: AI model to use (default: `gemini`)
- `gemini` — Gemini 2.5 Flash with native video understanding (recommended)
- `gpt4o` — GPT-4o with frame extraction via ffmpeg
- `-p, --prompt`: Custom analysis prompt (optional)

**Note**: Exactly one of `--file`, `--slack-file`, or `--slack-url` must be provided.

## Examples

```bash
# Analyze a screen recording with Gemini (default)
bun ../bot/cli.ts video read -f ./recording.mp4

# Use GPT-4o fallback
bun ../bot/cli.ts video read -f ./demo.mp4 -m gpt4o

# Analyze a Slack-shared video by file ID
bun ../bot/cli.ts video read --slack-file F0AMXCK3JQ1

# Custom prompt for bug analysis
bun ../bot/cli.ts video read -f ./bug.mp4 -p "What bug is shown? Steps to reproduce?"
```

## Output

Detailed text description of the video content, followed by model and usage metadata.

## Notes

- Gemini sends full video as base64 — best temporal understanding
- GPT-4o extracts frames at 1fps via ffmpeg, samples every other — requires ffmpeg
- Supported: .mp4, .webm, .mov, .avi, .mkv, .m4v
- Env vars: `GEMINI_API_KEY` for Gemini, `OPENAI_API_KEY` for GPT-4o
69 changes: 69 additions & 0 deletions .claude/skills/video-read/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: video-read
description: Analyze video files (screen recordings, demos, walkthroughs) using AI vision models. Use when the user wants to understand, describe, or extract information from video content. Supports local files and Slack-hosted videos.
allowed-tools: Bash
model: haiku
---

# Video Read

Analyze video files using Gemini 2.5 Flash (native video understanding) or GPT-4o (frame extraction fallback).

## Usage

**Local video file:**

```bash
prbot video read -f <video_path> [-m gemini|gpt4o] [-p "custom prompt"]
```

**Slack file by ID:**

```bash
prbot video read --slack-file <FILE_ID> [-m gemini|gpt4o] [-p "custom prompt"]
```

**Slack file by URL:**

```bash
prbot video read --slack-url "<SLACK_FILE_URL>" [-m gemini|gpt4o] [-p "custom prompt"]
```

## Parameters

- `-f, --file`: Local video file path (.mp4, .webm, .mov, .avi, .mkv)
- `--slack-file`: Slack file ID (auto-downloads then analyzes)
- `--slack-url`: Slack file URL (auto-downloads then analyzes)
- `-m, --model`: AI model to use (default: `gemini`)
- `gemini` — Gemini 2.5 Flash with native video understanding (recommended)
- `gpt4o` — GPT-4o with frame extraction via ffmpeg
- `-p, --prompt`: Custom analysis prompt (optional, default focuses on step-by-step actions, UI state, errors)

**Note**: Exactly one of `--file`, `--slack-file`, or `--slack-url` must be provided.

## Examples

```bash
# Analyze a screen recording with Gemini (default, best quality)
prbot video read -f ./recording.mp4

# Use GPT-4o fallback
prbot video read -f ./demo.mp4 -m gpt4o

# Analyze a video shared in Slack
prbot video read --slack-file F0AMXCK3JQ1

# Custom analysis prompt
prbot video read -f ./bug-repro.mp4 -p "What bug is being demonstrated? What are the exact steps to reproduce it?"
```

## Output

Outputs a detailed text description of the video content, followed by model and usage metadata.

## Notes

- Gemini path sends the full video as base64 inline data — best for understanding temporal changes
- GPT-4o path extracts frames at 1fps with ffmpeg, samples every other frame — requires ffmpeg installed
- Supported formats: .mp4, .webm, .mov, .avi, .mkv, .m4v
- Requires `GEMINI_API_KEY` env var for Gemini, `OPENAI_API_KEY` for GPT-4o
18 changes: 17 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ on:
branches:
- main
jobs:
run_comfy_pr:
comfy_pr_test:
runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkout action needs to be configured to checkout the PR branch with proper ref when running on pull_request events. By default, actions/checkout@v4 checks out a merge commit in pull_request context, which prevents pushing back to the PR branch. You should add ref: ${{ github.head_ref }} to the checkout step to ensure the actual PR branch is checked out, allowing the auto-commit to work properly.

Copilot uses AI. Check for mistakes.
with:
ref: ${{ github.head_ref }}
# setup comfy-cli
- uses: actions/setup-python@v5
with:
Expand All @@ -30,6 +34,18 @@ jobs:
# setup comfy-pr
# Run Comfy-PR Tests
- run: bun i
- run: bunx oxlint --fix
- run: bunx oxfmt --write
- name: Commit lint/format fixes
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if ! git diff --quiet; then
git add -A
HUSKY=0 git commit -m "style: auto-fix lint and formatting [skip ci]"
git push
fi
- run: bun test
timeout-minutes: 8
env:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,4 @@ tmp/
TODO.md
REPORT.md
.data
.logs
6 changes: 3 additions & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bun
bunx tsgo
bunx oxlint --fix
bunx oxfmt
bun typecheck
bun lint
bunx lint-staged

36 changes: 19 additions & 17 deletions app/tasks/gh-desktop-release-notification/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ const createMockCollection = (collectionName?: string) => {
}
}
// Check for deliveryId (webhook tests)
if ((filter as { deliveryId?: string }).deliveryId && d.deliveryId === (filter as { deliveryId?: string }).deliveryId) return doc;
if (
(filter as { deliveryId?: string }).deliveryId &&
d.deliveryId === (filter as { deliveryId?: string }).deliveryId
)
return doc;
}
// Fallback to findOneAndUpdate results for backward compatibility
const existingOp = dbOperations.find(
(op) => op.type === "findOneAndUpdate" && op.result,
);
const existingOp = dbOperations.find((op) => op.type === "findOneAndUpdate" && op.result);
if (existingOp && filter.version) {
const result = existingOp.result as { coreVersion?: string } | undefined;
if (result?.coreVersion === filter.version) {
Expand All @@ -69,7 +71,7 @@ const createMockCollection = (collectionName?: string) => {
},
insertOne: async (doc: unknown) => {
const id = `mock_id_${++docIdCounter}`;
const docWithId = { ...doc as object, _id: id };
const docWithId = { ...(doc as object), _id: id };
docs.set(id, docWithId);
return { insertedId: id };
},
Expand Down Expand Up @@ -125,6 +127,14 @@ mock.module("./upsertSlackMessage", () => ({
url: `https://slack.com/message/${Date.now()}`,
};
},
upsertSlackMarkdownMessage: async (msg: SlackMessageType) => {
mockSlackMessages.push(msg);
return {
...msg,
url: `https://slack.com/message/${Date.now()}`,
};
},
mdFmt: async (md: string) => md,
}));

// Now import the module to test (after all mocks are set up)
Expand Down Expand Up @@ -174,9 +184,7 @@ describe("GithubDesktopReleaseNotificationTask", () => {
expect(saveOps.length).toBeGreaterThanOrEqual(1);

// Check if any save operation has slackMessageDrafting
const hasDraftingMessage = saveOps.some(
(op) => op.args[1]?.$set?.slackMessageDrafting,
);
const hasDraftingMessage = saveOps.some((op) => op.args[1]?.$set?.slackMessageDrafting);
expect(hasDraftingMessage).toBe(true);

// Ensure slackMessage was NOT set for draft
Expand Down Expand Up @@ -261,9 +269,7 @@ describe("GithubDesktopReleaseNotificationTask", () => {
expect(saveOps.length).toBeGreaterThanOrEqual(1);

// Check if any save operation has slackMessage
const hasStableMessage = saveOps.some(
(op) => op.args[1]?.$set?.slackMessage,
);
const hasStableMessage = saveOps.some((op) => op.args[1]?.$set?.slackMessage);
expect(hasStableMessage).toBe(true);
});

Expand Down Expand Up @@ -343,9 +349,7 @@ describe("GithubDesktopReleaseNotificationTask", () => {
expect(saveOps.length).toBeGreaterThanOrEqual(1);

// Check if any save operation has slackMessageDrafting
const hasDraftingMessage = saveOps.some(
(op) => op.args[1]?.$set?.slackMessageDrafting,
);
const hasDraftingMessage = saveOps.some((op) => op.args[1]?.$set?.slackMessageDrafting);
expect(hasDraftingMessage).toBe(true);
});
});
Expand All @@ -372,9 +376,7 @@ describe("GithubDesktopReleaseNotificationTask", () => {

// Verify coreVersion was extracted
const saveOps = dbOperations.filter((op) => op.type === "findOneAndUpdate");
const hasCoreVersion = saveOps.some(
(op) => op.args[1]?.$set?.coreVersion === "v0.2.0",
);
const hasCoreVersion = saveOps.some((op) => op.args[1]?.$set?.coreVersion === "v0.2.0");
expect(hasCoreVersion).toBe(true);
});
});
Expand Down
Loading