From claude-office
Retroactively import past Claude Code sessions into your activity log — browse, filter, and choose what to log. No arguments needed — just run /import-activity to get started.
How this command is triggered — by the user, by Claude, or both
Slash command
/claude-office:import-activity (no arguments needed)The summary Claude sees in its command listing — used to decide when to auto-load this command
## When to Use When sessions were not captured by the session-end hook — before the plugin was installed, during offline work, or from other projects. Browse what's available in `~/.claude/projects/`, preview sessions, and selectively import them. ## Context Identity and vault path from `<claude-office-session>` tags. ## Examples ## Output File Routing **Single-project import** (user specified a project or picked one): → Write to `team/<identity>/activity/activity.md` (the standard file) **Multi-project bulk import** (`--all` or user selects sessions across projects): → Write to **...
When sessions were not captured by the session-end hook — before the plugin was installed, during offline work, or from other projects. Browse what's available in ~/.claude/projects/, preview sessions, and selectively import them.
Identity and vault path from <claude-office-session> tags.
/import-activity # Interactive — show all projects, let me pick
/import-activity ingram-cloud # Show sessions from ingram-cloud only
/import-activity --since 2026-03-25 # Everything since March 25
/import-activity --all # Import all unlogged sessions (full detail, no truncation, no confirmation)
/import-activity ingram-cloud --minimal # Just prompts, no tools/tokens
/import-activity --all --with-responses # Full detail including AI responses
/import-activity cyberspace --prompts-only # Just the session header + every prompt
Single-project import (user specified a project or picked one):
→ Write to team/<identity>/activity/activity.md (the standard file)
Multi-project bulk import (--all or user selects sessions across projects):
→ Write to separate files per project: team/<identity>/activity/activity-<project>.md
→ This keeps bulk imports organized and avoids flooding the main activity file
→ The user can later merge entries into activity/activity.md or keep them separate
File naming: derive <project> from the repo name (e.g., activity-ingram-cloud.md, activity-cyberspace.md). If the file doesn't exist yet, create it with the same header format as activity/activity.md:
# Activity — <identity> / <project>
<!--
Retroactively imported from ~/.claude session transcripts.
Entries below were not captured by the session-end hook at the time.
-->
When the user runs /import-activity with no arguments, present a quick overview of what's available and what they can do:
I found N sessions across M projects in your Claude Code history:
Project Sessions Date range ingram-cloud 12 Mar 20 – Apr 01 cyberspace 8 Mar 25 – Mar 31 ... ... ... What would you like to do?
- Import everything → I'll split into per-project files (
activity-ingram-cloud.md,activity-cyberspace.md, etc.) to keep things organized- Pick a specific project → I'll show individual sessions so you can cherry-pick
- Filter by date → e.g., "since last Monday"
Options you can use next time:
/import-activity ingram-cloud— filter to one project/import-activity --since 2026-03-25— filter by date/import-activity --all— import everything at once/import-activity --all --with-responses— include AI responses too
When the user picks "import everything" from this flow, default to per-project files and full detail level.
Scan ~/.claude/projects/ for session JSONL files. Each subfolder is a project — the folder name is a path-encoded project directory (e.g., C--Users-galaxy-Documents-GitHub-ingram-cloud → C:\Users\galaxy\Documents\GitHub\ingram-cloud).
Use this shell snippet to build the overview:
# ── Session scanner ──────────────────────────────────────────────────────────
# Reads all .jsonl transcripts under ~/.claude/projects/ and prints one JSON
# object per session. Claude runs this, captures output, and presents it.
#
# ── Toggle what to extract ───────────────────────────────────────────────────
EXTRACT_PROMPTS=true # false → skip prompt text
EXTRACT_TOOLS=true # false → skip tool counting
EXTRACT_TOKENS=true # false → skip token sums
EXTRACT_FILES=true # false → skip edited-file tracking
EXTRACT_RESPONSES=false # true → include AI response text (large output!)
# ── Filters (set before running) ─────────────────────────────────────────────
FILTER_PROJECT="" # substring match on project folder name, e.g. "ingram-cloud"
FILTER_SINCE="" # ISO date, e.g. "2026-03-25" — skip sessions before this
# ─────────────────────────────────────────────────────────────────────────────
CLAUDE_DIR="$HOME/.claude/projects"
for project_dir in "$CLAUDE_DIR"/*/; do
project_name=$(basename "$project_dir")
# ── Project filter ──
if [ -n "$FILTER_PROJECT" ]; then
echo "$project_name" | grep -qi "$FILTER_PROJECT" || continue
fi
for jsonl in "$project_dir"*.jsonl; do
[ -f "$jsonl" ] || continue
session_id=$(basename "$jsonl" .jsonl)
node -e "
const fs = require('fs');
const lines = fs.readFileSync(process.argv[1], 'utf8').trim().split('\n');
const projectName = process.argv[2];
const sessionId = process.argv[3];
const filterSince = process.argv[4] || '';
// ── Toggles (mirrored from shell) ──
const EXTRACT_PROMPTS = $EXTRACT_PROMPTS;
const EXTRACT_TOOLS = $EXTRACT_TOOLS;
const EXTRACT_TOKENS = $EXTRACT_TOKENS;
const EXTRACT_FILES = $EXTRACT_FILES;
const EXTRACT_RESPONSES = $EXTRACT_RESPONSES;
let firstTs = '', lastTs = '';
let repo = '', branch = '', entrypoint = '';
let msgCount = 0;
const tools = {};
let tokensIn = 0, tokensOut = 0;
const prompts = []; // every user prompt, full text, no truncation
const responses = []; // AI response text blocks (opt-in)
const editedFiles = new Set();
for (const line of lines) {
try {
const o = JSON.parse(line);
// ── Timestamps (always needed for filtering + duration) ──
if (o.timestamp) {
if (!firstTs) firstTs = o.timestamp;
lastTs = o.timestamp;
}
// ── Metadata (always extracted — cheap) ──
if (o.gitBranch && !branch) branch = o.gitBranch;
if (o.cwd && !repo) repo = o.cwd.replace(/\\\\\\\\/g, '/').split('/').pop();
if (o.entrypoint && !entrypoint) entrypoint = o.entrypoint;
// ── User prompts — NO length cap, NO truncation ──
if (o.type === 'user' && o.message) {
msgCount++;
if (EXTRACT_PROMPTS) {
const mc = o.message.content;
let txt = typeof mc === 'string' ? mc : '';
if (Array.isArray(mc)) txt = mc.filter(b => b.type === 'text').map(b => b.text).join(' ');
txt = txt.replace(/[\\r]/g, '').trim();
// Keep full text. Only skip empty or system-injected messages.
if (txt && txt.length > 2 && !txt.startsWith('<'))
prompts.push(txt);
}
}
// ── Assistant responses (opt-in — can be very large) ──
if (o.type === 'assistant' && o.message) {
if (EXTRACT_TOKENS) {
const u = o.message.usage;
if (u) { tokensIn += (u.input_tokens || 0); tokensOut += (u.output_tokens || 0); }
}
if (Array.isArray(o.message.content)) {
for (const b of o.message.content) {
if (b.type === 'tool_use') {
if (EXTRACT_TOOLS) tools[b.name] = (tools[b.name] || 0) + 1;
if (EXTRACT_FILES) {
const inp = b.input || {};
const fp = inp.file_path || inp.path || '';
if (fp && (b.name === 'Edit' || b.name === 'Write' || b.name === 'MultiEdit')) {
editedFiles.add(fp.replace(/\\\\\\\\/g, '/').split('/').pop());
}
}
}
// ── Extract AI text responses ──
if (EXTRACT_RESPONSES && b.type === 'text' && b.text) {
responses.push(b.text.trim());
}
}
}
}
} catch (e) {}
}
// ── Date filter ──
if (filterSince && firstTs && firstTs < filterSince) process.exit(0);
// ── Skip empty sessions ──
if (msgCount === 0) process.exit(0);
// ── Duration ──
let durationMin = 0;
if (firstTs && lastTs) durationMin = Math.round((new Date(lastTs) - new Date(firstTs)) / 60000);
// ── Tool summary ──
const toolSummary = EXTRACT_TOOLS
? Object.entries(tools).sort((a,b) => b[1]-a[1]).map(([n,c]) => n + (c>1 ? '\u00d7'+c : '')).join(', ')
: '';
// ── Token summary ──
const tokenSummary = EXTRACT_TOKENS && tokensOut > 0
? tokensIn + 'in/' + tokensOut + 'out'
: '';
// ── File list ──
const fileList = EXTRACT_FILES
? [...editedFiles].sort().join(', ')
: '';
// ── Output: one JSON object per session ──
console.log(JSON.stringify({
project: projectName,
session_id: sessionId,
first_ts: firstTs,
last_ts: lastTs,
duration_min: durationMin,
msg_count: msgCount,
repo: repo,
branch: branch,
entrypoint: entrypoint,
prompts: prompts,
responses: EXTRACT_RESPONSES ? responses : undefined,
tools: toolSummary,
tokens: tokenSummary,
files: fileList
}));
" "$jsonl" "$project_name" "$session_id" "$FILTER_SINCE" 2>/dev/null || true
done
done
Show the user a table of discovered sessions, grouped by project:
### ingram-cloud (12 sessions)
| # | Date | Duration | Prompts | Topic | Logged? |
|---|------|----------|---------|-------|---------|
| 1 | 2026-03-25 14:30 | 45min | 8 | refactor auth middleware | No |
| 2 | 2026-03-26 09:15 | 12min | 3 | fix deployment pipeline | Yes |
...
### cyberspace (8 sessions)
| # | Date | Duration | Prompts | Topic | Logged? |
...
To check if a session is already logged, grep the user's activity/activity.md AND any activity/activity-*.md files for the session ID (first 8 chars).
Ask the user what they want to import:
Before writing, ask what level of detail the user wants:
| Level | What's included |
|---|---|
| full (default) | Header + all prompts (complete text) + tools + tokens + files + projects |
| extra | Everything in full + AI replies as well |
| standard | Header + first prompt as topic + tools + tokens + files (no full prompt list) |
| minimal | Header + first prompt as topic only |
| prompts-only | Header + every prompt (complete text), nothing else |
| with-responses | Everything in full + AI response text (can be very large) |
| custom | Pick and choose: prompts? responses? tools? tokens? files? |
If the user specified a level flag in their invocation, skip this step and use the indicated level:
--all → full (all prompts untruncated + tools + tokens + files)--minimal → minimal--prompts-only → prompts-only--with-responses → with-responsesWhen --with-responses is used: warn the user that this generates a lot of text and suggest they review and process the imported data afterward:
This will import full AI responses — the resulting activity file will be large. After import, you may want to review and condense the entries (e.g., summarize key responses, extract decisions, move insights to project docs).
For each selected session, write an entry to the appropriate activity file (see Output File Routing above).
You don't need to use this script initially, you can simply read the files, only use the script to extract session informations, and avoid overly using it.
CRITICAL: Never truncate prompts. Do NOT abbreviate, summarize, add "...", or shorten any prompt text. Every prompt must appear exactly as the user typed it, character for character, no matter how long. This is a logging tool — fidelity is the entire point. You should use a script to copy, not reading it yourself.
MANDATORY: Use a single Bash+node script to format AND write all entries directly to the file. Do NOT:
The scanner in step 1 already extracted full prompt text into JSON. The formatter below reads that JSON and writes entries. Use this script, do not improvise your own:
# ── Entry formatter — reads scanner JSON (one object per line) from stdin,
# writes formatted markdown entries to $ACTIVITY_FILE ──
# Pipe the scanner output into this, or cat a saved file into it.
node -e "
const fs = require('fs');
const input = fs.readFileSync(0, 'utf8').trim().split('\n');
const loggedRaw = (process.argv[2] || '').split(',').filter(Boolean);
const logged = new Set(loggedRaw);
const sessions = [];
for (const line of input) {
try {
const s = JSON.parse(line);
if (s.msg_count <= 2 && !s.tools && s.prompts.length === 0) continue;
if (logged.has(s.session_id.substring(0, 8))) continue;
sessions.push(s);
} catch(e) {}
}
sessions.sort((a,b) => new Date(a.first_ts) - new Date(b.first_ts));
const out = [];
for (const s of sessions) {
const d = new Date(s.first_ts);
const date = d.toISOString().slice(0,10);
const time = d.toISOString().slice(11,16);
const dur = s.duration_min;
const repo = s.repo || 'unknown';
let header = '## ' + date + ' ' + time;
if (dur > 0) header += ' (' + dur + 'min)';
header += ' — repo:' + repo;
if (s.branch && s.branch !== 'HEAD') header += ' branch:' + s.branch;
header += ' session:' + s.session_id.substring(0, 8);
if (s.entrypoint) header += ' via:' + s.entrypoint;
out.push(header);
// First prompt as bold topic — FULL TEXT, no truncation ever
if (s.prompts.length > 0) {
out.push('- **' + s.prompts[0].replace(/\*/g, '') + '**');
}
// Remaining prompts — FULL TEXT, no truncation ever
if (s.prompts.length > 1) {
out.push('- Prompts (' + s.prompts.length + '):');
for (let i = 1; i < s.prompts.length; i++) {
out.push(' - ' + s.prompts[i].replace(/\*/g, ''));
}
}
// Tools + tokens on one line
const stats = [];
if (s.tools) stats.push('Tools: ' + s.tools);
if (s.tokens) stats.push('Tokens: ' + s.tokens);
if (stats.length) out.push('- ' + stats.join(' | '));
// Files
if (s.files) out.push('- Files: ' + s.files);
out.push(''); // blank line between entries
}
fs.appendFileSync(process.argv[1], '\n' + out.join('\n'));
console.error('Wrote ' + sessions.length + ' entries');
" "\$ACTIVITY_FILE" "\$LOGGED_IDS" < "\$SCANNER_OUTPUT"
Usage: after running the scanner and saving output to $SCANNER_OUTPUT, and after grepping existing session IDs into a comma-separated $LOGGED_IDS string, pipe into this formatter. It handles dedup, sorting, and writing in one shot.
When --with-responses is used, the format changes to show prompt/response pairs:
## YYYY-MM-DD HH:MM (Xmin) — repo:name branch:main session:abcd1234 via:cli
### Prompt 1
> user prompt text here, complete and untruncated
**Response:**
AI response text here...
### Prompt 2
> user prompt text here, complete and untruncated
**Response:**
AI response text here...
---
- Tools: Read×5, Edit×3 | Tokens: 2000in/800out
- Files: auth.ts, config.json
Use the same Bash/node approach to write response text — do not type it out manually.
Write entries in chronological order (oldest first). Append to the end of the target file.
After writing, report what was imported:
Imported 12 sessions across 3 projects:
activity-ingram-cloud.md— 5 sessionsactivity-cyberspace.md— 4 sessionsactivity-claude-office.md— 3 sessionsThese files contain raw session data. Consider reviewing them to extract key decisions, insights, or summaries into your project docs.
Before writing any entry, check if the session ID (first 8 chars) already appears in activity/activity.md or any activity/activity-*.md file. Skip duplicates and mention the count:
Skipped 2 sessions already in your activity log.
After a large import (5+ sessions), remind the user:
You now have a detailed record of your past sessions. Some things you might want to do:
- Scan for key decisions and move them to the relevant
projects/<project>/decisions.md- Identify architectural insights worth capturing in
architecture.md- Flag sessions where you solved tricky problems — those are valuable for future reference
- Summarize clusters of related sessions into a narrative (e.g., "auth refactor arc: sessions 1-5")
This is a suggestion, not an automated step. The user decides what to process.
The script in Step 1 has toggles at the top. These map to the granularity levels:
# ── What to extract per session ──────────────────────────────────────────────
EXTRACT_PROMPTS=true # Set false to skip prompt text
EXTRACT_TOOLS=true # Set false to skip tool-use counting
EXTRACT_TOKENS=true # Set false to skip token sums
EXTRACT_FILES=true # Set false to skip edited-file tracking
EXTRACT_RESPONSES=false # Set true to include AI response text (large!)
| Level | PROMPTS | TOOLS | TOKENS | FILES | RESPONSES |
|---|---|---|---|---|---|
| full | true | true | true | true | false |
| standard | false | true | true | true | false |
| minimal | false | false | false | false | false |
| prompts-only | true | false | false | false | false |
| with-responses | true | true | true | true | true |
| custom | ask | ask | ask | ask | ask |
team/<identity>/activity/activity.md or team/<identity>/activity/activity-<project>.mdnpx claudepluginhub ingram-technologies/claude-office --plugin claude-office