Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,11 @@ LEAPFLOW_RECORDING_MODE=video
# HuggingFace Hub credentials (future)
# HF_TOKEN=hf_xxxxx
# HF_ENDPOINT=https://huggingface.co

# ═════════════════════════════════════════════════════════════════════
# Scheduler — Long-horizon async task execution
# ═════════════════════════════════════════════════════════════════════
# LEAPFLOW_SCHEDULER_ENABLED=true
# LEAPFLOW_SCHEDULER_TICK_SECONDS=60
# LEAPFLOW_SCHEDULER_GRACE_SECONDS=120.0
# LEAPFLOW_SCHEDULER_DEFAULT_TIER=auto
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,4 @@ leapflow.sock

temp
.DS_Store
AGENTS.md
8 changes: 8 additions & 0 deletions src/leapflow/_env_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,12 @@
# HuggingFace Hub credentials (future)
# HF_TOKEN=hf_xxxxx
# HF_ENDPOINT=https://huggingface.co

# ═════════════════════════════════════════════════════════════════════
# Scheduler — Long-horizon async task execution
# ═════════════════════════════════════════════════════════════════════
# LEAPFLOW_SCHEDULER_ENABLED=true
# LEAPFLOW_SCHEDULER_TICK_SECONDS=60
# LEAPFLOW_SCHEDULER_GRACE_SECONDS=120.0
# LEAPFLOW_SCHEDULER_DEFAULT_TIER=auto
"""
43 changes: 41 additions & 2 deletions src/leapflow/cli/commands/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ async def _hub_whoami(ctx: "Context", args: List[str]) -> int:
async def _hub_push(ctx: "Context", args: List[str]) -> int:
"""Push a skill to the Hub."""
from leapflow.hub import ContentSanitizer, SkillSerializer, Visibility
from leapflow.hub.protocol import VersionConflictError

if not args:
print(" Usage: hub push <skill-name> [--visibility private|public] [--version v1.0.0]")
Expand Down Expand Up @@ -215,6 +216,14 @@ async def _hub_push(ctx: "Context", args: List[str]) -> int:
warnings = sanitizer.scan(bundle)
_print_warnings(warnings, "Sanitization")

# Step 3b: Check for blocking findings
if sanitizer.has_blocking_findings(warnings) and "--force" not in args:
print(" HIGH severity findings detected. Use --force to push anyway.")
confirmed = await _confirm("Push despite high-severity warnings?", dangerous=True, skill_name=skill_name)
if not confirmed:
print(" Push aborted.")
return 0

# Step 4: Show summary
visibility = Visibility(visibility_str)
client = _build_hub_client(ctx)
Expand All @@ -241,8 +250,38 @@ async def _hub_push(ctx: "Context", args: List[str]) -> int:
print(" Cancelled.")
return 0

# Step 6: Push
result = await client.push(bundle, skill_name=bundle.manifest.name, visibility=visibility)
# Step 6: Auto-fill author info
import dataclasses
from datetime import datetime, timezone

try:
user_info = await client.backend.authenticate()
username = user_info.username
except Exception:
username = ""

if username:
manifest = dataclasses.replace(
bundle.manifest,
author=bundle.manifest.author or username,
updated_by=username,
updated_at=datetime.now(timezone.utc).isoformat(),
)
bundle = dataclasses.replace(bundle, manifest=manifest)

# Step 7: Push
force = "--force" in args
try:
result = await client.push(bundle, skill_name=bundle.manifest.name, visibility=visibility, force=force)
except VersionConflictError as e:
print(f" Version conflict: {e}")
confirmed = await _confirm("Force push anyway?")
if confirmed:
result = await client.push(bundle, skill_name=bundle.manifest.name, visibility=visibility, force=True)
else:
print(" Push aborted.")
return 0

print(f"\n Pushed successfully!")
print(f" Repo: {result.repo_id}")
print(f" Version: {result.version}")
Expand Down
15 changes: 15 additions & 0 deletions src/leapflow/cli/commands/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ def _bridge_online() -> bool:
print(" skills disable <name> — Disable a learned skill")
print(" skills delete <name> — Delete a learned skill")
print(" hub <subcommand> — Hub operations (push/pull/sync/search)")
print(" arm <skill> --trigger — Arm a skill for scheduled execution")
print(" tasks — List/manage scheduled tasks")
print(" shortcut list — List quick-reply shortcuts")
print(" shortcut add <p>=<r> — Add shortcut (pattern = reply)")
print(" shortcut remove <p> — Remove a shortcut")
Expand Down Expand Up @@ -405,6 +407,19 @@ def _bridge_online() -> bool:
await cmd_hub(ctx, hub_args)
continue

# ── Scheduler commands ──
if line.startswith("arm"):
from leapflow.cli.commands.scheduler import cmd_arm
arm_args = line.split()[1:] if len(line.split()) > 1 else []
await cmd_arm(ctx, arm_args)
continue

if line.startswith("tasks"):
from leapflow.cli.commands.scheduler import cmd_tasks
tasks_args = line.split()[1:] if len(line.split()) > 1 else []
await cmd_tasks(ctx, tasks_args)
continue

# ── Run command ──
if line.startswith("run "):
trigger_or_name = line[4:].strip()
Expand Down
Loading