
- 4 installs
- 4 repo stars
- Updated July 21, 2026
- kkoppenhaver/cc-skills
Helps with ai & agent building tasks.
About
linkedin is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- AI & Agent Building
- AI-coding skill
Linkedin by the numbers
- 4 all-time installs (skills.sh)
- Ranked #13,372 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/kkoppenhaver/cc-skills --skill linkedinAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 4 |
| Last updated | July 21, 2026 |
| Repository | kkoppenhaver/cc-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
LinkedIn Post Writer
Write LinkedIn posts in Keanan's distinctive voice: upbeat, builder-centric, mixing practical how-tos with candid, self-deprecating humor.
Voice Summary
Primary emotions: capability, curiosity, momentum Secondary notes: playful warmth, inclusion, low-ego confidence Reader should feel: "I can try this right now, and it'll probably work. Also, this is fun and I'm not alone."
Write like a hands-on practitioner sharing a fresh discovery: conversational, concise, and generous with specifics. Keep the focus on what you did, how you did it, and how the reader can do it.
Scheduling Context
Important: Before scheduling, check today's date from the system context (provided in the env section). Use this to correctly map day names to calendar dates (e.g., if today is Tuesday Jan 20, then "Wednesday" = Jan 21, not Jan 22).
Workflow
1. Ask what the user wants to post about (if not already clear) 2. Check currently scheduled posts: python3 scripts/typefully_scheduler.py --list-scheduled 3. Draft the post following the voice guide in references/voice-guide.md 4. Run through preflight/postflight checklists 5. Present draft to user for approval 6. If approved, write to temp file and schedule via Typefully
Tools
Typefully Scheduler
Use scripts/typefully_scheduler.py to schedule LinkedIn posts via Typefully API. The script is designed for automation (no interactive prompts).
Setup: Add to ~/.claude/settings.json:
{
"env": {
"TYPEFULLY_API_KEY": "your-api-key",
"TYPEFULLY_SOCIAL_SET_ID": "your-social-set-id"
}
}Get your API key from Typefully Settings > API & Integrations. To find your social set ID, run: python3 scripts/typefully_scheduler.py --list-social-sets
Usage:
# Check what's already scheduled (do this first to avoid conflicts)
python3 scripts/typefully_scheduler.py --list-scheduled
# Schedule a post from a file
python3 scripts/typefully_scheduler.py --file /tmp/linkedin-post.txt --schedule next-free-slot
# Schedule for a specific time (ISO 8601 format)
python3 scripts/typefully_scheduler.py --file /tmp/linkedin-post.txt --schedule 2026-01-20T18:00:00Z
# Publish immediately
python3 scripts/typefully_scheduler.py --file /tmp/linkedin-post.txt --schedule now
# List social sets
python3 scripts/typefully_scheduler.py --list-social-sets
# View connected platforms
python3 scripts/typefully_scheduler.py --detailsScheduling workflow: 1. Write post content to a temp file (e.g., /tmp/linkedin-post.txt) 2. Run the scheduler with --file and --schedule flags 3. The script will show existing scheduled posts before scheduling
Note: Posts about the same topic can go out on Twitter and LinkedIn at the same time - that's fine. Just avoid scheduling unrelated posts at conflicting times.
Quick Reference
Structure Blueprints
Quick Hack/Tip: 1. Hook - name the friction in one line 2. Contrast - old vs. new in two short blocks 3. Steps - 1-3 lines with prompt/command to replicate 4. Payoff - what changed: time saved, result 5. CTA - soft invite to try or respond
Process Mini-Guide: 1. Goal statement - what you wanted to achieve 2. Toolchain overview - who/what you used 3. Stepwise narrative - chronology with 3-6 concise beats 4. Outcome and metrics - what you got; any artifacts 5. Reflection - what you'd improve (light self-deprecation) 6. Resources + CTA - links, repo, "reply if..."
Syntax Constraints
- Sentence length: 8-18 words average; 10-20% very short punchlines (2-6 words)
- Paragraphs: 1-3 sentences; use blank lines between sections
- Exclamation points: 1-2 per 120 words
- Questions: up to 2 per 120 words
- Emojis: 0-2 per post; expressive, context-aligned
- Quotes/code: use for prompts and commands; single lines
- Banned hedges: "maybe," "kinda," "sort of," "I think," "probably"
- Avoid: "leverage," "utilize," "optimize" (vague), "solution," "content," "efficiency"
Preflight Checklist
- [ ] One clear friction or goal in line one
- [ ] Chose contrast OR stepwise narrative (not both)
- [ ] At least two concrete specifics (tool names, commands, files)
- [ ] Quantified effort or outcome with at least one number
- [ ] Single, light human moment (aside, wink, or emoji)
- [ ] Prompt/command is copy-ready and short
- [ ] Paragraphs ≤3 sentences and mostly under 18 words
- [ ] Exactly one soft CTA aligned to the content
- [ ] Avoided hedges and corporate filler
- [ ] Links present only where helpful and labeled clearly
Postflight Checklist
- [ ] Hook lands in first sentence (names the friction)
- [ ] Can skim and see the win (before/after block if needed)
- [ ] ≥2 proper nouns and ≥1 numeral
- [ ] At least one prompt/command/link for actionability
- [ ] One playful beat max
- [ ] Average sentence length 8-18 words
- [ ] Emojis/exclamations ≤2 each
- [ ] CTA present and optional (not pushy)
- [ ] Artifact mentioned (repo/file/transcript)
- [ ] Fresh phrasing (no recycled/templated lines)
Detailed Voice Guide
For emotional mechanics, lexicon, and rewrite operators, see references/voice-guide.md.
LinkedIn Voice Guide
System Instructions
- Write like a hands-on practitioner sharing a fresh discovery: conversational, concise, and generous with specifics. Keep the focus on what you did, how you did it, and how the reader can do it.
- Avoid copying phrasing from examples; emulate patterns and effects only. Recast any recurring structures in fresh language.
- Prioritize clarity over cleverness. If a witty line obscures the takeaway, cut or simplify it.
- Lead with a hook that states the situation or friction in one line. Follow with the fix and the minimal steps to replicate.
- Use light self-deprecation to humanize, not to undercut competence. One wink per post is plenty.
- Show the toolchain. Name tools, commands, prompts, links, and time spent. Prefer concrete nouns and actions.
- Contrast is core. Where relevant, present a "then vs. now" framing to spotlight the improvement.
- Ask for engagement with a simple, specific CTA (reply, try a command, check a link). Make it optional, friendly, and brief.
- Emojis and emphasis are seasoning. Use sparingly to punctuate enthusiasm or humor, not as decoration.
- Edit for scannability: short paragraphs, strategic line breaks, and occasional standalone lines for impact.
Tone Recipe (Emotional Engineering)
Primary emotions: capability, curiosity, momentum Secondary notes: playful warmth, inclusion, low-ego confidence Reader should feel: "I can try this right now, and it'll probably work. Also, this is fun and I'm not alone."
Tactics
- Open with a relatable friction or goal, then immediately show an easier path.
- Use before/after framing to make progress undeniable.
- Quantify effort (minutes, steps) and outcomes (links, files, transcripts).
- Include one human moment (a joke, a tiny confession) to keep it friendly.
- End with a crisp next step (reply, run a command, visit a link).
Emotional Mechanics Map
| Device | Effect | How to Apply | Signals |
|---|---|---|---|
| Relatable friction opener | Lowers reader defensiveness | Name a specific pain in one line | Verbs like "stuck," "tired of," "what's next?" |
| Before/after split | Makes improvement feel obvious | Label old approach vs. new with parallel structure | Two short headings separated by blank line |
| Micro-case study | Builds credibility and teaches | Narrate your steps with tool names, prompts, outputs | "I used…," "then…," "now…" |
| Quantified ease | Conveys momentum and attainability | Cite time, count of steps, or number of sources | Numerals, "minutes," "couple," "CSV" |
| Inline prompts/commands | Turns insight into action | Show the exact string or command to copy | Quotes, monospace/code ticks, link previews |
| Light self-deprecation | Humanizes and invites | One playful aside acknowledging limits | Aside in parentheses, single expressive emoji |
| Stack of punchy fragments | Heightens emphasis | Three short lines escalating to a payoff | 3 consecutive one-sentence lines |
| Friendly CTA | Converts interest into action | Ask for a reply, click, or try-it-now | "If you…," "Want to…," "Here's the link:" |
Rewrite Operators
Clarity and Inclusion
- When to use: Vague claims like "it helps a lot"
- Action: Swap generalities for concrete artifact, time, or count
- Constraints: Add ≥1 numeral and ≥1 named tool
- Before: "It sped things up."
- After: "In 20 minutes, [tool] pulled 12 items into [filetype]."
Momentum via Contrast
- When to use: Describing a change without structure
- Action: Split into "before" and "after" lines with parallel wording
- Constraints: Each block ≤2 sentences; mirror verbs
- Before: "I changed my approach."
- After: "Before: [manual step], [blocker]. After: [automated step], [result]."
Immediacy and Actionability
- When to use: Mentioning a task the reader could try
- Action: Add an exact prompt/command string
- Constraints: One line in quotes or code; ≤120 characters
- Before: "I generated a draft."
- After: "Run: '[command with args]'"
Warmth and Humility
- When to use: Declaring a limitation or fear
- Action: Add a brief, playful aside acknowledging it
- Constraints: One clause; optional single emoji
- Before: "I'm not a designer."
- After: "I'm no designer (work in progress), but this got me there."
Scannability and Emphasis
- When to use: Dense paragraph with multiple ideas
- Action: Break into 3 stacked one-liners building to a payoff
- Constraints: 3 lines; each ≤8 words
- Example: "Ask. Adjust. Ship. Different day, faster results."
- Note: Use sparingly—once per post max.
Credibility
- When to use: Tool or workflow claim without evidence
- Action: Add artifacts (repo link, file type, screenshot mention)
- Constraints: Include ≥1 link or filename
- Example: "Code here: [url]. Output: [name].csv"
Engagement
- When to use: Concluding without a next step
- Action: Add a simple, specific CTA aligned to the content
- Constraints: One sentence; no pressure wording
- Example: "Got a [topic] coming up? Reply and I'll share a starter prompt."
Relatability
- When to use: Opening line feels abstract
- Action: Swap abstract setup with a lived moment or quote
- Constraints: One sentence; present tense preferred
- Before: "Tooling is complex."
- After: "I cloned [repo] and hit a dependency wall."
Trust through Constraints
- When to use: Bold claim or hype term
- Action: Add a caveat about limits or next tweaks
- Constraints: One clause; place after the result
- Example: "Works well—still tightening [edge case]."
Lexicon Guidance
Avoid → Prefer
| Avoid | Prefer |
|---|---|
| "utilize" | "use" |
| "leverage" | "use" or "build on" |
| "optimize" (vague) | "cut [time/steps] by [X]" |
| "solution" (abstract) | "script," "workflow," "command" |
| "content" | "video," "post," "deck," "transcript" |
| "efficiency" | "minutes saved," "steps skipped" |
Signature Patterns
- Before/after headers to frame contrast
- A three-line emphasis stack ending in a punchline
- A winky in-joke marker (used sparingly)
- Tiny "case in point" story to ground advice
- "Here's how" pivot into steps
- Minimal command/prompt line a reader can copy
Situational Traits
- Hard promo/milestone: Permissible to celebrate and include urgency and a link; keep it friendly and time-bounded.
- Teaser ("video coming soon"): Use only when production is actually in progress; keep to one line.
- Humor via hyperbole: Okay for frustration or triumph; avoid mocking others or exaggerating results.
- Safety caveat: When suggesting code/tooling for production, include a brief "not production-ready yet" note.
- Community ask: When soliciting votes/feedback, lead with value or gratitude before the ask.
- Personal anecdote: Use for rapport; keep it short and tie back to the main thread if possible.
Keanan's Preferences
Patterns learned from direct feedback:
Parallel Structure
Combine repeated structures into grammatically complete sentences rather than fragmenting them.
- Avoid: "It can't see your browser. Can't check your Notion. Can't interact with anything outside your files."
- Prefer: "It can't see your browser, can't check your Notion, and can't interact with anything outside your files."
Outcome Over Tool Name
Explain what a tool does rather than just naming it. Lead with the benefit.
- Avoid: "Playwright is literally one line to install"
- Prefer: "gives Claude its own browser - literally one line to install"
Trust the Reader
Don't over-explain concepts inline. Link to the full post for details. Cut explanatory sections that re-teach what the linked article covers.
Acceptable Shorthand
"CC" is acceptable shorthand for "Claude Code" in casual contexts.
Specificity on the Payoff
Add concrete details to the exciting part. Make the outcome vivid.
- Avoid: "navigate a browser"
- Prefer: "navigate a real web browser"
Link Phrasing
Use conversational phrasing for links.
- Avoid: "Full post:"
- Prefer: "Full post is here:"
Ground AI Techniques in Human Analogies
Make AI workflows relatable by comparing them to human collaboration patterns.
- Avoid: "Claude Code interviews you to surface missing context."
- Prefer: "That's not how I'd collaborate with a coworker either. I'd give them as much information as I had and ask 'does that make sense?' That's how I work with CC now too."
Parenthetical Context for Use Cases
Add specificity without bloating the main sentence by using parentheticals for different scenarios.
- Example: "It asks about goals, constraints, audience (if it's a writing project), and edge cases (if it's a coding project)."
Playful Payoff Lines
Use warm, energetic language for the "result" moment instead of clinical descriptions.
- Avoid: "First drafts come out dramatically better."
- Prefer: "A few questions later, we're aligned and all the fun kicks off."
CC4M Shorthand
"CC4M" is acceptable shorthand for "Claude Code for Marketers" in CTAs.
- Example: "For more info on how I use this technique, check out the latest CC4M post:"
Conversational Over Structured (Blog Promo Posts)
When promoting a blog post, don't force the Quick Hack or Process Mini-Guide blueprints. Write like you're telling a friend about something you just set up. State the friction casually in one line, show the steps if they're interesting, add caveats as natural asides (not formal sections), and point to the post. The numbered steps can be the star without needing a before/after contrast wrapper around them.
- Avoid: Walking through every step of the old workflow, then every step of the new one, then a formal "here's what changed" payoff
- Prefer: "I was spending so much time copy-pasting between X and Y. [Tool] makes this a 3-step loop instead: [steps]. Fair warning: [caveat]. Full walkthrough here: [link]"
Asides Over Sections
Caveats, tangential observations, and alternative tools should read like conversational asides, not structured subsections. Use phrases like "Fair warning:", "Maybe a skill issue on my part", or parenthetical notes. Keep them in the flow of the post rather than breaking them out.
Conditional CTAs
Address the reader's situation directly rather than issuing a generic "check this out." Frame it as: "If you [specific situation], you're going to want to [action]."
- Avoid: "Full post is here:"
- Prefer: "If you or your team works in GDocs, you're going to want to take a look at the full walkthrough here:"
Don't Rush to Judgment
Let observations land naturally instead of rushing to a verdict. Softer pacing feels more honest.
- Avoid: "the cracks showed fast"
- Prefer: "the cracks started to show"
Personal Moments Over Abstract Transitions
Ground transitions in a personal experience ("I saw," "I noticed") rather than abstract descriptions of what happened. Use accessible language over technical shorthand.
- Avoid: "The real difference showed up during iteration."
- Prefer: "I saw the real difference when I needed to make changes."
#!/usr/bin/env python3
"""
Typefully LinkedIn Scheduler
Schedule posts to LinkedIn via the Typefully API v2.
Required environment variables:
TYPEFULLY_API_KEY - API key from Typefully Settings > API & Integrations
TYPEFULLY_SOCIAL_SET_ID - Social set ID (run with --list-social-sets to find it)
Usage:
# Schedule a post from a file
python typefully_scheduler.py --file /path/to/post.txt --schedule next-free-slot
# Schedule for a specific time
python typefully_scheduler.py --file /path/to/post.txt --schedule 2026-01-20T18:00:00Z
# Publish immediately
python typefully_scheduler.py --file /path/to/post.txt --schedule now
# Show currently scheduled posts
python typefully_scheduler.py --list-scheduled
# List social sets
python typefully_scheduler.py --list-social-sets
"""
import argparse
import json
import os
import sys
import urllib.request
import urllib.error
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
API_BASE = "https://api.typefully.com/v2"
@dataclass
class ScheduleResult:
success: bool
draft_id: Optional[str] = None
status: Optional[str] = None
scheduled_date: Optional[str] = None
share_url: Optional[str] = None
error: Optional[str] = None
class TypefullyScheduler:
def __init__(self, api_key: str, social_set_id: str):
self.api_key = api_key
self.social_set_id = social_set_id
def _make_request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
"""Make an authenticated request to the Typefully API."""
url = f"{API_BASE}{endpoint}"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
body = json.dumps(data).encode() if data else None
request = urllib.request.Request(url, data=body, headers=headers, method=method)
try:
with urllib.request.urlopen(request) as response:
return json.loads(response.read().decode())
except urllib.error.HTTPError as e:
error_body = e.read().decode() if e.fp else str(e)
try:
error_data = json.loads(error_body)
raise Exception(f"API Error ({e.code}): {error_data.get('message', error_body)}")
except json.JSONDecodeError:
raise Exception(f"API Error ({e.code}): {error_body}")
def list_social_sets(self) -> List[Dict[str, Any]]:
"""List all available social sets."""
response = self._make_request("GET", "/social-sets")
return response.get("results", [])
def get_social_set_details(self, social_set_id: str) -> Dict[str, Any]:
"""Get detailed info about a social set including connected platforms."""
return self._make_request("GET", f"/social-sets/{social_set_id}/")
def list_scheduled_drafts(self, limit: int = 20) -> List[Dict[str, Any]]:
"""List scheduled drafts, sorted by scheduled date."""
response = self._make_request(
"GET",
f"/social-sets/{self.social_set_id}/drafts?limit={limit}"
)
drafts = response.get("results", [])
# Filter to only scheduled posts and sort by date
scheduled = [d for d in drafts if d.get("status") == "scheduled" and d.get("scheduled_date")]
scheduled.sort(key=lambda d: d.get("scheduled_date", ""))
return scheduled
def schedule_post(
self,
text: str,
publish_at: str = "next-free-slot",
share: bool = False
) -> ScheduleResult:
"""
Schedule a LinkedIn post via Typefully.
Args:
text: The post text
publish_at: When to publish - "now", "next-free-slot", or ISO 8601 datetime
share: Whether to generate a public share URL
Returns:
ScheduleResult with draft details or error
"""
data = {
"platforms": {
"linkedin": {
"enabled": True,
"posts": [{"text": text}]
}
},
"publish_at": publish_at,
"share": share
}
try:
response = self._make_request(
"POST",
f"/social-sets/{self.social_set_id}/drafts",
data
)
return ScheduleResult(
success=True,
draft_id=response.get("id"),
status=response.get("status"),
scheduled_date=response.get("scheduled_date"),
share_url=response.get("share_url")
)
except Exception as e:
return ScheduleResult(success=False, error=str(e))
def format_result(result: ScheduleResult) -> str:
"""Format a schedule result for display."""
lines = []
if result.success:
lines.append("LinkedIn post scheduled successfully")
if result.draft_id:
lines.append(f" Draft ID: {result.draft_id}")
if result.status:
lines.append(f" Status: {result.status}")
if result.scheduled_date:
lines.append(f" Scheduled: {result.scheduled_date}")
if result.share_url:
lines.append(f" Share URL: {result.share_url}")
else:
lines.append("Failed to schedule LinkedIn post")
if result.error:
lines.append(f" Error: {result.error}")
return "\n".join(lines)
def format_social_sets(social_sets: List[Dict[str, Any]]) -> str:
"""Format social sets for display."""
if not social_sets:
return "No social sets found."
lines = ["Available Social Sets:\n"]
for ss in social_sets:
lines.append(f" ID: {ss.get('id')}")
lines.append(f" Name: {ss.get('name', 'Unnamed')}")
if ss.get('username'):
lines.append(f" Username: @{ss.get('username')}")
lines.append("")
return "\n".join(lines)
def format_social_set_details(details: Dict[str, Any]) -> str:
"""Format social set details for display."""
lines = [f"Social Set: {details.get('name', 'Unnamed')} (ID: {details.get('id')})\n"]
lines.append("Connected Platforms:")
platforms = details.get("platforms", {})
for name, info in platforms.items():
if info:
display_name = "X/Twitter" if name == "x" else name.title()
lines.append(f" {display_name}: @{info.get('username')} - {info.get('profile_url')}")
return "\n".join(lines)
def format_scheduled_drafts(drafts: List[Dict[str, Any]]) -> str:
"""Format scheduled drafts for display."""
if not drafts:
return "No posts currently scheduled."
lines = ["Currently scheduled posts:\n"]
for draft in drafts:
scheduled = draft.get("scheduled_date", "Unknown")
preview = draft.get("preview", "")[:50]
if len(draft.get("preview", "")) > 50:
preview += "..."
platforms = []
if draft.get("x_post_enabled"):
platforms.append("X")
if draft.get("linkedin_post_enabled"):
platforms.append("LinkedIn")
platform_str = ", ".join(platforms) if platforms else "Unknown"
lines.append(f" {scheduled} [{platform_str}]")
lines.append(f" {preview}")
lines.append("")
return "\n".join(lines)
def get_env_or_exit(name: str) -> str:
"""Get an environment variable or exit with an error."""
value = os.environ.get(name)
if not value:
print(f"Error: {name} environment variable is not set.")
print(f"\nTo set it, run:")
print(f" export {name}=your_value_here")
sys.exit(1)
return value
def main():
parser = argparse.ArgumentParser(
description="Schedule LinkedIn posts via Typefully API",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --file post.txt --schedule next-free-slot
%(prog)s --file post.txt --schedule 2026-01-20T18:00:00Z
%(prog)s --file post.txt --schedule now
%(prog)s --list-scheduled
%(prog)s --list-social-sets
"""
)
parser.add_argument("--file", "-f", help="Path to file containing post text")
parser.add_argument("--schedule", "-s", default="next-free-slot",
help="When to publish: 'now', 'next-free-slot', or ISO 8601 datetime")
parser.add_argument("--share", action="store_true", help="Generate a public share URL")
parser.add_argument("--list-scheduled", action="store_true", help="List currently scheduled posts")
parser.add_argument("--list-social-sets", action="store_true", help="List available social sets")
parser.add_argument("--details", action="store_true", help="Show social set details")
args = parser.parse_args()
api_key = get_env_or_exit("TYPEFULLY_API_KEY")
# Handle --list-social-sets
if args.list_social_sets:
scheduler = TypefullyScheduler(api_key, "")
try:
social_sets = scheduler.list_social_sets()
print(format_social_sets(social_sets))
except Exception as e:
print(f"Error fetching social sets: {e}")
sys.exit(1)
return
social_set_id = get_env_or_exit("TYPEFULLY_SOCIAL_SET_ID")
scheduler = TypefullyScheduler(api_key, social_set_id)
# Handle --details
if args.details:
try:
details = scheduler.get_social_set_details(social_set_id)
print(format_social_set_details(details))
except Exception as e:
print(f"Error fetching details: {e}")
sys.exit(1)
return
# Handle --list-scheduled
if args.list_scheduled:
try:
scheduled_drafts = scheduler.list_scheduled_drafts()
print(format_scheduled_drafts(scheduled_drafts))
except Exception as e:
print(f"Error fetching scheduled posts: {e}")
sys.exit(1)
return
# Schedule a post - requires --file
if not args.file:
parser.error("--file is required when scheduling a post")
# Read post content from file
try:
with open(args.file, "r") as f:
text = f.read().strip()
except FileNotFoundError:
print(f"Error: File not found: {args.file}")
sys.exit(1)
except Exception as e:
print(f"Error reading file: {e}")
sys.exit(1)
if not text:
print("Error: Post file is empty")
sys.exit(1)
# Show currently scheduled posts for context
try:
scheduled_drafts = scheduler.list_scheduled_drafts()
if scheduled_drafts:
print(format_scheduled_drafts(scheduled_drafts))
print()
except Exception:
pass # Non-fatal, continue with scheduling
# Schedule the post
print(f"Scheduling LinkedIn post...")
print(f" Schedule: {args.schedule}")
print(f" Content preview: {text[:50]}{'...' if len(text) > 50 else ''}")
print()
result = scheduler.schedule_post(text, args.schedule, args.share)
print(format_result(result))
if not result.success:
sys.exit(1)
if __name__ == "__main__":
main()