diff --git a/README.md b/README.md index 076c35cc..beb04892 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Standalone capabilities that make your Open Brain smarter. | ------ | ------------ | ----------- | | [Auto-Capture Protocol](recipes/auto-capture/) | Stores ACT NOW items and session summaries in Open Brain at session close using the reusable Auto-Capture skill | [@jaredirish](https://github.com/jaredirish) | | [Panning for Gold](recipes/panning-for-gold/) | Mine brain dumps and voice transcripts for actionable ideas — battle-tested across 13+ sessions | [@jaredirish](https://github.com/jaredirish) | -| [Claudeception](recipes/claudeception/) | Self-improving system that creates new skills from work sessions — skills that create other skills | [@jaredirish](https://github.com/jaredirish) | +| [Aiception (formerly Claudeception)](recipes/claudeception/) | Self-improving system that creates new skills from work sessions — skills that create other skills | [@jaredirish](https://github.com/jaredirish) | | [Schema-Aware Routing](recipes/schema-aware-routing/) | LLM-powered routing that distributes unstructured text across multiple database tables | [@claydunker-yalc](https://github.com/claydunker-yalc) | | [Fingerprint Dedup Backfill](recipes/fingerprint-dedup-backfill/) | Backfill content fingerprints and safely remove duplicate thoughts | [@alanshurafa](https://github.com/alanshurafa) | | [Source Filtering](recipes/source-filtering/) | Filter thoughts by source and backfill missing metadata for early imports | [@matthallett1](https://github.com/matthallett1) | @@ -107,7 +107,7 @@ Plain-text skill packs you can drop into Claude Code, Codex, or other AI clients | [Research Synthesis Skill Pack](skills/research-synthesis/) | Synthesizes source sets into findings, contradictions, confidence markers, and next questions | [@NateBJones](https://github.com/NateBJones) | | [Meeting Synthesis Skill Pack](skills/meeting-synthesis/) | Converts meeting notes or transcripts into decisions, action items, risks, and follow-up artifacts | [@NateBJones](https://github.com/NateBJones) | | [Panning for Gold Skill Pack](skills/panning-for-gold/) | Turns brain dumps and transcripts into evaluated idea inventories | [@jaredirish](https://github.com/jaredirish) | -| [Claudeception Skill Pack](skills/claudeception/) | Extracts reusable lessons from work sessions into new skills | [@jaredirish](https://github.com/jaredirish) | +| [Aiception Skill Pack (formerly Claudeception)](skills/claudeception/) | Extracts reusable lessons from work sessions into new skills | [@jaredirish](https://github.com/jaredirish) | | [Work Operating Model Skill Pack](skills/work-operating-model/) | Runs a five-layer elicitation interview and saves the approved operating model into Open Brain | [@jonathanedwards](https://github.com/jonathanedwards) | ### [`/dashboards`](dashboards/) — Frontend Templates diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md index 2a57846b..cff472d0 100644 --- a/docs/01-getting-started.md +++ b/docs/01-getting-started.md @@ -356,9 +356,7 @@ Follow the prompts — it may ask for your Mac password. Once it finishes, close **Without Homebrew:** -```bash -npm install -g supabase -``` +`npm install -g supabase` is not supported. If you want a global `supabase` command, install the standalone CLI from the [official Supabase CLI guide](https://supabase.com/docs/guides/local-development/cli/getting-started). Otherwise, run every command below with `npx supabase ...`. Verify it worked: @@ -366,6 +364,9 @@ Verify it worked: supabase --version ``` +> [!NOTE] +> If you're using `npx` instead of a global `supabase` binary, run `npx supabase --version` here and prefix the rest of the commands in this section the same way. + ![6.3](https://img.shields.io/badge/6.3-Log_In-555?style=for-the-badge&labelColor=1E88E5) This connects your local terminal to your Supabase account so the CLI can deploy to your project. It'll open your browser to authenticate — just follow the prompts and come back here when it says "You are now logged in." @@ -418,10 +419,11 @@ supabase secrets set OPENROUTER_API_KEY=your-openrouter-key-here > [!CAUTION] > Make sure the access key you set here **exactly matches** what you saved in your credential tracker. If they don't match, you'll get 401 errors when connecting your AI. - +> > **If you ever rotate your OpenRouter key:** you must re-run the `supabase secrets set` command above with the new key, AND update any local `.env` files that reference it. The edge function reads from Supabase secrets at runtime — updating the key on openrouter.ai alone won't propagate here. See the [FAQ on key rotation](03-faq.md#api-key-rotation) for the full checklist. ### Create the Function + ![6.6](https://img.shields.io/badge/6.6-Download_the_Server_Files-555?style=for-the-badge&labelColor=1E88E5) Three commands, run them one at a time in order: @@ -680,7 +682,7 @@ Pick your AI client below: 🤖 7.1 — Claude Desktop > [!NOTE] -> No JSON config files. No Node.js. No terminal. This is the simplest connection method. +> These steps are for Anthropic's official Claude Desktop app on macOS and Windows. Linux/community ports vary and aren't officially covered by this Connectors UI flow. No JSON config files. No Node.js. No terminal. This is the simplest connection method. 1. Open Claude Desktop → **Settings** → **Connectors** 2. Click **Add custom connector** @@ -690,27 +692,6 @@ Pick your AI client below: That's it. Start a new conversation, and Claude will have access to your Open Brain tools. You can enable or disable it per conversation via the "+" button → Connectors. -> [!TIP] -> **Prefer JSON config?** If you'd rather use `claude_desktop_config.json` instead of the Connectors UI, use `supergateway` (not `mcp-remote` — see note below): -> -> ```json -> { -> "mcpServers": { -> "open-brain": { -> "command": "npx", -> "args": [ -> "-y", -> "supergateway", -> "--streamableHttp", -> "https://YOUR_PROJECT_REF.supabase.co/functions/v1/open-brain-mcp?key=your-access-key-from-step-5" -> ] -> } -> } -> } -> ``` -> -> ⚠️ **Do not use `mcp-remote` for Claude Desktop.** It performs OAuth discovery against the Supabase domain, which returns a 404 that causes the connection to fail before Claude Desktop's short startup timeout. `supergateway --streamableHttp` connects directly with no OAuth handshake. (`mcp-remote` works fine in Codex because its `startup_timeout_sec = 30` gives enough time for the OAuth fallback.) -
@@ -880,7 +861,7 @@ Your AI should retrieve the thought you just saved. **❌ Claude Desktop tools don't appear** -Make sure you added the connector in Settings → Connectors (not by editing the JSON config file). Verify the connector is enabled for your conversation — click the "+" button at the bottom of the chat, then Connectors, and check that Open Brain is toggled on. If the connector was added but tools still don't show, try removing and re-adding it with the same URL. +On the official macOS/Windows Claude Desktop app, make sure you added the connector in Settings → Connectors. Verify the connector is enabled for your conversation — click the "+" button at the bottom of the chat, then Connectors, and check that Open Brain is toggled on. If the connector was added but tools still don't show, try removing and re-adding it with the same URL. If you're using a Linux/community port, its connector behavior can differ and isn't covered by this guide. **❌ ChatGPT doesn't use the Open Brain tools** @@ -890,10 +871,6 @@ First, confirm Developer Mode is enabled (Settings → Apps & Connectors → Adv Your `service_role` doesn't have table-level permissions. This happens on newer Supabase projects where CRUD grants are no longer automatic. Go back to Step 2.5 and run the `GRANT` SQL, then retry. -**❌ Claude Desktop JSON config: "Couldn't reach the MCP server"** - -If you're using `claude_desktop_config.json` with `mcp-remote`, switch to `supergateway --streamableHttp` instead. `mcp-remote` performs OAuth discovery against the Supabase domain (`/.well-known/oauth-authorization-server`), which returns a 404 that stalls the connection past Claude Desktop's startup timeout. `supergateway` connects directly with no OAuth handshake. See Step 7.1 for the config. (This does not affect Codex, which has a configurable `startup_timeout_sec` that gives `mcp-remote` enough time to fall back.) - **❌ Getting 401 errors** The access key doesn't match what's stored in Supabase secrets. Double-check that the `?key=` value in your URL matches your MCP Access Key exactly. If you're using the header approach (Claude Code or mcp-remote), the header must be `x-brain-key` (lowercase, with the dash). diff --git a/docs/video-walkthrough-script.md b/docs/video-walkthrough-script.md index a468d7c0..f216c0bc 100644 --- a/docs/video-walkthrough-script.md +++ b/docs/video-walkthrough-script.md @@ -1,6 +1,6 @@ # Open Brain Setup — Video Walkthrough Script -**Format:** Screen recording with voiceover. Mac only. Claude Desktop demo. +**Format:** Screen recording with voiceover. Mac only. Official Anthropic Claude Desktop app demo. **Tone:** Casual, confident, unhurried. You've done this before and you're showing a friend. **Note:** Real credentials shown on screen — delete the demo Supabase project before publishing. diff --git a/extensions/job-hunt/README.md b/extensions/job-hunt/README.md index f7b4a2ed..9a256e25 100644 --- a/extensions/job-hunt/README.md +++ b/extensions/job-hunt/README.md @@ -132,6 +132,14 @@ Schedule a phone screen interview for my TechCorp application, tomorrow at 2pm Show me my pipeline overview - how many applications, what stages, upcoming interviews ``` +``` +Add a job contact at TechCorp: Jessica Lee, recruiter, jessica@techcorp.com +``` + +``` +Show me my TechCorp job contacts +``` + ``` Link the TechCorp recruiter to my professional CRM ``` @@ -146,15 +154,16 @@ A recruiter you're talking to during the job search is also a professional conta **Example workflow:** -1. You add a job contact: "Jessica Lee, TechCorp recruiter, jessica@techcorp.com" +1. You add a job contact with `add_job_contact`: "Jessica Lee, TechCorp recruiter, jessica@techcorp.com" 2. You have multiple interactions: phone screen, interview coordination, offer negotiation -3. Your agent uses `link_contact_to_professional_crm` to create a professional_contacts record in Extension 5 -4. The `professional_crm_contact_id` field is set, creating a bidirectional link -5. After the job search ends, Jessica is already in your CRM with full context: company, role, all notes from the job search +3. If you need to recover the contact later, your agent uses `search_job_contacts` to find the right `job_contact_id` +4. Your agent uses `link_contact_to_professional_crm` to create a professional_contacts record in Extension 5 +5. The `professional_crm_contact_id` field is set, creating a bidirectional link +6. After the job search ends, Jessica is already in your CRM with full context: company, role, all notes from the job search **How it works technically:** -The tool takes a `job_contact_id` from the `job_contacts` table. It retrieves the contact details and creates a corresponding record in Extension 5's `professional_contacts` table. The `professional_crm_contact_id` field stores the link — this is application-managed rather than a database foreign key, because the two extensions live in separate table domains and you might install one without the other. This means: +The bridge tool takes a `job_contact_id` from the `job_contacts` table. In normal use, your agent creates that row with `add_job_contact`, and if it needs to recover the UUID later it can call `search_job_contacts` first. Once it has the ID, it retrieves the contact details and creates a corresponding record in Extension 5's `professional_contacts` table. The `professional_crm_contact_id` field stores the link — this is application-managed rather than a database foreign key, because the two extensions live in separate table domains and you might install one without the other. This means: - Future interactions in the job hunt also appear in the CRM context - You can track the relationship long-term in Extension 5 @@ -175,12 +184,14 @@ This is the power of a fully interconnected Open Brain — context flows across 1. **`add_company`** — Add a company to track (name, industry, website, size, location, remote_policy, notes, glassdoor_rating) 2. **`add_job_posting`** — Add a specific role at a company (company_id, title, url, salary_min, salary_max, requirements, nice_to_haves, source, posted_date) -3. **`submit_application`** — Record a submitted application (job_posting_id, status, applied_date, resume_version, cover_letter_notes, referral_contact) -4. **`schedule_interview`** — Schedule an interview for an application (application_id, interview_type, scheduled_at, duration_minutes, interviewer_name, interviewer_title, notes) -5. **`log_interview_notes`** — Add feedback/notes after an interview, update status to completed (interview_id, feedback, rating 1-5) -6. **`get_pipeline_overview`** — Dashboard summary: counts by application status, upcoming interviews in next N days, recent activity. This is your "how's it going?" tool. -7. **`get_upcoming_interviews`** — List interviews in the next N days with full company/role context -8. **`link_contact_to_professional_crm`** — **CROSS-EXTENSION BRIDGE** — Takes a job_contact_id, creates/links to a professional_contacts record in Extension 5, sets professional_crm_contact_id +3. **`add_job_contact`** — Add a recruiter, hiring manager, referral, or interviewer to `job_contacts` (company_id, name, title, email, phone, linkedin_url, role_in_process, notes, last_contacted) +4. **`submit_application`** — Record a submitted application (job_posting_id, status, applied_date, resume_version, cover_letter_notes, referral_contact) +5. **`schedule_interview`** — Schedule an interview for an application (application_id, interview_type, scheduled_at, duration_minutes, interviewer_name, interviewer_title, notes) +6. **`log_interview_notes`** — Add feedback/notes after an interview, update status to completed (interview_id, feedback, rating 1-5) +7. **`get_pipeline_overview`** — Dashboard summary: counts by application status, upcoming interviews in next N days, recent activity. This is your "how's it going?" tool. +8. **`get_upcoming_interviews`** — List interviews in the next N days with full company/role context +9. **`search_job_contacts`** — Search or list job contacts to recover recruiter/interviewer records and IDs before linking or follow-up +10. **`link_contact_to_professional_crm`** — **CROSS-EXTENSION BRIDGE** — Takes a job_contact_id, creates/links to a professional_contacts record in Extension 5, sets professional_crm_contact_id ## Expected Outcome diff --git a/extensions/job-hunt/index.ts b/extensions/job-hunt/index.ts index 839a581a..0e89b2e6 100644 --- a/extensions/job-hunt/index.ts +++ b/extensions/job-hunt/index.ts @@ -79,6 +79,27 @@ const getUpcomingInterviewsSchema = z.object({ days_ahead: z.number().optional().describe("Number of days to look ahead (default: 14)"), }); +const jobContactRoleSchema = z.enum(["recruiter", "hiring_manager", "referral", "interviewer", "other"]); + +const addJobContactSchema = z.object({ + company_id: z.string().optional().describe("Company ID (UUID)"), + name: z.string().describe("Contact's full name"), + title: z.string().optional().describe("Job title"), + email: z.string().optional().describe("Email address"), + phone: z.string().optional().describe("Phone number"), + linkedin_url: z.string().optional().describe("LinkedIn profile URL"), + role_in_process: jobContactRoleSchema.optional().describe("Role in your hiring process"), + notes: z.string().optional().describe("Additional notes about this contact"), + last_contacted: z.string().optional().describe("Last contact timestamp (ISO 8601)"), +}); + +const searchJobContactsSchema = z.object({ + query: z.string().optional().describe("Search term across contact name, title, email, notes, or company name"), + company_id: z.string().optional().describe("Filter to a specific company ID (UUID)"), + role_in_process: jobContactRoleSchema.optional().describe("Filter by role in your hiring process"), + only_unlinked: z.boolean().optional().describe("If true, only return contacts not yet linked to Professional CRM"), +}); + const linkContactToProfessionalCRMSchema = z.object({ job_contact_id: z.string().describe("Job contact ID (UUID)"), }); @@ -150,6 +171,43 @@ async function handleAddJobPosting(supabase: any, args: z.infer, userId: string): Promise { + const { company_id, name, title, email, phone, linkedin_url, role_in_process, notes, last_contacted } = args; + + const { data, error } = await supabase + .from("job_contacts") + .insert({ + user_id: userId, + company_id: company_id || null, + name, + title: title || null, + email: email || null, + phone: phone || null, + linkedin_url: linkedin_url || null, + role_in_process: role_in_process || null, + notes: notes || null, + last_contacted: last_contacted || null, + }) + .select(` + *, + companies ( + id, + name + ) + `) + .single(); + + if (error) { + throw new Error(`Failed to add job contact: ${error.message}`); + } + + return JSON.stringify({ + success: true, + message: `Added job contact: ${name}`, + job_contact: data, + }, null, 2); +} + async function handleSubmitApplication(supabase: any, args: z.infer, userId: string): Promise { const { job_posting_id, status, applied_date, resume_version, @@ -331,6 +389,77 @@ async function handleGetUpcomingInterviews(supabase: any, args: z.infer, userId: string): Promise { + const { query, company_id, role_in_process, only_unlinked } = args; + const normalizedQuery = query?.trim(); + let matchingCompanyIds: string[] = []; + + if (normalizedQuery) { + const { data: companies, error: companyError } = await supabase + .from("companies") + .select("id") + .eq("user_id", userId) + .ilike("name", `%${normalizedQuery}%`); + + if (companyError) { + throw new Error(`Failed to search companies for matching contacts: ${companyError.message}`); + } + + matchingCompanyIds = companies.map((company: { id: string }) => company.id); + } + + let queryBuilder = supabase + .from("job_contacts") + .select(` + *, + companies ( + id, + name + ) + `) + .eq("user_id", userId); + + if (normalizedQuery) { + const filters = [ + `name.ilike.%${normalizedQuery}%`, + `title.ilike.%${normalizedQuery}%`, + `email.ilike.%${normalizedQuery}%`, + `notes.ilike.%${normalizedQuery}%`, + `role_in_process.ilike.%${normalizedQuery}%`, + ]; + + if (matchingCompanyIds.length > 0) { + filters.push(`company_id.in.(${matchingCompanyIds.join(",")})`); + } + + queryBuilder = queryBuilder.or(filters.join(",")); + } + + if (company_id) { + queryBuilder = queryBuilder.eq("company_id", company_id); + } + + if (role_in_process) { + queryBuilder = queryBuilder.eq("role_in_process", role_in_process); + } + + if (only_unlinked) { + queryBuilder = queryBuilder.is("professional_crm_contact_id", null); + } + + const { data, error } = await queryBuilder.order("created_at", { ascending: false }); + + if (error) { + throw new Error(`Failed to search job contacts: ${error.message}`); + } + + return JSON.stringify({ + success: true, + count: data.length, + contacts: data, + }, null, 2); +} + async function handleLinkContactToProfessionalCRM(supabase: any, args: z.infer, userId: string): Promise { const { job_contact_id } = args; @@ -485,6 +614,13 @@ app.post("*", async (c) => { async (args) => wrap(() => handleAddJobPosting(supabase, args, userId)) ); + server.tool( + "add_job_contact", + "Add a recruiter, hiring manager, referral, or interviewer to your job search contacts", + addJobContactSchema.shape, + async (args) => wrap(() => handleAddJobContact(supabase, args, userId)) + ); + server.tool( "submit_application", "Record a submitted application", @@ -520,6 +656,13 @@ app.post("*", async (c) => { async (args) => wrap(() => handleGetUpcomingInterviews(supabase, args, userId)) ); + server.tool( + "search_job_contacts", + "Search or list job contacts so you can find the right recruiter/interviewer and their ID", + searchJobContactsSchema.shape, + async (args) => wrap(() => handleSearchJobContacts(supabase, args, userId)) + ); + server.tool( "link_contact_to_professional_crm", "CROSS-EXTENSION: Link a job contact to Extension 5 Professional CRM, creating a professional_contacts record", diff --git a/extensions/professional-crm/index.ts b/extensions/professional-crm/index.ts index bae936bb..10f58edf 100644 --- a/extensions/professional-crm/index.ts +++ b/extensions/professional-crm/index.ts @@ -19,13 +19,11 @@ const app = new Hono(); // POST /mcp - Main MCP endpoint app.post("*", async (c) => { - // Force JSON-only responses. SSE causes a reconnect loop on stateless edge functions: - // the client sends Accept: text/event-stream (per MCP spec), the transport opens an SSE - // stream, the function terminates, client reconnects in ~1-2s -- ~43k idle invocations/day. - // Stripping text/event-stream forces plain JSON responses and breaks the loop. - { + // Keep the transport compatible with MCP content negotiation. Some connectors omit + // text/event-stream, but @hono/mcp expects POST requests to accept both response types. + if (!c.req.header("accept")?.includes("text/event-stream")) { const headers = new Headers(c.req.raw.headers); - headers.set("Accept", "application/json"); + headers.set("Accept", "application/json, text/event-stream"); const patched = new Request(c.req.raw.url, { method: c.req.raw.method, headers, @@ -491,7 +489,11 @@ app.post("*", async (c) => { }, ); - const transport = new StreamableHTTPTransport(); + // Use the SDK's JSON response mode to avoid SSE reconnect churn on stateless edge functions. + const transport = new StreamableHTTPTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); await server.connect(transport); return transport.handleRequest(c); }); diff --git a/integrations/discord-capture/README.md b/integrations/discord-capture/README.md index 591963c7..841ec404 100644 --- a/integrations/discord-capture/README.md +++ b/integrations/discord-capture/README.md @@ -11,7 +11,7 @@ A Discord bot that monitors designated channels and captures messages into Open - Working Open Brain setup ([guide](../../docs/01-getting-started.md)) - A Discord account with permission to add bots to your server - Discord Developer Portal access (free) -- Supabase CLI installed (`npm i -g supabase`) +- Supabase CLI available ([Homebrew/Scoop/standalone binary or `npx supabase`](https://supabase.com/docs/guides/local-development/cli/getting-started); `npm i -g supabase` is not supported) - OpenRouter API key (for generating embeddings) ## Credential Tracker diff --git a/recipes/chatgpt-conversation-import/README.md b/recipes/chatgpt-conversation-import/README.md index e973baba..25b26a9a 100644 --- a/recipes/chatgpt-conversation-import/README.md +++ b/recipes/chatgpt-conversation-import/README.md @@ -93,6 +93,8 @@ The script will: Progress prints to the console with ETA as it runs. A sync log (`chatgpt-sync-log.json`) tracks which conversations have been imported, so you can safely re-run the script after future exports without duplicating data. Conversations with new messages since the last import are automatically re-processed. +On Windows, the importer reads export JSON files as UTF-8 explicitly, so conversations containing non-ASCII characters won't depend on your system code page. The sync log is written next to [`import-chatgpt.py`](./import-chatgpt.py), not into whatever directory you launched the command from. + ### 7. Verify in your database Open your Supabase dashboard → Table Editor → `thoughts`. You should see new rows with: @@ -335,7 +337,7 @@ Solution: This is normal occasionally — the LLM sometimes returns malformed JS Solution: Conversations with fewer than 2 messages (single-turn) are always filtered. Untitled conversations with 5 or fewer messages are also filtered. Conversations with 10+ messages are always processed regardless of content. Run with `--dry-run --verbose` to see what's being filtered and why. **Issue: Want to re-import after a new ChatGPT export** -Solution: Just run the script again pointing at your new export. The sync log (`chatgpt-sync-log.json`) tracks which conversations have been processed and their `update_time`. Only new conversations and conversations with new messages will be re-processed. If you want to start fresh, delete `chatgpt-sync-log.json`. +Solution: Just run the script again pointing at your new export. The sync log (`chatgpt-sync-log.json`) next to `import-chatgpt.py` tracks which conversations have been processed and their `update_time`. Only new conversations and conversations with new messages will be re-processed. If you want to start fresh, delete that file. **Issue: `Failed to generate embedding` errors** Solution: Check that your OpenRouter API key is valid and has credits. Go to openrouter.ai/credits to verify your balance. The embedding model (text-embedding-3-small) costs $0.02 per million tokens — even a large import costs pennies. diff --git a/recipes/chatgpt-conversation-import/chatgpt_parser.py b/recipes/chatgpt-conversation-import/chatgpt_parser.py index dfdafce2..36ea6e43 100644 --- a/recipes/chatgpt-conversation-import/chatgpt_parser.py +++ b/recipes/chatgpt-conversation-import/chatgpt_parser.py @@ -12,6 +12,7 @@ """ import hashlib +import io import json import os import re @@ -78,7 +79,7 @@ def extract_conversations(source_path): all_conversations = [] for name in sorted(candidates): - with zf.open(name) as f: + with zf.open(name) as raw_file, io.TextIOWrapper(raw_file, encoding="utf-8-sig") as f: convs = json.load(f) if isinstance(convs, list): all_conversations.extend(convs) @@ -103,7 +104,7 @@ def _load_conversations_from_dir(directory): all_conversations = [] for name in candidates: filepath = os.path.join(directory, name) - with open(filepath) as f: + with open(filepath, encoding="utf-8-sig") as f: convs = json.load(f) if isinstance(convs, list): all_conversations.extend(convs) diff --git a/recipes/chatgpt-conversation-import/import-chatgpt.py b/recipes/chatgpt-conversation-import/import-chatgpt.py index 87623473..d2c3105c 100644 --- a/recipes/chatgpt-conversation-import/import-chatgpt.py +++ b/recipes/chatgpt-conversation-import/import-chatgpt.py @@ -64,7 +64,8 @@ # ─── Configuration ─────────────────────────────────────────────────────────── -SYNC_LOG_PATH = Path("chatgpt-sync-log.json") +SCRIPT_DIR = Path(__file__).resolve().parent +SYNC_LOG_PATH = SCRIPT_DIR / "chatgpt-sync-log.json" OPENROUTER_BASE = "https://openrouter.ai/api/v1" OLLAMA_BASE = "http://localhost:11434" @@ -247,7 +248,7 @@ def build_focus_instruction(focus_arg): def load_sync_log(): """Load sync log from disk. Returns dict with ingested_ids and last_sync.""" try: - with open(SYNC_LOG_PATH) as f: + with SYNC_LOG_PATH.open(encoding="utf-8") as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {"ingested_ids": {}, "last_sync": ""} @@ -255,7 +256,7 @@ def load_sync_log(): def save_sync_log(log): """Save sync log to disk.""" - with open(SYNC_LOG_PATH, "w") as f: + with SYNC_LOG_PATH.open("w", encoding="utf-8", newline="\n") as f: json.dump(log, f, indent=2) @@ -1153,7 +1154,7 @@ def main(): def _write_report(filepath, entries, stats): """Write a markdown report of imported conversations.""" - with open(filepath, "w") as f: + with Path(filepath).open("w", encoding="utf-8", newline="\n") as f: mode = "DRY RUN" if stats["dry_run"] else "LIVE" f.write(f"# ChatGPT Import Report ({mode})\n\n") f.write(f"Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}\n\n") diff --git a/recipes/claudeception/README.md b/recipes/claudeception/README.md index 14c2d863..4ca48cfa 100644 --- a/recipes/claudeception/README.md +++ b/recipes/claudeception/README.md @@ -1,4 +1,4 @@ -# Claudeception +# Aiception (Formerly Claudeception)
@@ -12,13 +12,18 @@ *Skills that create other skills.* -A continuous learning system that extracts reusable knowledge from work sessions and codifies it into new AI coding tool skills. When you discover something non-obvious (a debugging technique, a workaround, an error resolution), Claudeception evaluates whether it's worth preserving and creates a structured skill file automatically. +The repo keeps the historical `claudeception` folder and recipe name for continuity, but the +actual installable Claude Code skill should now be named `aiception`. Anthropic reserves +skill names containing `claude` or `anthropic` in frontmatter, so the old installed name +is no longer compatible. + +A continuous learning system that extracts reusable knowledge from work sessions and codifies it into new AI coding tool skills. When you discover something non-obvious (a debugging technique, a workaround, an error resolution), Aiception evaluates whether it's worth preserving and creates a structured skill file automatically. **This is the meta-skill.** Every other recipe in OB1 does a specific thing. This one creates new things from the act of working. ## What It Does -During normal work, Claudeception watches for extractable knowledge: +During normal work, Aiception watches for extractable knowledge: | Discovery Type | Example | What Gets Created | |----------------|---------|-------------------| @@ -50,24 +55,24 @@ No additional credentials needed for this recipe. ### 1. Create the skill directory ```bash -mkdir -p ~/.claude/skills/claudeception +mkdir -p ~/.claude/skills/aiception ``` ### 2. Copy the skill file ```bash -cp claudeception.skill.md ~/.claude/skills/claudeception/SKILL.md +cp claudeception.skill.md ~/.claude/skills/aiception/SKILL.md ``` ### 3. Verify Claude Code picks up the skill -Restart Claude Code. To verify, say "what did we learn?" or run `/claudeception` at the end of a work session. Claude should reference the Claudeception methodology. +Restart Claude Code. To verify, say "what did we learn?" or run `/aiception` at the end of a work session. Claude should reference the Aiception methodology. ### 4. Work normally -Claudeception fires automatically after tasks involving non-obvious investigation. You can also trigger it manually: +Aiception fires automatically after tasks involving non-obvious investigation. You can also trigger it manually: -- `/claudeception` at end of session (retrospective mode) +- `/aiception` at end of session (retrospective mode) - "save this as a skill" after a discovery - "what did we learn?" to review the session @@ -94,7 +99,7 @@ A typical week of active development produces 1-3 new skills. Not every session ## Open Brain Integration -Claudeception connects to Open Brain at two points: +Aiception connects to Open Brain at two points: **Before creating (search):** Queries `search_thoughts` with keywords from the discovery. If related knowledge already exists in Open Brain, it updates the existing skill instead of creating a duplicate. @@ -124,14 +129,14 @@ The skill file format may differ, but the extraction process and quality criteri ## Troubleshooting -**Issue:** Claudeception fires too often, creating low-value skills. +**Issue:** Aiception fires too often, creating low-value skills. **Solution:** Check the quality criteria in the skill file. A skill must be reusable, non-trivial, specific, and verified. If it only helps with one instance and won't recur, it's not a skill. **Issue:** Skills aren't being discovered in future sessions. **Solution:** Check the `description` field in the skill's frontmatter. It needs specific trigger conditions (error messages, symptoms, tool names) for Claude Code's semantic matching to surface it. Vague descriptions like "helps with React" won't match. **Issue:** Open Brain search returns nothing but a similar skill exists locally. -**Solution:** The skill may have been created before Open Brain integration was added. Run `/claudeception` in retrospective mode to capture existing skills to Open Brain. +**Solution:** The skill may have been created before Open Brain integration was added. Run `/aiception` in retrospective mode to capture existing skills to Open Brain. **Issue:** Too many skills accumulating (30+). **Solution:** Review the 5 least-recently-modified skills. If they haven't fired in 30+ days, either the trigger conditions are too narrow (update them) or the knowledge is no longer relevant (deprecate). Add a `deprecated: true` note to the frontmatter rather than deleting. diff --git a/recipes/claudeception/claudeception.skill.md b/recipes/claudeception/claudeception.skill.md index bd0ad388..02b5e009 100644 --- a/recipes/claudeception/claudeception.skill.md +++ b/recipes/claudeception/claudeception.skill.md @@ -1,11 +1,11 @@ --- -name: claudeception -description: Continuous learning system that extracts reusable knowledge from work sessions. Triggers: (1) /claudeception command, (2) "save this as a skill" or "extract a skill", (3) "what did we learn?", (4) After non-obvious debugging or trial-and-error discovery. Creates new skills when valuable, reusable knowledge is identified. Integrates with Open Brain to prevent duplicates. +name: aiception +description: Continuous learning system that extracts reusable knowledge from work sessions. Triggers: (1) /aiception command, (2) "save this as a skill" or "extract a skill", (3) "what did we learn?", (4) After non-obvious debugging or trial-and-error discovery. Creates new skills when valuable, reusable knowledge is identified. Integrates with Open Brain to prevent duplicates. author: Jared Irish version: 2.0.0 --- -# Claudeception +# Aiception A continuous learning system that extracts reusable knowledge from work sessions and codifies it into new skills. This enables autonomous improvement over time. @@ -143,7 +143,7 @@ Before finalizing, verify: ## Retrospective Mode -When `/claudeception` is invoked at session end: +When `/aiception` is invoked at session end: 1. Review the session for extractable knowledge 2. List candidates with brief justifications @@ -216,4 +216,4 @@ Invoke this skill after completing a task when ANY of these apply: 4. Discovered configuration that differs from standard patterns 5. Tried multiple approaches before finding what worked -Also invoke when the user runs `/claudeception`, says "save this as a skill", or asks "what did we learn?" +Also invoke when the user runs `/aiception`, says "save this as a skill", or asks "what did we learn?" diff --git a/recipes/claudeception/metadata.json b/recipes/claudeception/metadata.json index dba9cbf9..a06a8d0a 100644 --- a/recipes/claudeception/metadata.json +++ b/recipes/claudeception/metadata.json @@ -1,5 +1,5 @@ { - "name": "Claudeception", + "name": "Aiception (formerly Claudeception)", "description": "Continuous learning system that extracts reusable knowledge from work sessions and creates new skills. Skills that create other skills. Integrates with Open Brain to search for existing knowledge before creating and capture new skills after.", "category": "recipes", "author": { diff --git a/recipes/daily-digest/README.md b/recipes/daily-digest/README.md index c95b9c68..aaa5ba0b 100644 --- a/recipes/daily-digest/README.md +++ b/recipes/daily-digest/README.md @@ -112,7 +112,7 @@ A fully self-contained approach using a Supabase Edge Function, pg_cron trigger, ### Prerequisites (planned) -- Supabase CLI installed (`npm i -g supabase`) +- Supabase CLI available ([Homebrew/Scoop/standalone binary or `npx supabase`](https://supabase.com/docs/guides/local-development/cli/getting-started); `npm i -g supabase` is not supported) - OpenRouter API key (for generating the summary) - Email service: Resend or SendGrid (free tier) diff --git a/schemas/workflow-status/migration.sql b/schemas/workflow-status/migration.sql index 7e80d56b..11cda2ce 100644 --- a/schemas/workflow-status/migration.sql +++ b/schemas/workflow-status/migration.sql @@ -14,4 +14,4 @@ CREATE INDEX IF NOT EXISTS idx_thoughts_status ON thoughts (status) WHERE status -- Backfill: set existing task and idea thoughts to 'new' status UPDATE thoughts SET status = 'new', status_updated_at = now() -WHERE type IN ('task', 'idea') AND status IS NULL; +WHERE metadata->>'type' IN ('task', 'idea') AND status IS NULL; diff --git a/skills/README.md b/skills/README.md index 12c4faba..1d05afbb 100644 --- a/skills/README.md +++ b/skills/README.md @@ -12,7 +12,7 @@ Reusable AI client skills and prompt packs for Open Brain workflows. These are t | [Meeting Synthesis Skill Pack](meeting-synthesis/) | Converts meeting notes or transcripts into decisions, action items, risks, and follow-up artifacts | [@NateBJones](https://github.com/NateBJones) | | [Heavy File Ingestion Skill Pack](heavy-file-ingestion/) | Converts PDFs, decks, spreadsheets, and other bulky files into markdown, CSV, and a cheap structural index before analysis | [@NateBJones](https://github.com/NateBJones) | | [Panning for Gold Skill Pack](panning-for-gold/) | Turns brain dumps and transcripts into evaluated idea inventories | [@jaredirish](https://github.com/jaredirish) | -| [Claudeception Skill Pack](claudeception/) | Extracts reusable lessons from work sessions into new skills | [@jaredirish](https://github.com/jaredirish) | +| [Aiception Skill Pack (formerly Claudeception)](claudeception/) | Extracts reusable lessons from work sessions into new skills | [@jaredirish](https://github.com/jaredirish) | | [Work Operating Model Skill Pack](work-operating-model/) | Runs a five-layer work elicitation interview and turns the approved result into structured Open Brain records plus exports | [@jonathanedwards](https://github.com/jonathanedwards) | ## How Skills Differ From Recipes diff --git a/skills/claudeception/README.md b/skills/claudeception/README.md index d0075f1b..d722110b 100644 --- a/skills/claudeception/README.md +++ b/skills/claudeception/README.md @@ -1,4 +1,4 @@ -# Claudeception +# Aiception (Formerly Claudeception)
@@ -10,9 +10,13 @@ *Standalone skill pack for extracting reusable lessons from work sessions and turning them into new skills.* +The repo keeps the historical `claudeception` folder name for continuity, but the installable +skill should now be named `aiception`. Anthropic reserves skill names containing `claude` +or `anthropic` in skill frontmatter, so `aiception` is the compatible replacement name. + ## What It Does -Claudeception watches for hard-won knowledge during real work: debugging breakthroughs, misleading errors, undocumented behavior, and repeatable workflow shortcuts. When the knowledge is specific, verified, and reusable, it helps turn that discovery into a new skill and records it back into Open Brain. +Aiception watches for hard-won knowledge during real work: debugging breakthroughs, misleading errors, undocumented behavior, and repeatable workflow shortcuts. When the knowledge is specific, verified, and reusable, it helps turn that discovery into a new skill and records it back into Open Brain. ## Supported Clients @@ -30,7 +34,7 @@ Claudeception watches for hard-won knowledge during real work: debugging breakth ## Installation 1. Copy [`SKILL.md`](./SKILL.md) into your client's skill/rules directory. -2. For Claude Code, place it at `~/.claude/skills/claudeception/SKILL.md`. +2. For Claude Code, place it at `~/.claude/skills/aiception/SKILL.md`. 3. Restart or reload your AI client so the skill becomes available. 4. If your client does not support native skill files, adapt the contents into that client's reusable project rules or system prompt. diff --git a/skills/claudeception/SKILL.md b/skills/claudeception/SKILL.md index 50c45f6a..805736a5 100644 --- a/skills/claudeception/SKILL.md +++ b/skills/claudeception/SKILL.md @@ -1,11 +1,11 @@ --- -name: claudeception -description: "Continuous learning system that extracts reusable knowledge from work sessions. Triggers: (1) /claudeception command, (2) 'save this as a skill' or 'extract a skill from this', (3) 'what did we learn?', (4) after non-obvious debugging or trial-and-error discovery. Creates new skills when valuable reusable knowledge is identified. Integrates with Open Brain to prevent duplicates." +name: aiception +description: "Continuous learning system that extracts reusable knowledge from work sessions. Triggers: (1) /aiception command, (2) 'save this as a skill' or 'extract a skill from this', (3) 'what did we learn?', (4) after non-obvious debugging or trial-and-error discovery. Creates new skills when valuable reusable knowledge is identified. Integrates with Open Brain to prevent duplicates." author: Jared Irish version: 2.0.0 --- -# Claudeception +# Aiception A continuous learning system that extracts reusable knowledge from work sessions and codifies it into new skills. This enables autonomous improvement over time. @@ -145,7 +145,7 @@ Before finalizing, verify: ## Retrospective Mode -When `/claudeception` is invoked at session end: +When `/aiception` is invoked at session end: 1. Review the session for extractable knowledge 2. List candidates with brief justifications @@ -222,4 +222,4 @@ Invoke this skill after completing a task when ANY of these apply: 4. Discovered configuration that differs from standard patterns 5. Tried multiple approaches before finding what worked -Also invoke when the user runs `/claudeception`, says "save this as a skill", or asks "what did we learn?" +Also invoke when the user runs `/aiception`, says "save this as a skill", or asks "what did we learn?" diff --git a/skills/claudeception/metadata.json b/skills/claudeception/metadata.json index ee5e01ab..c0f39bb4 100644 --- a/skills/claudeception/metadata.json +++ b/skills/claudeception/metadata.json @@ -1,5 +1,5 @@ { - "name": "Claudeception", + "name": "Aiception (formerly Claudeception)", "description": "Standalone skill pack that extracts reusable knowledge from work sessions, turns it into new skills, and captures those lessons back into Open Brain.", "category": "skills", "author": {