fix: address plugin layout review feedback

This commit is contained in:
Claire Novotny
2026-04-24 11:52:48 -04:00
parent 72495c1c14
commit f03cb866aa
25 changed files with 54 additions and 27 deletions
+4 -3
View File
@@ -1,16 +1,17 @@
{ {
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "last30days-skill", "name": "last30days-skill",
"description": "Research any topic across Reddit, X, YouTube, TikTok, Instagram, HN, Polymarket, GitHub, and 5+ more sources.",
"owner": { "owner": {
"name": "Matt Van Horn", "name": "Matt Van Horn",
"url": "https://github.com/mvanhorn" "url": "https://github.com/mvanhorn"
}, },
"metadata": {
"description": "Marketplace hosting the Last 30 Days research plugin."
},
"plugins": [ "plugins": [
{ {
"name": "last30days", "name": "last30days",
"description": "Research any topic across Reddit, X, YouTube, TikTok, Instagram, HN, Polymarket, GitHub, and 5+ more sources.", "description": "Research any topic across Reddit, X, YouTube, TikTok, Instagram, HN, Polymarket, GitHub, and 5+ more sources.",
"version": "3.0.10", "version": "3.1.1",
"author": { "author": {
"name": "Matt Van Horn", "name": "Matt Van Horn",
"url": "https://github.com/mvanhorn" "url": "https://github.com/mvanhorn"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "last30days", "name": "last30days",
"version": "3.1.0", "version": "3.1.1",
"description": "Research any topic across Reddit, X, YouTube, TikTok, Instagram, Hacker News, Polymarket, GitHub, and 5+ more sources. AI agent scores by upvotes, likes, and real money - not editors.", "description": "Research any topic across Reddit, X, YouTube, TikTok, Instagram, Hacker News, Polymarket, GitHub, and 5+ more sources. AI agent scores by upvotes, likes, and real money - not editors.",
"author": { "author": {
"name": "Matt Van Horn", "name": "Matt Van Horn",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "last30days", "name": "last30days",
"version": "3.0.10", "version": "3.1.1",
"description": "Research any topic across Reddit, X, YouTube, TikTok, Instagram, Hacker News, Polymarket, GitHub, and more sources.", "description": "Research any topic across Reddit, X, YouTube, TikTok, Instagram, Hacker News, Polymarket, GitHub, and more sources.",
"author": { "author": {
"name": "Matt Van Horn", "name": "Matt Van Horn",
+7
View File
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.1.1] - 2026-04-24
### Fixed
- **Codex plugin layout.** Move the canonical runtime payload under `skills/last30days/` and update Codex/Claude plugin metadata and tests for the relocated engine path.
- **Claude Code cache resolution.** Resolve Claude plugin installs to `skills/last30days/scripts/last30days.py` after the plugin-layout restructure.
## [3.1.0] - 2026-04-22 ## [3.1.0] - 2026-04-22
Consolidates the 3.0.10 to 3.0.14 dev cycle (commenter handles, `--competitors`, per-entity Step 0.55, vs-mode N passes, comparison title attribution) and republishes the OpenClaw bundle, which had been frozen on ClawHub at `3.0.0-open` since April 8. Consolidates the 3.0.10 to 3.0.14 dev cycle (commenter handles, `--competitors`, per-entity Step 0.55, vs-mode N passes, comparison title attribution) and republishes the OpenClaw bundle, which had been frozen on ClawHub at `3.0.0-open` since April 8.
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "last30days-skill" name = "last30days-skill"
version = "3.0.10" version = "3.1.1"
description = "Multi-source last-30-days research skill" description = "Multi-source last-30-days research skill"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+10 -4
View File
@@ -1,6 +1,6 @@
--- ---
name: last30days name: last30days
version: "3.0.10" version: "3.1.1"
description: "Research what people actually say about any topic in the last 30 days. Pulls posts and engagement from Reddit, X, YouTube, TikTok, Hacker News, Polymarket, GitHub, and the web." description: "Research what people actually say about any topic in the last 30 days. Pulls posts and engagement from Reddit, X, YouTube, TikTok, Hacker News, Polymarket, GitHub, and the web."
argument-hint: 'last30days nvidia earnings reaction | last30days AI video tools | last30days what users want in react' argument-hint: 'last30days nvidia earnings reaction | last30days AI video tools | last30days what users want in react'
allowed-tools: Bash, Read, Write, AskUserQuestion, WebSearch allowed-tools: Bash, Read, Write, AskUserQuestion, WebSearch
@@ -233,7 +233,7 @@ If your Bash call to `last30days.py` does NOT include the FULL pre-flight checkl
--- ---
# last30days v3.0.10: Research Any Topic from the Last 30 Days # last30days v3.1.1: Research Any Topic from the Last 30 Days
> **Permissions overview:** Reads public web/platform data and optionally saves research briefings to `LAST30DAYS_MEMORY_DIR` (defaults to `~/Documents/Last30Days`). X/Twitter search uses optional user-provided tokens (AUTH_TOKEN/CT0 env vars). Bluesky search uses optional app password (BSKY_HANDLE/BSKY_APP_PASSWORD env vars - create at bsky.app/settings/app-passwords). All credential usage and data writes are documented in the [Security & Permissions](#security--permissions) section. > **Permissions overview:** Reads public web/platform data and optionally saves research briefings to `LAST30DAYS_MEMORY_DIR` (defaults to `~/Documents/Last30Days`). X/Twitter search uses optional user-provided tokens (AUTH_TOKEN/CT0 env vars). Bluesky search uses optional app password (BSKY_HANDLE/BSKY_APP_PASSWORD env vars - create at bsky.app/settings/app-passwords). All credential usage and data writes are documented in the [Security & Permissions](#security--permissions) section.
@@ -877,7 +877,13 @@ SKILL_ROOT="${SKILL_ROOT%/}"
if [ -z "$SKILL_ROOT" ] || [ ! -f "$SKILL_ROOT/scripts/last30days.py" ]; then if [ -z "$SKILL_ROOT" ] || [ ! -f "$SKILL_ROOT/scripts/last30days.py" ]; then
CLAUDE_PLUGIN_ROOT="$(ls -d "$HOME/.claude/plugins/cache/last30days-skill/last30days/"*/ 2>/dev/null | sort -V | tail -1)" CLAUDE_PLUGIN_ROOT="$(ls -d "$HOME/.claude/plugins/cache/last30days-skill/last30days/"*/ 2>/dev/null | sort -V | tail -1)"
CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT%/}" CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT%/}"
[ -n "$CLAUDE_PLUGIN_ROOT" ] && [ -f "$CLAUDE_PLUGIN_ROOT/scripts/last30days.py" ] && SKILL_ROOT="$CLAUDE_PLUGIN_ROOT" if [ -n "$CLAUDE_PLUGIN_ROOT" ]; then
if [ -f "$CLAUDE_PLUGIN_ROOT/skills/last30days/scripts/last30days.py" ]; then
SKILL_ROOT="$CLAUDE_PLUGIN_ROOT/skills/last30days"
elif [ -f "$CLAUDE_PLUGIN_ROOT/scripts/last30days.py" ]; then
SKILL_ROOT="$CLAUDE_PLUGIN_ROOT"
fi
fi
fi fi
# Fallback for repo checkout / Gemini / local development hosts where the plugin cache does not exist. # Fallback for repo checkout / Gemini / local development hosts where the plugin cache does not exist.
@@ -890,7 +896,7 @@ fi
if [ -z "${SKILL_ROOT:-}" ] || [ ! -f "$SKILL_ROOT/scripts/last30days.py" ]; then if [ -z "${SKILL_ROOT:-}" ] || [ ! -f "$SKILL_ROOT/scripts/last30days.py" ]; then
echo "ERROR: Could not find scripts/last30days.py in Codex/Claude plugin cache or repo checkout" >&2 echo "ERROR: Could not find scripts/last30days.py in Codex/Claude plugin cache or repo checkout" >&2
echo "Expected Codex: $HOME/.codex/plugins/cache/{MARKETPLACE}/last30days/{VERSION}/skills/last30days/scripts/last30days.py" >&2 echo "Expected Codex: $HOME/.codex/plugins/cache/{MARKETPLACE}/last30days/{VERSION}/skills/last30days/scripts/last30days.py" >&2
echo "Expected Claude: $HOME/.claude/plugins/cache/last30days-skill/last30days/{VERSION}/scripts/last30days.py" >&2 echo "Expected Claude: $HOME/.claude/plugins/cache/last30days-skill/last30days/{VERSION}/skills/last30days/scripts/last30days.py" >&2
exit 1 exit 1
fi fi
+1 -1
View File
@@ -11,7 +11,7 @@ COMMON_TARGETS=(
# but local development needs the cache kept in sync with the repo. # but local development needs the cache kept in sync with the repo.
# Do NOT add ~/.claude/skills/last30days - it creates a duplicate # Do NOT add ~/.claude/skills/last30days - it creates a duplicate
# /last30days-3 in the slash command menu alongside the plugin version. # /last30days-3 in the slash command menu alongside the plugin version.
"$HOME/.claude/plugins/cache/last30days-skill-private/last30days-3/3.0.10" "$HOME/.claude/plugins/cache/last30days-skill-private/last30days-3/3.1.1"
"$HOME/.claude/plugins/cache/last30days-skill-private/last30days-3-nogem/3.0.0-nogem" "$HOME/.claude/plugins/cache/last30days-skill-private/last30days-3-nogem/3.0.0-nogem"
"$HOME/.agents/skills/last30days" "$HOME/.agents/skills/last30days"
"$HOME/.codex/skills/last30days" "$HOME/.codex/skills/last30days"
+3
View File
@@ -4,12 +4,15 @@ import hashlib
import os import os
import sqlite3 import sqlite3
import subprocess import subprocess
import sys
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from unittest import mock from unittest import mock
import pytest import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days"))
from scripts.lib.chrome_cookies import ( from scripts.lib.chrome_cookies import (
CHROME_COOKIES_DB, CHROME_COOKIES_DB,
CHROME_IV_HEX, CHROME_IV_HEX,
+1 -1
View File
@@ -10,7 +10,7 @@ from contextlib import redirect_stderr
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
import last30days as cli import last30days as cli
+1 -1
View File
@@ -13,7 +13,7 @@ from pathlib import Path
from unittest import mock from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import fanout from lib import fanout
+1 -1
View File
@@ -17,7 +17,7 @@ from pathlib import Path
from unittest import mock from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
def _fake_report(topic: str): def _fake_report(topic: str):
+1 -1
View File
@@ -11,7 +11,7 @@ from pathlib import Path
from unittest import mock from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import competitors from lib import competitors
+1 -1
View File
@@ -13,7 +13,7 @@ from pathlib import Path
from unittest import mock from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
import last30days as cli import last30days as cli
@@ -11,7 +11,7 @@ from pathlib import Path
from unittest import mock from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
def _fake_report(topic: str): def _fake_report(topic: str):
+3
View File
@@ -2,6 +2,7 @@
import configparser import configparser
import sqlite3 import sqlite3
import sys
import textwrap import textwrap
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
@@ -9,6 +10,8 @@ from unittest.mock import patch
import pytest import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days"))
from scripts.lib.cookie_extract import ( from scripts.lib.cookie_extract import (
extract_cookies, extract_cookies,
extract_firefox_cookies, extract_firefox_cookies,
@@ -1,3 +1,8 @@
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days"))
from scripts.lib import env from scripts.lib import env
+2 -2
View File
@@ -10,11 +10,11 @@ import unittest
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
def _engine() -> Path: def _engine() -> Path:
return REPO_ROOT / "scripts" / "last30days.py" return REPO_ROOT / "skills" / "last30days" / "scripts" / "last30days.py"
class FooterNudgeSuppressionTests(unittest.TestCase): class FooterNudgeSuppressionTests(unittest.TestCase):
+1 -1
View File
@@ -10,7 +10,7 @@ from contextlib import redirect_stderr
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import planner from lib import planner
+1 -1
View File
@@ -8,7 +8,7 @@ import unittest
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import polymarket from lib import polymarket
+1 -1
View File
@@ -9,7 +9,7 @@ import unittest
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
import last30days as cli import last30days as cli
from lib import render, schema from lib import render, schema
+2
View File
@@ -9,6 +9,8 @@ from unittest.mock import patch
import pytest import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days"))
# Import the internal parser directly for testability (avoids platform check) # Import the internal parser directly for testability (avoids platform check)
from scripts.lib.safari_cookies import ( from scripts.lib.safari_cookies import (
_parse_binary_cookies, _parse_binary_cookies,
+2 -2
View File
@@ -15,11 +15,11 @@ import unittest
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
def _engine_path() -> Path: def _engine_path() -> Path:
return REPO_ROOT / "scripts" / "last30days.py" return REPO_ROOT / "skills" / "last30days" / "scripts" / "last30days.py"
class PerEntitySaveFilesTests(unittest.TestCase): class PerEntitySaveFilesTests(unittest.TestCase):
+1 -1
View File
@@ -15,7 +15,7 @@ from contextlib import redirect_stderr
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import planner from lib import planner
+1 -1
View File
@@ -6,7 +6,7 @@ import unittest
from pathlib import Path from pathlib import Path
from unittest import mock from unittest import mock
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts")) sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import xurl_x from lib import xurl_x
Generated
+1 -1
View File
@@ -197,7 +197,7 @@ wheels = [
[[package]] [[package]]
name = "last30days-skill" name = "last30days-skill"
version = "3.0.10" version = "3.1.1"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "requests" }, { name = "requests" },