import.md repeatedly promised baseline comparison, rolling 4-week averages, and baselines.json/metadata.json the importer "creates" — but no code anywhere writes either file; detectAlerts flags intra-batch std-dev from the batch's own mean, not a stored baseline. Step 5b's `cat baselines.json` was always empty, so the command fell forever to "first import — baselines will be established" on every import. Rewrote 5 sections (Step 5, 5b, 7, State Tracking, Reference Files) to the truth: real CLI fields + YYYY-MM-DD-<shortid> filename, honest intra-batch surfacing, cross-week analysis deferred to /linkedin:report. Verified with a grounded run against a throwaway fixture. Axes a/b/c/d all PASS post-fix; lint 81/0/0, counts 29/19 unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
295 lines
12 KiB
Markdown
295 lines
12 KiB
Markdown
---
|
||
name: linkedin:import
|
||
description: |
|
||
Import a LinkedIn analytics CSV export into the structured analytics system.
|
||
Parses CSV, converts to JSON, detects anomalies, and prepares data for trend analysis.
|
||
Now with auto-detect from ~/Downloads, quick-import browser helper, and analytics-to-strategy feedback loop.
|
||
Use when the user wants to import analytics data from LinkedIn.
|
||
Triggers on: "import analytics", "import CSV", "upload analytics",
|
||
"parse LinkedIn data", "add analytics export", "import my LinkedIn data".
|
||
allowed-tools:
|
||
- Bash
|
||
- Read
|
||
- Glob
|
||
- Write
|
||
- AskUserQuestion
|
||
---
|
||
|
||
# LinkedIn Analytics Import Workflow
|
||
|
||
You are a LinkedIn analytics data import assistant. Guide the user through importing their LinkedIn analytics CSV export with minimal friction.
|
||
|
||
## Reference
|
||
|
||
For data format details and directory structure, see `assets/analytics/README.md`.
|
||
|
||
> **Why CSV (as of 2026-05).** Post-level analytics via LinkedIn's API is
|
||
> partner-gated (vetted Community Management app + verified org + Page) and **not
|
||
> self-serve** for a personal profile, so the CSV export is the practical floor.
|
||
> Saves are visible in native post analytics (count-only) but have no self-serve
|
||
> API pull; dwell is internal-only for organic posts. See the README boundaries.
|
||
|
||
## Step 1: Check for CSV Files in Exports Directory
|
||
|
||
First, check if any CSV files exist in the exports directory:
|
||
|
||
```bash
|
||
ls -lh ${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/exports/*.csv 2>/dev/null || echo "No CSV files found"
|
||
```
|
||
|
||
**If files found:** Skip to Step 3.
|
||
|
||
## Step 1b: Auto-Detect from ~/Downloads
|
||
|
||
If no files in exports directory, scan `~/Downloads/` for recent LinkedIn CSV files:
|
||
|
||
```bash
|
||
find ~/Downloads -maxdepth 1 -name "*.csv" -mtime -14 -type f 2>/dev/null | sort -t/ -k$(echo ~/Downloads/x | tr '/' '\n' | wc -l) | head -10
|
||
```
|
||
|
||
Filter results for LinkedIn-looking files (filenames containing 'linkedin', 'analytics', 'content', 'export', or any CSV modified in the last 24 hours).
|
||
|
||
**If matching files found**, present them using AskUserQuestion:
|
||
|
||
Options:
|
||
- **Import specific file** — Select one of the detected files
|
||
- **Import all** — Import all matching CSV files
|
||
- **Quick-import** — Open LinkedIn Analytics in browser and auto-detect download
|
||
- **Skip** — Show manual instructions instead
|
||
|
||
On file selection, copy the file to the exports directory:
|
||
```bash
|
||
cp "<selected-file>" ${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/exports/
|
||
```
|
||
|
||
Then continue to Step 4.
|
||
|
||
## Step 2: If No Files Found Anywhere
|
||
|
||
If no CSV files exist in exports or ~/Downloads, offer two options:
|
||
|
||
**Option A: Quick-import (recommended)**
|
||
|
||
Run the quick-import helper that opens LinkedIn Analytics in the browser and watches for the download:
|
||
|
||
```bash
|
||
node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/quick-import.mjs
|
||
```
|
||
|
||
This will:
|
||
1. Open `linkedin.com/analytics/creator/content/` in your browser
|
||
2. Watch ~/Downloads for new CSV files
|
||
3. Auto-copy detected files to the exports directory
|
||
|
||
After the script completes, continue to Step 4.
|
||
|
||
**Option B: Manual export**
|
||
|
||
1. Go to [linkedin.com/analytics/creator/content/](https://linkedin.com/analytics/creator/content/)
|
||
2. Click the **"Export"** button (top right)
|
||
3. LinkedIn will download a CSV file
|
||
4. Move it to: `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/exports/`
|
||
|
||
```bash
|
||
mv ~/Downloads/linkedin_analytics_export*.csv ${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/exports/
|
||
```
|
||
|
||
Once done, run `/linkedin:import` again.
|
||
|
||
## Step 3: Select Files to Import
|
||
|
||
If CSV files exist in the exports directory:
|
||
|
||
1. **List the files** with details (name, size, date)
|
||
2. **Ask the user** which file to import using AskUserQuestion:
|
||
|
||
Options:
|
||
- **Latest** — Import the most recent file only
|
||
- **All** — Import all CSV files
|
||
- **Select** — Choose a specific file
|
||
- **Cancel** — Exit import
|
||
|
||
## Step 4: Run Import
|
||
|
||
The import CLI runs under `tsx` and depends on `csv-parse`. Both live in the
|
||
**gitignored** `scripts/analytics/node_modules/`, so on a fresh clone they are
|
||
absent and the CLI would crash with `ERR_MODULE_NOT_FOUND`. Install them once
|
||
first (idempotent — a fast no-op when already present):
|
||
|
||
```bash
|
||
cd "${CLAUDE_PLUGIN_ROOT}/scripts/analytics" && npm install --silent
|
||
```
|
||
|
||
Once the user selects, run the import CLI:
|
||
|
||
```bash
|
||
"${CLAUDE_PLUGIN_ROOT}/scripts/analytics/node_modules/.bin/tsx" "${CLAUDE_PLUGIN_ROOT}/scripts/analytics/src/cli.ts" import <filename>
|
||
```
|
||
|
||
If importing multiple files, run the command for each file sequentially.
|
||
|
||
## Step 5: Capture and Present Results
|
||
|
||
The CLI prints (see `cli.ts` `handleImport`):
|
||
- `Posts imported:` — count of valid rows (rows with an empty title or an unparseable date are skipped, each with a `Warning:` line)
|
||
- `Date range:` — earliest to latest post in the batch
|
||
- `Batch ID:` and `Saved to: posts/<file>` — the batch file written
|
||
- `Saves entered:` — only when the CSV carried a `Saves` column (manual entry)
|
||
- An anomaly block — either `Immediate alerts detected:` with 🔴/⚠️/ℹ️ spike/drop lines, or `No anomalies detected in imported data.`
|
||
|
||
**Surface the CLI's output to the user** — for example:
|
||
|
||
```
|
||
Import successful!
|
||
─────────────────────────────────────
|
||
Posts imported: 42
|
||
Date range: 2025-12-01 to 2026-01-29
|
||
Saved to: posts/2025-12-01-batch-a1b2c3d4.json
|
||
Saves entered: 1,204 across 18 post(s) (manual)
|
||
|
||
Immediate alerts detected:
|
||
─────────────────────────────────────
|
||
ℹ️ [INFO] Post "AI agents are eating..." has unusually high impressions: 21,400 (2.4 std deviations above mean)
|
||
```
|
||
|
||
The saved file is named `posts/YYYY-MM-DD-<shortid>.json` (the batch's earliest post date + a short batch id), not by ISO week.
|
||
|
||
### Step 5b: Surface the Anomalies the Importer Detected
|
||
|
||
The import CLI runs **intra-batch** anomaly detection during Step 4 (`detectAlerts`
|
||
in `cli.ts`): for the just-imported batch it flags any post whose impressions
|
||
deviate sharply — by standard deviation — from *that batch's own mean*, printing
|
||
either `Immediate alerts detected:` (🔴/⚠️/ℹ️ spike/drop lines) or
|
||
`No anomalies detected in imported data.`
|
||
|
||
Surface those lines as-is, and state the scope honestly: a flagged post stands out
|
||
**among the posts in this export**, not against a stored historical baseline — the
|
||
importer keeps no baseline file. Cross-week comparison is Step 6's job.
|
||
|
||
**Present as:**
|
||
```
|
||
### Import Summary — <batch date range>
|
||
|
||
X posts imported (Y skipped: empty title or unparseable date)
|
||
|
||
#### Standout in this batch
|
||
ℹ️ "[hook text...]" — 21,400 impressions (2.4 std dev above this batch's mean)
|
||
⚠️ "[hook text...]" — 180 impressions (2.1 std dev below this batch's mean)
|
||
|
||
(or: "No standout deviations within this batch.")
|
||
```
|
||
|
||
Cross-week analysis — best day of week, format performance, week-over-week trend,
|
||
rolling averages — is **not** computed here; it is produced by `/linkedin:report`
|
||
(Step 6), which reads the full post history via the `trends`/`heatmap` CLI. Defer
|
||
that analysis to Step 6 rather than restating it.
|
||
|
||
## Step 6: Analytics-to-Strategy Feedback Loop
|
||
|
||
After successful import, the analysis fan-out (pillar performance, format
|
||
performance, day-of-week heatmap, actionable recommendations) is **delegated
|
||
to `/linkedin:report`** — both commands consume the same `trends` CLI from
|
||
`scripts/analytics/`, and keeping a second analysis pipeline here drifted
|
||
out of sync with `report.md`.
|
||
|
||
### Step 6a: Run the report
|
||
|
||
Invoke the report generator and surface its output inline:
|
||
|
||
```
|
||
Run /linkedin:report (period: 4w)
|
||
```
|
||
|
||
`/linkedin:report` will:
|
||
|
||
1. Read `expertise_areas` from `~/.claude/linkedin-studio.local.md`
|
||
2. Call `trends` for impressions and engagement_rate over the last 4 weeks:
|
||
```bash
|
||
"${CLAUDE_PLUGIN_ROOT}/scripts/analytics/node_modules/.bin/tsx" "${CLAUDE_PLUGIN_ROOT}/scripts/analytics/src/cli.ts" trends --period 4w --metric impressions
|
||
"${CLAUDE_PLUGIN_ROOT}/scripts/analytics/node_modules/.bin/tsx" "${CLAUDE_PLUGIN_ROOT}/scripts/analytics/src/cli.ts" trends --period 4w --metric engagement_rate
|
||
```
|
||
3. Produce the Content Pillar Performance, Format Performance, and
|
||
Day-of-Week Performance tables, plus exactly 3 actionable recommendations
|
||
4. Return its summary back to this import flow
|
||
|
||
If `/linkedin:report` is unavailable (analytics dir empty, tsx missing),
|
||
fall back to a one-line status: "Import complete — run `/linkedin:report`
|
||
manually when analytics are ready."
|
||
|
||
### Step 6b: Update State with Import Date
|
||
|
||
After successful import and analysis, update the state file:
|
||
|
||
```
|
||
Read ~/.claude/linkedin-studio.local.md
|
||
Set last_import_date to today (YYYY-MM-DD)
|
||
Set last_import_week to current ISO week (YYYY-WXX)
|
||
Write the updated state file
|
||
```
|
||
|
||
## Step 7: Next Steps
|
||
|
||
Present next steps using AskUserQuestion based on the analysis results:
|
||
|
||
**If the report's trend is down** (impressions or engagement trending DOWN):
|
||
- "Run /linkedin:report for full weekly breakdown"
|
||
- "Run content audit to review strategy"
|
||
- "Analyze your top post to understand what worked"
|
||
|
||
**If the report's trend is up** (impressions or engagement trending UP):
|
||
- "Run /linkedin:report for the full numbers"
|
||
- "Create more content in your top format"
|
||
- "Draft your next post while insights are fresh"
|
||
|
||
**If first import:**
|
||
- "Run /linkedin:report for your first performance report"
|
||
- "Import 2-3 more weeks for trend analysis"
|
||
- "Tip: Export weekly every Monday for best tracking"
|
||
|
||
**If mixed results:**
|
||
- "Run /linkedin:report for complete breakdown"
|
||
- "Review trend analysis for diverging metrics"
|
||
- "Check which formats and topics drove results"
|
||
|
||
Present using AskUserQuestion with the top 3 most relevant suggestions.
|
||
|
||
## Step 8: Demographics Sync Suggestion
|
||
|
||
After completing the import workflow, check if `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/audience-insights/demographics.md` still has placeholder data:
|
||
|
||
```bash
|
||
grep -c '\[Industry name\]\|\[Function\]\|\[Country\]\|\[X\]%' ${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/audience-insights/demographics.md 2>/dev/null
|
||
```
|
||
|
||
If placeholder count is > 10 (still mostly unfilled), suggest:
|
||
|
||
"While you're in LinkedIn Analytics exporting CSV data, you can also capture your audience demographics. Run `/linkedin:setup` and choose option 5 (Demographics) to fill in your audience insights with real data."
|
||
|
||
## Error Handling
|
||
|
||
If the import fails:
|
||
|
||
1. **Check the CSV format** - LinkedIn sometimes changes export format
|
||
2. **Verify the file path** - Ensure the file is in `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/exports/`
|
||
3. **Check file permissions** - The CLI needs read access
|
||
4. **Show the error message** and suggest solutions
|
||
|
||
**Common errors:**
|
||
|
||
- `File not found`: Check the filename (case-sensitive)
|
||
- `Invalid CSV format`: Verify this is a LinkedIn analytics export
|
||
- `Permission denied`: Check file permissions with `ls -l`
|
||
|
||
## Reference Files
|
||
|
||
The import system creates:
|
||
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/posts/YYYY-MM-DD-<shortid>.json` - one JSON batch per import (earliest post date + short batch id)
|
||
|
||
Weekly and monthly report files (under `weekly-reports/` and `monthly-reports/`) are created separately by `/linkedin:report`, not by import.
|
||
|
||
## State Tracking
|
||
|
||
After import:
|
||
- A new batch file `posts/YYYY-MM-DD-<shortid>.json` is written, one per import — existing batch files are never overwritten; `loadAllPosts` deduplicates by post id at read time (latest import wins)
|
||
- Intra-batch spike/drop alerts are computed and surfaced (std deviation from the batch's own mean — no persisted baseline)
|
||
- `last_import_date` and `last_import_week` are updated in the state file (`~/.claude/linkedin-studio.local.md`, see Step 6b)
|