flonat-research 0.1.0
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/.claude/agents/domain-reviewer.md +336 -0
- package/.claude/agents/fixer.md +226 -0
- package/.claude/agents/paper-critic.md +370 -0
- package/.claude/agents/peer-reviewer.md +289 -0
- package/.claude/agents/proposal-reviewer.md +215 -0
- package/.claude/agents/referee2-reviewer.md +367 -0
- package/.claude/agents/references/journal-referee-profiles.md +354 -0
- package/.claude/agents/references/paper-critic/council-personas.md +77 -0
- package/.claude/agents/references/paper-critic/council-prompts.md +198 -0
- package/.claude/agents/references/peer-reviewer/report-template.md +199 -0
- package/.claude/agents/references/peer-reviewer/sa-prompts.md +260 -0
- package/.claude/agents/references/peer-reviewer/security-scan.md +188 -0
- package/.claude/agents/references/proposal-reviewer/report-template.md +144 -0
- package/.claude/agents/references/proposal-reviewer/sa-prompts.md +149 -0
- package/.claude/agents/references/referee-config.md +114 -0
- package/.claude/agents/references/referee2-reviewer/audit-checklists.md +287 -0
- package/.claude/agents/references/referee2-reviewer/report-template.md +334 -0
- package/.claude/rules/design-before-results.md +52 -0
- package/.claude/rules/ignore-agents-md.md +17 -0
- package/.claude/rules/ignore-gemini-md.md +17 -0
- package/.claude/rules/lean-claude-md.md +45 -0
- package/.claude/rules/learn-tags.md +99 -0
- package/.claude/rules/overleaf-separation.md +67 -0
- package/.claude/rules/plan-first.md +175 -0
- package/.claude/rules/read-docs-first.md +50 -0
- package/.claude/rules/scope-discipline.md +28 -0
- package/.claude/settings.json +125 -0
- package/.context/current-focus.md +33 -0
- package/.context/preferences/priorities.md +36 -0
- package/.context/preferences/task-naming.md +28 -0
- package/.context/profile.md +29 -0
- package/.context/projects/_index.md +41 -0
- package/.context/projects/papers/nudge-exp.md +22 -0
- package/.context/projects/papers/uncertainty.md +31 -0
- package/.context/resources/claude-scientific-writer-review.md +48 -0
- package/.context/resources/cunningham-multi-analyst-agents.md +104 -0
- package/.context/resources/cunningham-multilang-code-audit.md +62 -0
- package/.context/resources/google-ai-co-scientist-review.md +72 -0
- package/.context/resources/karpathy-llm-council-review.md +58 -0
- package/.context/resources/multi-coder-reliability-protocol.md +175 -0
- package/.context/resources/pedro-santanna-takeaways.md +96 -0
- package/.context/resources/venue-rankings/abs_ajg_2024.csv +1823 -0
- package/.context/resources/venue-rankings/abs_ajg_2024_econ.csv +356 -0
- package/.context/resources/venue-rankings/cabs_4_4star_theory.csv +40 -0
- package/.context/resources/venue-rankings/core_2026.csv +801 -0
- package/.context/resources/venue-rankings.md +147 -0
- package/.context/workflows/README.md +69 -0
- package/.context/workflows/daily-review.md +91 -0
- package/.context/workflows/meeting-actions.md +108 -0
- package/.context/workflows/replication-protocol.md +155 -0
- package/.context/workflows/weekly-review.md +113 -0
- package/.mcp-server-biblio/formatters.py +158 -0
- package/.mcp-server-biblio/pyproject.toml +11 -0
- package/.mcp-server-biblio/server.py +678 -0
- package/.mcp-server-biblio/sources/__init__.py +14 -0
- package/.mcp-server-biblio/sources/base.py +73 -0
- package/.mcp-server-biblio/sources/formatters.py +83 -0
- package/.mcp-server-biblio/sources/models.py +22 -0
- package/.mcp-server-biblio/sources/multi_source.py +243 -0
- package/.mcp-server-biblio/sources/openalex_source.py +183 -0
- package/.mcp-server-biblio/sources/scopus_source.py +309 -0
- package/.mcp-server-biblio/sources/wos_source.py +508 -0
- package/.mcp-server-biblio/uv.lock +896 -0
- package/.scripts/README.md +161 -0
- package/.scripts/ai_pattern_density.py +446 -0
- package/.scripts/conf +445 -0
- package/.scripts/config.py +122 -0
- package/.scripts/count_inventory.py +275 -0
- package/.scripts/daily_digest.py +288 -0
- package/.scripts/done +177 -0
- package/.scripts/extract_meeting_actions.py +223 -0
- package/.scripts/focus +176 -0
- package/.scripts/generate-codex-agents-md.py +217 -0
- package/.scripts/inbox +194 -0
- package/.scripts/notion_helpers.py +325 -0
- package/.scripts/openalex/query_helpers.py +306 -0
- package/.scripts/papers +227 -0
- package/.scripts/query +223 -0
- package/.scripts/session-history.py +201 -0
- package/.scripts/skill-health.py +516 -0
- package/.scripts/skill-log-miner.py +273 -0
- package/.scripts/sync-to-codex.sh +252 -0
- package/.scripts/task +213 -0
- package/.scripts/tasks +190 -0
- package/.scripts/week +206 -0
- package/CLAUDE.md +197 -0
- package/LICENSE +21 -0
- package/MEMORY.md +38 -0
- package/README.md +269 -0
- package/docs/agents.md +44 -0
- package/docs/bibliography-setup.md +55 -0
- package/docs/council-mode.md +36 -0
- package/docs/getting-started.md +245 -0
- package/docs/hooks.md +38 -0
- package/docs/mcp-servers.md +82 -0
- package/docs/notion-setup.md +109 -0
- package/docs/rules.md +33 -0
- package/docs/scripts.md +303 -0
- package/docs/setup-overview/setup-overview.pdf +0 -0
- package/docs/skills.md +70 -0
- package/docs/system.md +159 -0
- package/hooks/block-destructive-git.sh +66 -0
- package/hooks/context-monitor.py +114 -0
- package/hooks/postcompact-restore.py +157 -0
- package/hooks/precompact-autosave.py +181 -0
- package/hooks/promise-checker.sh +124 -0
- package/hooks/protect-source-files.sh +81 -0
- package/hooks/resume-context-loader.sh +53 -0
- package/hooks/startup-context-loader.sh +102 -0
- package/package.json +51 -0
- package/packages/cli-council/.github/workflows/claude-code-review.yml +44 -0
- package/packages/cli-council/.github/workflows/claude.yml +50 -0
- package/packages/cli-council/README.md +100 -0
- package/packages/cli-council/pyproject.toml +43 -0
- package/packages/cli-council/src/cli_council/__init__.py +19 -0
- package/packages/cli-council/src/cli_council/__main__.py +185 -0
- package/packages/cli-council/src/cli_council/backends/__init__.py +8 -0
- package/packages/cli-council/src/cli_council/backends/base.py +81 -0
- package/packages/cli-council/src/cli_council/backends/claude.py +25 -0
- package/packages/cli-council/src/cli_council/backends/codex.py +27 -0
- package/packages/cli-council/src/cli_council/backends/gemini.py +26 -0
- package/packages/cli-council/src/cli_council/checkpoint.py +212 -0
- package/packages/cli-council/src/cli_council/config.py +51 -0
- package/packages/cli-council/src/cli_council/council.py +391 -0
- package/packages/cli-council/src/cli_council/models.py +46 -0
- package/packages/llm-council/.github/workflows/claude-code-review.yml +44 -0
- package/packages/llm-council/.github/workflows/claude.yml +50 -0
- package/packages/llm-council/README.md +453 -0
- package/packages/llm-council/pyproject.toml +42 -0
- package/packages/llm-council/src/llm_council/__init__.py +23 -0
- package/packages/llm-council/src/llm_council/__main__.py +259 -0
- package/packages/llm-council/src/llm_council/checkpoint.py +193 -0
- package/packages/llm-council/src/llm_council/client.py +253 -0
- package/packages/llm-council/src/llm_council/config.py +232 -0
- package/packages/llm-council/src/llm_council/council.py +482 -0
- package/packages/llm-council/src/llm_council/models.py +46 -0
- package/packages/mcp-bibliography/MEMORY.md +31 -0
- package/packages/mcp-bibliography/_app.py +226 -0
- package/packages/mcp-bibliography/formatters.py +158 -0
- package/packages/mcp-bibliography/log/2026-03-13-2100.md +35 -0
- package/packages/mcp-bibliography/pyproject.toml +15 -0
- package/packages/mcp-bibliography/run.sh +20 -0
- package/packages/mcp-bibliography/scholarly_formatters.py +83 -0
- package/packages/mcp-bibliography/server.py +1857 -0
- package/packages/mcp-bibliography/tools/__init__.py +28 -0
- package/packages/mcp-bibliography/tools/_registry.py +19 -0
- package/packages/mcp-bibliography/tools/altmetric.py +107 -0
- package/packages/mcp-bibliography/tools/core.py +92 -0
- package/packages/mcp-bibliography/tools/dblp.py +52 -0
- package/packages/mcp-bibliography/tools/openalex.py +296 -0
- package/packages/mcp-bibliography/tools/opencitations.py +102 -0
- package/packages/mcp-bibliography/tools/openreview.py +179 -0
- package/packages/mcp-bibliography/tools/orcid.py +131 -0
- package/packages/mcp-bibliography/tools/scholarly.py +575 -0
- package/packages/mcp-bibliography/tools/unpaywall.py +63 -0
- package/packages/mcp-bibliography/tools/zenodo.py +123 -0
- package/packages/mcp-bibliography/uv.lock +711 -0
- package/scripts/setup.sh +143 -0
- package/skills/beamer-deck/SKILL.md +199 -0
- package/skills/beamer-deck/references/quality-rubric.md +54 -0
- package/skills/beamer-deck/references/review-prompts.md +106 -0
- package/skills/bib-validate/SKILL.md +261 -0
- package/skills/bib-validate/references/council-mode.md +34 -0
- package/skills/bib-validate/references/deep-verify.md +79 -0
- package/skills/bib-validate/references/fix-mode.md +36 -0
- package/skills/bib-validate/references/openalex-verification.md +45 -0
- package/skills/bib-validate/references/preprint-check.md +31 -0
- package/skills/bib-validate/references/ref-manager-crossref.md +41 -0
- package/skills/bib-validate/references/report-template.md +82 -0
- package/skills/code-archaeology/SKILL.md +141 -0
- package/skills/code-review/SKILL.md +265 -0
- package/skills/code-review/references/quality-rubric.md +67 -0
- package/skills/consolidate-memory/SKILL.md +208 -0
- package/skills/context-status/SKILL.md +126 -0
- package/skills/creation-guard/SKILL.md +230 -0
- package/skills/devils-advocate/SKILL.md +130 -0
- package/skills/devils-advocate/references/competing-hypotheses.md +83 -0
- package/skills/init-project/SKILL.md +115 -0
- package/skills/init-project-course/references/memory-and-settings.md +92 -0
- package/skills/init-project-course/references/organise-templates.md +94 -0
- package/skills/init-project-course/skill.md +147 -0
- package/skills/init-project-light/skill.md +139 -0
- package/skills/init-project-research/SKILL.md +368 -0
- package/skills/init-project-research/references/atlas-pipeline-sync.md +70 -0
- package/skills/init-project-research/references/atlas-schema.md +81 -0
- package/skills/init-project-research/references/confirmation-report.md +39 -0
- package/skills/init-project-research/references/domain-profile-template.md +104 -0
- package/skills/init-project-research/references/interview-round3.md +34 -0
- package/skills/init-project-research/references/literature-discovery.md +43 -0
- package/skills/init-project-research/references/scaffold-details.md +197 -0
- package/skills/init-project-research/templates/field-calibration.md +60 -0
- package/skills/init-project-research/templates/pipeline-manifest.md +63 -0
- package/skills/init-project-research/templates/run-all.sh +116 -0
- package/skills/init-project-research/templates/seed-files.md +337 -0
- package/skills/insights-deck/SKILL.md +151 -0
- package/skills/interview-me/SKILL.md +157 -0
- package/skills/latex/SKILL.md +141 -0
- package/skills/latex/references/latex-configs.md +183 -0
- package/skills/latex-autofix/SKILL.md +230 -0
- package/skills/latex-autofix/references/known-errors.md +183 -0
- package/skills/latex-autofix/references/quality-rubric.md +50 -0
- package/skills/latex-health-check/SKILL.md +161 -0
- package/skills/learn/SKILL.md +220 -0
- package/skills/learn/scripts/validate_skill.py +265 -0
- package/skills/lessons-learned/SKILL.md +201 -0
- package/skills/literature/SKILL.md +335 -0
- package/skills/literature/references/agent-templates.md +393 -0
- package/skills/literature/references/bibliometric-apis.md +44 -0
- package/skills/literature/references/cli-council-search.md +79 -0
- package/skills/literature/references/openalex-api-guide.md +371 -0
- package/skills/literature/references/openalex-common-queries.md +381 -0
- package/skills/literature/references/openalex-workflows.md +248 -0
- package/skills/literature/references/reference-manager-sync.md +36 -0
- package/skills/literature/references/scopus-api-guide.md +208 -0
- package/skills/literature/references/wos-api-guide.md +308 -0
- package/skills/multi-perspective/SKILL.md +311 -0
- package/skills/multi-perspective/references/computational-many-analysts.md +77 -0
- package/skills/pipeline-manifest/SKILL.md +226 -0
- package/skills/pre-submission-report/SKILL.md +153 -0
- package/skills/process-reviews/SKILL.md +244 -0
- package/skills/process-reviews/references/rr-routing.md +101 -0
- package/skills/project-deck/SKILL.md +87 -0
- package/skills/project-safety/SKILL.md +135 -0
- package/skills/proofread/SKILL.md +254 -0
- package/skills/proofread/references/quality-rubric.md +104 -0
- package/skills/python-env/SKILL.md +57 -0
- package/skills/quarto-deck/SKILL.md +226 -0
- package/skills/quarto-deck/references/markdown-format.md +143 -0
- package/skills/quarto-deck/references/quality-rubric.md +54 -0
- package/skills/save-context/SKILL.md +174 -0
- package/skills/session-log/SKILL.md +98 -0
- package/skills/shared/concept-validation-gate.md +161 -0
- package/skills/shared/council-protocol.md +265 -0
- package/skills/shared/distribution-diagnostics.md +164 -0
- package/skills/shared/engagement-stratified-sampling.md +218 -0
- package/skills/shared/escalation-protocol.md +74 -0
- package/skills/shared/external-audit-protocol.md +205 -0
- package/skills/shared/intercoder-reliability.md +256 -0
- package/skills/shared/mcp-degradation.md +81 -0
- package/skills/shared/method-probing-questions.md +163 -0
- package/skills/shared/multi-language-conventions.md +143 -0
- package/skills/shared/paid-api-safety.md +174 -0
- package/skills/shared/palettes.md +90 -0
- package/skills/shared/progressive-disclosure.md +92 -0
- package/skills/shared/project-documentation-content.md +443 -0
- package/skills/shared/project-documentation-format.md +281 -0
- package/skills/shared/project-documentation.md +100 -0
- package/skills/shared/publication-output.md +138 -0
- package/skills/shared/quality-scoring.md +70 -0
- package/skills/shared/reference-resolution.md +77 -0
- package/skills/shared/research-quality-rubric.md +165 -0
- package/skills/shared/rhetoric-principles.md +54 -0
- package/skills/shared/skill-design-patterns.md +272 -0
- package/skills/shared/skill-index.md +240 -0
- package/skills/shared/system-documentation.md +334 -0
- package/skills/shared/tikz-rules.md +402 -0
- package/skills/shared/validation-tiers.md +121 -0
- package/skills/shared/venue-guides/README.md +46 -0
- package/skills/shared/venue-guides/cell_press_style.md +483 -0
- package/skills/shared/venue-guides/conferences_formatting.md +564 -0
- package/skills/shared/venue-guides/cs_conference_style.md +463 -0
- package/skills/shared/venue-guides/examples/cell_summary_example.md +247 -0
- package/skills/shared/venue-guides/examples/medical_structured_abstract.md +313 -0
- package/skills/shared/venue-guides/examples/nature_abstract_examples.md +213 -0
- package/skills/shared/venue-guides/examples/neurips_introduction_example.md +245 -0
- package/skills/shared/venue-guides/journals_formatting.md +486 -0
- package/skills/shared/venue-guides/medical_journal_styles.md +535 -0
- package/skills/shared/venue-guides/ml_conference_style.md +556 -0
- package/skills/shared/venue-guides/nature_science_style.md +405 -0
- package/skills/shared/venue-guides/reviewer_expectations.md +417 -0
- package/skills/shared/venue-guides/venue_writing_styles.md +321 -0
- package/skills/split-pdf/SKILL.md +172 -0
- package/skills/split-pdf/methodology.md +48 -0
- package/skills/sync-notion/SKILL.md +93 -0
- package/skills/system-audit/SKILL.md +157 -0
- package/skills/system-audit/references/sub-agent-prompts.md +294 -0
- package/skills/task-management/SKILL.md +131 -0
- package/skills/update-focus/SKILL.md +204 -0
- package/skills/update-project-doc/SKILL.md +194 -0
- package/skills/validate-bib/SKILL.md +242 -0
- package/skills/validate-bib/references/council-mode.md +34 -0
- package/skills/validate-bib/references/deep-verify.md +71 -0
- package/skills/validate-bib/references/openalex-verification.md +45 -0
- package/skills/validate-bib/references/preprint-check.md +31 -0
- package/skills/validate-bib/references/report-template.md +62 -0
package/.scripts/conf
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Conference tracking CLI.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
conf # show upcoming deadlines
|
|
7
|
+
conf --search # open search links in browser
|
|
8
|
+
conf --add # add a new conference
|
|
9
|
+
conf --deadlines # show all deadlines
|
|
10
|
+
conf --remind # weekly reminder check
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import webbrowser
|
|
18
|
+
import urllib.request
|
|
19
|
+
import urllib.error
|
|
20
|
+
import urllib.parse
|
|
21
|
+
from datetime import datetime, timedelta
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
NOTION_TOKEN = os.environ.get("NOTION_TOKEN")
|
|
26
|
+
DATABASE_ID = "YOUR-CONFERENCES-DATABASE-ID-HERE"
|
|
27
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
28
|
+
CONTEXT_PATH = BASE_DIR / ".context" / "conferences.md"
|
|
29
|
+
|
|
30
|
+
# Search keywords derived from research topics
|
|
31
|
+
SEARCH_KEYWORDS = [
|
|
32
|
+
# Add your research topic keywords for Google CFP searches
|
|
33
|
+
"your research topic 1",
|
|
34
|
+
"your research topic 2",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
TOPICS = [
|
|
38
|
+
# Conference topic tags — match your Notion "Topics" multi-select options
|
|
39
|
+
"Topic A",
|
|
40
|
+
"Topic B",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def query_conferences(filter_obj=None):
|
|
45
|
+
"""Query conferences from Notion."""
|
|
46
|
+
if not NOTION_TOKEN:
|
|
47
|
+
print("❌ NOTION_TOKEN not set")
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
|
|
50
|
+
data = {}
|
|
51
|
+
if filter_obj:
|
|
52
|
+
data["filter"] = filter_obj
|
|
53
|
+
|
|
54
|
+
data["sorts"] = [{"property": "Deadline", "direction": "ascending"}]
|
|
55
|
+
|
|
56
|
+
req = urllib.request.Request(
|
|
57
|
+
f"https://api.notion.com/v1/databases/{DATABASE_ID}/query",
|
|
58
|
+
data=json.dumps(data).encode("utf-8"),
|
|
59
|
+
headers={
|
|
60
|
+
"Authorization": f"Bearer {NOTION_TOKEN}",
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"Notion-Version": "2022-06-28",
|
|
63
|
+
},
|
|
64
|
+
method="POST",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
with urllib.request.urlopen(req) as response:
|
|
69
|
+
return json.loads(response.read().decode("utf-8"))
|
|
70
|
+
except urllib.error.HTTPError as e:
|
|
71
|
+
error_body = json.loads(e.read().decode("utf-8"))
|
|
72
|
+
print(f"❌ Error: {e.code}")
|
|
73
|
+
print(error_body.get("message", str(error_body)))
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_property(page, prop_name, prop_type):
|
|
78
|
+
"""Extract property value from a Notion page."""
|
|
79
|
+
prop = page.get("properties", {}).get(prop_name, {})
|
|
80
|
+
|
|
81
|
+
if prop_type == "title":
|
|
82
|
+
title_list = prop.get("title", [])
|
|
83
|
+
return title_list[0].get("plain_text", "") if title_list else ""
|
|
84
|
+
elif prop_type == "select":
|
|
85
|
+
select = prop.get("select")
|
|
86
|
+
return select.get("name", "") if select else ""
|
|
87
|
+
elif prop_type == "multi_select":
|
|
88
|
+
items = prop.get("multi_select", [])
|
|
89
|
+
return [item.get("name", "") for item in items]
|
|
90
|
+
elif prop_type == "date":
|
|
91
|
+
date = prop.get("date")
|
|
92
|
+
return date.get("start", "") if date else ""
|
|
93
|
+
elif prop_type == "url":
|
|
94
|
+
return prop.get("url", "")
|
|
95
|
+
elif prop_type == "rich_text":
|
|
96
|
+
text_list = prop.get("rich_text", [])
|
|
97
|
+
return text_list[0].get("plain_text", "") if text_list else ""
|
|
98
|
+
return ""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def add_conference(name, deadline=None, conf_date=None, topics=None, location=None, url=None, status="Tracking"):
|
|
102
|
+
"""Add a conference to Notion."""
|
|
103
|
+
if not NOTION_TOKEN:
|
|
104
|
+
print("❌ NOTION_TOKEN not set")
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
|
|
107
|
+
properties = {
|
|
108
|
+
"Conference": {"title": [{"text": {"content": name}}]},
|
|
109
|
+
"Status": {"select": {"name": status}},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if deadline:
|
|
113
|
+
properties["Deadline"] = {"date": {"start": deadline}}
|
|
114
|
+
|
|
115
|
+
if conf_date:
|
|
116
|
+
properties["Conference Date"] = {"date": {"start": conf_date}}
|
|
117
|
+
|
|
118
|
+
if topics:
|
|
119
|
+
properties["Topics"] = {"multi_select": [{"name": t} for t in topics]}
|
|
120
|
+
|
|
121
|
+
if location:
|
|
122
|
+
properties["Location"] = {"rich_text": [{"text": {"content": location}}]}
|
|
123
|
+
|
|
124
|
+
if url:
|
|
125
|
+
properties["URL"] = {"url": url}
|
|
126
|
+
|
|
127
|
+
data = {
|
|
128
|
+
"parent": {"database_id": DATABASE_ID},
|
|
129
|
+
"properties": properties,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
req = urllib.request.Request(
|
|
133
|
+
"https://api.notion.com/v1/pages",
|
|
134
|
+
data=json.dumps(data).encode("utf-8"),
|
|
135
|
+
headers={
|
|
136
|
+
"Authorization": f"Bearer {NOTION_TOKEN}",
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
"Notion-Version": "2022-06-28",
|
|
139
|
+
},
|
|
140
|
+
method="POST",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
with urllib.request.urlopen(req) as response:
|
|
145
|
+
result = json.loads(response.read().decode("utf-8"))
|
|
146
|
+
print(f"✅ Added: {name}")
|
|
147
|
+
if deadline:
|
|
148
|
+
print(f" Deadline: {deadline}")
|
|
149
|
+
print(f" {result.get('url', '')}")
|
|
150
|
+
return True
|
|
151
|
+
except urllib.error.HTTPError as e:
|
|
152
|
+
error_body = json.loads(e.read().decode("utf-8"))
|
|
153
|
+
print(f"❌ Error: {e.code}")
|
|
154
|
+
print(error_body.get("message", str(error_body)))
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def show_deadlines(upcoming_only=True):
|
|
159
|
+
"""Show conference deadlines."""
|
|
160
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
161
|
+
|
|
162
|
+
filter_obj = None
|
|
163
|
+
if upcoming_only:
|
|
164
|
+
filter_obj = {
|
|
165
|
+
"and": [
|
|
166
|
+
{"property": "Deadline", "date": {"on_or_after": today}},
|
|
167
|
+
{"property": "Status", "select": {"does_not_equal": "Passed"}}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
result = query_conferences(filter_obj)
|
|
172
|
+
pages = result.get("results", [])
|
|
173
|
+
|
|
174
|
+
if not pages:
|
|
175
|
+
print("No upcoming conference deadlines.")
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
print("📅 Conference Deadlines")
|
|
179
|
+
print("=" * 60)
|
|
180
|
+
|
|
181
|
+
for page in pages:
|
|
182
|
+
name = get_property(page, "Conference", "title")
|
|
183
|
+
deadline = get_property(page, "Deadline", "date")
|
|
184
|
+
status = get_property(page, "Status", "select")
|
|
185
|
+
topics = get_property(page, "Topics", "multi_select")
|
|
186
|
+
url = get_property(page, "URL", "url") or get_property(page, "userDefined:URL", "url")
|
|
187
|
+
|
|
188
|
+
# Calculate days until deadline
|
|
189
|
+
if deadline:
|
|
190
|
+
deadline_date = datetime.strptime(deadline, "%Y-%m-%d")
|
|
191
|
+
days_left = (deadline_date - datetime.now()).days
|
|
192
|
+
|
|
193
|
+
if days_left < 0:
|
|
194
|
+
urgency = "⚠️ PASSED"
|
|
195
|
+
elif days_left <= 7:
|
|
196
|
+
urgency = "🔴"
|
|
197
|
+
elif days_left <= 30:
|
|
198
|
+
urgency = "🟡"
|
|
199
|
+
else:
|
|
200
|
+
urgency = "🟢"
|
|
201
|
+
|
|
202
|
+
days_str = f"{days_left}d" if days_left >= 0 else "passed"
|
|
203
|
+
else:
|
|
204
|
+
urgency = "⚪"
|
|
205
|
+
days_str = "no date"
|
|
206
|
+
|
|
207
|
+
print(f"\n{urgency} {name}")
|
|
208
|
+
print(f" Deadline: {deadline or 'TBD'} ({days_str})")
|
|
209
|
+
if topics:
|
|
210
|
+
print(f" Topics: {', '.join(topics)}")
|
|
211
|
+
if status and status != "Tracking":
|
|
212
|
+
print(f" Status: {status}")
|
|
213
|
+
if url:
|
|
214
|
+
print(f" {url}")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def load_search_keywords():
|
|
218
|
+
"""Load search keywords from context file."""
|
|
219
|
+
keywords = []
|
|
220
|
+
try:
|
|
221
|
+
with open(CONTEXT_PATH, "r") as f:
|
|
222
|
+
content = f.read()
|
|
223
|
+
|
|
224
|
+
# Extract keywords from the code block under "## Search Keywords"
|
|
225
|
+
if "## Search Keywords" in content:
|
|
226
|
+
section = content.split("## Search Keywords")[1]
|
|
227
|
+
if "```" in section:
|
|
228
|
+
code_block = section.split("```")[1]
|
|
229
|
+
# Remove language identifier if present (e.g., 'python', 'text')
|
|
230
|
+
lines = code_block.strip().split("\n")
|
|
231
|
+
for line in lines:
|
|
232
|
+
line = line.strip()
|
|
233
|
+
if line and not line.startswith("#"):
|
|
234
|
+
keywords.append(line)
|
|
235
|
+
except FileNotFoundError:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
return keywords if keywords else [
|
|
239
|
+
"human-AI collaboration",
|
|
240
|
+
"multi-criteria decision",
|
|
241
|
+
"behavioural operations research",
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def open_search_links():
|
|
246
|
+
"""Open Google CFP searches in browser."""
|
|
247
|
+
print("🔍 Opening Google CFP searches...")
|
|
248
|
+
print()
|
|
249
|
+
|
|
250
|
+
keywords = load_search_keywords()
|
|
251
|
+
google_base = "https://www.google.com/search"
|
|
252
|
+
year = datetime.now().year
|
|
253
|
+
|
|
254
|
+
# Open first 5 keywords (to not overwhelm)
|
|
255
|
+
for keyword in keywords[:5]:
|
|
256
|
+
query = f'"call for papers" "{keyword}" {year}'
|
|
257
|
+
encoded = urllib.parse.quote(query)
|
|
258
|
+
url = f"{google_base}?q={encoded}"
|
|
259
|
+
print(f" Opening: {keyword}")
|
|
260
|
+
webbrowser.open(url)
|
|
261
|
+
|
|
262
|
+
if len(keywords) > 5:
|
|
263
|
+
print()
|
|
264
|
+
print(f"📝 {len(keywords) - 5} more keywords in context file:")
|
|
265
|
+
for keyword in keywords[5:]:
|
|
266
|
+
print(f" {keyword}")
|
|
267
|
+
|
|
268
|
+
print()
|
|
269
|
+
print(f"💡 Edit keywords: {CONTEXT_PATH}")
|
|
270
|
+
print("💡 Add conferences: conf --add")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def weekly_reminder():
|
|
274
|
+
"""Show weekly reminder."""
|
|
275
|
+
today = datetime.now()
|
|
276
|
+
week_ahead = (today + timedelta(days=7)).strftime("%Y-%m-%d")
|
|
277
|
+
month_ahead = (today + timedelta(days=30)).strftime("%Y-%m-%d")
|
|
278
|
+
|
|
279
|
+
print("📆 Weekly Conference Check")
|
|
280
|
+
print("=" * 50)
|
|
281
|
+
print(f"Today: {today.strftime('%A, %d %B %Y')}")
|
|
282
|
+
print()
|
|
283
|
+
|
|
284
|
+
# Get upcoming deadlines
|
|
285
|
+
result = query_conferences({
|
|
286
|
+
"and": [
|
|
287
|
+
{"property": "Deadline", "date": {"on_or_after": today.strftime("%Y-%m-%d")}},
|
|
288
|
+
{"property": "Deadline", "date": {"on_or_before": month_ahead}},
|
|
289
|
+
]
|
|
290
|
+
})
|
|
291
|
+
pages = result.get("results", [])
|
|
292
|
+
|
|
293
|
+
urgent = [p for p in pages if get_property(p, "Deadline", "date") <= week_ahead]
|
|
294
|
+
upcoming = [p for p in pages if get_property(p, "Deadline", "date") > week_ahead]
|
|
295
|
+
|
|
296
|
+
if urgent:
|
|
297
|
+
print(f"🔴 THIS WEEK ({len(urgent)}):")
|
|
298
|
+
for page in urgent:
|
|
299
|
+
name = get_property(page, "Conference", "title")
|
|
300
|
+
deadline = get_property(page, "Deadline", "date")
|
|
301
|
+
print(f" • {name} — {deadline}")
|
|
302
|
+
|
|
303
|
+
if upcoming:
|
|
304
|
+
print(f"\n🟡 NEXT 30 DAYS ({len(upcoming)}):")
|
|
305
|
+
for page in upcoming:
|
|
306
|
+
name = get_property(page, "Conference", "title")
|
|
307
|
+
deadline = get_property(page, "Deadline", "date")
|
|
308
|
+
print(f" • {name} — {deadline}")
|
|
309
|
+
|
|
310
|
+
if not urgent and not upcoming:
|
|
311
|
+
print("No deadlines in the next 30 days.")
|
|
312
|
+
|
|
313
|
+
print()
|
|
314
|
+
print("📋 Weekly checklist:")
|
|
315
|
+
print(" [ ] Run 'conf --search' to find new CFPs")
|
|
316
|
+
print(" [ ] Add new conferences with 'conf --add'")
|
|
317
|
+
print(" [ ] Review papers ready for submission")
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def validate_date(date_str):
|
|
321
|
+
"""Validate date format (YYYY-MM-DD). Returns True if valid or empty."""
|
|
322
|
+
if not date_str:
|
|
323
|
+
return True
|
|
324
|
+
try:
|
|
325
|
+
datetime.strptime(date_str, "%Y-%m-%d")
|
|
326
|
+
return True
|
|
327
|
+
except ValueError:
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def get_input(prompt, allow_empty=True, validate_fn=None, error_msg=None):
|
|
332
|
+
"""Get input with exit/cancel support and optional validation."""
|
|
333
|
+
while True:
|
|
334
|
+
value = input(prompt).strip()
|
|
335
|
+
|
|
336
|
+
# Allow exit at any point
|
|
337
|
+
if value.lower() in ("exit", "cancel", "q", "quit"):
|
|
338
|
+
print("❌ Cancelled.")
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
# Check if empty is allowed
|
|
342
|
+
if not value:
|
|
343
|
+
if allow_empty:
|
|
344
|
+
return ""
|
|
345
|
+
print("This field is required. Type 'exit' to cancel.")
|
|
346
|
+
continue
|
|
347
|
+
|
|
348
|
+
# Run validation if provided
|
|
349
|
+
if validate_fn and not validate_fn(value):
|
|
350
|
+
print(error_msg or "Invalid input. Try again or type 'exit' to cancel.")
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
return value
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def interactive_add():
|
|
357
|
+
"""Interactively add a conference."""
|
|
358
|
+
print("➕ Add Conference")
|
|
359
|
+
print("-" * 30)
|
|
360
|
+
print("(Type 'exit' at any prompt to cancel)\n")
|
|
361
|
+
|
|
362
|
+
name = get_input("Conference name: ", allow_empty=False)
|
|
363
|
+
if name is None:
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
deadline = get_input(
|
|
367
|
+
"Deadline (YYYY-MM-DD, or press Enter to skip): ",
|
|
368
|
+
validate_fn=validate_date,
|
|
369
|
+
error_msg="Invalid date format. Use YYYY-MM-DD (e.g., 2026-03-15)"
|
|
370
|
+
)
|
|
371
|
+
if deadline is None:
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
conf_date = get_input(
|
|
375
|
+
"Conference date (YYYY-MM-DD, or press Enter to skip): ",
|
|
376
|
+
validate_fn=validate_date,
|
|
377
|
+
error_msg="Invalid date format. Use YYYY-MM-DD (e.g., 2026-06-20)"
|
|
378
|
+
)
|
|
379
|
+
if conf_date is None:
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
location = get_input("Location (or press Enter to skip): ")
|
|
383
|
+
if location is None:
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
url = get_input("URL (or press Enter to skip): ")
|
|
387
|
+
if url is None:
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
print("\nAvailable topics:")
|
|
391
|
+
for i, topic in enumerate(TOPICS, 1):
|
|
392
|
+
print(f" {i}. {topic}")
|
|
393
|
+
|
|
394
|
+
topic_input = get_input("Topics (comma-separated numbers, or press Enter to skip): ")
|
|
395
|
+
if topic_input is None:
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
topics = []
|
|
399
|
+
if topic_input:
|
|
400
|
+
try:
|
|
401
|
+
indices = [int(x.strip()) - 1 for x in topic_input.split(",")]
|
|
402
|
+
topics = [TOPICS[i] for i in indices if 0 <= i < len(TOPICS)]
|
|
403
|
+
except (ValueError, IndexError):
|
|
404
|
+
print("⚠️ Invalid topic selection, skipping topics.")
|
|
405
|
+
|
|
406
|
+
add_conference(
|
|
407
|
+
name=name,
|
|
408
|
+
deadline=deadline or None,
|
|
409
|
+
conf_date=conf_date or None,
|
|
410
|
+
topics=topics or None,
|
|
411
|
+
location=location or None,
|
|
412
|
+
url=url or None,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def main():
|
|
417
|
+
parser = argparse.ArgumentParser(description="Conference tracking")
|
|
418
|
+
parser.add_argument("--search", action="store_true", help="Open search links in browser")
|
|
419
|
+
parser.add_argument("--add", action="store_true", help="Add a new conference")
|
|
420
|
+
parser.add_argument("--deadlines", action="store_true", help="Show all deadlines")
|
|
421
|
+
parser.add_argument("--all", action="store_true", help="Include past conferences")
|
|
422
|
+
parser.add_argument("--remind", action="store_true", help="Weekly reminder")
|
|
423
|
+
parser.add_argument("--topics", action="store_true", help="List available topics")
|
|
424
|
+
|
|
425
|
+
args = parser.parse_args()
|
|
426
|
+
|
|
427
|
+
if args.search:
|
|
428
|
+
open_search_links()
|
|
429
|
+
elif args.add:
|
|
430
|
+
interactive_add()
|
|
431
|
+
elif args.deadlines or args.all:
|
|
432
|
+
show_deadlines(upcoming_only=not args.all)
|
|
433
|
+
elif args.remind:
|
|
434
|
+
weekly_reminder()
|
|
435
|
+
elif args.topics:
|
|
436
|
+
print("Available topics:")
|
|
437
|
+
for topic in TOPICS:
|
|
438
|
+
print(f" • {topic}")
|
|
439
|
+
else:
|
|
440
|
+
# Default: show upcoming deadlines
|
|
441
|
+
show_deadlines(upcoming_only=True)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
if __name__ == "__main__":
|
|
445
|
+
main()
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration for Task Management Scripts
|
|
3
|
+
|
|
4
|
+
This file contains API keys and database IDs needed for automation.
|
|
5
|
+
IMPORTANT: Keep this file secure and don't commit API keys to version control.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Auto-load .env file from project root
|
|
12
|
+
try:
|
|
13
|
+
from dotenv import load_dotenv
|
|
14
|
+
_env_path = Path(__file__).parent.parent / ".env"
|
|
15
|
+
load_dotenv(_env_path)
|
|
16
|
+
except ImportError:
|
|
17
|
+
pass # dotenv not installed, rely on environment variables
|
|
18
|
+
|
|
19
|
+
# Notion Configuration
|
|
20
|
+
# Get your integration token from: https://www.notion.so/my-integrations
|
|
21
|
+
NOTION_TOKEN = os.environ.get("NOTION_TOKEN", "your-notion-integration-token")
|
|
22
|
+
|
|
23
|
+
# Database IDs (extracted from the user's Notion workspace)
|
|
24
|
+
# NOTE: These are Notion page IDs (from URLs). The Notion MCP tools and
|
|
25
|
+
# CLAUDE.md use separate database IDs — both formats are valid.
|
|
26
|
+
DATABASES = {
|
|
27
|
+
"tasks_tracker": "YOUR-TASKS-DATABASE-ID-HERE",
|
|
28
|
+
"research_pipeline": "YOUR-PIPELINE-DATABASE-ID-HERE",
|
|
29
|
+
"conferences": "YOUR-CONFERENCES-DATABASE-ID-HERE",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Page IDs
|
|
33
|
+
PAGES = {
|
|
34
|
+
"dashboard": "YOUR-DASHBOARD-PAGE-ID-HERE",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Local paths
|
|
38
|
+
BASE_DIR = Path(__file__).parent.parent
|
|
39
|
+
CONTEXT_DIR = BASE_DIR / ".context"
|
|
40
|
+
SCRIPTS_DIR = BASE_DIR / ".scripts"
|
|
41
|
+
|
|
42
|
+
# Project mappings (for task categorisation)
|
|
43
|
+
# Keep in sync with .context/projects/_index.md
|
|
44
|
+
PROJECTS = [
|
|
45
|
+
# Add your project names here — keep in sync with .context/projects/_index.md
|
|
46
|
+
"Paper Revision",
|
|
47
|
+
"Literature Review",
|
|
48
|
+
"Conference Submission",
|
|
49
|
+
"Teaching Prep",
|
|
50
|
+
"Personal Admin",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Source types for tasks
|
|
54
|
+
SOURCES = [
|
|
55
|
+
"Meeting",
|
|
56
|
+
"Email",
|
|
57
|
+
"Supervisor request",
|
|
58
|
+
"Self-initiated",
|
|
59
|
+
"Deadline/calendar",
|
|
60
|
+
"Idea capture",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Task types
|
|
64
|
+
TASK_TYPES = [
|
|
65
|
+
"🐞 Bug",
|
|
66
|
+
"💬 Feature request",
|
|
67
|
+
"💅 Polish",
|
|
68
|
+
"Claim",
|
|
69
|
+
"📝 Writing",
|
|
70
|
+
"📚 Reading",
|
|
71
|
+
"🔬 Research",
|
|
72
|
+
"📅 Meeting",
|
|
73
|
+
"📋 Admin",
|
|
74
|
+
"📧 Communication",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Priority levels
|
|
78
|
+
PRIORITIES = ["High", "Medium", "Low"]
|
|
79
|
+
|
|
80
|
+
# Status options (GTD-style)
|
|
81
|
+
STATUSES = ["Inbox", "Not started", "In progress", "Waiting", "Someday", "Done"]
|
|
82
|
+
|
|
83
|
+
# Areas of responsibility (ongoing, no end date)
|
|
84
|
+
AREAS = [
|
|
85
|
+
"Research",
|
|
86
|
+
"Teaching",
|
|
87
|
+
"Career",
|
|
88
|
+
"Personal",
|
|
89
|
+
"Health",
|
|
90
|
+
"Learning",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# Universities
|
|
94
|
+
UNIVERSITIES = [
|
|
95
|
+
# Add your institutions here
|
|
96
|
+
"University A", "University B",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def validate_config():
|
|
101
|
+
"""Check that essential configuration is present."""
|
|
102
|
+
issues = []
|
|
103
|
+
|
|
104
|
+
if NOTION_TOKEN == "your-notion-integration-token":
|
|
105
|
+
issues.append("NOTION_TOKEN not set. Set the NOTION_TOKEN environment variable.")
|
|
106
|
+
|
|
107
|
+
if not CONTEXT_DIR.exists():
|
|
108
|
+
issues.append(f"Context directory not found: {CONTEXT_DIR}")
|
|
109
|
+
|
|
110
|
+
return issues
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
issues = validate_config()
|
|
115
|
+
if issues:
|
|
116
|
+
print("Configuration issues:")
|
|
117
|
+
for issue in issues:
|
|
118
|
+
print(f" - {issue}")
|
|
119
|
+
else:
|
|
120
|
+
print("Configuration valid!")
|
|
121
|
+
print(f"Context directory: {CONTEXT_DIR}")
|
|
122
|
+
print(f"Scripts directory: {SCRIPTS_DIR}")
|