crewly 1.20.131 → 1.20.133

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.
Files changed (186) hide show
  1. package/config/constants.ts +49 -0
  2. package/config/index.ts +2 -0
  3. package/config/skills/agent/core/find-skill/SKILL.md +58 -0
  4. package/config/skills/agent/core/find-skill/execute.sh +67 -0
  5. package/config/skills/agent/core/find-skill/execute.test.sh +69 -0
  6. package/config/skills/agent/core/generate-pdf/SKILL.md +24 -29
  7. package/config/skills/agent/core/generate-pdf/execute.sh +22 -78
  8. package/config/skills/agent/core/generate-pdf/execute.test.sh +45 -0
  9. package/config/skills/agent/core/install-skill/SKILL.md +67 -0
  10. package/config/skills/agent/core/install-skill/execute.sh +80 -0
  11. package/config/skills/agent/core/install-skill/execute.test.sh +74 -0
  12. package/config/skills/agent/pdf-tools/SKILL.md +103 -0
  13. package/config/skills/agent/pdf-tools/execute.sh +301 -0
  14. package/config/skills/agent/pdf-tools/execute.test.sh +218 -0
  15. package/config/skills/agent/pdf-tools/instructions.md +14 -0
  16. package/config/skills/agent/pdf-tools/pdf_tools.py +268 -0
  17. package/config/skills/agent/pdf-tools/skill.json +103 -0
  18. package/config/skills/agent/pdf-tools/style.css +143 -0
  19. package/config/skills/agent/transcribe-audio/SKILL.md +42 -5
  20. package/config/skills/agent/transcribe-audio/execute.sh +44 -11
  21. package/config/skills/agent/transcribe-audio/execute.test.sh +90 -0
  22. package/config/skills/agent/transcribe-audio/install-whisper-cpp.sh +126 -0
  23. package/config/skills/agent/transcribe-audio/install-whisper-cpp.test.sh +69 -0
  24. package/config/skills/agent/transcribe-audio/skill.json +67 -6
  25. package/config/skills/marketplace-registry.ts +8 -1
  26. package/config/skills/registry.json +337 -3
  27. package/dist/backend/backend/src/constants.d.ts +71 -0
  28. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  29. package/dist/backend/backend/src/constants.js +96 -0
  30. package/dist/backend/backend/src/constants.js.map +1 -1
  31. package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.d.ts +56 -0
  32. package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.d.ts.map +1 -0
  33. package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.js +114 -0
  34. package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.js.map +1 -0
  35. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts +25 -0
  36. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
  37. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +73 -55
  38. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
  39. package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts +3 -0
  40. package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts.map +1 -1
  41. package/dist/backend/backend/src/controllers/cloud/cloud.routes.js +9 -0
  42. package/dist/backend/backend/src/controllers/cloud/cloud.routes.js.map +1 -1
  43. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.d.ts +49 -0
  44. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.d.ts.map +1 -0
  45. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.js +185 -0
  46. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.js.map +1 -0
  47. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.d.ts +21 -0
  48. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.d.ts.map +1 -0
  49. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.js +29 -0
  50. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.js.map +1 -0
  51. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  52. package/dist/backend/backend/src/routes/api.routes.js +5 -0
  53. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  54. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +14 -0
  55. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  56. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +33 -1
  57. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  58. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.d.ts +132 -0
  59. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.d.ts.map +1 -0
  60. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.js +228 -0
  61. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.js.map +1 -0
  62. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.d.ts +115 -0
  63. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.d.ts.map +1 -0
  64. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.js +160 -0
  65. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.js.map +1 -0
  66. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  67. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +8 -0
  68. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  69. package/dist/backend/backend/src/services/marketplace/marketplace.service.d.ts.map +1 -1
  70. package/dist/backend/backend/src/services/marketplace/marketplace.service.js +11 -3
  71. package/dist/backend/backend/src/services/marketplace/marketplace.service.js.map +1 -1
  72. package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.d.ts +281 -0
  73. package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.d.ts.map +1 -0
  74. package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.js +559 -0
  75. package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.js.map +1 -0
  76. package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
  77. package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
  78. package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
  79. package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
  80. package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.d.ts +138 -0
  81. package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.d.ts.map +1 -0
  82. package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.js +281 -0
  83. package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.js.map +1 -0
  84. package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.d.ts +338 -0
  85. package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.d.ts.map +1 -0
  86. package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.js +761 -0
  87. package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.js.map +1 -0
  88. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  89. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +6 -0
  90. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  91. package/dist/backend/backend/src/types/marketplace.types.d.ts +16 -1
  92. package/dist/backend/backend/src/types/marketplace.types.d.ts.map +1 -1
  93. package/dist/backend/backend/src/utils/inbound-file-hint.utils.d.ts +55 -0
  94. package/dist/backend/backend/src/utils/inbound-file-hint.utils.d.ts.map +1 -0
  95. package/dist/backend/backend/src/utils/inbound-file-hint.utils.js +78 -0
  96. package/dist/backend/backend/src/utils/inbound-file-hint.utils.js.map +1 -0
  97. package/dist/backend/build-info.json +2 -2
  98. package/dist/backend/config/constants.d.ts +48 -0
  99. package/dist/backend/config/constants.d.ts.map +1 -1
  100. package/dist/backend/config/constants.js +48 -0
  101. package/dist/backend/config/constants.js.map +1 -1
  102. package/dist/backend/config/index.d.ts +2 -2
  103. package/dist/backend/config/index.d.ts.map +1 -1
  104. package/dist/backend/config/index.js +2 -2
  105. package/dist/backend/config/index.js.map +1 -1
  106. package/dist/cli/backend/src/constants.d.ts +71 -0
  107. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  108. package/dist/cli/backend/src/constants.js +96 -0
  109. package/dist/cli/backend/src/constants.js.map +1 -1
  110. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +14 -0
  111. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  112. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +33 -1
  113. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  114. package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.d.ts +132 -0
  115. package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.d.ts.map +1 -0
  116. package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.js +228 -0
  117. package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.js.map +1 -0
  118. package/dist/cli/backend/src/services/marketplace/marketplace.service.d.ts +101 -0
  119. package/dist/cli/backend/src/services/marketplace/marketplace.service.d.ts.map +1 -0
  120. package/dist/cli/backend/src/services/marketplace/marketplace.service.js +392 -0
  121. package/dist/cli/backend/src/services/marketplace/marketplace.service.js.map +1 -0
  122. package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.d.ts +281 -0
  123. package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.d.ts.map +1 -0
  124. package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.js +559 -0
  125. package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.js.map +1 -0
  126. package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.d.ts +138 -0
  127. package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.d.ts.map +1 -0
  128. package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.js +281 -0
  129. package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.js.map +1 -0
  130. package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.d.ts +338 -0
  131. package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.d.ts.map +1 -0
  132. package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.js +761 -0
  133. package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.js.map +1 -0
  134. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  135. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +6 -0
  136. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  137. package/dist/cli/backend/src/types/marketplace.types.d.ts +219 -0
  138. package/dist/cli/backend/src/types/marketplace.types.d.ts.map +1 -0
  139. package/dist/cli/backend/src/types/marketplace.types.js +6 -0
  140. package/dist/cli/backend/src/types/marketplace.types.js.map +1 -0
  141. package/dist/cli/backend/src/utils/inbound-file-hint.utils.d.ts +55 -0
  142. package/dist/cli/backend/src/utils/inbound-file-hint.utils.d.ts.map +1 -0
  143. package/dist/cli/backend/src/utils/inbound-file-hint.utils.js +78 -0
  144. package/dist/cli/backend/src/utils/inbound-file-hint.utils.js.map +1 -0
  145. package/dist/cli/backend/src/utils/package-root.d.ts +15 -0
  146. package/dist/cli/backend/src/utils/package-root.d.ts.map +1 -0
  147. package/dist/cli/backend/src/utils/package-root.js +38 -0
  148. package/dist/cli/backend/src/utils/package-root.js.map +1 -0
  149. package/dist/cli/cli/src/commands/cloud.d.ts +45 -25
  150. package/dist/cli/cli/src/commands/cloud.d.ts.map +1 -1
  151. package/dist/cli/cli/src/commands/cloud.js +153 -47
  152. package/dist/cli/cli/src/commands/cloud.js.map +1 -1
  153. package/dist/cli/cli/src/commands/install.d.ts +13 -1
  154. package/dist/cli/cli/src/commands/install.d.ts.map +1 -1
  155. package/dist/cli/cli/src/commands/install.js +40 -3
  156. package/dist/cli/cli/src/commands/install.js.map +1 -1
  157. package/dist/cli/cli/src/commands/onboard-checklist.js +3 -3
  158. package/dist/cli/cli/src/commands/onboard-checklist.js.map +1 -1
  159. package/dist/cli/cli/src/commands/skills.d.ts +58 -0
  160. package/dist/cli/cli/src/commands/skills.d.ts.map +1 -0
  161. package/dist/cli/cli/src/commands/skills.js +132 -0
  162. package/dist/cli/cli/src/commands/skills.js.map +1 -0
  163. package/dist/cli/cli/src/index.js +18 -7
  164. package/dist/cli/cli/src/index.js.map +1 -1
  165. package/dist/cli/cli/src/utils/archive-creator.d.ts +14 -0
  166. package/dist/cli/cli/src/utils/archive-creator.d.ts.map +1 -1
  167. package/dist/cli/cli/src/utils/archive-creator.js +24 -3
  168. package/dist/cli/cli/src/utils/archive-creator.js.map +1 -1
  169. package/dist/cli/cli/src/utils/cloud-submit.d.ts.map +1 -1
  170. package/dist/cli/cli/src/utils/cloud-submit.js +14 -5
  171. package/dist/cli/cli/src/utils/cloud-submit.js.map +1 -1
  172. package/dist/cli/cli/src/utils/package-validator.d.ts +2 -0
  173. package/dist/cli/cli/src/utils/package-validator.d.ts.map +1 -1
  174. package/dist/cli/cli/src/utils/package-validator.js +16 -0
  175. package/dist/cli/cli/src/utils/package-validator.js.map +1 -1
  176. package/dist/cli/config/constants.d.ts +48 -0
  177. package/dist/cli/config/constants.d.ts.map +1 -1
  178. package/dist/cli/config/constants.js +48 -0
  179. package/dist/cli/config/constants.js.map +1 -1
  180. package/dist/cli/config/index.d.ts +2 -2
  181. package/dist/cli/config/index.d.ts.map +1 -1
  182. package/dist/cli/config/index.js +2 -2
  183. package/dist/cli/config/index.js.map +1 -1
  184. package/frontend/dist/assets/{index-5b98bf77.js → index-e6c403b6.js} +265 -265
  185. package/frontend/dist/index.html +1 -1
  186. package/package.json +1 -1
@@ -1243,6 +1243,55 @@ export const REDIS_CONSTANTS = {
1243
1243
  },
1244
1244
  } as const;
1245
1245
 
1246
+ /**
1247
+ * Device-code pairing with Crewly Cloud (crewly-auth `/api/cloud/device/*`).
1248
+ *
1249
+ * A machine signs in to the owner's Cloud account without anyone at it: it
1250
+ * shows a short code + link (`crewlyai.com/cloud/pair?code=ABCD-2345`), the
1251
+ * owner approves on their phone, and the machine polls until it receives its
1252
+ * token pair. Shared by `crewly cloud login` (CLI) and the backend's
1253
+ * background pairing used by `/setup` and Settings → Cloud.
1254
+ */
1255
+ export const CLOUD_DEVICE_PAIRING_CONSTANTS = {
1256
+ /** crewly-auth endpoints (relative to the Cloud API URL) */
1257
+ CLOUD_ENDPOINTS: {
1258
+ START: '/api/cloud/device/start',
1259
+ POLL: '/api/cloud/device/poll',
1260
+ REDEEM: '/api/cloud/device/redeem',
1261
+ },
1262
+ /** Local backend endpoints (owner-only; mounted under /api/cloud) */
1263
+ LOCAL_ENDPOINTS: {
1264
+ START: '/api/cloud/device/start',
1265
+ STATUS: '/api/cloud/device/status',
1266
+ CANCEL: '/api/cloud/device/cancel',
1267
+ },
1268
+ /** Poll outcomes reported by crewly-auth (RFC 8628 names where one exists) */
1269
+ POLL_STATUS: {
1270
+ PENDING: 'authorization_pending',
1271
+ SLOW_DOWN: 'slow_down',
1272
+ EXPIRED: 'expired',
1273
+ DENIED: 'denied',
1274
+ APPROVED: 'approved',
1275
+ },
1276
+ /** Poll interval when the Cloud does not say (seconds) */
1277
+ DEFAULT_INTERVAL_S: 5,
1278
+ /** Added to the interval on slow_down / rate limiting (seconds) */
1279
+ SLOW_DOWN_INCREMENT_S: 5,
1280
+ /** Longest interval the client will back off to (seconds) */
1281
+ MAX_INTERVAL_S: 60,
1282
+ /** Pairing lifetime when the Cloud does not say (seconds) */
1283
+ DEFAULT_EXPIRES_IN_S: 900,
1284
+ /** Timeout for one start / poll request (ms) */
1285
+ REQUEST_TIMEOUT_MS: 15_000,
1286
+ /** Consecutive network / server errors tolerated while polling */
1287
+ MAX_CONSECUTIVE_POLL_ERRORS: 10,
1288
+ /** `purpose` sent with a pairing (shown to the owner on the approve page) */
1289
+ PURPOSES: {
1290
+ CLI: 'cli',
1291
+ SETUP: 'setup',
1292
+ },
1293
+ } as const;
1294
+
1246
1295
  /**
1247
1296
  * Type helpers for extracting literal types from constants
1248
1297
  */
package/config/index.ts CHANGED
@@ -40,6 +40,7 @@ import {
40
40
  SERVER_CONSTANTS,
41
41
  API_SECURITY_CONSTANTS,
42
42
  EVENT_DEBOUNCE_MS,
43
+ CLOUD_DEVICE_PAIRING_CONSTANTS,
43
44
 
44
45
  // Type helpers
45
46
  type AgentStatus,
@@ -69,6 +70,7 @@ export {
69
70
  SERVER_CONSTANTS,
70
71
  API_SECURITY_CONSTANTS,
71
72
  EVENT_DEBOUNCE_MS,
73
+ CLOUD_DEVICE_PAIRING_CONSTANTS,
72
74
 
73
75
  // Type helpers
74
76
  type AgentStatus,
@@ -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. Uses pandoc for Markdown→HTML and weasyprint for HTML→PDF rendering.
4
- version: 1.0.0
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
- - pandoc
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 (Chinese/Japanese/Korean) font support.
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**: Uses `pandoc` with `--standalone` and an inline CJK-compatible CSS header
50
- 2. **HTML → PDF**: Uses `weasyprint` (NOT Chrome headless, which cannot render CJK characters)
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 file (defaults to input path with `.pdf` extension) |
58
- | `title` | No | Document title for the HTML metadata |
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 config/skills/agent/core/generate-pdf/execute.sh '{"input":"/tmp/report.md","output":"/tmp/report.pdf","title":"My Report"}'
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
- # Generate PDF from Markdown with CJK font support
3
- # Uses pandoc (md→html) + weasyprint (html→pdf)
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
- MD_PATH=$(printf '%s' "$INPUT" | jq -r '.input // empty')
12
- PDF_PATH=$(printf '%s' "$INPUT" | jq -r '.output // empty')
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
- require_param "input" "$MD_PATH"
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
- # Default output: same path with .pdf extension
18
- if [ -z "$PDF_PATH" ]; then
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`.