Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# output.py is CRLF on main and ruff format keeps it that way; let git diff --check accept it.
packages/deepctl-core/src/deepctl_core/output.py whitespace=cr-at-eol
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@ dg skills remove --all # Delete the folders deepctl installed

deepctl replaces or deletes a folder only if skills.json records it for that tool, it is a real folder directly in that tool's skills folder, it holds deepctl's `.deepctl-skill` marker, and its contents are exactly what deepctl installed. So folders from `npx skills add`, and symlinked skill folders, are left alone.

These commands abort if the filesystem cannot provide locking or no-replace directory moves. Installed skill folders and skills.json stay as they were, though the tool's skills folder and `~/.deepctl/skills/skills.json.lock` may already have been created. While another `dg skills` command is changing skills, the next one waits up to 30 seconds for it to finish.
These commands abort if the filesystem cannot provide locking or no-replace directory moves. Installed skill folders and skills.json stay as they were, though the tool's skills folder and `~/.deepctl/skills/skills.json.lock` may already have been created. While another `dg skills`, `dg login` or `dg plugin` command is changing skills, the next one waits up to 30 seconds for it to finish.

`dg login` offers to install the skills, and `dg plugin install`, `update` and `remove` refresh the installed ones, through the same path as `dg skills install` and `update`. A skills installation failure prints a warning on stderr and does not change those commands' exit codes. Ctrl-C still cancels the command.

If you edit a deepctl folder, or add a file or link to it, deepctl leaves it alone: `update` stops without changing anything and `remove` won't delete it. To get updates again, rename or move your edited copy, or delete it yourself. Opening a skill folder in Finder or Explorer can add `.DS_Store`, `Thumbs.db` or `desktop.ini`, which counts as an edit.

Expand Down
60 changes: 27 additions & 33 deletions packages/deepctl-cmd-login/src/deepctl_cmd_login/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
ProfilesResult,
get_output_format,
)
from deepctl_core.output import print_warning
from rich.console import Console
from rich.markup import escape
from rich.prompt import Prompt

from .models import LoginResult, LogoutResult, WhoamiResult
Expand Down Expand Up @@ -237,43 +239,35 @@ def _maybe_prompt_skills_setup(self) -> None:
console.print("[dim]No tools selected.[/dim]")
return

# Install skills for selected tools
from deepctl_core.skill_generator import (
_commands_hash,
collect_command_metadata,
save_skills_state,
)
# The path 'dg skills install' uses (B3): one fetch, every tool
# preflighted, each tool recorded as it lands.
from deepctl_core import skill_generator as sg
from deepctl_core.skill_bundle import resolve_skills_ref

console.print("\n[blue]Installing Deepgram skills...[/blue]")

import importlib.metadata
from datetime import datetime, timezone

commands = collect_command_metadata()
try:
version = importlib.metadata.version("deepctl")
except importlib.metadata.PackageNotFoundError:
version = "0.0.0"

for gen in selected:
paths = gen.install(commands, version)
cmd_hash = _commands_hash(commands)
state["installed_skills"][gen.cli_name] = {
"paths": [str(p) for p in paths],
"installed_at": datetime.now(timezone.utc).isoformat(),
"version": version,
"commands_hash": cmd_hash,
}
for p in paths:
console.print(f" [green]✓[/green] {gen.display_name} → {p}")

save_skills_state(state)
console.print(
"\n[green]Skills installed![/green] "
"[dim]Run 'dg skills update' after plugin changes.[/dim]"
if gen.skills_root() is None:
print_warning(escape(sg._msg("E15", gen)), stderr=True)
ref, count = resolve_skills_ref(), 0
for gen, paths, leftover in sg.install_for([(g, ref) for g in selected]):
count += len(paths)
line = f"{gen.display_name} → {gen.skills_root()} ({len(paths)} skills)"
console.print(f" [green]✓[/green] {escape(line)}")
if leftover:
print_warning(escape(sg._msg("E12", staging=leftover)), stderr=True)
if count:
console.print(
"\n[green]Skills installed![/green] "
"[dim]Run 'dg skills update' to get newer skills.[/dim]"
)
except Exception as exc: # Never fails the login, and never silent (B3).
from deepctl_core.skill_generator import warn_install_failure

warn_install_failure(
"Skills setup did not finish",
exc,
"run 'dg skills install' to try again",
)
except Exception:
pass # Best-effort — never fail the login

def _cli_auth(
self,
Expand Down
Loading
Loading