From instashare
InstaShare — upload the current Claude Code chat session to instashare.to and return a public share link. The active session is the most recently modified `~/.claude/projects/<slug>/<uuid>.jsonl`; this skill POSTs that file to the InstaShare API in one shell call and prints the returned URL. Use when the user asks to "share this chat", "make a link to this conversation", "InstaShare this", "publish this session", or similar.
How this skill is triggered — by the user, by Claude, or both
Slash command
/instashare:instashareThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Upload the current Claude Code session JSONL to the InstaShare API and report the public link. One shell call does it — the freshest `.jsonl` by mtime in the project's slug directory is reliably the active session.
Upload the current Claude Code session JSONL to the InstaShare API and report the public link. One shell call does it — the freshest .jsonl by mtime in the project's slug directory is reliably the active session.
If this session has already been shared in a previous run, the script reuses the existing URL: it reads a sidecar file <jsonl-path>.instashare.json (created by the first upload) and PUTs the new content to the same slug. The public URL stays the same so any link the user already pasted continues to work and now shows the latest transcript.
No account required. The first share creates an anonymous device identifier (claim_token) that the skill stores in ~/.claude/instashare/credentials.json. Every subsequent share is grouped under the same device. The skill prints a one-time claim link so the user can sign in with Google later and pull every share from this device into a personal /mine page — past and future. Clicking the claim link is optional; the public share URLs work either way.
Endpoint defaults to https://instashare.to. Override with the INSTASHARE_API_URL env var (use http://localhost:3000 for local dev).
The shared page shows a header line like Opus 4.8 (1M context) with medium effort · Claude Max. Plan is read from ~/.claude.json automatically and the model also has a server-side fallback parsed from the transcript, but effort and the model's context-window marker live only in the running session — so before you run the snippet, set two variables from your own current session:
$sessionModel / session_model → your current model exactly as shown in the status line, including any context-window marker, e.g. Opus 4.8 (1M context).$sessionEffort / session_effort → your current reasoning effort, e.g. medium (the bare word, no "effort" suffix).If you genuinely don't know one of them, leave it as the empty string — the page degrades gracefully (the server still fills in the model from the transcript; effort/plan are simply omitted). Don't guess or invent values.
Pick by platform. The script computes the slug itself, finds the session, scrubs known secret patterns from the body in memory, decides POST-vs-PUT from the sidecar, uploads the scrubbed body, and prints the URL.
$apiBase = if ($env:INSTASHARE_API_URL) { $env:INSTASHARE_API_URL } else { 'https://instashare.to' }
$credentialsDir = Join-Path $env:USERPROFILE ".claude\instashare"
$credentialsFile = Join-Path $credentialsDir "credentials.json"
$deviceHostname = $env:COMPUTERNAME
# Session info shown in the shared-page header. Model and effort are live-session
# state only the running agent knows — BEFORE running, fill them from the current
# session (see "Fill in the session info" below); leave '' if unknown (the server
# falls back to the model parsed from the transcript). Plan is read from disk.
$sessionModel = '' # e.g. 'Opus 4.8 (1M context)'
$sessionEffort = '' # e.g. 'medium'
$sessionPlan = ''
$claudeConfig = Join-Path $env:USERPROFILE ".claude.json"
if (Test-Path $claudeConfig) {
try {
$orgType = (Get-Content $claudeConfig -Raw -Encoding UTF8 | ConvertFrom-Json).oauthAccount.organizationType
switch ($orgType) {
'claude_max' { $sessionPlan = 'Claude Max' }
'claude_pro' { $sessionPlan = 'Claude Pro' }
'claude_team' { $sessionPlan = 'Claude Team' }
'claude_enterprise' { $sessionPlan = 'Claude Enterprise' }
}
} catch { }
}
# Load the per-machine claim token if we have one. Server uses its hash to
# group every share from this device under a single identity so the user
# can later claim them all to an account.
$claimToken = $null
if (Test-Path $credentialsFile) {
try {
$claimToken = (Get-Content $credentialsFile -Raw -Encoding UTF8 | ConvertFrom-Json).claimToken
} catch { }
}
$cwd = (Get-Location).Path
# Slug = cwd with ':' and '\' replaced by '-'. .Replace (literal) avoids the
# doubled-backslash regex literal that the sandbox path guard can misread as a
# protected '\\' target (it pairs any such literal with a destructive cmdlet).
$slug = $cwd.Replace(':', '-').Replace('\', '-')
$projDir = Join-Path $env:USERPROFILE ".claude\projects\$slug"
$src = Get-ChildItem "$projDir\*.jsonl" -ErrorAction Stop |
Sort-Object LastWriteTime -Descending | Select-Object -First 1
$body = Get-Content $src.FullName -Raw -Encoding UTF8
$sidecar = "$($src.FullName).instashare.json"
# Redact common secrets before upload. Curated patterns only — provider-specific
# first so e.g. OPENAI_API_KEY=sk-... is labelled openai-key rather than env-secret.
# Each rule has its own replacement string so cookie/auth-header rules can preserve
# the surrounding header name and quoting.
$patterns = @(
@{ name = 'anthropic-key'; regex = 'sk-ant-[A-Za-z0-9_\-]{20,}'; replacement = '[REDACTED:anthropic-key]' },
@{ name = 'openai-key'; regex = 'sk-(?!ant-)[A-Za-z0-9_\-]{20,}'; replacement = '[REDACTED:openai-key]' },
@{ name = 'aws-access-key-id'; regex = '\b(?:AKIA|ASIA)[A-Z0-9]{16}\b'; replacement = '[REDACTED:aws-access-key-id]' },
@{ name = 'github-token'; regex = '\b(?:gh[posru]_|github_pat_)[A-Za-z0-9_]{20,}\b'; replacement = '[REDACTED:github-token]' },
@{ name = 'slack-token'; regex = 'xox[abprs]-[A-Za-z0-9\-]{10,}'; replacement = '[REDACTED:slack-token]' },
@{ name = 'stripe-key'; regex = '\b(?:sk|pk|rk)_live_[A-Za-z0-9]{20,}\b'; replacement = '[REDACTED:stripe-key]' },
@{ name = 'google-api-key'; regex = '\bAIza[A-Za-z0-9_\-]{35}\b'; replacement = '[REDACTED:google-api-key]' },
@{ name = 'jwt'; regex = '\beyJ[A-Za-z0-9_\-]{10,}\.eyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\b'; replacement = '[REDACTED:jwt]' },
@{ name = 'http-auth'; regex = '(?i)Authorization:\s*(?:Basic|Bearer|Digest|Token|OAuth)\s+[A-Za-z0-9+/=._\-]{8,}'; replacement = 'Authorization: [REDACTED:http-auth]' },
@{ name = 'cookie-header'; regex = '(?i)(?:Cookie|Set-Cookie):\s*[A-Za-z_][A-Za-z0-9_\-]*=[^"''\\\r\n]{10,}'; replacement = '[REDACTED:cookie-header]' },
@{ name = 'curl-cookie-arg'; regex = '(?<=\s)(?:-b|--cookie)\s+''[A-Za-z_][A-Za-z0-9_\-]*=[^''\\\r\n]{10,}'''; replacement = "-b '[REDACTED:cookie-header]'" },
# Bare cookie chains (no `Cookie:` header in front) — 3+ name=value pairs separated by `; `.
# Catches pasted DevTools output, COOKIE=… shell vars, heredoc cookie files, etc.
# `(?<!\\)` avoids starting a match right after a JSON-escape backslash
# (e.g. inside `"…cookie\nname=val…"`), which would otherwise corrupt the
# JSON by leaving an orphan `\`.
@{ name = 'cookie-chain'; regex = '(?<!\\)(?:[A-Za-z_][A-Za-z0-9_\-]{2,}=[^;"''\\\r\n]{8,};\s+){2,}[A-Za-z_][A-Za-z0-9_\-]{2,}=[^;"''\\\r\n]{8,}'; replacement = '[REDACTED:cookie-chain]' },
@{ name = 'pem-private-key'; regex = '-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----'; replacement = '[REDACTED:pem-private-key]' },
# InstaShare's own tokens. A prior run of this skill prints the delete URL (and,
# on first run, the claim URL) into the transcript, and the sidecar/credentials
# JSON may get read into the chat — re-sharing the session must not publish them.
# Matches the URL forms and deleteToken/claimToken JSON fields, plain or JSONL-escaped.
@{ name = 'instashare-token'; regex = '(/delete\?token=|/claim\?key=|\\?"(?:deleteToken|claimToken)\\?"\s*:\s*\\?")[A-Za-z0-9_\-]{16,}'; replacement = '$1[REDACTED:instashare-token]' }
)
$redactionSummary = [ordered]@{}
foreach ($p in $patterns) {
$matchCount = ([regex]::Matches($body, $p.regex)).Count
if ($matchCount -gt 0) {
$body = [regex]::Replace($body, $p.regex, $p.replacement)
$redactionSummary[$p.name] = $matchCount
}
}
$envRx = '\b([A-Z][A-Z0-9_]{0,40}(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\s*=\s*([A-Za-z0-9_\-+/=]{16,})'
$envCount = ([regex]::Matches($body, $envRx)).Count
if ($envCount -gt 0) {
$body = [regex]::Replace($body, $envRx, '$1=[REDACTED:env-secret]')
$redactionSummary['env-secret'] = $envCount
}
function Build-Headers {
param($Token)
$h = @{ 'x-device-hostname' = $deviceHostname }
if ($Token) { $h['x-claim-token'] = $Token }
if ($sessionModel) { $h['x-session-model'] = $sessionModel }
if ($sessionEffort) { $h['x-session-effort'] = $sessionEffort }
if ($sessionPlan) { $h['x-session-plan'] = $sessionPlan }
return $h
}
$resp = $null
if (Test-Path $sidecar) {
try {
$prev = Get-Content $sidecar -Raw -Encoding UTF8 | ConvertFrom-Json
$putUri = "$apiBase/api/chats/$($prev.slug)"
if ($prev.deleteToken) { $putUri = "$putUri`?token=$($prev.deleteToken)" }
$resp = Invoke-RestMethod -Uri $putUri -Method Put -Body $body `
-ContentType 'text/plain; charset=utf-8' -Headers (Build-Headers $claimToken)
} catch {
$code = if ($_.Exception.Response) { [int]$_.Exception.Response.StatusCode } else { 0 }
if ($code -ne 404 -and $code -ne 401 -and $code -ne 403) { throw }
}
}
if (-not $resp) {
try {
$resp = Invoke-RestMethod -Uri "$apiBase/api/chats" -Method Post `
-Body $body -ContentType 'text/plain; charset=utf-8' -Headers (Build-Headers $claimToken)
} catch {
$code = if ($_.Exception.Response) { [int]$_.Exception.Response.StatusCode } else { 0 }
if ($code -eq 401 -and $claimToken) {
# Server revoked / forgot our claim token. Neutralize the stored copy and
# retry anonymously; the server mints a fresh one we save below. We blank
# the file with '{}' instead of Remove-Item so the sandbox's static guard
# doesn't see a destructive cmdlet here and block the whole script — it
# otherwise pairs Remove-Item with a backslash literal and reads it as a
# delete of the protected root path '\\'. (On next run '{}' parses to no
# claimToken, so the device falls back to anonymous exactly as a missing
# file would.)
'{}' | Out-File -FilePath $credentialsFile -Encoding utf8
$claimToken = $null
$resp = Invoke-RestMethod -Uri "$apiBase/api/chats" -Method Post `
-Body $body -ContentType 'text/plain; charset=utf-8' -Headers (Build-Headers $null)
} else {
throw
}
}
# Save the returned claim token (new on first run, same on subsequent — but
# always overwrite to support server-side rotation cleanly).
if ($resp.claimToken) {
New-Item -ItemType Directory -Force -Path $credentialsDir | Out-Null
@{ claimToken = $resp.claimToken } | ConvertTo-Json |
Out-File -FilePath $credentialsFile -Encoding utf8
}
$deleteToken = if ($resp.deleteUrl -and $resp.deleteUrl -match 'token=') { ($resp.deleteUrl -split 'token=')[-1] } else { '' }
@{ slug = $resp.slug; deleteToken = $deleteToken; url = $resp.url } |
ConvertTo-Json | Out-File -FilePath $sidecar -Encoding utf8
}
Write-Output $resp.url
Write-Output ("Delete: " + $resp.deleteUrl)
if (-not $resp.linked -and $resp.claimUrl) {
Write-Output ("Make these yours: " + $resp.claimUrl)
}
if ($redactionSummary.Count -gt 0) {
$summary = ($redactionSummary.GetEnumerator() | ForEach-Object { "$($_.Value) $($_.Key)" }) -join ', '
Write-Output "Redacted before upload: $summary"
}
api_base="${INSTASHARE_API_URL:-https://instashare.to}"
credentials_dir="$HOME/.claude/instashare"
credentials_file="$credentials_dir/credentials.json"
device_hostname=$(hostname 2>/dev/null || echo unknown)
# Session info shown in the shared-page header. Model and effort are live-session
# state only the running agent knows — BEFORE running, fill them from the current
# session (see "Fill in the session info" below); leave empty if unknown (the
# server falls back to the model parsed from the transcript). Plan is read from disk.
session_model="" # e.g. 'Opus 4.8 (1M context)'
session_effort="" # e.g. 'medium'
session_plan=""
if [ -f "$HOME/.claude.json" ]; then
org_type=$(python3 -c 'import json,sys; print((json.load(open(sys.argv[1])).get("oauthAccount") or {}).get("organizationType","") or "")' "$HOME/.claude.json" 2>/dev/null || echo "")
case "$org_type" in
claude_max) session_plan="Claude Max" ;;
claude_pro) session_plan="Claude Pro" ;;
claude_team) session_plan="Claude Team" ;;
claude_enterprise) session_plan="Claude Enterprise" ;;
esac
fi
session_headers=()
[ -n "$session_model" ] && session_headers+=(-H "x-session-model: $session_model")
[ -n "$session_effort" ] && session_headers+=(-H "x-session-effort: $session_effort")
[ -n "$session_plan" ] && session_headers+=(-H "x-session-plan: $session_plan")
# Load the per-machine claim token if we have one. Server uses its hash to
# group every share from this device under a single identity so the user
# can later claim them all to an account.
claim_token=""
if [ -f "$credentials_file" ]; then
claim_token=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("claimToken","") or "")' "$credentials_file" 2>/dev/null || echo "")
fi
cwd="$(pwd)"
slug=$(echo "$cwd" | sed 's#/#-#g')
src=$(ls -t "$HOME/.claude/projects/$slug"/*.jsonl 2>/dev/null | head -1)
[ -z "$src" ] && { echo "No session files in $HOME/.claude/projects/$slug" >&2; exit 1; }
sidecar="$src.instashare.json"
tmp=$(mktemp)
redacted=$(mktemp)
resp=""
# Redact common secrets before upload. Curated patterns only — provider-specific
# first so e.g. OPENAI_API_KEY=sk-... is labelled openai-key rather than env-secret.
summary=$(python3 - "$src" "$redacted" <<'PY'
import re, sys
# (regex, replacement). Provider-specific rules first so e.g. OPENAI_API_KEY=sk-...
# is labelled openai-key rather than env-secret. Cookie/auth-header rules keep
# their surrounding header name or curl flag intact.
PATTERNS = [
('anthropic-key', r'sk-ant-[A-Za-z0-9_\-]{20,}', '[REDACTED:anthropic-key]'),
('openai-key', r'sk-(?!ant-)[A-Za-z0-9_\-]{20,}', '[REDACTED:openai-key]'),
('aws-access-key-id', r'\b(?:AKIA|ASIA)[A-Z0-9]{16}\b', '[REDACTED:aws-access-key-id]'),
('github-token', r'\b(?:gh[posru]_|github_pat_)[A-Za-z0-9_]{20,}\b', '[REDACTED:github-token]'),
('slack-token', r'xox[abprs]-[A-Za-z0-9\-]{10,}', '[REDACTED:slack-token]'),
('stripe-key', r'\b(?:sk|pk|rk)_live_[A-Za-z0-9]{20,}\b', '[REDACTED:stripe-key]'),
('google-api-key', r'\bAIza[A-Za-z0-9_\-]{35}\b', '[REDACTED:google-api-key]'),
('jwt', r'\beyJ[A-Za-z0-9_\-]{10,}\.eyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\b', '[REDACTED:jwt]'),
('http-auth', r'(?i)Authorization:\s*(?:Basic|Bearer|Digest|Token|OAuth)\s+[A-Za-z0-9+/=._\-]{8,}', 'Authorization: [REDACTED:http-auth]'),
('cookie-header', r'''(?i)(?:Cookie|Set-Cookie):\s*[A-Za-z_][A-Za-z0-9_\-]*=[^"'\\\r\n]{10,}''', '[REDACTED:cookie-header]'),
('curl-cookie-arg', r"""(?<=\s)(?:-b|--cookie)\s+'[A-Za-z_][A-Za-z0-9_\-]*=[^'\\\r\n]{10,}'""", "-b '[REDACTED:cookie-header]'"),
# Bare cookie chains (no `Cookie:` header in front) — 3+ name=value pairs
# separated by `; `. Catches pasted DevTools output, COOKIE=… shell vars,
# heredoc cookie files, etc. `(?<!\\)` avoids starting a match right after
# a JSON-escape backslash (e.g. inside `"…cookie\nname=val…"`), which
# would otherwise corrupt the JSON by leaving an orphan `\`.
('cookie-chain', r'''(?<!\\)(?:[A-Za-z_][A-Za-z0-9_\-]{2,}=[^;"'\\\r\n]{8,};\s+){2,}[A-Za-z_][A-Za-z0-9_\-]{2,}=[^;"'\\\r\n]{8,}''', '[REDACTED:cookie-chain]'),
('pem-private-key', r'-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----', '[REDACTED:pem-private-key]'),
# InstaShare's own tokens. A prior run of this skill prints the delete URL
# (and, on first run, the claim URL) into the transcript, and the
# sidecar/credentials JSON may get read into the chat — re-sharing the
# session must not publish them. Matches the URL forms and
# deleteToken/claimToken JSON fields, plain or JSONL-escaped.
('instashare-token', r'(/delete\?token=|/claim\?key=|\\?"(?:deleteToken|claimToken)\\?"\s*:\s*\\?")[A-Za-z0-9_\-]{16,}', r'\1[REDACTED:instashare-token]'),
]
text = open(sys.argv[1], 'r', encoding='utf-8').read()
counts = {}
for name, rx, repl in PATTERNS:
n = len(re.findall(rx, text))
if n:
counts[name] = n
text = re.sub(rx, repl, text)
text, n = re.subn(
r'\b([A-Z][A-Z0-9_]{0,40}(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\s*=\s*([A-Za-z0-9_\-+/=]{16,})',
r'\1=[REDACTED:env-secret]', text)
if n:
counts['env-secret'] = n
open(sys.argv[2], 'w', encoding='utf-8').write(text)
print(', '.join(f'{v} {k}' for k, v in counts.items()))
PY
)
post_chats() {
local with_claim="$1"
local headers=(-H "x-device-hostname: $device_hostname")
if [ -n "$with_claim" ]; then headers+=(-H "x-claim-token: $with_claim"); fi
curl -sS -o "$tmp" -w '%{http_code}' \
-X POST "$api_base/api/chats" \
"${headers[@]}" \
"${session_headers[@]}" \
-H 'Content-Type: text/plain; charset=utf-8' \
--data-binary "@$redacted"
}
if [ -f "$sidecar" ]; then
prev_slug=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["slug"])' "$sidecar")
prev_token=$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("deleteToken","") or "")' "$sidecar")
put_uri="$api_base/api/chats/$prev_slug"
[ -n "$prev_token" ] && put_uri="$put_uri?token=$prev_token"
put_headers=(-H "x-device-hostname: $device_hostname")
[ -n "$claim_token" ] && put_headers+=(-H "x-claim-token: $claim_token")
code=$(curl -sS -o "$tmp" -w '%{http_code}' \
-X PUT "$put_uri" \
"${put_headers[@]}" \
"${session_headers[@]}" \
-H 'Content-Type: text/plain; charset=utf-8' \
--data-binary "@$redacted")
if [ "$code" = "200" ]; then resp="$(cat "$tmp")"; fi
fi
if [ -z "$resp" ]; then
code=$(post_chats "$claim_token")
if [ "$code" = "401" ] && [ -n "$claim_token" ]; then
# Server revoked / forgot our claim token. Drop it and retry anonymously.
rm -f "$credentials_file"
claim_token=""
code=$(post_chats "")
fi
if [ "$code" != "200" ] && [ "$code" != "201" ]; then
cat "$tmp" >&2
rm -f "$tmp" "$redacted"
echo "" >&2
echo "Upload failed (HTTP $code)." >&2
exit 1
fi
resp="$(cat "$tmp")"
# Save / refresh claim token. Always overwrite so server-side rotation works.
new_claim=$(echo "$resp" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("claimToken","") or "")' 2>/dev/null || echo "")
if [ -n "$new_claim" ]; then
mkdir -p "$credentials_dir"
python3 -c 'import json,sys; json.dump({"claimToken": sys.argv[1]}, open(sys.argv[2],"w"))' "$new_claim" "$credentials_file"
chmod 600 "$credentials_file" 2>/dev/null || true
fi
echo "$resp" | python3 -c '
import json, sys
d = json.load(sys.stdin)
url = d.get("deleteUrl", "")
token = url.split("token=")[-1] if "token=" in url else ""
print(json.dumps({"slug": d["slug"], "deleteToken": token, "url": d["url"]}))
' > "$sidecar"
fi
rm -f "$tmp" "$redacted"
echo "$resp" | python3 -c '
import sys, json
d = json.load(sys.stdin)
print(d["url"])
print("Delete:", d["deleteUrl"])
if not d.get("linked") and d.get("claimUrl"):
print("Make these yours:", d["claimUrl"])
'
[ -n "$summary" ] && echo "Redacted before upload: $summary"
The command prints the public URL on the first line and a delete URL on the second. Until the device has been claimed, a third line offers the claim URL that links every share from this device to a Google account. After claiming, that line disappears and shares show up immediately at <api_base>/mine.
Make these yours: line, mention that opening it (and signing in with Google) attaches every share from this device — past and future — to a /mine page. This is optional; the public link works without it.Redacted before upload: … line appears, the skill replaced matches of a curated pattern set (AWS / GitHub / Slack / Stripe / OpenAI / Anthropic / Google keys, JWTs, PEM private-key blocks, HTTP Authorization: Basic|Bearer|… headers, Cookie: / Set-Cookie: header values, curl -b 'cookie=val; …' flags, InstaShare's own delete/claim tokens from a previous run of this skill, and *_KEY= / *_TOKEN= / *_SECRET= / *_PASSWORD= assignments) with [REDACTED:<kind>] markers before upload. The scan is best-effort — custom or proprietary token formats won't match — so still suggest reviewing the public link if the chat may contain anything else sensitive..jsonl in the project's slug directory, so a single ls -t … | head -1 reliably picks it — no separate Glob/Read pre-flight needed./, \, : replaced by -, case preserved. That matches the directory Claude Code creates, including any leading dash on macOS/Linux (/Users/foo → -Users-foo). Lowercasing it or stripping the leading dash will miss the directory on case-preserving filesystems.$body against a curated pattern set (provider API keys, JWTs, PEM private-key blocks, HTTP Authorization / Cookie / Set-Cookie header values, curl -b 'cookies' flags, InstaShare's own delete/claim tokens printed by a previous run, and *_KEY= / *_TOKEN= / *_SECRET= / *_PASSWORD= style env assignments) and replaces matches with [REDACTED:<kind>]. Everything else is uploaded verbatim. Secrets matching one of the patterns therefore never leave the machine — they're scrubbed before the HTTPS body is even constructed. The original .jsonl on disk is never modified.<jsonl-path>.instashare.json containing {slug, deleteToken, url}. On the next invocation it PUTs to /api/chats/<slug>?token=<deleteToken>, which replaces the stored JSONL and recomputes stats while preserving the slug, created-at, and delete token. If the chat was deleted server-side (PUT returns 404) or the sidecar is stale/tampered (401/403), the script falls back to POST and overwrites the sidecar. Delete the sidecar manually to force a brand-new URL.~/.claude/instashare/credentials.json is written on the first upload and reused thereafter. It's sent as x-claim-token on POST and PUT so the server can group all uploads from this device. If the server revokes it (the user clicked Revoke on /account), the next upload retries anonymously, the server mints a fresh token, and the user sees a fresh claim URL.x-session-model, x-session-effort, and x-session-plan ride alongside the upload so the shared page can show e.g. Opus 4.8 (1M context) with medium effort · Claude Max. Plan comes from ~/.claude.json (oauthAccount.organizationType); model/effort are filled in by the agent from the live session because the harness never writes them to the transcript (the persisted message.model is the bare id without the context-window marker, and effort isn't recorded at all). All three are optional — the server parses the base model from the transcript as a fallback, so a header-less or headless upload still shows the model.~/.claude/instashare/credentials.json) are written locally; the .jsonl itself is read-only from the script's perspective.Provides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub instafill/instashare-skill --plugin instashare