Skip to content

Add LM Studio import script for prompts.csv#1149

Open
btbowman wants to merge 1 commit intof:mainfrom
btbowman:claude/elastic-gagarin
Open

Add LM Studio import script for prompts.csv#1149
btbowman wants to merge 1 commit intof:mainfrom
btbowman:claude/elastic-gagarin

Conversation

@btbowman
Copy link
Copy Markdown

@btbowman btbowman commented Apr 15, 2026

Summary

  • Adds scripts/import-to-lmstudio.py — a utility script that reads prompts.csv and writes each prompt as a config preset JSON file to ~/.lmstudio/config-presets/
  • Makes all 1645 community prompts available as system-prompt presets inside LM Studio (no API key or internet connection required)
  • Supports --dev-only flag to import only for_devs=TRUE prompts, and --dry-run to preview without writing files

Usage

python3 scripts/import-to-lmstudio.py              # import all prompts
python3 scripts/import-to-lmstudio.py --dev-only   # for_devs=TRUE only
python3 scripts/import-to-lmstudio.py --dry-run    # preview without writing

Then restart LM Studio — presets appear in the presets panel, each pre-loaded with the corresponding system prompt.

Test plan

  • Run --dry-run and verify expected preset names are printed
  • Run without flags and verify files appear in ~/.lmstudio/config-presets/
  • Open LM Studio, check presets panel shows imported prompts
  • Verify --dev-only only imports for_devs=TRUE rows
  • Verify re-running skips existing files without error

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a bulk-import tool to load prompts from CSV into LM Studio configuration presets
    • Includes filtering option for developer-only entries
    • Supports dry-run mode to preview changes before applying
    • Automatically handles naming conflicts

scripts/import-to-lmstudio.py reads prompts.csv and writes each
prompt as a config preset JSON file to ~/.lmstudio/config-presets/,
making all 1645 prompts available as system-prompt presets in LM Studio.

Supports --dev-only (for_devs=TRUE only) and --dry-run flags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 15, 2026 02:42
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

A new Python script is introduced to bulk-import prompt entries from a CSV file into LM Studio configuration preset files. The script reads prompts.csv, supports filtering via --dev-only and --dry-run flags, converts prompt names to slugified identifiers with collision handling, and generates JSON preset files in the LM Studio config directory.

Changes

Cohort / File(s) Summary
LM Studio Import Script
scripts/import-to-lmstudio.py
New Python script that parses prompts.csv and bulk-imports prompt entries as LM Studio configuration presets. Implements CSV reading with DictReader, slug generation with collision detection, JSON preset creation with system prompt configuration, and file I/O operations. Supports --dev-only flag to filter developer-only entries and --dry-run flag for preview mode.

Sequence Diagram

sequenceDiagram
    participant User
    participant Script as import-to-lmstudio.py
    participant CSV as prompts.csv
    participant FSys as File System
    participant LMS as LM Studio Config

    User->>Script: Run with flags (--dev-only, --dry-run)
    Script->>CSV: Open and read
    CSV-->>Script: CSV data
    loop For each row
        Script->>Script: Parse act & prompt
        Script->>Script: Apply dev filter if --dev-only
        Script->>Script: Slugify act name
        Script->>Script: Handle slug collisions
        Script->>Script: Build JSON preset
    end
    alt --dry-run mode
        Script->>User: Print preview & exit
    else Normal mode
        Script->>FSys: Check presets directory
        FSys-->>Script: Directory status
        loop For each preset
            Script->>FSys: Check if file exists
            alt File does not exist
                Script->>LMS: Write preset JSON
                LMS-->>FSys: File created
            else File exists
                Script->>Script: Count as skipped
            end
        end
        Script->>User: Print summary (found/imported/skipped)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰✨ A rabbit hops through CSV rows,
Each prompt transformed to preset flows,
With slugs so clean and collisions tamed,
LM Studio presets now are framed!
Dry runs dance before the deed,
Then write with grace, precisely freed. 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: adding a new LM Studio import script for prompts.csv. It is concise, specific, and accurately reflects the primary purpose of the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 48da967a36

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +86 to +90
slug = slugify(act)

# deduplicate: if slug collides, append a counter
if slug in seen_slugs:
seen_slugs[slug] += 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Deduplicate slugs with a case-insensitive key

The collision map currently keys on the original slug casing, which breaks on case-insensitive filesystems (default macOS/Windows): slugs like Life-Coach and Life-coach are treated as different until dest.exists() is checked, so the later prompt is skipped as “already existed.” This repo’s prompts.csv already has case-only name pairs (for example Life Coach/Life coach), so some prompts are silently dropped on those platforms.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
scripts/import-to-lmstudio.py (2)

72-72: Use _ for the unused loop variable in dry-run preview.

Ruff B007 is valid here; prompt isn’t used in the loop body.

🔧 Proposed fix
-        for act, prompt in prompts[:5]:
+        for act, _ in prompts[:5]:
             print(f"  • {act!r} → {slugify(act)}.preset.json")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/import-to-lmstudio.py` at line 72, The loop "for act, prompt in
prompts[:5]:" uses the second tuple element only as an unused variable in the
dry-run preview; change it to use the conventional discard identifier by
replacing "prompt" with "_" (i.e., "for act, _ in prompts[:5]:") so the unused
variable is explicit and Ruff B007 is satisfied; locate this in the dry-run
preview loop that iterates over the "prompts" sequence.

55-56: Add explicit handling for missing/unreadable CSV file.

A friendly error + non-zero exit is better than a raw traceback for CLI usage.

🔧 Proposed fix
-    with open(CSV_PATH, newline="", encoding="utf-8") as f:
-        reader = csv.DictReader(f)
+    try:
+        f = open(CSV_PATH, newline="", encoding="utf-8")
+    except OSError as exc:
+        print(f"Error reading {CSV_PATH}: {exc}", file=sys.stderr)
+        sys.exit(1)
+
+    with f:
+        reader = csv.DictReader(f)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/import-to-lmstudio.py` around lines 55 - 56, Wrap the CSV open/read
block (the "with open(CSV_PATH, newline='', encoding='utf-8') as f: reader =
csv.DictReader(f)" code) in a try/except that catches FileNotFoundError,
PermissionError, and UnicodeDecodeError (or a generic OSError) and prints a
friendly error to stderr (or uses an existing logger) including CSV_PATH and the
exception message, then exit with a non-zero status (sys.exit(1)); ensure sys is
imported if not already and avoid printing a full traceback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/import-to-lmstudio.py`:
- Around line 23-26: slugify may return an empty string which leads to invalid
filenames/ids like ".preset.json" and preset ids like "@local:". Update slugify
(or its caller) to provide a fallback when the normalized result is empty (e.g.,
"default" or "preset-{timestamp}"); specifically, change the slugify function to
return a non-empty fallback when text normalizes to "" and ensure the code that
builds the preset filename/ID (the code that uses slugify to form ".preset.json"
and "@local:{slug}") uses that guaranteed non-empty slug.
- Around line 29-33: The identifier currently uses slugify(act) inside
make_preset, which diverges from the deduplicated filename slug; change
make_preset to accept the already-deduplicated slug (e.g., add a slug parameter)
and build the identifier using that slug (replace f"@local:{slugify(act)}" with
f"@local:{slug}"), and update all calls that create presets (where the filename
slug is computed/uniquified) to pass the deduplicated slug into make_preset so
the JSON identifier matches the output filename.

---

Nitpick comments:
In `@scripts/import-to-lmstudio.py`:
- Line 72: The loop "for act, prompt in prompts[:5]:" uses the second tuple
element only as an unused variable in the dry-run preview; change it to use the
conventional discard identifier by replacing "prompt" with "_" (i.e., "for act,
_ in prompts[:5]:") so the unused variable is explicit and Ruff B007 is
satisfied; locate this in the dry-run preview loop that iterates over the
"prompts" sequence.
- Around line 55-56: Wrap the CSV open/read block (the "with open(CSV_PATH,
newline='', encoding='utf-8') as f: reader = csv.DictReader(f)" code) in a
try/except that catches FileNotFoundError, PermissionError, and
UnicodeDecodeError (or a generic OSError) and prints a friendly error to stderr
(or uses an existing logger) including CSV_PATH and the exception message, then
exit with a non-zero status (sys.exit(1)); ensure sys is imported if not already
and avoid printing a full traceback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b71104e8-5a4a-4985-84a5-1b20b3be01bf

📥 Commits

Reviewing files that changed from the base of the PR and between ff2310a and 48da967.

📒 Files selected for processing (1)
  • scripts/import-to-lmstudio.py

Comment on lines +23 to +26
def slugify(text: str) -> str:
text = re.sub(r"[^\w\s-]", "", text).strip()
text = re.sub(r"[\s_-]+", "-", text)
return text[:60]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against empty slugs before generating identifiers/files.

If act normalizes to an empty slug, Line 95 can write .preset.json and the preset id becomes @local:. Add a fallback slug.

🔧 Proposed fix
 def slugify(text: str) -> str:
     text = re.sub(r"[^\w\s-]", "", text).strip()
     text = re.sub(r"[\s_-]+", "-", text)
-    return text[:60]
+    slug = text[:60]
+    return slug or "preset"

Also applies to: 95-95

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/import-to-lmstudio.py` around lines 23 - 26, slugify may return an
empty string which leads to invalid filenames/ids like ".preset.json" and preset
ids like "@local:". Update slugify (or its caller) to provide a fallback when
the normalized result is empty (e.g., "default" or "preset-{timestamp}");
specifically, change the slugify function to return a non-empty fallback when
text normalizes to "" and ensure the code that builds the preset filename/ID
(the code that uses slugify to form ".preset.json" and "@local:{slug}") uses
that guaranteed non-empty slug.

Comment on lines +29 to +33
def make_preset(act: str, prompt: str) -> dict:
return {
"identifier": f"@local:{slugify(act)}",
"name": act,
"changed": True,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether base slug collisions exist in prompts.csv (which would trigger identifier duplication risk).
python - <<'PY'
import csv, re, collections

def slugify(text: str) -> str:
    text = re.sub(r"[^\w\s-]", "", text).strip()
    text = re.sub(r"[\s_-]+", "-", text)
    return text[:60]

acts = []
with open("prompts.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        act = (row.get("act") or "").strip()
        prompt = (row.get("prompt") or "").strip()
        if act and prompt:
            acts.append(act)

counter = collections.Counter(slugify(a) for a in acts)
dupes = [(k, v) for k, v in counter.items() if v > 1]

print(f"valid_rows={len(acts)}")
print(f"duplicate_base_slugs={len(dupes)}")
for slug, n in dupes[:25]:
    print(f"{slug}\t{n}")
PY

Repository: f/prompts.chat

Length of output: 297


🏁 Script executed:

cat -n scripts/import-to-lmstudio.py

Repository: f/prompts.chat

Length of output: 4089


🏁 Script executed:

#!/bin/bash
# Identify key sections: lines 29-33 and 85-103
sed -n '20,110p' scripts/import-to-lmstudio.py | cat -n

Repository: f/prompts.chat

Length of output: 3422


🏁 Script executed:

#!/bin/bash
# Check prompts.csv structure and size
ls -lh prompts.csv
head -1 prompts.csv | wc -c
# Try reading with increased field size limit
python - <<'PY'
import csv, re, collections

def slugify(text: str) -> str:
    text = re.sub(r"[^\w\s-]", "", text).strip()
    text = re.sub(r"[\s_-]+", "-", text)
    return text[:60]

acts = []
try:
    with open("prompts.csv", newline="", encoding="utf-8") as f:
        # Increase field size limit
        csv.field_size_limit(10_000_000)
        for row in csv.DictReader(f):
            act = (row.get("act") or "").strip()
            prompt = (row.get("prompt") or "").strip()
            if act and prompt:
                acts.append(act)
    
    counter = collections.Counter(slugify(a) for a in acts)
    dupes = [(k, v) for k, v in counter.items() if v > 1]
    
    print(f"valid_rows={len(acts)}")
    print(f"duplicate_base_slugs={len(dupes)}")
    if dupes:
        print("\nDuplicate slugs found:")
        for slug, n in dupes[:25]:
            print(f"  {slug}\t{n}")
    else:
        print("No duplicate base slugs found")
except Exception as e:
    print(f"Error: {e}")
PY

Repository: f/prompts.chat

Length of output: 190


Pass the deduplicated slug to make_preset() to ensure the identifier matches the filename.

The identifier field (line 31) always uses slugify(act), but the filename slug (lines 86–91) gets a -<n> suffix when collisions occur. This causes multiple files to have identical identifiers in their JSON, risking preset conflicts in LM Studio. While the current prompts.csv data contains no duplicate base slugs (verified across 1645 rows), the code structure is vulnerable and should be fixed.

🔧 Proposed fix
-def make_preset(act: str, prompt: str) -> dict:
+def make_preset(act: str, prompt: str, slug: str) -> dict:
     return {
-        "identifier": f"@local:{slugify(act)}",
+        "identifier": f"@local:{slug}",
         "name": act,
         "changed": True,
         "operation": {
@@ -101,2 +101,2 @@ for act, prompt in prompts:
-        preset = make_preset(act, prompt)
+        preset = make_preset(act, prompt, slug)
         dest.write_text(json.dumps(preset, indent=2, ensure_ascii=False), encoding="utf-8")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/import-to-lmstudio.py` around lines 29 - 33, The identifier currently
uses slugify(act) inside make_preset, which diverges from the deduplicated
filename slug; change make_preset to accept the already-deduplicated slug (e.g.,
add a slug parameter) and build the identifier using that slug (replace
f"@local:{slugify(act)}" with f"@local:{slug}"), and update all calls that
create presets (where the filename slug is computed/uniquified) to pass the
deduplicated slug into make_preset so the JSON identifier matches the output
filename.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a standalone utility script to import the repository’s prompts.csv entries into LM Studio by generating per-prompt config preset JSON files under the user’s LM Studio presets directory.

Changes:

  • Introduces scripts/import-to-lmstudio.py to parse prompts.csv and write LM Studio config preset JSON files.
  • Adds CLI flags --dev-only (filter for_devs=TRUE) and --dry-run (preview without writing).
  • Implements basic filename slugging + per-run deduplication for output preset filenames.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +13 to +15
import os
import re
import sys
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

os and sys are imported but never used in this script. Removing unused imports will avoid lint/IDE warnings and keeps dependencies clear.

Suggested change
import os
import re
import sys
import re

Copilot uses AI. Check for mistakes.


def main():
parser = argparse.ArgumentParser(description="Import prompts.chat CSV into LM Studio presets")
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The argparse description says "Import prompts.chat CSV..." but the script reads prompts.csv (and the module docstring also refers to prompts.csv). This looks like a copy/paste typo; update the description so --help output matches actual behavior.

Suggested change
parser = argparse.ArgumentParser(description="Import prompts.chat CSV into LM Studio presets")
parser = argparse.ArgumentParser(description="Import prompts.csv into LM Studio presets")

Copilot uses AI. Check for mistakes.
@f
Copy link
Copy Markdown
Owner

f commented Apr 15, 2026

This looks nice. Can you update the PR according to the agent reviews?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants