feat: refresh skills metadata and toggles
This commit is contained in:
@@ -15,6 +15,7 @@ struct SkillStatus: Codable, Identifiable {
|
|||||||
let baseDir: String
|
let baseDir: String
|
||||||
let skillKey: String
|
let skillKey: String
|
||||||
let primaryEnv: String?
|
let primaryEnv: String?
|
||||||
|
let emoji: String?
|
||||||
let always: Bool
|
let always: Bool
|
||||||
let disabled: Bool
|
let disabled: Bool
|
||||||
let eligible: Bool
|
let eligible: Bool
|
||||||
|
|||||||
@@ -96,32 +96,30 @@ private struct SkillRow: View {
|
|||||||
private var missingConfig: [String] { self.skill.missing.config }
|
private var missingConfig: [String] { self.skill.missing.config }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
HStack(alignment: .top, spacing: 12) {
|
HStack(alignment: .top, spacing: 12) {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
Text(self.skill.emoji ?? "✨")
|
||||||
Text(self.skill.name)
|
.font(.title2)
|
||||||
.font(.headline)
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
HStack(alignment: .firstTextBaseline, spacing: 8) {
|
||||||
|
Text(self.skill.name)
|
||||||
|
.font(.headline)
|
||||||
|
self.statusBadge
|
||||||
|
}
|
||||||
Text(self.skill.description)
|
Text(self.skill.description)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
Text(self.sourceLabel)
|
self.metaRow
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
self.statusBadge
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.skill.disabled {
|
if self.skill.disabled {
|
||||||
Text("Disabled in config")
|
Text("Disabled in config")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
} else if self.skill.eligible {
|
} else if !self.skill.eligible {
|
||||||
Text("Enabled")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
} else {
|
|
||||||
self.missingSummary
|
self.missingSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +138,18 @@ private struct SkillRow: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var sourceLabel: String {
|
private var sourceLabel: String {
|
||||||
self.skill.source.replacingOccurrences(of: "clawdis-", with: "")
|
switch self.skill.source {
|
||||||
|
case "clawdis-bundled":
|
||||||
|
return "Bundled"
|
||||||
|
case "clawdis-managed":
|
||||||
|
return "Managed"
|
||||||
|
case "clawdis-workspace":
|
||||||
|
return "Workspace"
|
||||||
|
case "clawdis-extra":
|
||||||
|
return "Extra"
|
||||||
|
default:
|
||||||
|
return self.skill.source
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var statusBadge: some View {
|
private var statusBadge: some View {
|
||||||
@@ -156,7 +165,33 @@ private struct SkillRow: View {
|
|||||||
.foregroundStyle(.orange)
|
.foregroundStyle(.orange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.font(.subheadline)
|
.font(.caption)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var metaRow: some View {
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
SkillTag(text: self.sourceLabel)
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Text(self.enabledLabel)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Toggle("", isOn: self.enabledBinding)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
.labelsHidden()
|
||||||
|
.disabled(self.isBusy)
|
||||||
|
}
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var enabledLabel: String {
|
||||||
|
self.skill.disabled ? "Disabled" : "Enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var enabledBinding: Binding<Bool> {
|
||||||
|
Binding(
|
||||||
|
get: { !self.skill.disabled },
|
||||||
|
set: { self.onToggleEnabled($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -199,16 +234,6 @@ private struct SkillRow: View {
|
|||||||
|
|
||||||
private var actionRow: some View {
|
private var actionRow: some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if self.skill.disabled {
|
|
||||||
Button("Enable") { self.onToggleEnabled(true) }
|
|
||||||
.buttonStyle(.borderedProminent)
|
|
||||||
.disabled(self.isBusy)
|
|
||||||
} else {
|
|
||||||
Button("Disable") { self.onToggleEnabled(false) }
|
|
||||||
.buttonStyle(.bordered)
|
|
||||||
.disabled(self.isBusy)
|
|
||||||
}
|
|
||||||
|
|
||||||
ForEach(self.installOptions) { option in
|
ForEach(self.installOptions) { option in
|
||||||
Button(option.label) { self.onInstall(option) }
|
Button(option.label) { self.onInstall(option) }
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
@@ -254,6 +279,20 @@ private struct SkillRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct SkillTag: View {
|
||||||
|
let text: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text(self.text)
|
||||||
|
.font(.caption2.weight(.semibold))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.padding(.vertical, 2)
|
||||||
|
.background(Color.secondary.opacity(0.12))
|
||||||
|
.clipShape(Capsule())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private struct EnvEditorState: Identifiable {
|
private struct EnvEditorState: Identifiable {
|
||||||
let skillKey: String
|
let skillKey: String
|
||||||
let skillName: String
|
let skillName: String
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ The macOS app surfaces Clawdis skills via the gateway; it does not parse skills
|
|||||||
- Requirements are derived from `metadata.clawdis.requires` in each `SKILL.md`.
|
- Requirements are derived from `metadata.clawdis.requires` in each `SKILL.md`.
|
||||||
|
|
||||||
## Install actions
|
## Install actions
|
||||||
- `metadata.clawdis.install` defines install options (brew/node/go/pnpm/git/shell).
|
- `metadata.clawdis.install` defines install options (brew/node/go/pnpm/shell).
|
||||||
- The app calls `skills.install` to run installers on the gateway host.
|
- The app calls `skills.install` to run installers on the gateway host.
|
||||||
|
- The gateway surfaces only one preferred installer when multiple are provided (brew when available, otherwise node manager from `skillsInstall`).
|
||||||
|
|
||||||
## Env/API keys
|
## Env/API keys
|
||||||
- The app stores keys in `~/.clawdis/clawdis.json` under `skills.<skillKey>`.
|
- The app stores keys in `~/.clawdis/clawdis.json` under `skills.<skillKey>`.
|
||||||
|
|||||||
@@ -54,11 +54,12 @@ metadata: {"clawdis":{"requires":{"bins":["uv"],"env":["GEMINI_API_KEY"],"config
|
|||||||
|
|
||||||
Fields under `metadata.clawdis`:
|
Fields under `metadata.clawdis`:
|
||||||
- `always: true` — always include the skill (skip other gates).
|
- `always: true` — always include the skill (skip other gates).
|
||||||
|
- `emoji` — optional emoji used by the macOS Skills UI.
|
||||||
- `requires.bins` — list; each must exist on `PATH`.
|
- `requires.bins` — list; each must exist on `PATH`.
|
||||||
- `requires.env` — list; env var must exist **or** be provided in config.
|
- `requires.env` — list; env var must exist **or** be provided in config.
|
||||||
- `requires.config` — list of `clawdis.json` paths that must be truthy.
|
- `requires.config` — list of `clawdis.json` paths that must be truthy.
|
||||||
- `primaryEnv` — env var name associated with `skills.<name>.apiKey`.
|
- `primaryEnv` — env var name associated with `skills.<name>.apiKey`.
|
||||||
- `install` — optional array of installer specs used by the macOS Skills UI (brew/node/go/pnpm/git/shell).
|
- `install` — optional array of installer specs used by the macOS Skills UI (brew/node/go/pnpm/shell).
|
||||||
|
|
||||||
Installer example:
|
Installer example:
|
||||||
|
|
||||||
@@ -66,10 +67,14 @@ Installer example:
|
|||||||
---
|
---
|
||||||
name: gemini
|
name: gemini
|
||||||
description: Use Gemini CLI for coding assistance and Google search lookups.
|
description: Use Gemini CLI for coding assistance and Google search lookups.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["gemini"]},"install":[{"id":"brew","kind":"brew","formula":"gemini-cli","bins":["gemini"],"label":"Install Gemini CLI (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"♊️","requires":{"bins":["gemini"]},"install":[{"id":"brew","kind":"brew","formula":"gemini-cli","bins":["gemini"],"label":"Install Gemini CLI (brew)"}]}}
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- If multiple installers are listed, the gateway picks a **single** preferred option (brew when available, otherwise node).
|
||||||
|
- Node installs honor `skillsInstall.nodeManager` in `clawdis.json` (default: npm).
|
||||||
|
|
||||||
If no `metadata.clawdis` is present, the skill is always eligible (unless disabled in config).
|
If no `metadata.clawdis` is present, the skill is always eligible (unless disabled in config).
|
||||||
|
|
||||||
## Config overrides (`~/.clawdis/clawdis.json`)
|
## Config overrides (`~/.clawdis/clawdis.json`)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: bird
|
name: bird
|
||||||
description: X/Twitter CLI for reading, searching, and posting via cookies or Sweetistics.
|
description: X/Twitter CLI for reading, searching, and posting via cookies or Sweetistics.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["bird"]},"install":[{"id":"pnpm-build","kind":"shell","command":"if [ ! -d ~/Projects/bird ]; then git clone https://github.com/steipete/bird.git ~/Projects/bird; fi && cd ~/Projects/bird && pnpm install && pnpm run binary","bins":["bird"],"label":"Clone + build bird (pnpm)"}]}}
|
metadata: {"clawdis":{"emoji":"🐦","requires":{"bins":["bird"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/bird","bins":["bird"],"label":"Install bird (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# bird
|
# bird
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: blucli
|
name: blucli
|
||||||
description: BluOS CLI (blu) for discovery, playback, grouping, and volume.
|
description: BluOS CLI (blu) for discovery, playback, grouping, and volume.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["blu"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/blucli/cmd/blu@latest","bins":["blu"],"label":"Install blucli (go)"}]}}
|
metadata: {"clawdis":{"emoji":"🫐","requires":{"bins":["blu"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/blucli/cmd/blu@latest","bins":["blu"],"label":"Install blucli (go)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# blucli (blu)
|
# blucli (blu)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: brave-search
|
name: brave-search
|
||||||
description: Web search and content extraction via Brave Search API.
|
description: Web search and content extraction via Brave Search API.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["node"],"env":["BRAVE_API_KEY"]},"primaryEnv":"BRAVE_API_KEY"}}
|
metadata: {"clawdis":{"emoji":"🦁","requires":{"bins":["node"],"env":["BRAVE_API_KEY"]},"primaryEnv":"BRAVE_API_KEY"}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Brave Search
|
# Brave Search
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: camsnap
|
name: camsnap
|
||||||
description: Capture frames or clips from RTSP/ONVIF cameras.
|
description: Capture frames or clips from RTSP/ONVIF cameras.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["camsnap"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/camsnap","bins":["camsnap"],"label":"Install camsnap (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"📸","requires":{"bins":["camsnap"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/camsnap","bins":["camsnap"],"label":"Install camsnap (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# camsnap
|
# camsnap
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: clawdis-browser
|
name: clawdis-browser
|
||||||
description: Control clawd's dedicated browser (tabs, snapshots, actions) via the clawdis CLI.
|
description: Control clawd's dedicated browser (tabs, snapshots, actions) via the clawdis CLI.
|
||||||
metadata: {"clawdis":{"requires":{"config":["browser.enabled"]}}}
|
metadata: {"clawdis":{"emoji":"🧭","requires":{"config":["browser.enabled"]}}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Clawdis Browser
|
# Clawdis Browser
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: clawdis-cron
|
name: clawdis-cron
|
||||||
description: Schedule jobs and wakeups via Clawdis Gateway cron.* RPC.
|
description: Schedule jobs and wakeups via Clawdis Gateway cron.* RPC.
|
||||||
metadata: {"clawdis":{"always":true}}
|
metadata: {"clawdis":{"emoji":"⏰","always":true}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Clawdis Cron
|
# Clawdis Cron
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: clawdis-nodes
|
name: clawdis-nodes
|
||||||
description: Discover, interpret, and target Clawdis nodes (paired devices) via the Gateway/CLI. Use when an agent must find available nodes, choose the best target machine, or reason about presence vs node availability (Tailnet/Tailscale optional).
|
description: Discover, interpret, and target Clawdis nodes (paired devices) via the Gateway/CLI. Use when an agent must find available nodes, choose the best target machine, or reason about presence vs node availability (Tailnet/Tailscale optional).
|
||||||
|
metadata: {"clawdis":{"emoji":"🛰️"}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Clawdis Nodes
|
# Clawdis Nodes
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: clawdis-notify
|
name: clawdis-notify
|
||||||
description: Send system notifications to specific Clawdis nodes (macOS computers) via the Gateway and CLI. Use when you need to alert a person or confirm a remote action on a particular machine, or when an agent must push a notification to another computer.
|
description: Send system notifications to specific Clawdis nodes (macOS computers) via the Gateway and CLI. Use when you need to alert a person or confirm a remote action on a particular machine, or when an agent must push a notification to another computer.
|
||||||
|
metadata: {"clawdis":{"emoji":"🔔"}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Clawdis Notify
|
# Clawdis Notify
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: eightctl
|
name: eightctl
|
||||||
description: Control Eight Sleep pods (status, temperature, alarms, schedules).
|
description: Control Eight Sleep pods (status, temperature, alarms, schedules).
|
||||||
metadata: {"clawdis":{"requires":{"bins":["eightctl"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/eightctl/cmd/eightctl@latest","bins":["eightctl"],"label":"Install eightctl (go)"}]}}
|
metadata: {"clawdis":{"emoji":"🎛️","requires":{"bins":["eightctl"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/eightctl/cmd/eightctl@latest","bins":["eightctl"],"label":"Install eightctl (go)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# eightctl
|
# eightctl
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: gemini
|
name: gemini
|
||||||
description: Gemini CLI for one-shot Q&A, summaries, and generation.
|
description: Gemini CLI for one-shot Q&A, summaries, and generation.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["gemini"]},"install":[{"id":"brew","kind":"brew","formula":"gemini-cli","bins":["gemini"],"label":"Install Gemini CLI (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"♊️","requires":{"bins":["gemini"]},"install":[{"id":"brew","kind":"brew","formula":"gemini-cli","bins":["gemini"],"label":"Install Gemini CLI (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Gemini CLI
|
# Gemini CLI
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: gog
|
name: gog
|
||||||
description: Google Workspace CLI for Gmail, Calendar, Drive, and Contacts.
|
description: Google Workspace CLI for Gmail, Calendar, Drive, and Contacts.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["gog"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/gogcli","bins":["gog"],"label":"Install gog (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"🎮","requires":{"bins":["gog"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/gogcli","bins":["gog"],"label":"Install gog (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# gog
|
# gog
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: imsg
|
name: imsg
|
||||||
description: iMessage/SMS CLI for listing chats, history, watch, and sending.
|
description: iMessage/SMS CLI for listing chats, history, watch, and sending.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["imsg"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/imsg/cmd/imsg@latest","bins":["imsg"],"label":"Install imsg (go)"}]}}
|
metadata: {"clawdis":{"emoji":"📨","requires":{"bins":["imsg"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/imsg/cmd/imsg@latest","bins":["imsg"],"label":"Install imsg (go)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# imsg
|
# imsg
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: mcporter
|
name: mcporter
|
||||||
description: Manage and call MCP servers (list, call, auth, daemon).
|
description: Manage and call MCP servers (list, call, auth, daemon).
|
||||||
metadata: {"clawdis":{"requires":{"bins":["mcporter"]},"install":[{"id":"node","kind":"node","package":"mcporter","bins":["mcporter"],"label":"Install mcporter (node)"}]}}
|
metadata: {"clawdis":{"emoji":"📦","requires":{"bins":["mcporter"]},"install":[{"id":"node","kind":"node","package":"mcporter","bins":["mcporter"],"label":"Install mcporter (node)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# mcporter
|
# mcporter
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: nano-banana-pro
|
name: nano-banana-pro
|
||||||
description: Generate or edit images via Gemini 3 Pro Image (Nano Banana Pro).
|
description: Generate or edit images via Gemini 3 Pro Image (Nano Banana Pro).
|
||||||
metadata: {"clawdis":{"requires":{"bins":["uv"],"env":["GEMINI_API_KEY"]},"primaryEnv":"GEMINI_API_KEY","install":[{"id":"uv-brew","kind":"brew","formula":"uv","bins":["uv"],"label":"Install uv (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"🍌","requires":{"bins":["uv"],"env":["GEMINI_API_KEY"]},"primaryEnv":"GEMINI_API_KEY","install":[{"id":"uv-brew","kind":"brew","formula":"uv","bins":["uv"],"label":"Install uv (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Nano Banana Pro (Gemini 3 Pro Image)
|
# Nano Banana Pro (Gemini 3 Pro Image)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: nano-pdf
|
name: nano-pdf
|
||||||
description: Edit PDFs with natural-language instructions using the nano-pdf CLI.
|
description: Edit PDFs with natural-language instructions using the nano-pdf CLI.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["nano-pdf"]},"install":[{"id":"pipx","kind":"shell","command":"python3 -m pip install --user pipx && python3 -m pipx ensurepath && pipx install nano-pdf","bins":["nano-pdf"],"label":"Install nano-pdf (pipx)"},{"id":"pip","kind":"shell","command":"python3 -m pip install --user nano-pdf","bins":["nano-pdf"],"label":"Install nano-pdf (pip --user)"}]}}
|
metadata: {"clawdis":{"emoji":"📄","requires":{"bins":["nano-pdf"]},"install":[{"id":"pipx","kind":"shell","command":"python3 -m pip install --user pipx && python3 -m pipx ensurepath && pipx install nano-pdf","bins":["nano-pdf"],"label":"Install nano-pdf (pipx)"},{"id":"pip","kind":"shell","command":"python3 -m pip install --user nano-pdf","bins":["nano-pdf"],"label":"Install nano-pdf (pip --user)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# nano-pdf
|
# nano-pdf
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: openai-image-gen
|
name: openai-image-gen
|
||||||
description: Batch-generate images via OpenAI Images API. Random prompt sampler + `index.html` gallery.
|
description: Batch-generate images via OpenAI Images API. Random prompt sampler + `index.html` gallery.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["python3"],"env":["OPENAI_API_KEY"]},"primaryEnv":"OPENAI_API_KEY","install":[{"id":"python-brew","kind":"brew","formula":"python","bins":["python3"],"label":"Install Python (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"🖼️","requires":{"bins":["python3"],"env":["OPENAI_API_KEY"]},"primaryEnv":"OPENAI_API_KEY","install":[{"id":"python-brew","kind":"brew","formula":"python","bins":["python3"],"label":"Install Python (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# OpenAI Image Gen
|
# OpenAI Image Gen
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: openai-whisper-api
|
name: openai-whisper-api
|
||||||
description: Transcribe audio via OpenAI Audio Transcriptions API (Whisper).
|
description: Transcribe audio via OpenAI Audio Transcriptions API (Whisper).
|
||||||
metadata: {"clawdis":{"requires":{"bins":["curl"],"env":["OPENAI_API_KEY"]},"primaryEnv":"OPENAI_API_KEY"}}
|
metadata: {"clawdis":{"emoji":"☁️","requires":{"bins":["curl"],"env":["OPENAI_API_KEY"]},"primaryEnv":"OPENAI_API_KEY"}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# OpenAI Whisper API (curl)
|
# OpenAI Whisper API (curl)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: openai-whisper
|
name: openai-whisper
|
||||||
description: Local speech-to-text with the Whisper CLI (no API key).
|
description: Local speech-to-text with the Whisper CLI (no API key).
|
||||||
metadata: {"clawdis":{"requires":{"bins":["whisper"]},"install":[{"id":"brew","kind":"brew","formula":"openai-whisper","bins":["whisper"],"label":"Install OpenAI Whisper (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"🎙️","requires":{"bins":["whisper"]},"install":[{"id":"brew","kind":"brew","formula":"openai-whisper","bins":["whisper"],"label":"Install OpenAI Whisper (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Whisper (CLI)
|
# Whisper (CLI)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: openhue
|
name: openhue
|
||||||
description: Control Philips Hue lights/scenes via the OpenHue CLI.
|
description: Control Philips Hue lights/scenes via the OpenHue CLI.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["openhue"]},"install":[{"id":"brew","kind":"brew","formula":"openhue/cli/openhue-cli","bins":["openhue"],"label":"Install OpenHue CLI (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"💡","requires":{"bins":["openhue"]},"install":[{"id":"brew","kind":"brew","formula":"openhue/cli/openhue-cli","bins":["openhue"],"label":"Install OpenHue CLI (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# OpenHue CLI
|
# OpenHue CLI
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: oracle
|
name: oracle
|
||||||
description: Run a second-model review or debug session with the oracle CLI.
|
description: Run a second-model review or debug session with the oracle CLI.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["oracle"]},"install":[{"id":"node","kind":"node","package":"@steipete/oracle","bins":["oracle"],"label":"Install oracle (node)"}]}}
|
metadata: {"clawdis":{"emoji":"🧿","requires":{"bins":["oracle"]},"install":[{"id":"node","kind":"node","package":"@steipete/oracle","bins":["oracle"],"label":"Install oracle (node)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# oracle
|
# oracle
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: peekaboo
|
name: peekaboo
|
||||||
description: Capture and automate macOS UI with the Peekaboo CLI.
|
description: Capture and automate macOS UI with the Peekaboo CLI.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["peekaboo"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/peekaboo","bins":["peekaboo"],"label":"Install Peekaboo (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"👀","requires":{"bins":["peekaboo"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/peekaboo","bins":["peekaboo"],"label":"Install Peekaboo (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Peekaboo
|
# Peekaboo
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: qmd
|
name: qmd
|
||||||
description: Local search/indexing CLI (BM25 + vectors + rerank) with MCP mode.
|
description: Local search/indexing CLI (BM25 + vectors + rerank) with MCP mode.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["qmd"]},"install":[{"id":"node","kind":"node","package":"https://github.com/tobi/qmd","bins":["qmd"],"label":"Install qmd (node)"}]}}
|
metadata: {"clawdis":{"emoji":"📝","requires":{"bins":["qmd"]},"install":[{"id":"node","kind":"node","package":"https://github.com/tobi/qmd","bins":["qmd"],"label":"Install qmd (node)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# qmd
|
# qmd
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: sag
|
name: sag
|
||||||
description: ElevenLabs text-to-speech with mac-style say UX.
|
description: ElevenLabs text-to-speech with mac-style say UX.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["sag"],"env":["ELEVENLABS_API_KEY"]},"primaryEnv":"ELEVENLABS_API_KEY","install":[{"id":"brew","kind":"brew","formula":"steipete/tap/sag","bins":["sag"],"label":"Install sag (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"🗣️","requires":{"bins":["sag"],"env":["ELEVENLABS_API_KEY"]},"primaryEnv":"ELEVENLABS_API_KEY","install":[{"id":"brew","kind":"brew","formula":"steipete/tap/sag","bins":["sag"],"label":"Install sag (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# sag
|
# sag
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: sonoscli
|
name: sonoscli
|
||||||
description: Control Sonos speakers (discover/status/play/volume/group).
|
description: Control Sonos speakers (discover/status/play/volume/group).
|
||||||
metadata: {"clawdis":{"requires":{"bins":["sonos"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/sonoscli/cmd/sonos@latest","bins":["sonos"],"label":"Install sonoscli (go)"}]}}
|
metadata: {"clawdis":{"emoji":"🔊","requires":{"bins":["sonos"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/sonoscli/cmd/sonos@latest","bins":["sonos"],"label":"Install sonoscli (go)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Sonos CLI
|
# Sonos CLI
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: spotify-player
|
name: spotify-player
|
||||||
description: Terminal Spotify client (TUI + CLI commands) for playback and search.
|
description: Terminal Spotify client (TUI + CLI commands) for playback and search.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["spotify_player"]},"install":[{"id":"brew","kind":"brew","formula":"spotify_player","bins":["spotify_player"],"label":"Install spotify-player (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"🎵","requires":{"bins":["spotify_player"]},"install":[{"id":"brew","kind":"brew","formula":"spotify_player","bins":["spotify_player"],"label":"Install spotify-player (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# spotify_player
|
# spotify_player
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: summarize
|
name: summarize
|
||||||
description: Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube).
|
description: Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube).
|
||||||
metadata: {"clawdis":{"requires":{"bins":["summarize"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/summarize","bins":["summarize"],"label":"Install summarize (brew)"},{"id":"node","kind":"node","package":"@steipete/summarize","bins":["summarize"],"label":"Install summarize (node)"}]}}
|
metadata: {"clawdis":{"emoji":"🧾","requires":{"bins":["summarize"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/summarize","bins":["summarize"],"label":"Install summarize (brew)"},{"id":"node","kind":"node","package":"@steipete/summarize","bins":["summarize"],"label":"Install summarize (node)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Summarize
|
# Summarize
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: video-frames
|
name: video-frames
|
||||||
description: Extract frames or short clips from videos using ffmpeg.
|
description: Extract frames or short clips from videos using ffmpeg.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["ffmpeg"]},"install":[{"id":"brew","kind":"brew","formula":"ffmpeg","bins":["ffmpeg"],"label":"Install ffmpeg (brew)"}]}}
|
metadata: {"clawdis":{"emoji":"🎞️","requires":{"bins":["ffmpeg"]},"install":[{"id":"brew","kind":"brew","formula":"ffmpeg","bins":["ffmpeg"],"label":"Install ffmpeg (brew)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Video Frames (ffmpeg)
|
# Video Frames (ffmpeg)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: wacli
|
name: wacli
|
||||||
description: WhatsApp CLI for sync, search, and sending messages.
|
description: WhatsApp CLI for sync, search, and sending messages.
|
||||||
metadata: {"clawdis":{"requires":{"bins":["wacli"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/wacli/cmd/wacli@latest","bins":["wacli"],"label":"Install wacli (go)"}]}}
|
metadata: {"clawdis":{"emoji":"📱","requires":{"bins":["wacli"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/wacli/cmd/wacli@latest","bins":["wacli"],"label":"Install wacli (go)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# wacli
|
# wacli
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ import { runCommandWithTimeout } from "../process/exec.js";
|
|||||||
import { resolveUserPath } from "../utils.js";
|
import { resolveUserPath } from "../utils.js";
|
||||||
import {
|
import {
|
||||||
loadWorkspaceSkillEntries,
|
loadWorkspaceSkillEntries,
|
||||||
|
resolveSkillsInstallPreferences,
|
||||||
type SkillEntry,
|
type SkillEntry,
|
||||||
type SkillInstallSpec,
|
type SkillInstallSpec,
|
||||||
|
type SkillsInstallPreferences,
|
||||||
} from "./skills.js";
|
} from "./skills.js";
|
||||||
|
import type { ClawdisConfig } from "../config/config.js";
|
||||||
|
|
||||||
export type SkillInstallRequest = {
|
export type SkillInstallRequest = {
|
||||||
workspaceDir: string;
|
workspaceDir: string;
|
||||||
skillName: string;
|
skillName: string;
|
||||||
installId: string;
|
installId: string;
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
|
config?: ClawdisConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SkillInstallResult = {
|
export type SkillInstallResult = {
|
||||||
@@ -40,7 +44,24 @@ function runShell(command: string, timeoutMs: number) {
|
|||||||
return runCommandWithTimeout(["/bin/zsh", "-lc", command], { timeoutMs });
|
return runCommandWithTimeout(["/bin/zsh", "-lc", command], { timeoutMs });
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInstallCommand(spec: SkillInstallSpec): {
|
function buildNodeInstallCommand(
|
||||||
|
packageName: string,
|
||||||
|
prefs: SkillsInstallPreferences,
|
||||||
|
): string[] {
|
||||||
|
switch (prefs.nodeManager) {
|
||||||
|
case "pnpm":
|
||||||
|
return ["pnpm", "add", "-g", packageName];
|
||||||
|
case "bun":
|
||||||
|
return ["bun", "add", "-g", packageName];
|
||||||
|
default:
|
||||||
|
return ["npm", "install", "-g", packageName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildInstallCommand(
|
||||||
|
spec: SkillInstallSpec,
|
||||||
|
prefs: SkillsInstallPreferences,
|
||||||
|
): {
|
||||||
argv: string[] | null;
|
argv: string[] | null;
|
||||||
shell: string | null;
|
shell: string | null;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
@@ -55,7 +76,10 @@ function buildInstallCommand(spec: SkillInstallSpec): {
|
|||||||
case "node": {
|
case "node": {
|
||||||
if (!spec.package)
|
if (!spec.package)
|
||||||
return { argv: null, shell: null, error: "missing node package" };
|
return { argv: null, shell: null, error: "missing node package" };
|
||||||
return { argv: ["npm", "install", "-g", spec.package], shell: null };
|
return {
|
||||||
|
argv: buildNodeInstallCommand(spec.package, prefs),
|
||||||
|
shell: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
case "go": {
|
case "go": {
|
||||||
if (!spec.module)
|
if (!spec.module)
|
||||||
@@ -74,18 +98,6 @@ function buildInstallCommand(spec: SkillInstallSpec): {
|
|||||||
const cmd = `cd ${JSON.stringify(repoPath)} && pnpm install && pnpm run ${JSON.stringify(spec.script)}`;
|
const cmd = `cd ${JSON.stringify(repoPath)} && pnpm install && pnpm run ${JSON.stringify(spec.script)}`;
|
||||||
return { argv: null, shell: cmd };
|
return { argv: null, shell: cmd };
|
||||||
}
|
}
|
||||||
case "git": {
|
|
||||||
if (!spec.url || !spec.destination) {
|
|
||||||
return {
|
|
||||||
argv: null,
|
|
||||||
shell: null,
|
|
||||||
error: "missing git url/destination",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const dest = resolveUserPath(spec.destination);
|
|
||||||
const cmd = `if [ -d ${JSON.stringify(dest)} ]; then echo "Already cloned"; else git clone ${JSON.stringify(spec.url)} ${JSON.stringify(dest)}; fi`;
|
|
||||||
return { argv: null, shell: cmd };
|
|
||||||
}
|
|
||||||
case "shell": {
|
case "shell": {
|
||||||
if (!spec.command)
|
if (!spec.command)
|
||||||
return { argv: null, shell: null, error: "missing shell command" };
|
return { argv: null, shell: null, error: "missing shell command" };
|
||||||
@@ -127,7 +139,8 @@ export async function installSkill(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = buildInstallCommand(spec);
|
const prefs = resolveSkillsInstallPreferences(params.config);
|
||||||
|
const command = buildInstallCommand(spec, prefs);
|
||||||
if (command.error) {
|
if (command.error) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import {
|
|||||||
loadWorkspaceSkillEntries,
|
loadWorkspaceSkillEntries,
|
||||||
resolveConfigPath,
|
resolveConfigPath,
|
||||||
resolveSkillConfig,
|
resolveSkillConfig,
|
||||||
|
resolveSkillsInstallPreferences,
|
||||||
type SkillEntry,
|
type SkillEntry,
|
||||||
type SkillInstallSpec,
|
type SkillInstallSpec,
|
||||||
|
type SkillsInstallPreferences,
|
||||||
} from "./skills.js";
|
} from "./skills.js";
|
||||||
|
|
||||||
export type SkillStatusConfigCheck = {
|
export type SkillStatusConfigCheck = {
|
||||||
@@ -33,6 +35,7 @@ export type SkillStatusEntry = {
|
|||||||
baseDir: string;
|
baseDir: string;
|
||||||
skillKey: string;
|
skillKey: string;
|
||||||
primaryEnv?: string;
|
primaryEnv?: string;
|
||||||
|
emoji?: string;
|
||||||
always: boolean;
|
always: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
eligible: boolean;
|
eligible: boolean;
|
||||||
@@ -60,45 +63,78 @@ function resolveSkillKey(entry: SkillEntry): string {
|
|||||||
return entry.clawdis?.skillKey ?? entry.skill.name;
|
return entry.clawdis?.skillKey ?? entry.skill.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeInstallOptions(entry: SkillEntry): SkillInstallOption[] {
|
function selectPreferredInstallSpec(
|
||||||
|
install: SkillInstallSpec[],
|
||||||
|
prefs: SkillsInstallPreferences,
|
||||||
|
): { spec: SkillInstallSpec; index: number } | undefined {
|
||||||
|
if (install.length === 0) return undefined;
|
||||||
|
const indexed = install.map((spec, index) => ({ spec, index }));
|
||||||
|
const findKind = (kind: SkillInstallSpec["kind"]) =>
|
||||||
|
indexed.find((item) => item.spec.kind === kind);
|
||||||
|
|
||||||
|
const brewSpec = findKind("brew");
|
||||||
|
const nodeSpec = findKind("node");
|
||||||
|
const goSpec = findKind("go");
|
||||||
|
const pnpmSpec = findKind("pnpm");
|
||||||
|
const shellSpec = findKind("shell");
|
||||||
|
|
||||||
|
if (prefs.preferBrew && hasBinary("brew") && brewSpec) return brewSpec;
|
||||||
|
if (nodeSpec) return nodeSpec;
|
||||||
|
if (brewSpec) return brewSpec;
|
||||||
|
if (goSpec) return goSpec;
|
||||||
|
if (pnpmSpec) return pnpmSpec;
|
||||||
|
if (shellSpec) return shellSpec;
|
||||||
|
return indexed[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeInstallOptions(
|
||||||
|
entry: SkillEntry,
|
||||||
|
prefs: SkillsInstallPreferences,
|
||||||
|
): SkillInstallOption[] {
|
||||||
const install = entry.clawdis?.install ?? [];
|
const install = entry.clawdis?.install ?? [];
|
||||||
if (install.length === 0) return [];
|
if (install.length === 0) return [];
|
||||||
return install.map((spec, index) => {
|
const preferred = selectPreferredInstallSpec(install, prefs);
|
||||||
const id = (spec.id ?? `${spec.kind}-${index}`).trim();
|
if (!preferred) return [];
|
||||||
const bins = spec.bins ?? [];
|
const { spec, index } = preferred;
|
||||||
let label = (spec.label ?? "").trim();
|
const id = (spec.id ?? `${spec.kind}-${index}`).trim();
|
||||||
if (!label) {
|
const bins = spec.bins ?? [];
|
||||||
if (spec.kind === "brew" && spec.formula) {
|
let label = (spec.label ?? "").trim();
|
||||||
label = `Install ${spec.formula} (brew)`;
|
if (spec.kind === "node" && spec.package) {
|
||||||
} else if (spec.kind === "node" && spec.package) {
|
label = `Install ${spec.package} (${prefs.nodeManager})`;
|
||||||
label = `Install ${spec.package} (node)`;
|
}
|
||||||
} else if (spec.kind === "go" && spec.module) {
|
if (!label) {
|
||||||
label = `Install ${spec.module} (go)`;
|
if (spec.kind === "brew" && spec.formula) {
|
||||||
} else if (spec.kind === "pnpm" && spec.repoPath) {
|
label = `Install ${spec.formula} (brew)`;
|
||||||
label = `Install ${spec.repoPath} (pnpm)`;
|
} else if (spec.kind === "node" && spec.package) {
|
||||||
} else if (spec.kind === "git" && spec.url) {
|
label = `Install ${spec.package} (${prefs.nodeManager})`;
|
||||||
label = `Clone ${spec.url}`;
|
} else if (spec.kind === "go" && spec.module) {
|
||||||
} else {
|
label = `Install ${spec.module} (go)`;
|
||||||
label = "Run installer";
|
} else if (spec.kind === "pnpm" && spec.repoPath) {
|
||||||
}
|
label = `Install ${spec.repoPath} (pnpm)`;
|
||||||
|
} else {
|
||||||
|
label = "Run installer";
|
||||||
}
|
}
|
||||||
return {
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
id,
|
id,
|
||||||
kind: spec.kind,
|
kind: spec.kind,
|
||||||
label,
|
label,
|
||||||
bins,
|
bins,
|
||||||
};
|
},
|
||||||
});
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSkillStatus(
|
function buildSkillStatus(
|
||||||
entry: SkillEntry,
|
entry: SkillEntry,
|
||||||
config?: ClawdisConfig,
|
config?: ClawdisConfig,
|
||||||
|
prefs?: SkillsInstallPreferences,
|
||||||
): SkillStatusEntry {
|
): SkillStatusEntry {
|
||||||
const skillKey = resolveSkillKey(entry);
|
const skillKey = resolveSkillKey(entry);
|
||||||
const skillConfig = resolveSkillConfig(config, skillKey);
|
const skillConfig = resolveSkillConfig(config, skillKey);
|
||||||
const disabled = skillConfig?.enabled === false;
|
const disabled = skillConfig?.enabled === false;
|
||||||
const always = entry.clawdis?.always === true;
|
const always = entry.clawdis?.always === true;
|
||||||
|
const emoji = entry.clawdis?.emoji ?? entry.frontmatter.emoji;
|
||||||
|
|
||||||
const requiredBins = entry.clawdis?.requires?.bins ?? [];
|
const requiredBins = entry.clawdis?.requires?.bins ?? [];
|
||||||
const requiredEnv = entry.clawdis?.requires?.env ?? [];
|
const requiredEnv = entry.clawdis?.requires?.env ?? [];
|
||||||
@@ -145,6 +181,7 @@ function buildSkillStatus(
|
|||||||
baseDir: entry.skill.baseDir,
|
baseDir: entry.skill.baseDir,
|
||||||
skillKey,
|
skillKey,
|
||||||
primaryEnv: entry.clawdis?.primaryEnv,
|
primaryEnv: entry.clawdis?.primaryEnv,
|
||||||
|
emoji,
|
||||||
always,
|
always,
|
||||||
disabled,
|
disabled,
|
||||||
eligible,
|
eligible,
|
||||||
@@ -155,7 +192,10 @@ function buildSkillStatus(
|
|||||||
},
|
},
|
||||||
missing,
|
missing,
|
||||||
configChecks,
|
configChecks,
|
||||||
install: normalizeInstallOptions(entry),
|
install: normalizeInstallOptions(
|
||||||
|
entry,
|
||||||
|
prefs ?? resolveSkillsInstallPreferences(config),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,9 +211,12 @@ export function buildWorkspaceSkillStatus(
|
|||||||
opts?.managedSkillsDir ?? path.join(CONFIG_DIR, "skills");
|
opts?.managedSkillsDir ?? path.join(CONFIG_DIR, "skills");
|
||||||
const skillEntries =
|
const skillEntries =
|
||||||
opts?.entries ?? loadWorkspaceSkillEntries(workspaceDir, opts);
|
opts?.entries ?? loadWorkspaceSkillEntries(workspaceDir, opts);
|
||||||
|
const prefs = resolveSkillsInstallPreferences(opts?.config);
|
||||||
return {
|
return {
|
||||||
workspaceDir,
|
workspaceDir,
|
||||||
managedSkillsDir,
|
managedSkillsDir,
|
||||||
skills: skillEntries.map((entry) => buildSkillStatus(entry, opts?.config)),
|
skills: skillEntries.map((entry) =>
|
||||||
|
buildSkillStatus(entry, opts?.config, prefs),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
|||||||
|
|
||||||
export type SkillInstallSpec = {
|
export type SkillInstallSpec = {
|
||||||
id?: string;
|
id?: string;
|
||||||
kind: "brew" | "node" | "go" | "pnpm" | "git" | "shell";
|
kind: "brew" | "node" | "go" | "pnpm" | "shell";
|
||||||
label?: string;
|
label?: string;
|
||||||
bins?: string[];
|
bins?: string[];
|
||||||
formula?: string;
|
formula?: string;
|
||||||
@@ -21,8 +21,6 @@ export type SkillInstallSpec = {
|
|||||||
module?: string;
|
module?: string;
|
||||||
repoPath?: string;
|
repoPath?: string;
|
||||||
script?: string;
|
script?: string;
|
||||||
url?: string;
|
|
||||||
destination?: string;
|
|
||||||
command?: string;
|
command?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -30,6 +28,7 @@ export type ClawdisSkillMetadata = {
|
|||||||
always?: boolean;
|
always?: boolean;
|
||||||
skillKey?: string;
|
skillKey?: string;
|
||||||
primaryEnv?: string;
|
primaryEnv?: string;
|
||||||
|
emoji?: string;
|
||||||
requires?: {
|
requires?: {
|
||||||
bins?: string[];
|
bins?: string[];
|
||||||
env?: string[];
|
env?: string[];
|
||||||
@@ -38,6 +37,11 @@ export type ClawdisSkillMetadata = {
|
|||||||
install?: SkillInstallSpec[];
|
install?: SkillInstallSpec[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SkillsInstallPreferences = {
|
||||||
|
preferBrew: boolean;
|
||||||
|
nodeManager: "npm" | "pnpm" | "bun";
|
||||||
|
};
|
||||||
|
|
||||||
type ParsedSkillFrontmatter = Record<string, string>;
|
type ParsedSkillFrontmatter = Record<string, string>;
|
||||||
|
|
||||||
export type SkillEntry = {
|
export type SkillEntry = {
|
||||||
@@ -141,7 +145,6 @@ function parseInstallSpec(input: unknown): SkillInstallSpec | undefined {
|
|||||||
kind !== "node" &&
|
kind !== "node" &&
|
||||||
kind !== "go" &&
|
kind !== "go" &&
|
||||||
kind !== "pnpm" &&
|
kind !== "pnpm" &&
|
||||||
kind !== "git" &&
|
|
||||||
kind !== "shell"
|
kind !== "shell"
|
||||||
) {
|
) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -160,8 +163,6 @@ function parseInstallSpec(input: unknown): SkillInstallSpec | undefined {
|
|||||||
if (typeof raw.module === "string") spec.module = raw.module;
|
if (typeof raw.module === "string") spec.module = raw.module;
|
||||||
if (typeof raw.repoPath === "string") spec.repoPath = raw.repoPath;
|
if (typeof raw.repoPath === "string") spec.repoPath = raw.repoPath;
|
||||||
if (typeof raw.script === "string") spec.script = raw.script;
|
if (typeof raw.script === "string") spec.script = raw.script;
|
||||||
if (typeof raw.url === "string") spec.url = raw.url;
|
|
||||||
if (typeof raw.destination === "string") spec.destination = raw.destination;
|
|
||||||
if (typeof raw.command === "string") spec.command = raw.command;
|
if (typeof raw.command === "string") spec.command = raw.command;
|
||||||
|
|
||||||
return spec;
|
return spec;
|
||||||
@@ -179,6 +180,21 @@ const DEFAULT_CONFIG_VALUES: Record<string, boolean> = {
|
|||||||
"browser.enabled": true,
|
"browser.enabled": true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function resolveSkillsInstallPreferences(
|
||||||
|
config?: ClawdisConfig,
|
||||||
|
): SkillsInstallPreferences {
|
||||||
|
const raw = config?.skillsInstall;
|
||||||
|
const preferBrew = raw?.preferBrew ?? true;
|
||||||
|
const managerRaw =
|
||||||
|
typeof raw?.nodeManager === "string" ? raw.nodeManager.trim() : "";
|
||||||
|
const manager = managerRaw.toLowerCase();
|
||||||
|
const nodeManager =
|
||||||
|
manager === "pnpm" || manager === "bun" || manager === "npm"
|
||||||
|
? (manager as SkillsInstallPreferences["nodeManager"])
|
||||||
|
: "npm";
|
||||||
|
return { preferBrew, nodeManager };
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveConfigPath(
|
export function resolveConfigPath(
|
||||||
config: ClawdisConfig | undefined,
|
config: ClawdisConfig | undefined,
|
||||||
pathStr: string,
|
pathStr: string,
|
||||||
@@ -253,6 +269,8 @@ function resolveClawdisMetadata(
|
|||||||
return {
|
return {
|
||||||
always:
|
always:
|
||||||
typeof clawdisObj.always === "boolean" ? clawdisObj.always : undefined,
|
typeof clawdisObj.always === "boolean" ? clawdisObj.always : undefined,
|
||||||
|
emoji:
|
||||||
|
typeof clawdisObj.emoji === "string" ? clawdisObj.emoji : undefined,
|
||||||
skillKey:
|
skillKey:
|
||||||
typeof clawdisObj.skillKey === "string"
|
typeof clawdisObj.skillKey === "string"
|
||||||
? clawdisObj.skillKey
|
? clawdisObj.skillKey
|
||||||
|
|||||||
Reference in New Issue
Block a user