crewly 1.20.130 → 1.20.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/skills/agent/core/find-skill/SKILL.md +58 -0
- package/config/skills/agent/core/find-skill/execute.sh +67 -0
- package/config/skills/agent/core/find-skill/execute.test.sh +69 -0
- package/config/skills/agent/core/generate-pdf/SKILL.md +24 -29
- package/config/skills/agent/core/generate-pdf/execute.sh +22 -78
- package/config/skills/agent/core/generate-pdf/execute.test.sh +45 -0
- package/config/skills/agent/core/install-skill/SKILL.md +67 -0
- package/config/skills/agent/core/install-skill/execute.sh +80 -0
- package/config/skills/agent/core/install-skill/execute.test.sh +74 -0
- package/config/skills/agent/pdf-tools/SKILL.md +103 -0
- package/config/skills/agent/pdf-tools/execute.sh +301 -0
- package/config/skills/agent/pdf-tools/execute.test.sh +218 -0
- package/config/skills/agent/pdf-tools/instructions.md +14 -0
- package/config/skills/agent/pdf-tools/pdf_tools.py +268 -0
- package/config/skills/agent/pdf-tools/skill.json +103 -0
- package/config/skills/agent/pdf-tools/style.css +143 -0
- package/config/skills/agent/transcribe-audio/SKILL.md +42 -5
- package/config/skills/agent/transcribe-audio/execute.sh +44 -11
- package/config/skills/agent/transcribe-audio/execute.test.sh +90 -0
- package/config/skills/agent/transcribe-audio/install-whisper-cpp.sh +126 -0
- package/config/skills/agent/transcribe-audio/install-whisper-cpp.test.sh +69 -0
- package/config/skills/agent/transcribe-audio/skill.json +67 -6
- package/config/skills/marketplace-registry.ts +8 -1
- package/config/skills/registry.json +337 -3
- package/dist/backend/backend/src/constants.d.ts +71 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +96 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.d.ts +49 -0
- package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.js +185 -0
- package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.d.ts +21 -0
- package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.js +29 -0
- package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +5 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +14 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +33 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js +7 -0
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/backend/backend/src/services/marketplace/marketplace.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/marketplace/marketplace.service.js +11 -3
- package/dist/backend/backend/src/services/marketplace/marketplace.service.js.map +1 -1
- package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.d.ts +281 -0
- package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.js +559 -0
- package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.js.map +1 -0
- package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
- package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
- package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
- package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.d.ts +138 -0
- package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.d.ts.map +1 -0
- package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.js +281 -0
- package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.js.map +1 -0
- package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.d.ts +338 -0
- package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.js +761 -0
- package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +6 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/types/marketplace.types.d.ts +16 -1
- package/dist/backend/backend/src/types/marketplace.types.d.ts.map +1 -1
- package/dist/backend/backend/src/utils/inbound-file-hint.utils.d.ts +55 -0
- package/dist/backend/backend/src/utils/inbound-file-hint.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/inbound-file-hint.utils.js +78 -0
- package/dist/backend/backend/src/utils/inbound-file-hint.utils.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +71 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +96 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +14 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +33 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/event-bus/event-bus.service.js +7 -0
- package/dist/cli/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/cli/backend/src/services/marketplace/marketplace.service.d.ts +101 -0
- package/dist/cli/backend/src/services/marketplace/marketplace.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/marketplace/marketplace.service.js +392 -0
- package/dist/cli/backend/src/services/marketplace/marketplace.service.js.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.d.ts +281 -0
- package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.js +559 -0
- package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.js.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.d.ts +138 -0
- package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.js +281 -0
- package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.js.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.d.ts +338 -0
- package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.js +761 -0
- package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +6 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/cli/backend/src/types/marketplace.types.d.ts +219 -0
- package/dist/cli/backend/src/types/marketplace.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/marketplace.types.js +6 -0
- package/dist/cli/backend/src/types/marketplace.types.js.map +1 -0
- package/dist/cli/backend/src/utils/inbound-file-hint.utils.d.ts +55 -0
- package/dist/cli/backend/src/utils/inbound-file-hint.utils.d.ts.map +1 -0
- package/dist/cli/backend/src/utils/inbound-file-hint.utils.js +78 -0
- package/dist/cli/backend/src/utils/inbound-file-hint.utils.js.map +1 -0
- package/dist/cli/backend/src/utils/package-root.d.ts +15 -0
- package/dist/cli/backend/src/utils/package-root.d.ts.map +1 -0
- package/dist/cli/backend/src/utils/package-root.js +38 -0
- package/dist/cli/backend/src/utils/package-root.js.map +1 -0
- package/dist/cli/cli/src/commands/install.d.ts +13 -1
- package/dist/cli/cli/src/commands/install.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/install.js +40 -3
- package/dist/cli/cli/src/commands/install.js.map +1 -1
- package/dist/cli/cli/src/commands/skills.d.ts +58 -0
- package/dist/cli/cli/src/commands/skills.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/skills.js +132 -0
- package/dist/cli/cli/src/commands/skills.js.map +1 -0
- package/dist/cli/cli/src/index.js +12 -3
- package/dist/cli/cli/src/index.js.map +1 -1
- package/dist/cli/cli/src/utils/archive-creator.d.ts +14 -0
- package/dist/cli/cli/src/utils/archive-creator.d.ts.map +1 -1
- package/dist/cli/cli/src/utils/archive-creator.js +24 -3
- package/dist/cli/cli/src/utils/archive-creator.js.map +1 -1
- package/dist/cli/cli/src/utils/package-validator.d.ts +2 -0
- package/dist/cli/cli/src/utils/package-validator.d.ts.map +1 -1
- package/dist/cli/cli/src/utils/package-validator.js +16 -0
- package/dist/cli/cli/src/utils/package-validator.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Find Skill
|
|
3
|
+
description: Find a skill that gives you a capability you lack (a file type you cannot read, a missing tool). Searches bundled Crewly skills, installed skills and the marketplace; says which are official, installed and ready. Run it BEFORE telling the user you cannot do something.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: automation
|
|
6
|
+
skillType: claude-skill
|
|
7
|
+
assignableRoles:
|
|
8
|
+
- "*"
|
|
9
|
+
triggers:
|
|
10
|
+
- find skill
|
|
11
|
+
- missing capability
|
|
12
|
+
- cannot read this file
|
|
13
|
+
- not installed
|
|
14
|
+
- need a tool
|
|
15
|
+
tags:
|
|
16
|
+
- skills
|
|
17
|
+
- marketplace
|
|
18
|
+
- install
|
|
19
|
+
- capability
|
|
20
|
+
- auto-install
|
|
21
|
+
execution:
|
|
22
|
+
type: script
|
|
23
|
+
script:
|
|
24
|
+
file: execute.sh
|
|
25
|
+
interpreter: bash
|
|
26
|
+
timeoutMs: 60000
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Find Skill
|
|
30
|
+
|
|
31
|
+
Before you tell the user "I can't do that" or "X is not installed", look for a
|
|
32
|
+
skill that does it.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bash {{AGENT_SKILLS_PATH}}/core/find-skill/execute.sh --query "transcribe a voice message"
|
|
36
|
+
bash {{AGENT_SKILLS_PATH}}/core/find-skill/execute.sh --query "read a pdf" --limit 3
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Output
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{"success":true,"query":"transcribe a voice message",
|
|
43
|
+
"next":"transcribe-audio is an official skill (bundled with Crewly) that is not ready yet. Tell the user in one line that you are installing it (about 6 min), then run install-skill --id transcribe-audio and continue when the completion message arrives.",
|
|
44
|
+
"candidates":[{"id":"transcribe-audio","official":true,"officialReason":"bundled with Crewly",
|
|
45
|
+
"installed":true,"ready":false,"setup":{"declared":true,"estimatedMinutes":6,"satisfied":false,"missing":["whisper-cli","whisper-model"]},
|
|
46
|
+
"executePath":"/…/transcribe-audio/execute.sh","source":"bundled"}]}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Follow `next`:
|
|
50
|
+
|
|
51
|
+
- **ready** — use the skill now (`executePath`).
|
|
52
|
+
- **official, not ready** — tell the user in one line that you are installing it
|
|
53
|
+
and roughly how long it takes, then `install-skill --id <id>`.
|
|
54
|
+
- **third-party** (`official:false`) — ask the owner in chat first; install only
|
|
55
|
+
after they say yes (`install-skill --id <id> --approved-by-owner`).
|
|
56
|
+
- **no candidates** — say plainly what you cannot do.
|
|
57
|
+
|
|
58
|
+
Official = bundled with Crewly, or listed in the official registry by the Crewly Team.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# find-skill — find a skill that gives you a capability you lack.
|
|
4
|
+
#
|
|
5
|
+
# Searches the skills bundled with Crewly, installed marketplace skills and
|
|
6
|
+
# the marketplace registry by capability, name, tags and description.
|
|
7
|
+
# Backed by GET /api/skill-setup/find.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# bash execute.sh --query "transcribe a voice message"
|
|
11
|
+
# bash execute.sh --query "read a pdf" --limit 3
|
|
12
|
+
# bash execute.sh '{"query":"…"}'
|
|
13
|
+
#
|
|
14
|
+
# Output: {success, query, candidates:[{id, name, official, officialReason,
|
|
15
|
+
# installed, ready, setup:{declared, estimatedMinutes, satisfied, missing},
|
|
16
|
+
# executePath, …}], registryAvailable, next}
|
|
17
|
+
# `next` says what to do: use it, install it (official), or ask the owner.
|
|
18
|
+
# =============================================================================
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
22
|
+
|
|
23
|
+
print_usage() {
|
|
24
|
+
cat <<'EOF_USAGE'
|
|
25
|
+
Usage:
|
|
26
|
+
bash execute.sh --query "transcribe a voice message" [--limit 5]
|
|
27
|
+
bash execute.sh '{"query":"…","limit":5}'
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--query | -q What you need, in plain words (required)
|
|
31
|
+
--limit Maximum candidates (default 8)
|
|
32
|
+
--help | -h Show this help
|
|
33
|
+
EOF_USAGE
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
INPUT_JSON=""; QUERY=""; LIMIT=""
|
|
37
|
+
if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then INPUT_JSON="$1"; shift || true; fi
|
|
38
|
+
while [[ $# -gt 0 ]]; do
|
|
39
|
+
case "$1" in
|
|
40
|
+
--query|-q) [ $# -ge 2 ] || error_exit "--query requires a value"; QUERY="$2"; shift 2 ;;
|
|
41
|
+
--limit) [ $# -ge 2 ] || error_exit "--limit requires a value"; LIMIT="$2"; shift 2 ;;
|
|
42
|
+
--help|-h) print_usage; exit 0 ;;
|
|
43
|
+
*) error_exit "Unknown option: $1" ;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
if [ -n "$INPUT_JSON" ]; then
|
|
47
|
+
INPUT=$(read_json_input "$INPUT_JSON")
|
|
48
|
+
[ -z "$QUERY" ] && QUERY=$(printf '%s' "$INPUT" | jq -r '.query // empty')
|
|
49
|
+
[ -z "$LIMIT" ] && LIMIT=$(printf '%s' "$INPUT" | jq -r '.limit // empty')
|
|
50
|
+
fi
|
|
51
|
+
require_param "query (--query)" "$QUERY"
|
|
52
|
+
[ -z "$LIMIT" ] || [[ "$LIMIT" =~ ^[0-9]+$ ]] || error_exit "--limit must be a number"
|
|
53
|
+
|
|
54
|
+
ENDPOINT="/skill-setup/find?query=$(printf '%s' "$QUERY" | jq -sRr @uri)"
|
|
55
|
+
[ -n "$LIMIT" ] && ENDPOINT="${ENDPOINT}&limit=${LIMIT}"
|
|
56
|
+
|
|
57
|
+
ERR_FILE=$(mktemp); trap 'rm -f "$ERR_FILE"' EXIT
|
|
58
|
+
RESPONSE=$(api_call GET "$ENDPOINT" 2>"$ERR_FILE") || {
|
|
59
|
+
ERR=$(tail -n 1 "$ERR_FILE")
|
|
60
|
+
printf '%s' "$ERR" | jq -c '{success: false, error: (.details.error // .error // "find failed")}' 2>/dev/null \
|
|
61
|
+
|| jq -cn --arg e "$ERR" '{success: false, error: $e}'
|
|
62
|
+
exit 1
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
printf '%s' "$RESPONSE" | jq -c '.data | {success: true, query, next, registryAvailable,
|
|
66
|
+
candidates: [.candidates[] | {id, name, description: (.description | .[0:200]), official, officialReason,
|
|
67
|
+
installed, ready, setup, executePath, source}]}'
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for find-skill — run with: bash execute.test.sh </dev/null
|
|
3
|
+
# A python HTTP stub plays the backend; asserts the request and the output shape.
|
|
4
|
+
set -u
|
|
5
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
EXEC="$HERE/execute.sh"
|
|
7
|
+
PASS=0; FAIL=0
|
|
8
|
+
check() {
|
|
9
|
+
local name="$1"; local got="$2"; local want="$3"
|
|
10
|
+
if [ "$got" = "$want" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $name"; echo " want: $want"; echo " got: $got"; fi
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
PORT=18851
|
|
14
|
+
STUB_LOG="$(mktemp)"; export STUB_LOG; export STUB_PORT="$PORT"
|
|
15
|
+
python3 -u <<'PY' >/dev/null 2>&1 &
|
|
16
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
17
|
+
from urllib.parse import urlparse, parse_qs
|
|
18
|
+
import json, os
|
|
19
|
+
LOG = os.environ['STUB_LOG']; PORT = int(os.environ['STUB_PORT'])
|
|
20
|
+
class H(BaseHTTPRequestHandler):
|
|
21
|
+
def reply(self, code, body):
|
|
22
|
+
self.send_response(code); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
23
|
+
self.wfile.write(json.dumps(body).encode())
|
|
24
|
+
def do_GET(self):
|
|
25
|
+
u = urlparse(self.path); q = parse_qs(u.query)
|
|
26
|
+
open(LOG, 'w').write(json.dumps({'path': u.path, 'query': q, 'agent': self.headers.get('X-Agent-Session')}))
|
|
27
|
+
if q.get('query', [''])[0] == 'boom':
|
|
28
|
+
return self.reply(500, {'success': False, 'error': 'registry exploded'})
|
|
29
|
+
self.reply(200, {'success': True, 'data': {'query': q['query'][0], 'registryAvailable': True,
|
|
30
|
+
'next': 'transcribe-audio is an official skill (bundled with Crewly) that is not ready yet. Tell the user…',
|
|
31
|
+
'candidates': [{'id': 'transcribe-audio', 'name': 'transcribe-audio', 'description': 'x' * 500, 'official': True,
|
|
32
|
+
'officialReason': 'bundled with Crewly', 'installed': True, 'ready': False, 'tags': ['audio'], 'triggers': [],
|
|
33
|
+
'setup': {'declared': True, 'estimatedMinutes': 6, 'satisfied': False, 'missing': ['whisper-cli']},
|
|
34
|
+
'executePath': '/pkg/transcribe-audio/execute.sh', 'source': 'bundled', 'score': 12}]}})
|
|
35
|
+
def log_message(self, *a, **k): pass
|
|
36
|
+
HTTPServer(('127.0.0.1', PORT), H).serve_forever()
|
|
37
|
+
PY
|
|
38
|
+
STUB_PID=$!
|
|
39
|
+
disown "$STUB_PID" 2>/dev/null || true
|
|
40
|
+
trap 'kill "$STUB_PID" >/dev/null 2>&1; rm -f "$STUB_LOG"' EXIT
|
|
41
|
+
for i in $(seq 1 30); do curl -s -o /dev/null -m 0.2 "http://127.0.0.1:${PORT}/" 2>/dev/null && break; sleep 0.1; done
|
|
42
|
+
|
|
43
|
+
run() { CREWLY_SESSION_NAME=test-agent CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>/dev/null; }
|
|
44
|
+
run_err() { CREWLY_SESSION_NAME=test-agent CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>&1 >/dev/null | jq -rs 'last.error' 2>/dev/null; }
|
|
45
|
+
|
|
46
|
+
OUT=$(run --query "transcribe a voice message (m4a)" --limit 3)
|
|
47
|
+
check "path" "$(jq -r .path "$STUB_LOG")" "/api/skill-setup/find"
|
|
48
|
+
check "query encoded + sent" "$(jq -r '.query.query[0]' "$STUB_LOG")" "transcribe a voice message (m4a)"
|
|
49
|
+
check "limit sent" "$(jq -r '.query.limit[0]' "$STUB_LOG")" "3"
|
|
50
|
+
check "agent header" "$(jq -r .agent "$STUB_LOG")" "test-agent"
|
|
51
|
+
check "success" "$(printf '%s' "$OUT" | jq -r .success)" "true"
|
|
52
|
+
check "next passed through" "$(printf '%s' "$OUT" | jq -r '.next | startswith("transcribe-audio is an official skill")')" "true"
|
|
53
|
+
check "candidate fields" "$(printf '%s' "$OUT" | jq -c '.candidates[0] | {id, official, ready, source, missing: .setup.missing}')" '{"id":"transcribe-audio","official":true,"ready":false,"source":"bundled","missing":["whisper-cli"]}'
|
|
54
|
+
check "description trimmed" "$(printf '%s' "$OUT" | jq -r '.candidates[0].description | length')" "200"
|
|
55
|
+
check "internal score dropped" "$(printf '%s' "$OUT" | jq -r '.candidates[0] | has("score")')" "false"
|
|
56
|
+
|
|
57
|
+
run '{"query":"语音 转文字"}' >/dev/null
|
|
58
|
+
check "json input" "$(jq -r '.query.query[0]' "$STUB_LOG")" "语音 转文字"
|
|
59
|
+
|
|
60
|
+
OUT=$(run --query boom); RC=$?
|
|
61
|
+
check "backend error: exit 1" "$RC" "1"
|
|
62
|
+
check "backend error: message" "$(printf '%s' "$OUT" | jq -r .error)" "registry exploded"
|
|
63
|
+
|
|
64
|
+
check "missing query" "$(run_err)" "Missing required parameter: query (--query)"
|
|
65
|
+
check "bad limit" "$(run_err --query x --limit lots)" "--limit must be a number"
|
|
66
|
+
check "unknown option" "$(run_err --install)" "Unknown option: --install"
|
|
67
|
+
|
|
68
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
69
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Generate PDF
|
|
3
|
-
description: Convert a Markdown file to a styled PDF with full CJK (Chinese/Japanese/Korean) font support.
|
|
4
|
-
version:
|
|
3
|
+
description: Convert a Markdown (or HTML) file to a styled PDF with full CJK (Chinese/Japanese/Korean) font support. Thin wrapper over the pdf-tools skill — headless Chrome preferred, WeasyPrint fallback. For multi-part documents, merging or reading PDFs use pdf-tools directly.
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
category: document
|
|
6
6
|
skillType: claude-skill
|
|
7
7
|
assignableRoles:
|
|
@@ -29,7 +29,7 @@ tags:
|
|
|
29
29
|
- pdf
|
|
30
30
|
- document
|
|
31
31
|
- markdown
|
|
32
|
-
-
|
|
32
|
+
- chrome
|
|
33
33
|
- weasyprint
|
|
34
34
|
- cjk
|
|
35
35
|
execution:
|
|
@@ -42,47 +42,42 @@ execution:
|
|
|
42
42
|
|
|
43
43
|
# Generate PDF
|
|
44
44
|
|
|
45
|
-
Convert a Markdown file to a professionally styled PDF with full CJK
|
|
45
|
+
Convert a Markdown (or HTML) file to a professionally styled PDF with full CJK
|
|
46
|
+
(Chinese/Japanese/Korean) font support.
|
|
47
|
+
|
|
48
|
+
This skill is kept for compatibility; it calls **`pdf-tools`**
|
|
49
|
+
(`{{AGENT_SKILLS_PATH}}/pdf-tools/execute.sh`), which also merges several parts
|
|
50
|
+
into one PDF and reads PDFs. Prefer `pdf-tools` for anything beyond one file.
|
|
46
51
|
|
|
47
52
|
## Pipeline
|
|
48
53
|
|
|
49
|
-
1. **Markdown → HTML
|
|
50
|
-
|
|
54
|
+
1. **Markdown → HTML** with a clean A4 stylesheet (CJK font stacks: PingFang /
|
|
55
|
+
Hiragino on macOS, Noto CJK on Linux). HTML input is printed as-is.
|
|
56
|
+
2. **HTML → PDF** with headless Chrome/Chromium when installed, else WeasyPrint.
|
|
57
|
+
Chrome renders CJK correctly whenever CJK fonts are installed (macOS has them;
|
|
58
|
+
on Linux the pdf-tools setup installs `fonts-noto-cjk`).
|
|
51
59
|
|
|
52
60
|
## Parameters
|
|
53
61
|
|
|
54
62
|
| Parameter | Required | Description |
|
|
55
63
|
|-----------|----------|-------------|
|
|
56
|
-
| `input` | Yes | Path to the input Markdown file |
|
|
57
|
-
| `output` | No | Path for the output PDF
|
|
58
|
-
| `title` | No | Document title
|
|
64
|
+
| `input` | Yes | Path to the input Markdown (or HTML) file |
|
|
65
|
+
| `output` | No | Path for the output PDF (defaults to the input path with `.pdf`) |
|
|
66
|
+
| `title` | No | Document title |
|
|
67
|
+
| `css` | No | Extra stylesheet appended to the default one |
|
|
68
|
+
| `engine` | No | `auto` (default), `chrome`, `weasyprint` |
|
|
59
69
|
|
|
60
70
|
## Example
|
|
61
71
|
|
|
62
72
|
```bash
|
|
63
|
-
bash
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Minimal usage (auto-generates output path)
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
bash config/skills/agent/core/generate-pdf/execute.sh '{"input":"/tmp/report.md"}'
|
|
73
|
+
bash {{AGENT_SKILLS_PATH}}/core/generate-pdf/execute.sh '{"input":"/tmp/report.md","output":"/tmp/report.pdf","title":"My Report"}'
|
|
70
74
|
```
|
|
71
75
|
|
|
72
|
-
This produces `/tmp/report.pdf`.
|
|
73
|
-
|
|
74
|
-
## Important Notes
|
|
75
|
-
|
|
76
|
-
- **Always use this skill** instead of manually running pandoc + Chrome headless
|
|
77
|
-
- **Chrome headless cannot render Chinese/CJK characters** — they appear blank in the PDF
|
|
78
|
-
- **weasyprint** properly embeds CJK fonts (PingFang SC, Hiragino Sans GB, STHeiti) on macOS
|
|
79
|
-
- The skill auto-generates a CSS header with CJK font declarations if one doesn't exist
|
|
80
|
-
- Output is a clean, styled PDF suitable for sharing via Slack or email
|
|
81
|
-
|
|
82
76
|
## Output
|
|
83
77
|
|
|
84
|
-
JSON with the path to the generated PDF file:
|
|
85
|
-
|
|
86
78
|
```json
|
|
87
|
-
{"success": true, "pdf": "/tmp/report.pdf", "size": 364024}
|
|
79
|
+
{"success": true, "pdf": "/tmp/report.pdf", "size": 364024, "engine": "chrome"}
|
|
88
80
|
```
|
|
81
|
+
|
|
82
|
+
If a dependency is missing the result carries `"needsSetup": true, "skill": "pdf-tools"`:
|
|
83
|
+
tell the user you are setting up the PDF tools and run `install-skill --id pdf-tools`.
|
|
@@ -1,88 +1,32 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# generate-pdf — Markdown (or HTML) → styled PDF with CJK support.
|
|
4
|
+
#
|
|
5
|
+
# Kept for compatibility: the work is done by the `pdf-tools` skill
|
|
6
|
+
# (headless Chrome preferred, WeasyPrint fallback, CJK-ready stylesheet).
|
|
7
|
+
# Input and output shapes are unchanged:
|
|
8
|
+
# bash execute.sh '{"input":"/tmp/report.md","output":"/tmp/report.pdf","title":"My Report"}'
|
|
9
|
+
# → {"success":true,"pdf":"/tmp/report.pdf","size":364024,"engine":"chrome"}
|
|
10
|
+
# A missing dependency returns pdf-tools' `needsSetup` JSON
|
|
11
|
+
# (run install-skill --id pdf-tools).
|
|
12
|
+
# =============================================================================
|
|
4
13
|
set -euo pipefail
|
|
5
14
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
15
|
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
16
|
+
PDF_TOOLS="${SCRIPT_DIR}/../../pdf-tools/execute.sh"
|
|
7
17
|
|
|
8
18
|
INPUT=$(read_json_input "${1:-}")
|
|
9
19
|
[ -z "$INPUT" ] && error_exit "Usage: execute.sh '{\"input\":\"/tmp/report.md\",\"output\":\"/tmp/report.pdf\",\"title\":\"My Report\"}'"
|
|
20
|
+
[ -f "$PDF_TOOLS" ] || error_exit "pdf-tools skill not found at ${PDF_TOOLS}"
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
TITLE=$(printf '%s' "$INPUT" | jq -r '.title // empty')
|
|
22
|
+
SRC=$(printf '%s' "$INPUT" | jq -r '.input // empty')
|
|
23
|
+
require_param "input" "$SRC"
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
REQUEST=$(printf '%s' "$INPUT" | jq -c '{action: "render", input: .input}
|
|
26
|
+
+ (if .output then {output: .output} else {} end)
|
|
27
|
+
+ (if .title then {title: .title} else {} end)
|
|
28
|
+
+ (if .css then {css: .css} else {} end)
|
|
29
|
+
+ (if .engine then {engine: .engine} else {} end)')
|
|
16
30
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
PDF_PATH="${MD_PATH%.md}.pdf"
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
# Intermediate HTML path
|
|
23
|
-
HTML_PATH="${PDF_PATH%.pdf}.html"
|
|
24
|
-
|
|
25
|
-
# Default title from filename
|
|
26
|
-
if [ -z "$TITLE" ]; then
|
|
27
|
-
TITLE=$(basename "$MD_PATH" .md | tr '-' ' ' | tr '_' ' ')
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
# Ensure the CJK CSS header exists
|
|
31
|
-
CJK_HEADER="/tmp/cjk-header.html"
|
|
32
|
-
if [ ! -f "$CJK_HEADER" ]; then
|
|
33
|
-
cat > "$CJK_HEADER" << 'CSSEOF'
|
|
34
|
-
<style>
|
|
35
|
-
body {
|
|
36
|
-
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
|
|
37
|
-
max-width: 900px;
|
|
38
|
-
margin: 0 auto;
|
|
39
|
-
padding: 2rem;
|
|
40
|
-
line-height: 1.8;
|
|
41
|
-
color: #1a1a1a;
|
|
42
|
-
font-size: 14px;
|
|
43
|
-
}
|
|
44
|
-
h1, h2, h3, h4 {
|
|
45
|
-
font-family: -apple-system, "PingFang SC", "Hiragino Sans GB", sans-serif;
|
|
46
|
-
margin-top: 1.5em;
|
|
47
|
-
color: #111;
|
|
48
|
-
}
|
|
49
|
-
h1 { font-size: 1.8em; border-bottom: 2px solid #333; padding-bottom: 0.3em; }
|
|
50
|
-
h2 { font-size: 1.4em; border-bottom: 1px solid #ccc; padding-bottom: 0.2em; }
|
|
51
|
-
h3 { font-size: 1.15em; }
|
|
52
|
-
table { border-collapse: collapse; width: 100%; margin: 1em 0; font-size: 13px; }
|
|
53
|
-
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
|
54
|
-
th { background: #f5f5f5; font-weight: 600; }
|
|
55
|
-
tr:nth-child(even) { background: #fafafa; }
|
|
56
|
-
blockquote { border-left: 4px solid #4a90d9; margin: 1em 0; padding: 0.5em 1em; background: #f0f6ff; color: #333; }
|
|
57
|
-
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; font-size: 0.9em; }
|
|
58
|
-
hr { border: none; border-top: 1px solid #ddd; margin: 2em 0; }
|
|
59
|
-
strong { color: #111; }
|
|
60
|
-
</style>
|
|
61
|
-
CSSEOF
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
# Check dependencies
|
|
65
|
-
if ! command -v pandoc &>/dev/null; then
|
|
66
|
-
error_exit "pandoc is not installed. Run: brew install pandoc"
|
|
67
|
-
fi
|
|
68
|
-
if ! command -v weasyprint &>/dev/null; then
|
|
69
|
-
error_exit "weasyprint is not installed. Run: brew install weasyprint"
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
# Step 1: Markdown → HTML (with CJK CSS header)
|
|
73
|
-
pandoc "$MD_PATH" -o "$HTML_PATH" \
|
|
74
|
-
--standalone \
|
|
75
|
-
--metadata title="$TITLE" \
|
|
76
|
-
--include-in-header="$CJK_HEADER" 2>/dev/null
|
|
77
|
-
|
|
78
|
-
# Step 2: HTML → PDF (weasyprint handles CJK fonts correctly)
|
|
79
|
-
weasyprint "$HTML_PATH" "$PDF_PATH" 2>/dev/null
|
|
80
|
-
|
|
81
|
-
# Get file size
|
|
82
|
-
PDF_SIZE=$(wc -c < "$PDF_PATH" | tr -d ' ')
|
|
83
|
-
|
|
84
|
-
# Clean up intermediate HTML
|
|
85
|
-
rm -f "$HTML_PATH"
|
|
86
|
-
|
|
87
|
-
# Output result
|
|
88
|
-
echo "{\"success\":true,\"pdf\":\"${PDF_PATH}\",\"size\":${PDF_SIZE}}"
|
|
31
|
+
RESULT=$(bash "$PDF_TOOLS" "$REQUEST" </dev/null) || { printf '%s\n' "$RESULT"; exit 1; }
|
|
32
|
+
printf '%s' "$RESULT" | jq -c '{success, pdf, size, engine}'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for generate-pdf (wrapper over pdf-tools) — run with: bash execute.test.sh </dev/null
|
|
3
|
+
# Uses a fake Chrome and a stub python-markdown on an isolated PATH. Exit 0 on pass.
|
|
4
|
+
set -u
|
|
5
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
EXEC="$HERE/execute.sh"
|
|
7
|
+
PASS=0; FAIL=0
|
|
8
|
+
check() {
|
|
9
|
+
local name="$1"; local got="$2"; local want="$3"
|
|
10
|
+
if [ "$got" = "$want" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $name"; echo " want: $want"; echo " got: $got"; fi
|
|
11
|
+
}
|
|
12
|
+
JQ="$(command -v jq)"; BASH_BIN="${TEST_BASH:-$(command -v bash)}"
|
|
13
|
+
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
|
|
14
|
+
mkdir -p "$T/bin" "$T/stub/markdown"
|
|
15
|
+
for tool in bash jq python3 mktemp cat rm tail head dirname mkdir sed tr wc sleep kill id awk grep basename cp uname date; do
|
|
16
|
+
src="$(command -v "$tool" 2>/dev/null)" && ln -sf "$src" "$T/bin/$tool"
|
|
17
|
+
done
|
|
18
|
+
printf '%%PDF-1.4\n%% fake\n%%%%EOF\n' > "$T/fixture.pdf"
|
|
19
|
+
cat > "$T/chrome" <<EOT
|
|
20
|
+
#!/bin/bash
|
|
21
|
+
for a in "\$@"; do case "\$a" in --print-to-pdf=*) cp "$T/fixture.pdf" "\${a#--print-to-pdf=}" ;; esac; done
|
|
22
|
+
EOT
|
|
23
|
+
chmod 755 "$T/chrome"
|
|
24
|
+
printf 'def markdown(text, **kw):\n return "<p>" + text + "</p>"\n' > "$T/stub/markdown/__init__.py"
|
|
25
|
+
printf '# 报告\n' > "$T/report.md"
|
|
26
|
+
run() { env -i HOME="$T" PATH="$T/bin" CREWLY_HOME="$T/.crewly" PYTHONPATH="$T/stub" PDF_TOOLS_CHROME_PATHS="" "$@" "$BASH_BIN" "$EXEC" "$JSON" 2>/dev/null </dev/null; }
|
|
27
|
+
|
|
28
|
+
JSON="{\"input\":\"$T/report.md\",\"title\":\"报告\"}"
|
|
29
|
+
OUT=$(run CHROME_BIN="$T/chrome")
|
|
30
|
+
check "same output shape" "$(printf '%s' "$OUT" | "$JQ" -c 'keys')" '["engine","pdf","size","success"]'
|
|
31
|
+
check "success" "$(printf '%s' "$OUT" | "$JQ" -r .success)" "true"
|
|
32
|
+
check "default output path" "$(printf '%s' "$OUT" | "$JQ" -r .pdf)" "$T/report.pdf"
|
|
33
|
+
check "engine" "$(printf '%s' "$OUT" | "$JQ" -r .engine)" "chrome"
|
|
34
|
+
|
|
35
|
+
JSON="{\"input\":\"$T/report.md\",\"output\":\"$T/x/out.pdf\"}"
|
|
36
|
+
OUT=$(run); RC=$?
|
|
37
|
+
check "no engine: exit 1" "$RC" "1"
|
|
38
|
+
check "no engine: needsSetup from pdf-tools" "$(printf '%s' "$OUT" | "$JQ" -r '.needsSetup, .skill' | tr '\n' ' ')" "true pdf-tools "
|
|
39
|
+
|
|
40
|
+
JSON='{"title":"x"}'
|
|
41
|
+
ERR=$(env -i HOME="$T" PATH="$T/bin" "$BASH_BIN" "$EXEC" "$JSON" 2>&1 >/dev/null </dev/null)
|
|
42
|
+
check "missing input" "$(printf '%s' "$ERR" | "$JQ" -rs 'last.error')" "Missing required parameter: input"
|
|
43
|
+
|
|
44
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
45
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Install Skill
|
|
3
|
+
description: Install a skill (and the system tools, models and Python packages it declares) as a background job. Returns a job id at once; you get a [SKILL INSTALLED] / [SKILL INSTALL FAILED] message when it ends. Official skills install without asking; third-party ones need the owner's yes in chat first.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: automation
|
|
6
|
+
skillType: claude-skill
|
|
7
|
+
assignableRoles:
|
|
8
|
+
- "*"
|
|
9
|
+
triggers:
|
|
10
|
+
- install skill
|
|
11
|
+
- set up skill
|
|
12
|
+
- needsSetup
|
|
13
|
+
- missing dependency
|
|
14
|
+
tags:
|
|
15
|
+
- skills
|
|
16
|
+
- marketplace
|
|
17
|
+
- install
|
|
18
|
+
- setup
|
|
19
|
+
- auto-install
|
|
20
|
+
execution:
|
|
21
|
+
type: script
|
|
22
|
+
script:
|
|
23
|
+
file: execute.sh
|
|
24
|
+
interpreter: bash
|
|
25
|
+
timeoutMs: 60000
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# Install Skill
|
|
29
|
+
|
|
30
|
+
Use after `find-skill`, or when a skill answered `"needsSetup": true, "skill": "<id>"`.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bash {{AGENT_SKILLS_PATH}}/core/install-skill/execute.sh --id transcribe-audio --resume "transcribe the voice message Steve sent in #general"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It returns immediately:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{"success":true,"jobId":"3f9c1a2b","skillId":"transcribe-audio","state":"running","official":true,
|
|
40
|
+
"estimatedMinutes":6,"willNotify":["crewly-dev-1"],
|
|
41
|
+
"next":"Installing transcribe-audio in the background (about 6 min). Tell the user now…"}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
or `{"success":true,"state":"already-ready","executePath":"…"}` — use the skill now.
|
|
45
|
+
|
|
46
|
+
## The flow
|
|
47
|
+
|
|
48
|
+
1. Tell the user in **one line** that you are installing it and roughly how long it
|
|
49
|
+
takes ("I can't read audio yet — installing the transcription skill, about 6 min.").
|
|
50
|
+
2. Run `install-skill`. Pass `--resume` with what you were doing; it comes back to you.
|
|
51
|
+
3. When `[SKILL INSTALLED] …` arrives, tell the user it's ready and **do the original
|
|
52
|
+
task right away**. On `[SKILL INSTALL FAILED] …`, tell the user plainly what is
|
|
53
|
+
missing and the fix the message gives (e.g. a command the owner must run).
|
|
54
|
+
|
|
55
|
+
Never answer "X is not installed" and stop.
|
|
56
|
+
|
|
57
|
+
## Trust
|
|
58
|
+
|
|
59
|
+
- **Official** (bundled with Crewly, or published by the Crewly Team in the official
|
|
60
|
+
registry): installs without asking, including the system dependencies it declares.
|
|
61
|
+
- **Third-party**: refused with `reason: owner_approval_required`. Ask the owner in chat
|
|
62
|
+
(say what it is and who publishes it). Only after they say yes:
|
|
63
|
+
`install-skill --id <id> --approved-by-owner --owner-said "<their words>"`.
|
|
64
|
+
The backend checks the owner's recent chat for that yes; claiming it without one fails
|
|
65
|
+
(`owner_approval_not_found`).
|
|
66
|
+
|
|
67
|
+
Progress: `GET /api/skill-setup/jobs/<jobId>`. Setup log: `~/.crewly/logs/skill-setup/<id>.log`.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# install-skill — install a skill and its dependencies as a background job.
|
|
4
|
+
#
|
|
5
|
+
# Returns at once with a job id (or "already-ready"). When the job ends you get
|
|
6
|
+
# a message: [SKILL INSTALLED] … or [SKILL INSTALL FAILED] … with the reason.
|
|
7
|
+
# Tell the user in one line that you are installing it, then continue with
|
|
8
|
+
# other work or wait; resume the paused task when the message arrives.
|
|
9
|
+
# Backed by POST /api/skill-setup/install.
|
|
10
|
+
#
|
|
11
|
+
# Only official skills (bundled with Crewly, or published by the Crewly Team in
|
|
12
|
+
# the official registry) install without asking. A third-party skill needs the
|
|
13
|
+
# owner's yes in chat first; then pass --approved-by-owner (it is checked
|
|
14
|
+
# against the owner's chat history — claiming it without their yes fails).
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# bash execute.sh --id transcribe-audio --resume "transcribe clip.m4a for Steve"
|
|
18
|
+
# bash execute.sh --id some-skill --approved-by-owner --owner-said "好的,装 some-skill"
|
|
19
|
+
# bash execute.sh '{"id":"pdf-tools","resume":"…"}'
|
|
20
|
+
# =============================================================================
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
24
|
+
|
|
25
|
+
print_usage() {
|
|
26
|
+
cat <<'EOF_USAGE'
|
|
27
|
+
Usage:
|
|
28
|
+
bash execute.sh --id <skill-id> [--resume "what you were doing"] [--approved-by-owner --owner-said "…"] [--force]
|
|
29
|
+
bash execute.sh '{"id":"…","resume":"…","approvedByOwner":false}'
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--id Skill id from find-skill (required)
|
|
33
|
+
--resume What you were doing; echoed back in the completion message
|
|
34
|
+
--approved-by-owner The owner said yes in chat (third-party skills only; verified)
|
|
35
|
+
--owner-said Quote the owner's yes (recorded next to the verified message)
|
|
36
|
+
--force Re-run setup even if everything looks installed
|
|
37
|
+
--help | -h Show this help
|
|
38
|
+
EOF_USAGE
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
INPUT_JSON=""; ID=""; RESUME=""; APPROVED=false; FORCE=false; OWNER_SAID=""
|
|
42
|
+
if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then INPUT_JSON="$1"; shift || true; fi
|
|
43
|
+
while [[ $# -gt 0 ]]; do
|
|
44
|
+
case "$1" in
|
|
45
|
+
--id) [ $# -ge 2 ] || error_exit "--id requires a value"; ID="$2"; shift 2 ;;
|
|
46
|
+
--resume) [ $# -ge 2 ] || error_exit "--resume requires a value"; RESUME="$2"; shift 2 ;;
|
|
47
|
+
--approved-by-owner) APPROVED=true; shift ;;
|
|
48
|
+
--owner-said) [ $# -ge 2 ] || error_exit "--owner-said requires a value"; OWNER_SAID="$2"; shift 2 ;;
|
|
49
|
+
--force) FORCE=true; shift ;;
|
|
50
|
+
--help|-h) print_usage; exit 0 ;;
|
|
51
|
+
*) error_exit "Unknown option: $1" ;;
|
|
52
|
+
esac
|
|
53
|
+
done
|
|
54
|
+
if [ -n "$INPUT_JSON" ]; then
|
|
55
|
+
INPUT=$(read_json_input "$INPUT_JSON")
|
|
56
|
+
[ -z "$ID" ] && ID=$(printf '%s' "$INPUT" | jq -r '.id // empty')
|
|
57
|
+
[ -z "$RESUME" ] && RESUME=$(printf '%s' "$INPUT" | jq -r '.resume // empty')
|
|
58
|
+
[ "$(printf '%s' "$INPUT" | jq -r '.approvedByOwner // false')" = "true" ] && APPROVED=true
|
|
59
|
+
[ "$(printf '%s' "$INPUT" | jq -r '.force // false')" = "true" ] && FORCE=true
|
|
60
|
+
fi
|
|
61
|
+
require_param "id (--id)" "$ID"
|
|
62
|
+
# The quote travels as X-Agent-Authorization (lib.sh encodes it): recorded, never trusted.
|
|
63
|
+
[ -n "$OWNER_SAID" ] && export CREWLY_AGENT_AUTHORIZATION="$OWNER_SAID"
|
|
64
|
+
|
|
65
|
+
BODY=$(jq -cn --arg id "$ID" --arg resume "$RESUME" --argjson approved "$APPROVED" --argjson force "$FORCE" \
|
|
66
|
+
'{id: $id} + (if $resume != "" then {resume: $resume} else {} end)
|
|
67
|
+
+ (if $approved then {approvedByOwner: true} else {} end) + (if $force then {force: true} else {} end)')
|
|
68
|
+
|
|
69
|
+
ERR_FILE=$(mktemp); trap 'rm -f "$ERR_FILE"' EXIT
|
|
70
|
+
RESPONSE=$(api_call POST "/skill-setup/install" "$BODY" 2>"$ERR_FILE") || {
|
|
71
|
+
ERR=$(tail -n 1 "$ERR_FILE")
|
|
72
|
+
printf '%s' "$ERR" | jq -c '{success: false,
|
|
73
|
+
reason: (.details.code // "install_failed"),
|
|
74
|
+
error: (.details.error // .error // "install failed")}
|
|
75
|
+
+ (if (.details.code // "") | startswith("owner_approval") then {official: false, next: "Ask the owner in chat before installing this third-party skill."} else {} end)' 2>/dev/null \
|
|
76
|
+
|| jq -cn --arg e "$ERR" '{success: false, reason: "install_failed", error: $e}'
|
|
77
|
+
exit 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
printf '%s' "$RESPONSE" | jq -c '.data | {success: true} + .'
|