opencode-skills-antigravity 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/bdistill-behavioral-xray/SKILL.md +86 -0
- package/bundled-skills/bdistill-knowledge-extraction/SKILL.md +105 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/last30days/SKILL.md +6 -1
- package/bundled-skills/videodb/scripts/ws_listener.py +58 -11
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bdistill-behavioral-xray
|
|
3
|
+
description: "X-ray any AI model's behavioral patterns — refusal boundaries, hallucination tendencies, reasoning style, formatting defaults. No API key needed."
|
|
4
|
+
category: ai-testing
|
|
5
|
+
risk: safe
|
|
6
|
+
source: community
|
|
7
|
+
date_added: "2026-03-20"
|
|
8
|
+
author: FrancyJGLisboa
|
|
9
|
+
tags: [ai, testing, behavioral-analysis, model-evaluation, red-team, compliance, mcp]
|
|
10
|
+
tools: [claude, cursor, codex, copilot]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Behavioral X-Ray
|
|
14
|
+
|
|
15
|
+
Systematically probe an AI model's behavioral patterns and generate a visual report. The AI agent probes *itself* — no API key or external setup needed.
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
bdistill's Behavioral X-Ray runs 30 carefully designed probe questions across 6 dimensions, auto-tags each response with behavioral metadata, and compiles results into a styled HTML report with radar charts and actionable insights.
|
|
20
|
+
|
|
21
|
+
Use it to understand your model before building with it, compare models for task selection, or track behavioral drift over time.
|
|
22
|
+
|
|
23
|
+
## When to Use This Skill
|
|
24
|
+
|
|
25
|
+
- Use when you want to understand how your AI model actually behaves (not how it claims to)
|
|
26
|
+
- Use when choosing between models for a specific task
|
|
27
|
+
- Use when debugging unexpected refusals, hallucinations, or formatting issues
|
|
28
|
+
- Use for compliance auditing — documenting model behavior at deployment boundaries
|
|
29
|
+
- Use for red team assessments — systematic boundary mapping across safety dimensions
|
|
30
|
+
|
|
31
|
+
## How It Works
|
|
32
|
+
|
|
33
|
+
### Step 1: Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install bdistill
|
|
37
|
+
claude mcp add bdistill -- bdistill-mcp # Claude Code
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For other tools, add bdistill-mcp as an MCP server in your project config.
|
|
41
|
+
|
|
42
|
+
### Step 2: Run the probe
|
|
43
|
+
|
|
44
|
+
In Claude Code:
|
|
45
|
+
```
|
|
46
|
+
/xray # Full behavioral probe (30 questions)
|
|
47
|
+
/xray --dimensions refusal # Probe just one dimension
|
|
48
|
+
/xray-report # Generate report from completed probe
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
In any tool with MCP:
|
|
52
|
+
```
|
|
53
|
+
"X-ray your behavioral patterns"
|
|
54
|
+
"Test your refusal boundaries"
|
|
55
|
+
"Generate a behavioral report"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Probe Dimensions
|
|
59
|
+
|
|
60
|
+
| Dimension | What it measures |
|
|
61
|
+
|-----------|-----------------|
|
|
62
|
+
| **tool_use** | When does it call tools vs. answer from knowledge? |
|
|
63
|
+
| **refusal** | Where does it draw safety boundaries? Does it over-refuse? |
|
|
64
|
+
| **formatting** | Lists vs. prose? Code blocks? Length calibration? |
|
|
65
|
+
| **reasoning** | Does it show chain-of-thought? Handle trick questions? |
|
|
66
|
+
| **persona** | Identity, tone matching, composure under hostility |
|
|
67
|
+
| **grounding** | Hallucination resistance, fabrication traps, knowledge limits |
|
|
68
|
+
|
|
69
|
+
## Output
|
|
70
|
+
|
|
71
|
+
A styled HTML report showing:
|
|
72
|
+
- Refusal rate, hedge rate, chain-of-thought usage
|
|
73
|
+
- Per-dimension breakdown with bar charts
|
|
74
|
+
- Notable response examples with behavioral tags
|
|
75
|
+
- Actionable insights (e.g., "you already show CoT 85% of the time, no need to prompt for it")
|
|
76
|
+
|
|
77
|
+
## Best Practices
|
|
78
|
+
|
|
79
|
+
- Answer probe questions honestly — the value is in authentic behavioral data
|
|
80
|
+
- Run probes on the same model periodically to track behavioral drift
|
|
81
|
+
- Compare reports across models to make informed selection decisions
|
|
82
|
+
- Use adversarial knowledge extraction (`/distill --adversarial`) alongside behavioral probes for complete model profiling
|
|
83
|
+
|
|
84
|
+
## Related Skills
|
|
85
|
+
|
|
86
|
+
- `@bdistill-knowledge-extraction` - Extract structured domain knowledge from any AI model
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bdistill-knowledge-extraction
|
|
3
|
+
description: "Extract structured domain knowledge from AI models in-session or from local open-source models via Ollama. No API key needed."
|
|
4
|
+
category: ai-research
|
|
5
|
+
risk: safe
|
|
6
|
+
source: community
|
|
7
|
+
date_added: "2026-03-20"
|
|
8
|
+
author: FrancyJGLisboa
|
|
9
|
+
tags: [ai, knowledge-extraction, domain-specific, data-moat, mcp, reference-data]
|
|
10
|
+
tools: [claude, cursor, codex, copilot]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Knowledge Extraction
|
|
14
|
+
|
|
15
|
+
Extract structured, quality-scored domain knowledge from any AI model — in-session from closed models (no API key) or locally from open-source models via Ollama.
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
bdistill turns your AI subscription sessions into a compounding knowledge base. The agent answers targeted domain questions, bdistill structures and quality-scores the responses, and the output accumulates into a searchable, exportable reference dataset.
|
|
20
|
+
|
|
21
|
+
Adversarial mode challenges the agent's claims — forcing evidence, corrections, and acknowledged limitations — producing validated knowledge entries.
|
|
22
|
+
|
|
23
|
+
## When to Use This Skill
|
|
24
|
+
|
|
25
|
+
- Use when you need structured reference data on any domain (medical, legal, finance, cybersecurity)
|
|
26
|
+
- Use when building lookup tables, Q&A datasets, or research corpora
|
|
27
|
+
- Use when generating training data for traditional ML models (regression, classification — NOT competing LLMs)
|
|
28
|
+
- Use when you want cross-model comparison on domain knowledge
|
|
29
|
+
|
|
30
|
+
## How It Works
|
|
31
|
+
|
|
32
|
+
### Step 1: Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install bdistill
|
|
36
|
+
claude mcp add bdistill -- bdistill-mcp # Claude Code
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 2: Extract knowledge in-session
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
/distill medical cardiology # Preset domain
|
|
43
|
+
/distill --custom kubernetes docker helm # Custom terms
|
|
44
|
+
/distill --adversarial medical # With adversarial validation
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Step 3: Search, export, compound
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bdistill kb list # Show all domains
|
|
51
|
+
bdistill kb search "atrial fibrillation" # Keyword search
|
|
52
|
+
bdistill kb export -d medical -f csv # Export as spreadsheet
|
|
53
|
+
bdistill kb export -d medical -f markdown # Readable knowledge document
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Output Format
|
|
57
|
+
|
|
58
|
+
Structured reference JSONL — not training data:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"question": "What causes myocardial infarction?",
|
|
63
|
+
"answer": "Myocardial infarction results from acute coronary artery occlusion...",
|
|
64
|
+
"domain": "medical",
|
|
65
|
+
"category": "cardiology",
|
|
66
|
+
"tags": ["mechanistic", "evidence-based"],
|
|
67
|
+
"quality_score": 0.73,
|
|
68
|
+
"confidence": 1.08,
|
|
69
|
+
"validated": true,
|
|
70
|
+
"source_model": "Claude Sonnet 4"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Tabular ML Data Generation
|
|
75
|
+
|
|
76
|
+
Generate structured training data for traditional ML models:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
/schema sepsis | hr:float, bp:float, temp:float, wbc:float | risk:category[low,moderate,high,critical]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Exports as CSV ready for pandas/sklearn. Each row tracks source_model for cross-model analysis.
|
|
83
|
+
|
|
84
|
+
## Local Model Extraction (Ollama)
|
|
85
|
+
|
|
86
|
+
For open-source models running locally:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Install Ollama from https://ollama.com
|
|
90
|
+
ollama serve
|
|
91
|
+
ollama pull qwen3:4b
|
|
92
|
+
|
|
93
|
+
bdistill extract --domain medical --model qwen3:4b
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Security & Safety Notes
|
|
97
|
+
|
|
98
|
+
- In-session extraction uses your existing subscription — no additional API keys
|
|
99
|
+
- Local extraction runs entirely on your machine via Ollama
|
|
100
|
+
- No data is sent to external services
|
|
101
|
+
- Output is reference data, not LLM training format
|
|
102
|
+
|
|
103
|
+
## Related Skills
|
|
104
|
+
|
|
105
|
+
- `@bdistill-behavioral-xray` - X-ray a model's behavioral patterns
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Jetski/Cortex + Gemini Integration Guide
|
|
3
|
-
description: "Come usare antigravity-awesome-skills con Jetski/Cortex evitando l’overflow di contesto con 1.
|
|
3
|
+
description: "Come usare antigravity-awesome-skills con Jetski/Cortex evitando l’overflow di contesto con 1.306+ skill."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Jetski/Cortex + Gemini: integrazione sicura con 1.
|
|
6
|
+
# Jetski/Cortex + Gemini: integrazione sicura con 1.306+ skill
|
|
7
7
|
|
|
8
8
|
Questa guida mostra come integrare il repository `antigravity-awesome-skills` con un agente basato su **Jetski/Cortex + Gemini** (o framework simili) **senza superare il context window** del modello.
|
|
9
9
|
|
|
@@ -23,7 +23,7 @@ Non bisogna mai:
|
|
|
23
23
|
- concatenare il contenuto di tutte le `SKILL.md` in un singolo system prompt;
|
|
24
24
|
- reiniettare l’intera libreria per **ogni** richiesta.
|
|
25
25
|
|
|
26
|
-
Con oltre 1.
|
|
26
|
+
Con oltre 1.306 skill, questo approccio riempie il context window prima ancora di aggiungere i messaggi dell’utente, causando l’errore di truncation.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -20,7 +20,7 @@ This example shows one way to integrate **antigravity-awesome-skills** with a Je
|
|
|
20
20
|
- How to enforce a **maximum number of skills per turn** via `maxSkillsPerTurn`.
|
|
21
21
|
- How to choose whether to **truncate or error** when too many skills are requested via `overflowBehavior`.
|
|
22
22
|
|
|
23
|
-
This pattern avoids context overflow when you have 1,
|
|
23
|
+
This pattern avoids context overflow when you have 1,306+ skills installed.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -6,7 +6,7 @@ This document keeps the repository's GitHub-facing discovery copy aligned with t
|
|
|
6
6
|
|
|
7
7
|
Preferred positioning:
|
|
8
8
|
|
|
9
|
-
> Installable GitHub library of 1,
|
|
9
|
+
> Installable GitHub library of 1,306+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.
|
|
10
10
|
|
|
11
11
|
Key framing:
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Key framing:
|
|
|
20
20
|
|
|
21
21
|
Preferred description:
|
|
22
22
|
|
|
23
|
-
> Installable GitHub library of 1,
|
|
23
|
+
> Installable GitHub library of 1,306+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.
|
|
24
24
|
|
|
25
25
|
Preferred homepage:
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ Preferred homepage:
|
|
|
28
28
|
|
|
29
29
|
Preferred social preview:
|
|
30
30
|
|
|
31
|
-
- use a clean preview image that says `1,
|
|
31
|
+
- use a clean preview image that says `1,306+ Agentic Skills`;
|
|
32
32
|
- mention Claude Code, Cursor, Codex CLI, and Gemini CLI;
|
|
33
33
|
- avoid dense text and tiny logos that disappear in social cards.
|
|
34
34
|
|
|
@@ -69,7 +69,7 @@ For manual updates, you need:
|
|
|
69
69
|
The update process refreshes:
|
|
70
70
|
- Skills index (`skills_index.json`)
|
|
71
71
|
- Web app skills data (`apps\web-app\public\skills.json`)
|
|
72
|
-
- All 1,
|
|
72
|
+
- All 1,306+ skills from the skills directory
|
|
73
73
|
|
|
74
74
|
## When to Update
|
|
75
75
|
|
|
@@ -6,7 +6,7 @@ Antigravity Awesome Skills gives Claude Code users an installable library of `SK
|
|
|
6
6
|
|
|
7
7
|
## Why use this repo for Claude Code
|
|
8
8
|
|
|
9
|
-
- It includes 1,
|
|
9
|
+
- It includes 1,306+ skills instead of a narrow single-domain starter pack.
|
|
10
10
|
- It supports the standard `.claude/skills/` path and the Claude Code plugin marketplace flow.
|
|
11
11
|
- It includes onboarding docs, bundles, and workflows so new users do not need to guess where to begin.
|
|
12
12
|
- It covers both everyday engineering tasks and specialized work like security reviews, infrastructure, product planning, and documentation.
|
|
@@ -8,7 +8,7 @@ Antigravity Awesome Skills supports Gemini CLI through the `.gemini/skills/` pat
|
|
|
8
8
|
|
|
9
9
|
- It installs directly into the expected Gemini skills path.
|
|
10
10
|
- It includes both core software engineering skills and deeper agent/LLM-oriented skills.
|
|
11
|
-
- It helps new users get started with bundles and workflows rather than forcing a cold start from 1,
|
|
11
|
+
- It helps new users get started with bundles and workflows rather than forcing a cold start from 1,306+ files.
|
|
12
12
|
- It is useful whether you want a broad internal skill library or a single repo to test many workflows quickly.
|
|
13
13
|
|
|
14
14
|
## Install Gemini CLI Skills
|
|
@@ -18,7 +18,7 @@ Kiro is AWS's agentic AI IDE that combines:
|
|
|
18
18
|
|
|
19
19
|
Kiro's agentic capabilities are enhanced by skills that provide:
|
|
20
20
|
|
|
21
|
-
- **Domain expertise** across 1,
|
|
21
|
+
- **Domain expertise** across 1,306+ specialized areas
|
|
22
22
|
- **Best practices** from Anthropic, OpenAI, Google, Microsoft, and AWS
|
|
23
23
|
- **Workflow automation** for common development tasks
|
|
24
24
|
- **AWS-specific patterns** for serverless, infrastructure, and cloud architecture
|
|
@@ -12,7 +12,7 @@ Great question! Here's what just happened and what to do next:
|
|
|
12
12
|
|
|
13
13
|
When you ran `npx antigravity-awesome-skills` or cloned the repository, you:
|
|
14
14
|
|
|
15
|
-
✅ **Downloaded 1,
|
|
15
|
+
✅ **Downloaded 1,306+ skill files** to your computer (default: `~/.gemini/antigravity/skills/`; or a custom path like `~/.agent/skills/` if you used `--path`)
|
|
16
16
|
✅ **Made them available** to your AI assistant
|
|
17
17
|
❌ **Did NOT enable them all automatically** (they're just sitting there, waiting)
|
|
18
18
|
|
|
@@ -32,7 +32,7 @@ Bundles are **recommended lists** of skills grouped by role. They help you decid
|
|
|
32
32
|
|
|
33
33
|
**Analogy:**
|
|
34
34
|
|
|
35
|
-
- You installed a toolbox with 1,
|
|
35
|
+
- You installed a toolbox with 1,306+ tools (✅ done)
|
|
36
36
|
- Bundles are like **labeled organizer trays** saying: "If you're a carpenter, start with these 10 tools"
|
|
37
37
|
- You don't install bundles—you **pick skills from them**
|
|
38
38
|
|
|
@@ -192,7 +192,7 @@ Let's actually use a skill right now. Follow these steps:
|
|
|
192
192
|
|
|
193
193
|
## Step 5: Picking Your First Skills (Practical Advice)
|
|
194
194
|
|
|
195
|
-
Don't try to use all 1,
|
|
195
|
+
Don't try to use all 1,306+ skills at once. Here's a sensible approach:
|
|
196
196
|
|
|
197
197
|
If you want a tool-specific starting point before choosing skills, use:
|
|
198
198
|
|
|
@@ -323,7 +323,7 @@ Usually no, but if your AI doesn't recognize a skill:
|
|
|
323
323
|
|
|
324
324
|
### "Can I load all skills into the model at once?"
|
|
325
325
|
|
|
326
|
-
No. Even though you have 1,
|
|
326
|
+
No. Even though you have 1,306+ skills installed locally, you should **not** concatenate every `SKILL.md` into a single system prompt or context block.
|
|
327
327
|
|
|
328
328
|
The intended pattern is:
|
|
329
329
|
|
|
@@ -34,7 +34,7 @@ antigravity-awesome-skills/
|
|
|
34
34
|
├── 📄 CONTRIBUTING.md ← Contributor workflow
|
|
35
35
|
├── 📄 CATALOG.md ← Full generated catalog
|
|
36
36
|
│
|
|
37
|
-
├── 📁 skills/ ← 1,
|
|
37
|
+
├── 📁 skills/ ← 1,306+ skills live here
|
|
38
38
|
│ │
|
|
39
39
|
│ ├── 📁 brainstorming/
|
|
40
40
|
│ │ └── 📄 SKILL.md ← Skill definition
|
|
@@ -47,7 +47,7 @@ antigravity-awesome-skills/
|
|
|
47
47
|
│ │ └── 📁 2d-games/
|
|
48
48
|
│ │ └── 📄 SKILL.md ← Nested skills also supported
|
|
49
49
|
│ │
|
|
50
|
-
│ └── ... (1,
|
|
50
|
+
│ └── ... (1,306+ total)
|
|
51
51
|
│
|
|
52
52
|
├── 📁 apps/
|
|
53
53
|
│ └── 📁 web-app/ ← Interactive browser
|
|
@@ -100,7 +100,7 @@ antigravity-awesome-skills/
|
|
|
100
100
|
|
|
101
101
|
```
|
|
102
102
|
┌─────────────────────────┐
|
|
103
|
-
│ 1,
|
|
103
|
+
│ 1,306+ SKILLS │
|
|
104
104
|
└────────────┬────────────┘
|
|
105
105
|
│
|
|
106
106
|
┌────────────────────────┼────────────────────────┐
|
|
@@ -201,7 +201,7 @@ If you want a workspace-style manual install instead, cloning into `.agent/skill
|
|
|
201
201
|
│ ├── 📁 brainstorming/ │
|
|
202
202
|
│ ├── 📁 stripe-integration/ │
|
|
203
203
|
│ ├── 📁 react-best-practices/ │
|
|
204
|
-
│ └── ... (1,
|
|
204
|
+
│ └── ... (1,306+ total) │
|
|
205
205
|
└─────────────────────────────────────────┘
|
|
206
206
|
```
|
|
207
207
|
|
|
@@ -93,7 +93,12 @@ echo "Edit to add your API keys for enhanced research."
|
|
|
93
93
|
**Step 1: Run the research script**
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
|
|
96
|
+
TOPIC_FILE="$(mktemp)"
|
|
97
|
+
trap 'rm -f "$TOPIC_FILE"' EXIT
|
|
98
|
+
cat <<'LAST30DAYS_TOPIC' > "$TOPIC_FILE"
|
|
99
|
+
$ARGUMENTS
|
|
100
|
+
LAST30DAYS_TOPIC
|
|
101
|
+
python3 ~/.claude/skills/last30days/scripts/last30days.py "$(cat "$TOPIC_FILE")" --emit=compact 2>&1
|
|
97
102
|
```
|
|
98
103
|
|
|
99
104
|
The script will automatically:
|
|
@@ -6,7 +6,7 @@ Usage:
|
|
|
6
6
|
python scripts/ws_listener.py [OPTIONS] [output_dir]
|
|
7
7
|
|
|
8
8
|
Arguments:
|
|
9
|
-
output_dir Directory for output files (default: /
|
|
9
|
+
output_dir Directory for output files (default: XDG_STATE_HOME/videodb-events or VIDEODB_EVENTS_DIR env var)
|
|
10
10
|
|
|
11
11
|
Options:
|
|
12
12
|
--clear Clear the events file before starting (use when starting a new session)
|
|
@@ -20,14 +20,15 @@ Output (first line, for parsing):
|
|
|
20
20
|
WS_ID=<connection_id>
|
|
21
21
|
|
|
22
22
|
Examples:
|
|
23
|
-
python scripts/ws_listener.py &
|
|
24
|
-
python scripts/ws_listener.py --clear
|
|
25
|
-
python scripts/ws_listener.py --clear /
|
|
26
|
-
kill $(cat /
|
|
23
|
+
python scripts/ws_listener.py & # Run in background
|
|
24
|
+
python scripts/ws_listener.py --clear # Clear events and start fresh
|
|
25
|
+
python scripts/ws_listener.py --clear /path/mydir # Custom dir with clear
|
|
26
|
+
kill $(cat ~/.local/state/videodb-events/videodb_ws_pid) # Stop the listener
|
|
27
27
|
"""
|
|
28
28
|
import os
|
|
29
29
|
import sys
|
|
30
30
|
import json
|
|
31
|
+
import stat
|
|
31
32
|
import signal
|
|
32
33
|
import asyncio
|
|
33
34
|
from datetime import datetime, timezone
|
|
@@ -42,6 +43,8 @@ import videodb
|
|
|
42
43
|
MAX_RETRIES = 10
|
|
43
44
|
INITIAL_BACKOFF = 1 # seconds
|
|
44
45
|
MAX_BACKOFF = 60 # seconds
|
|
46
|
+
FILE_MODE = 0o600
|
|
47
|
+
DIR_MODE = 0o700
|
|
45
48
|
|
|
46
49
|
# Parse arguments
|
|
47
50
|
def parse_args():
|
|
@@ -78,18 +81,62 @@ def log(msg: str):
|
|
|
78
81
|
print(f"[{ts}] {msg}", flush=True)
|
|
79
82
|
|
|
80
83
|
|
|
84
|
+
def ensure_output_dir():
|
|
85
|
+
"""Create the output directory if needed and reject symlinked paths."""
|
|
86
|
+
if OUTPUT_DIR.exists():
|
|
87
|
+
if OUTPUT_DIR.is_symlink():
|
|
88
|
+
raise OSError(f"Refusing to use symlinked output directory: {OUTPUT_DIR}")
|
|
89
|
+
if not OUTPUT_DIR.is_dir():
|
|
90
|
+
raise OSError(f"Output path is not a directory: {OUTPUT_DIR}")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
OUTPUT_DIR.mkdir(parents=True, mode=DIR_MODE, exist_ok=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def secure_open(path: Path, *, append: bool):
|
|
97
|
+
"""Open a regular file without following symlinks."""
|
|
98
|
+
ensure_output_dir()
|
|
99
|
+
flags = os.O_WRONLY | os.O_CREAT
|
|
100
|
+
flags |= os.O_APPEND if append else os.O_TRUNC
|
|
101
|
+
flags |= getattr(os, "O_NOFOLLOW", 0)
|
|
102
|
+
|
|
103
|
+
fd = os.open(path, flags, FILE_MODE)
|
|
104
|
+
try:
|
|
105
|
+
file_stat = os.fstat(fd)
|
|
106
|
+
if not stat.S_ISREG(file_stat.st_mode):
|
|
107
|
+
raise OSError(f"Refusing to write non-regular file: {path}")
|
|
108
|
+
if file_stat.st_nlink != 1:
|
|
109
|
+
raise OSError(f"Refusing to write multiply linked file: {path}")
|
|
110
|
+
return fd
|
|
111
|
+
except Exception:
|
|
112
|
+
os.close(fd)
|
|
113
|
+
raise
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def secure_write_text(path: Path, content: str):
|
|
117
|
+
"""Write text to a regular file with private permissions."""
|
|
118
|
+
fd = secure_open(path, append=False)
|
|
119
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
120
|
+
handle.write(content)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def secure_append_text(path: Path, content: str):
|
|
124
|
+
"""Append text to a regular file with private permissions."""
|
|
125
|
+
fd = secure_open(path, append=True)
|
|
126
|
+
with os.fdopen(fd, "a", encoding="utf-8") as handle:
|
|
127
|
+
handle.write(content)
|
|
128
|
+
|
|
129
|
+
|
|
81
130
|
def append_event(event: dict):
|
|
82
131
|
"""Append event to JSONL file with timestamps."""
|
|
83
132
|
event["ts"] = datetime.now(timezone.utc).isoformat()
|
|
84
133
|
event["unix_ts"] = datetime.now(timezone.utc).timestamp()
|
|
85
|
-
|
|
86
|
-
f.write(json.dumps(event) + "\n")
|
|
134
|
+
secure_append_text(EVENTS_FILE, json.dumps(event) + "\n")
|
|
87
135
|
|
|
88
136
|
|
|
89
137
|
def write_pid():
|
|
90
138
|
"""Write PID file for easy process management."""
|
|
91
|
-
|
|
92
|
-
PID_FILE.write_text(str(os.getpid()))
|
|
139
|
+
secure_write_text(PID_FILE, str(os.getpid()))
|
|
93
140
|
|
|
94
141
|
|
|
95
142
|
def cleanup_pid():
|
|
@@ -115,7 +162,7 @@ async def listen_with_retry():
|
|
|
115
162
|
ws_id = ws.connection_id
|
|
116
163
|
|
|
117
164
|
# Ensure output directory exists
|
|
118
|
-
|
|
165
|
+
ensure_output_dir()
|
|
119
166
|
|
|
120
167
|
# Clear events file only on first connection if --clear flag is set
|
|
121
168
|
if _first_connection and CLEAR_EVENTS:
|
|
@@ -124,7 +171,7 @@ async def listen_with_retry():
|
|
|
124
171
|
_first_connection = False
|
|
125
172
|
|
|
126
173
|
# Write ws_id to file for easy retrieval
|
|
127
|
-
WS_ID_FILE
|
|
174
|
+
secure_write_text(WS_ID_FILE, ws_id)
|
|
128
175
|
|
|
129
176
|
# Print ws_id (parseable format for LLM)
|
|
130
177
|
if retry_count == 0:
|
package/package.json
CHANGED