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.
- package/config/constants.ts +49 -0
- package/config/index.ts +2 -0
- 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/cloud/cloud-device-pairing.controller.d.ts +56 -0
- package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.js +114 -0
- package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts +25 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +73 -55
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts +3 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js +9 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.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/cloud/cloud-device-pairing.client.d.ts +132 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.js +228 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.d.ts +115 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.js +160 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +8 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.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/backend/config/constants.d.ts +48 -0
- package/dist/backend/config/constants.d.ts.map +1 -1
- package/dist/backend/config/constants.js +48 -0
- package/dist/backend/config/constants.js.map +1 -1
- package/dist/backend/config/index.d.ts +2 -2
- package/dist/backend/config/index.d.ts.map +1 -1
- package/dist/backend/config/index.js +2 -2
- package/dist/backend/config/index.js.map +1 -1
- 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/cloud/cloud-device-pairing.client.d.ts +132 -0
- package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.d.ts.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.js +228 -0
- package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.js.map +1 -0
- 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/cloud.d.ts +45 -25
- package/dist/cli/cli/src/commands/cloud.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/cloud.js +153 -47
- package/dist/cli/cli/src/commands/cloud.js.map +1 -1
- 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/onboard-checklist.js +3 -3
- package/dist/cli/cli/src/commands/onboard-checklist.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 +18 -7
- 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/cloud-submit.d.ts.map +1 -1
- package/dist/cli/cli/src/utils/cloud-submit.js +14 -5
- package/dist/cli/cli/src/utils/cloud-submit.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/dist/cli/config/constants.d.ts +48 -0
- package/dist/cli/config/constants.d.ts.map +1 -1
- package/dist/cli/config/constants.js +48 -0
- package/dist/cli/config/constants.js.map +1 -1
- package/dist/cli/config/index.d.ts +2 -2
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +2 -2
- package/dist/cli/config/index.js.map +1 -1
- package/frontend/dist/assets/{index-5b98bf77.js → index-e6c403b6.js} +265 -265
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* pdf-tools default stylesheet (Crewly, MIT).
|
|
3
|
+
*
|
|
4
|
+
* Clean A4 document typography with CJK-first font stacks: PingFang /
|
|
5
|
+
* Hiragino on macOS, Noto Sans/Serif CJK (fonts-noto-cjk) on Linux,
|
|
6
|
+
* Microsoft YaHei on Windows. Used for Markdown input; HTML input keeps its
|
|
7
|
+
* own styling unless `addDefaultStyle` is set.
|
|
8
|
+
*
|
|
9
|
+
* Page helpers: <div class="page-break"></div> starts a new page;
|
|
10
|
+
* class="keep-together" avoids splitting a block across pages.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
@page {
|
|
14
|
+
size: A4;
|
|
15
|
+
margin: 18mm 16mm 20mm 16mm;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:root {
|
|
19
|
+
--text: #1f2328;
|
|
20
|
+
--muted: #57606a;
|
|
21
|
+
--accent: #2f6feb;
|
|
22
|
+
--rule: #d8dee4;
|
|
23
|
+
--code-bg: #f3f4f6;
|
|
24
|
+
--table-head: #f0f3f6;
|
|
25
|
+
--zebra: #fafbfc;
|
|
26
|
+
--sans: "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC",
|
|
27
|
+
"Microsoft YaHei", "WenQuanYi Micro Hei", -apple-system, "Helvetica Neue", Arial, sans-serif;
|
|
28
|
+
--serif: "Songti SC", "Noto Serif CJK SC", "Noto Serif SC", "Source Han Serif SC", Georgia, serif;
|
|
29
|
+
--mono: "SF Mono", Menlo, "JetBrains Mono", "Noto Sans Mono CJK SC", Consolas, monospace;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
html {
|
|
33
|
+
-webkit-print-color-adjust: exact;
|
|
34
|
+
print-color-adjust: exact;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
body {
|
|
38
|
+
font-family: var(--sans);
|
|
39
|
+
font-size: 10.5pt;
|
|
40
|
+
line-height: 1.7;
|
|
41
|
+
color: var(--text);
|
|
42
|
+
margin: 0;
|
|
43
|
+
text-align: justify;
|
|
44
|
+
word-break: normal;
|
|
45
|
+
overflow-wrap: anywhere;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
main.doc {
|
|
49
|
+
max-width: 100%;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
h1, h2, h3, h4, h5 {
|
|
53
|
+
font-family: var(--sans);
|
|
54
|
+
font-weight: 650;
|
|
55
|
+
line-height: 1.35;
|
|
56
|
+
color: #111;
|
|
57
|
+
break-after: avoid;
|
|
58
|
+
page-break-after: avoid;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
h1 {
|
|
62
|
+
font-size: 20pt;
|
|
63
|
+
margin: 0 0 10pt;
|
|
64
|
+
padding-bottom: 6pt;
|
|
65
|
+
border-bottom: 2px solid var(--text);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
h2 {
|
|
69
|
+
font-size: 14.5pt;
|
|
70
|
+
margin: 18pt 0 8pt;
|
|
71
|
+
padding-bottom: 3pt;
|
|
72
|
+
border-bottom: 1px solid var(--rule);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
h3 { font-size: 12pt; margin: 14pt 0 6pt; }
|
|
76
|
+
h4 { font-size: 10.5pt; margin: 12pt 0 4pt; color: var(--muted); }
|
|
77
|
+
|
|
78
|
+
p { margin: 0 0 8pt; }
|
|
79
|
+
|
|
80
|
+
a { color: var(--accent); text-decoration: none; }
|
|
81
|
+
|
|
82
|
+
ul, ol { margin: 0 0 8pt; padding-left: 18pt; }
|
|
83
|
+
li { margin: 2pt 0; }
|
|
84
|
+
|
|
85
|
+
blockquote {
|
|
86
|
+
margin: 10pt 0;
|
|
87
|
+
padding: 6pt 12pt;
|
|
88
|
+
border-left: 3px solid var(--accent);
|
|
89
|
+
background: #f5f8ff;
|
|
90
|
+
color: #333;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
blockquote > :last-child { margin-bottom: 0; }
|
|
94
|
+
|
|
95
|
+
code {
|
|
96
|
+
font-family: var(--mono);
|
|
97
|
+
font-size: 0.9em;
|
|
98
|
+
background: var(--code-bg);
|
|
99
|
+
padding: 1pt 4pt;
|
|
100
|
+
border-radius: 3px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pre {
|
|
104
|
+
font-family: var(--mono);
|
|
105
|
+
font-size: 8.5pt;
|
|
106
|
+
line-height: 1.5;
|
|
107
|
+
background: var(--code-bg);
|
|
108
|
+
padding: 8pt 10pt;
|
|
109
|
+
border-radius: 4px;
|
|
110
|
+
white-space: pre-wrap;
|
|
111
|
+
break-inside: avoid;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pre code { background: none; padding: 0; }
|
|
115
|
+
|
|
116
|
+
table {
|
|
117
|
+
width: 100%;
|
|
118
|
+
border-collapse: collapse;
|
|
119
|
+
margin: 10pt 0;
|
|
120
|
+
font-size: 9.5pt;
|
|
121
|
+
break-inside: auto;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
th, td {
|
|
125
|
+
border: 1px solid var(--rule);
|
|
126
|
+
padding: 4pt 7pt;
|
|
127
|
+
text-align: left;
|
|
128
|
+
vertical-align: top;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
th { background: var(--table-head); font-weight: 600; }
|
|
132
|
+
tr:nth-child(even) td { background: var(--zebra); }
|
|
133
|
+
thead { display: table-header-group; }
|
|
134
|
+
tr { break-inside: avoid; }
|
|
135
|
+
|
|
136
|
+
img { max-width: 100%; }
|
|
137
|
+
|
|
138
|
+
hr { border: none; border-top: 1px solid var(--rule); margin: 16pt 0; }
|
|
139
|
+
|
|
140
|
+
.page-break { break-after: page; page-break-after: always; }
|
|
141
|
+
.keep-together { break-inside: avoid; page-break-inside: avoid; }
|
|
142
|
+
.muted { color: var(--muted); }
|
|
143
|
+
.serif { font-family: var(--serif); }
|
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: transcribe-audio
|
|
3
|
-
description: Transcribe a local audio (or video) file to text with timestamps using Whisper. Defaults to local whisper.cpp (free, offline
|
|
4
|
-
category: content
|
|
3
|
+
description: Transcribe a local audio (or video) file — Slack voice messages / audio clips, meetings, podcasts, interviews — to text with timestamps using Whisper. Defaults to local whisper.cpp (free, offline) and falls back to the OpenAI Whisper API (whisper-1) when the local engine is not installed. When a dependency is missing it returns `needsSetup:true` — run `install-skill --id transcribe-audio` instead of telling the user it cannot be done.
|
|
4
|
+
category: content-creation
|
|
5
5
|
assignableRoles:
|
|
6
6
|
- "*"
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.1.0"
|
|
8
|
+
author: Crewly Team
|
|
8
9
|
tags:
|
|
9
10
|
- audio
|
|
11
|
+
- voice
|
|
12
|
+
- voice-message
|
|
13
|
+
- speech-to-text
|
|
10
14
|
- transcribe
|
|
11
15
|
- transcription
|
|
12
16
|
- whisper
|
|
13
17
|
- whisper.cpp
|
|
14
18
|
- openai
|
|
15
|
-
- speech-to-text
|
|
16
19
|
- timestamps
|
|
20
|
+
- m4a
|
|
21
|
+
- mp3
|
|
22
|
+
- video
|
|
23
|
+
- 语音
|
|
24
|
+
- 录音
|
|
25
|
+
- 转文字
|
|
26
|
+
triggers:
|
|
27
|
+
- transcribe audio
|
|
28
|
+
- transcribe voice message
|
|
29
|
+
- speech to text
|
|
30
|
+
- what does this recording say
|
|
31
|
+
- audio clip
|
|
17
32
|
---
|
|
18
33
|
|
|
19
34
|
# Transcribe Audio (Whisper)
|
|
@@ -91,10 +106,32 @@ downstream consumers have a stable shape).
|
|
|
91
106
|
|
|
92
107
|
## Dependencies & Setup
|
|
93
108
|
|
|
109
|
+
**Automatic:** everything below is declared in `skill.json` → `setup`. Run
|
|
110
|
+
`install-skill --id transcribe-audio` (agents) or `crewly skills setup transcribe-audio`
|
|
111
|
+
(terminal) and Crewly installs what is missing — ffmpeg, whisper.cpp and the
|
|
112
|
+
547 MB model (sha256-verified) — and reports "already satisfied" for the rest.
|
|
113
|
+
On Linux, whisper.cpp comes from the official prebuilt Ubuntu release binaries
|
|
114
|
+
(`install-whisper-cpp.sh`, pinned + sha256-checked) with a cmake source build as
|
|
115
|
+
the fallback; it lands in `~/.crewly/bin/whisper-cli`.
|
|
116
|
+
|
|
117
|
+
**When something is missing** the skill fails with machine-readable JSON:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{"success":false,"error":"ffmpeg is required but not installed","needsSetup":true,
|
|
121
|
+
"skill":"transcribe-audio","missing":["ffmpeg"],"hint":"Run install-skill --id transcribe-audio …"}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
On `needsSetup:true`, tell the user in one line that you are installing the
|
|
125
|
+
transcription skill (a few minutes), run `install-skill --id transcribe-audio`,
|
|
126
|
+
and continue when the `[SKILL INSTALLED]` message arrives. Never just reply
|
|
127
|
+
"whisper.cpp is not installed".
|
|
128
|
+
|
|
129
|
+
Manual details:
|
|
130
|
+
|
|
94
131
|
- **`ffmpeg`** — required for both engines (audio is normalized to 16 kHz mono WAV).
|
|
95
132
|
Install: `brew install ffmpeg`.
|
|
96
133
|
- **Local engine (`whisper.cpp`)** — needs the `whisper-cli` binary and a model file:
|
|
97
|
-
- Binary: `brew install whisper-cpp` (provides `whisper-cli`)
|
|
134
|
+
- Binary: `brew install whisper-cpp` (provides `whisper-cli`); on Linux `~/.crewly/bin/whisper-cli`. Override with `FLOPOST_WHISPER_BIN`.
|
|
98
135
|
- Model: `ggml-large-v3-turbo-q5_0.bin` in `~/.flopost/whisper/` or `~/.cache/whisper-models/`. Override with `FLOPOST_WHISPER_MODEL`.
|
|
99
136
|
- If the binary or model is missing, the skill falls back to OpenAI (or reports a clear hint when `engine:"local"` is forced).
|
|
100
137
|
- **OpenAI engine** — needs an OpenAI API key. Resolution order:
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
# bash execute.sh '{"audioFile":"/x.wav","engine":"openai"}'
|
|
16
16
|
#
|
|
17
17
|
# Requires: ffmpeg, jq, curl. Local engine also needs whisper-cli + a model file.
|
|
18
|
+
# All of it is declared in skill.json's `setup` block. When something is
|
|
19
|
+
# missing the error JSON carries `"needsSetup": true, "skill": "transcribe-audio"`
|
|
20
|
+
# so an agent can go straight to `install-skill --id transcribe-audio`
|
|
21
|
+
# instead of telling the user "whisper.cpp is not installed".
|
|
18
22
|
|
|
19
23
|
set -euo pipefail
|
|
20
24
|
|
|
@@ -25,14 +29,29 @@ WHISPER_CACHE_DIR="${HOME}/.cache/whisper-models"
|
|
|
25
29
|
OPENAI_TRANSCRIBE_URL="https://api.openai.com/v1/audio/transcriptions"
|
|
26
30
|
OPENAI_MODEL="whisper-1"
|
|
27
31
|
SETTINGS_URL="${CREWLY_API_URL:-http://localhost:${WEB_PORT:-8787}}/api/settings"
|
|
32
|
+
SKILL_ID="transcribe-audio"
|
|
33
|
+
# Crewly-managed binaries (the Linux whisper.cpp install lands here).
|
|
34
|
+
CREWLY_BIN_DIR="${CREWLY_HOME:-${HOME}/.crewly}/bin"
|
|
28
35
|
|
|
29
36
|
err_json() { printf '{"success":false,"error":%s}\n' "$(printf '%s' "$1" | jq -Rsa .)"; exit 1; }
|
|
30
37
|
|
|
38
|
+
# needs_setup_json <message> <missing...> — a failure an install can fix.
|
|
39
|
+
# Works without jq (jq itself may be the missing piece).
|
|
40
|
+
needs_setup_json() {
|
|
41
|
+
local msg="$1"; shift
|
|
42
|
+
local missing="" item esc
|
|
43
|
+
for item in "$@"; do missing="${missing:+${missing},}\"${item}\""; done
|
|
44
|
+
esc=$(printf '%s' "$msg" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | tr '\n' ' ')
|
|
45
|
+
printf '{"success":false,"error":"%s","needsSetup":true,"skill":"%s","missing":[%s],"hint":"Run install-skill --id %s (it installs the missing pieces in the background and messages you when done), then retry."}\n' \
|
|
46
|
+
"$esc" "$SKILL_ID" "$missing" "$SKILL_ID"
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
# ── Input parsing ───────────────────────────────────────────────────────────────
|
|
32
51
|
INPUT="${1:-}"
|
|
33
52
|
[ -z "$INPUT" ] && err_json "Usage: execute.sh '{\"audioFile\":\"/path/to/audio.m4a\"}'"
|
|
34
53
|
|
|
35
|
-
command -v jq >/dev/null 2>&1 ||
|
|
54
|
+
command -v jq >/dev/null 2>&1 || needs_setup_json "jq is required but not installed" jq
|
|
36
55
|
|
|
37
56
|
AUDIO_FILE=$(printf '%s' "$INPUT" | jq -r '.audioFile // empty')
|
|
38
57
|
OUTPUT_FILE=$(printf '%s' "$INPUT" | jq -r '.outputFile // empty')
|
|
@@ -43,14 +62,18 @@ ENGINE=$(printf '%s' "$INPUT" | jq -r '.engine // "auto"')
|
|
|
43
62
|
[ -f "$AUDIO_FILE" ] || err_json "audioFile not found: ${AUDIO_FILE}"
|
|
44
63
|
case "$ENGINE" in auto|local|openai) ;; *) err_json "engine must be one of: auto, local, openai (got: ${ENGINE})";; esac
|
|
45
64
|
|
|
46
|
-
command -v ffmpeg >/dev/null 2>&1 ||
|
|
65
|
+
command -v ffmpeg >/dev/null 2>&1 || needs_setup_json "ffmpeg is required but not installed" ffmpeg
|
|
47
66
|
|
|
48
67
|
# ── Local whisper.cpp detection (lifted from whisperModule.ts) ───────────────────
|
|
49
68
|
resolve_whisper_binary() {
|
|
50
69
|
if [ -n "${FLOPOST_WHISPER_BIN:-}" ] && [ -x "${FLOPOST_WHISPER_BIN}" ]; then echo "${FLOPOST_WHISPER_BIN}"; return; fi
|
|
51
70
|
if [ -x "${FLOPOST_WHISPER_DIR}/whisper-cli" ]; then echo "${FLOPOST_WHISPER_DIR}/whisper-cli"; return; fi
|
|
71
|
+
if [ -x "${CREWLY_BIN_DIR}/whisper-cli" ]; then echo "${CREWLY_BIN_DIR}/whisper-cli"; return; fi
|
|
52
72
|
if command -v whisper-cli >/dev/null 2>&1; then command -v whisper-cli; return; fi
|
|
53
|
-
|
|
73
|
+
# Homebrew locations (a backend started by launchd may lack them on PATH).
|
|
74
|
+
# TRANSCRIBE_WHISPER_BIN_CANDIDATES overrides the list (tests).
|
|
75
|
+
local candidates="${TRANSCRIBE_WHISPER_BIN_CANDIDATES-/opt/homebrew/bin/whisper-cli /usr/local/bin/whisper-cli}"
|
|
76
|
+
for p in $candidates; do
|
|
54
77
|
[ -x "$p" ] && { echo "$p"; return; }
|
|
55
78
|
done
|
|
56
79
|
echo ""
|
|
@@ -61,11 +84,10 @@ resolve_whisper_model() {
|
|
|
61
84
|
if [ -f "${WHISPER_CACHE_DIR}/${MODEL_FILENAME}" ]; then echo "${WHISPER_CACHE_DIR}/${MODEL_FILENAME}"; return; fi
|
|
62
85
|
echo ""
|
|
63
86
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
esac
|
|
87
|
+
# Names of the local-engine pieces that are missing (for needsSetup JSON).
|
|
88
|
+
missing_local_parts() {
|
|
89
|
+
[ -n "$WHISPER_BIN" ] || printf '%s\n' whisper-cli
|
|
90
|
+
[ -n "$WHISPER_MODEL" ] || printf '%s\n' whisper-model
|
|
69
91
|
}
|
|
70
92
|
|
|
71
93
|
WHISPER_BIN="$(resolve_whisper_binary)"
|
|
@@ -82,7 +104,10 @@ resolve_openai_key() {
|
|
|
82
104
|
# ── Choose engine ────────────────────────────────────────────────────────────────
|
|
83
105
|
CHOSEN=""
|
|
84
106
|
if [ "$ENGINE" = "local" ]; then
|
|
85
|
-
[ "$LOCAL_AVAILABLE"
|
|
107
|
+
if [ "$LOCAL_AVAILABLE" != true ]; then
|
|
108
|
+
# shellcheck disable=SC2046 # word-splitting the list of missing parts is intended
|
|
109
|
+
needs_setup_json "engine=local requested but the local whisper.cpp engine is not set up (binary: $( [ -n "$WHISPER_BIN" ] && echo found || echo missing ); model ${MODEL_FILENAME}: $( [ -n "$WHISPER_MODEL" ] && echo found || echo missing ))" $(missing_local_parts)
|
|
110
|
+
fi
|
|
86
111
|
CHOSEN="local"
|
|
87
112
|
elif [ "$ENGINE" = "openai" ]; then
|
|
88
113
|
CHOSEN="openai"
|
|
@@ -138,7 +163,7 @@ run_local() {
|
|
|
138
163
|
# ── Engine: OpenAI Whisper API ───────────────────────────────────────────────────
|
|
139
164
|
run_openai() {
|
|
140
165
|
local key; key="$(resolve_openai_key)"
|
|
141
|
-
[ -z "$key" ] && { fail "OpenAI engine selected but no OpenAI API key found. Set OPENAI_API_KEY (Crewly secrets) or configure Settings > API Keys."; return 1; }
|
|
166
|
+
[ -z "$key" ] && { fail "OpenAI engine selected but no OpenAI API key found. Set OPENAI_API_KEY (Crewly secrets) or configure Settings > API Keys."; : > "${WORK}/no-openai-key"; return 1; }
|
|
142
167
|
echo "{\"status\":\"transcribing\",\"engine\":\"openai-whisper-1\",\"message\":\"Uploading to OpenAI Whisper API...\"}" >&2
|
|
143
168
|
local resp="${WORK}/openai.json"
|
|
144
169
|
local lang_args=(); [ "$LANGUAGE" != "auto" ] && lang_args=(-F "language=${LANGUAGE}")
|
|
@@ -179,7 +204,15 @@ if [ "$CHOSEN" = "local" ]; then
|
|
|
179
204
|
err_json "$(cat "${WORK}/engine.err" 2>/dev/null)"
|
|
180
205
|
fi
|
|
181
206
|
else
|
|
182
|
-
|
|
207
|
+
if ! run_openai; then
|
|
208
|
+
# auto mode with no local engine and no OpenAI key: installing the local
|
|
209
|
+
# engine fixes it, so say so in a way an agent can act on.
|
|
210
|
+
if [ "$ENGINE" = "auto" ] && [ -f "${WORK}/no-openai-key" ]; then
|
|
211
|
+
# shellcheck disable=SC2046
|
|
212
|
+
needs_setup_json "No transcription engine is available: the local whisper.cpp engine is not set up and there is no OpenAI API key" $(missing_local_parts)
|
|
213
|
+
fi
|
|
214
|
+
err_json "$(cat "${WORK}/engine.err" 2>/dev/null)"
|
|
215
|
+
fi
|
|
183
216
|
fi
|
|
184
217
|
|
|
185
218
|
[ -f "$RESULT_FILE" ] || err_json "Transcription produced no result"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for transcribe-audio — run with: bash execute.test.sh </dev/null
|
|
3
|
+
# Uses fake ffmpeg / ffprobe / whisper-cli on an isolated PATH and HOME, so it
|
|
4
|
+
# never touches a real model or the network. Exit 0 on pass.
|
|
5
|
+
set -u
|
|
6
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
EXEC="$HERE/execute.sh"
|
|
8
|
+
PASS=0; FAIL=0
|
|
9
|
+
check() {
|
|
10
|
+
local name="$1"; local got="$2"; local want="$3"
|
|
11
|
+
if [ "$got" = "$want" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $name"; echo " want: $want"; echo " got: $got"; fi
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
JQ="$(command -v jq)"
|
|
15
|
+
BASH_BIN="${TEST_BASH:-$(command -v bash)}"
|
|
16
|
+
[ -n "$JQ" ] || { echo "jq is required to run these tests"; exit 1; }
|
|
17
|
+
T="$(mktemp -d)"
|
|
18
|
+
trap 'rm -rf "$T"' EXIT
|
|
19
|
+
mkdir -p "$T/bin" "$T/home"
|
|
20
|
+
# Minimal PATH: basic tools + jq, but no ffmpeg / whisper-cli.
|
|
21
|
+
for tool in bash jq mktemp cat rm tail dirname mkdir sed tr sysctl nproc uname basename; do
|
|
22
|
+
src="$(command -v "$tool" 2>/dev/null)" && ln -sf "$src" "$T/bin/$tool"
|
|
23
|
+
done
|
|
24
|
+
echo "fake audio" > "$T/clip.m4a"
|
|
25
|
+
|
|
26
|
+
run() {
|
|
27
|
+
env -i HOME="$T/home" PATH="$1" CREWLY_HOME="$T/home/.crewly" CREWLY_API_URL="http://127.0.0.1:9" \
|
|
28
|
+
TRANSCRIBE_WHISPER_BIN_CANDIDATES="" "$BASH_BIN" "$EXEC" "$2" 2>/dev/null </dev/null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# 1. ffmpeg missing → needsSetup, machine-readable.
|
|
32
|
+
OUT=$(run "$T/bin" "{\"audioFile\":\"$T/clip.m4a\"}"); RC=$?
|
|
33
|
+
check "no ffmpeg: exit 1" "$RC" "1"
|
|
34
|
+
check "no ffmpeg: needsSetup" "$(printf '%s' "$OUT" | "$JQ" -r .needsSetup)" "true"
|
|
35
|
+
check "no ffmpeg: skill" "$(printf '%s' "$OUT" | "$JQ" -r .skill)" "transcribe-audio"
|
|
36
|
+
check "no ffmpeg: missing" "$(printf '%s' "$OUT" | "$JQ" -c .missing)" '["ffmpeg"]'
|
|
37
|
+
check "no ffmpeg: hint names install-skill" "$(printf '%s' "$OUT" | "$JQ" -r '.hint | test("install-skill --id transcribe-audio")')" "true"
|
|
38
|
+
|
|
39
|
+
# Fake ffmpeg (writes the last argument, the WAV) and ffprobe.
|
|
40
|
+
cat > "$T/bin/ffmpeg" <<'EOF'
|
|
41
|
+
#!/bin/bash
|
|
42
|
+
for a in "$@"; do last="$a"; done
|
|
43
|
+
echo "RIFF" > "$last"
|
|
44
|
+
EOF
|
|
45
|
+
printf '#!/bin/bash\necho 1.5\n' > "$T/bin/ffprobe"
|
|
46
|
+
chmod 755 "$T/bin/ffmpeg" "$T/bin/ffprobe"
|
|
47
|
+
|
|
48
|
+
# 2. engine=local without whisper-cli or model → needsSetup listing both.
|
|
49
|
+
OUT=$(run "$T/bin" "{\"audioFile\":\"$T/clip.m4a\",\"engine\":\"local\"}"); RC=$?
|
|
50
|
+
check "local missing: exit 1" "$RC" "1"
|
|
51
|
+
check "local missing: needsSetup" "$(printf '%s' "$OUT" | "$JQ" -r .needsSetup)" "true"
|
|
52
|
+
check "local missing: missing parts" "$(printf '%s' "$OUT" | "$JQ" -c .missing)" '["whisper-cli","whisper-model"]'
|
|
53
|
+
|
|
54
|
+
# 3. auto engine, no local engine, no OpenAI key → needsSetup (not a bare error).
|
|
55
|
+
ln -sf "$(command -v curl)" "$T/bin/curl"
|
|
56
|
+
OUT=$(run "$T/bin" "{\"audioFile\":\"$T/clip.m4a\"}"); RC=$?
|
|
57
|
+
check "auto no engine: exit 1" "$RC" "1"
|
|
58
|
+
check "auto no engine: needsSetup" "$(printf '%s' "$OUT" | "$JQ" -r .needsSetup)" "true"
|
|
59
|
+
check "auto no engine: message" "$(printf '%s' "$OUT" | "$JQ" -r '.error | test("no OpenAI API key")')" "true"
|
|
60
|
+
|
|
61
|
+
# 4. engine=openai explicitly without a key → plain error, no needsSetup (installing does not help).
|
|
62
|
+
OUT=$(run "$T/bin" "{\"audioFile\":\"$T/clip.m4a\",\"engine\":\"openai\"}")
|
|
63
|
+
check "openai no key: not needsSetup" "$(printf '%s' "$OUT" | "$JQ" -r '.needsSetup // false')" "false"
|
|
64
|
+
|
|
65
|
+
# 5. Everything present (whisper-cli in $CREWLY_HOME/bin, model in ~/.cache) → transcript.
|
|
66
|
+
mkdir -p "$T/home/.crewly/bin" "$T/home/.cache/whisper-models"
|
|
67
|
+
: > "$T/home/.cache/whisper-models/ggml-large-v3-turbo-q5_0.bin"
|
|
68
|
+
cat > "$T/home/.crewly/bin/whisper-cli" <<'EOF'
|
|
69
|
+
#!/bin/bash
|
|
70
|
+
while [ $# -gt 0 ]; do case "$1" in -of) prefix="$2"; shift 2 ;; *) shift ;; esac; done
|
|
71
|
+
printf '{"result":{"language":"zh"},"transcription":[{"offsets":{"from":0,"to":1500},"text":" 你好,今天开会"},{"offsets":{"from":1500,"to":1600},"text":" [BLANK_AUDIO]"}]}' > "${prefix}.json"
|
|
72
|
+
EOF
|
|
73
|
+
chmod 755 "$T/home/.crewly/bin/whisper-cli"
|
|
74
|
+
OUT=$(run "$T/bin" "{\"audioFile\":\"$T/clip.m4a\",\"outputFile\":\"$T/out.md\"}"); RC=$?
|
|
75
|
+
check "local ok: exit 0" "$RC" "0"
|
|
76
|
+
check "local ok: success" "$(printf '%s' "$OUT" | "$JQ" -r .success)" "true"
|
|
77
|
+
check "local ok: engine" "$(printf '%s' "$OUT" | "$JQ" -r .engine)" "whisper.cpp"
|
|
78
|
+
check "local ok: text" "$(printf '%s' "$OUT" | "$JQ" -r .text)" "你好,今天开会"
|
|
79
|
+
check "local ok: blank tag dropped" "$(printf '%s' "$OUT" | "$JQ" -r .segmentCount)" "1"
|
|
80
|
+
check "local ok: markdown written" "$(grep -c '你好,今天开会' "$T/out.md")" "2"
|
|
81
|
+
|
|
82
|
+
# 6. The shipped setup block is valid JSON and names the skill's own install script.
|
|
83
|
+
check "setup: whisper linux script" "$("$JQ" -r '.setup.steps[] | select(.id=="whisper-cli") | .install.linux.script' "$HERE/skill.json")" "install-whisper-cpp.sh"
|
|
84
|
+
[ -f "$HERE/install-whisper-cpp.sh" ] && OK=yes || OK=no
|
|
85
|
+
check "setup: install script exists" "$OK" "yes"
|
|
86
|
+
bash -n "$HERE/install-whisper-cpp.sh" && OK=yes || OK=no
|
|
87
|
+
check "setup: install script parses" "$OK" "yes"
|
|
88
|
+
|
|
89
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
90
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# install-whisper-cpp.sh — put a working `whisper-cli` in $CREWLY_BIN_DIR on Linux.
|
|
4
|
+
#
|
|
5
|
+
# Run by the Crewly skill-setup runner (the `whisper-cli` step of this skill's
|
|
6
|
+
# setup block) when whisper-cli is not already installed. Never prompts.
|
|
7
|
+
#
|
|
8
|
+
# 1. Prebuilt: the official whisper.cpp release ships Ubuntu binaries
|
|
9
|
+
# (whisper-bin-ubuntu-{x64,arm64}.tar.gz). The pinned tarball is downloaded,
|
|
10
|
+
# its sha256 checked, extracted to $CREWLY_HOME/opt/whisper.cpp-<tag>/, and a
|
|
11
|
+
# wrapper $CREWLY_BIN_DIR/whisper-cli is written that sets LD_LIBRARY_PATH to
|
|
12
|
+
# the bundled shared libraries. A smoke test (`whisper-cli --help`) decides
|
|
13
|
+
# whether the binary runs here (it needs a recent enough glibc).
|
|
14
|
+
# 2. Source: when there is no prebuilt for this CPU, or it does not run, the
|
|
15
|
+
# same tag is built with cmake into $CREWLY_HOME/opt/ and whisper-cli copied
|
|
16
|
+
# to $CREWLY_BIN_DIR. Needs cmake, a C++ compiler, curl and tar; when they
|
|
17
|
+
# are missing and root/passwordless sudo is available ($CREWLY_SUDO), they
|
|
18
|
+
# are installed with apt-get, otherwise the script says what to install.
|
|
19
|
+
#
|
|
20
|
+
# Environment (set by the runner):
|
|
21
|
+
# CREWLY_HOME Crewly home (default ~/.crewly)
|
|
22
|
+
# CREWLY_BIN_DIR where whisper-cli goes (default $CREWLY_HOME/bin)
|
|
23
|
+
# CREWLY_SUDO '' = root, 'sudo -n' = passwordless sudo, 'unavailable'
|
|
24
|
+
# WHISPER_CPP_SKIP_PREBUILT=1 force the source build (testing)
|
|
25
|
+
# WHISPER_CPP_DOWNLOAD_BASE override the download host (testing)
|
|
26
|
+
# =============================================================================
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
# ── Pinned release (sha256 from the GitHub release asset digests) ─────────────
|
|
30
|
+
WHISPER_TAG="v1.9.2"
|
|
31
|
+
SHA256_UBUNTU_X64="46811a3ecf584307480a220b9ef5ff81b7b22dc41577cbc274ce3afc61f753b1"
|
|
32
|
+
SHA256_UBUNTU_ARM64="7e26fa6a36d9174d5c0bf033ccbc026c3b5e569e2ee787058241346ef5392719"
|
|
33
|
+
DOWNLOAD_BASE="${WHISPER_CPP_DOWNLOAD_BASE:-https://github.com/ggml-org/whisper.cpp}"
|
|
34
|
+
BUILD_JOBS="$(nproc 2>/dev/null || echo 2)"
|
|
35
|
+
|
|
36
|
+
CREWLY_HOME="${CREWLY_HOME:-${HOME}/.crewly}"
|
|
37
|
+
BIN_DIR="${CREWLY_BIN_DIR:-${CREWLY_HOME}/bin}"
|
|
38
|
+
OPT_DIR="${CREWLY_HOME}/opt"
|
|
39
|
+
SUDO_MODE="${CREWLY_SUDO-unavailable}"
|
|
40
|
+
|
|
41
|
+
log() { printf '[install-whisper-cpp] %s\n' "$*" >&2; }
|
|
42
|
+
die() { log "ERROR: $*"; exit 1; }
|
|
43
|
+
|
|
44
|
+
[ "$(uname -s)" = "Linux" ] || die "this installer is for Linux (on macOS the setup uses: brew install whisper-cpp)"
|
|
45
|
+
mkdir -p "$BIN_DIR" "$OPT_DIR"
|
|
46
|
+
|
|
47
|
+
WORK="$(mktemp -d "${TMPDIR:-/tmp}/whisper-cpp-XXXXXX")"
|
|
48
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
49
|
+
|
|
50
|
+
sha256_of() {
|
|
51
|
+
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
|
|
52
|
+
else shasum -a 256 "$1" | cut -d' ' -f1; fi
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Write the wrapper that makes the bundled .so files findable.
|
|
56
|
+
write_wrapper() {
|
|
57
|
+
local real="$1" libdir="$2"
|
|
58
|
+
cat > "${BIN_DIR}/whisper-cli" <<EOF
|
|
59
|
+
#!/bin/sh
|
|
60
|
+
# Crewly wrapper for whisper.cpp ${WHISPER_TAG} (written by install-whisper-cpp.sh)
|
|
61
|
+
LD_LIBRARY_PATH="${libdir}\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}" exec "${real}" "\$@"
|
|
62
|
+
EOF
|
|
63
|
+
chmod 755 "${BIN_DIR}/whisper-cli"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
smoke_test() { "${BIN_DIR}/whisper-cli" --help >/dev/null 2>&1; }
|
|
67
|
+
|
|
68
|
+
try_prebuilt() {
|
|
69
|
+
local arch asset sha
|
|
70
|
+
arch="$(uname -m)"
|
|
71
|
+
case "$arch" in
|
|
72
|
+
x86_64|amd64) asset="whisper-bin-ubuntu-x64.tar.gz"; sha="$SHA256_UBUNTU_X64" ;;
|
|
73
|
+
aarch64|arm64) asset="whisper-bin-ubuntu-arm64.tar.gz"; sha="$SHA256_UBUNTU_ARM64" ;;
|
|
74
|
+
*) log "no prebuilt binary for ${arch}"; return 1 ;;
|
|
75
|
+
esac
|
|
76
|
+
local url="${DOWNLOAD_BASE}/releases/download/${WHISPER_TAG}/${asset}"
|
|
77
|
+
log "downloading ${url}"
|
|
78
|
+
curl -fsSL --retry 3 -o "${WORK}/${asset}" "$url" || { log "download failed"; return 1; }
|
|
79
|
+
local got; got="$(sha256_of "${WORK}/${asset}")"
|
|
80
|
+
[ "$got" = "$sha" ] || { log "checksum mismatch for ${asset}: expected ${sha}, got ${got}"; return 1; }
|
|
81
|
+
local dest="${OPT_DIR}/whisper.cpp-${WHISPER_TAG}"
|
|
82
|
+
rm -rf "$dest"; mkdir -p "$dest"
|
|
83
|
+
tar -xzf "${WORK}/${asset}" -C "$dest" --strip-components=1
|
|
84
|
+
[ -x "${dest}/whisper-cli" ] || { log "whisper-cli not found in ${asset}"; return 1; }
|
|
85
|
+
write_wrapper "${dest}/whisper-cli" "$dest"
|
|
86
|
+
smoke_test || { log "the prebuilt whisper-cli does not run on this system (glibc too old?)"; return 1; }
|
|
87
|
+
log "installed prebuilt whisper.cpp ${WHISPER_TAG} → ${BIN_DIR}/whisper-cli"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ensure_build_tools() {
|
|
91
|
+
local missing=()
|
|
92
|
+
command -v cmake >/dev/null 2>&1 || missing+=(cmake)
|
|
93
|
+
{ command -v c++ >/dev/null 2>&1 || command -v g++ >/dev/null 2>&1; } || missing+=(build-essential)
|
|
94
|
+
command -v curl >/dev/null 2>&1 || missing+=(curl)
|
|
95
|
+
[ ${#missing[@]} -eq 0 ] && return 0
|
|
96
|
+
if [ "$SUDO_MODE" = "unavailable" ] || ! command -v apt-get >/dev/null 2>&1; then
|
|
97
|
+
die "building whisper.cpp needs: ${missing[*]}. Install them (e.g. sudo apt-get install -y ${missing[*]}) and run the setup again."
|
|
98
|
+
fi
|
|
99
|
+
log "installing build tools: ${missing[*]}"
|
|
100
|
+
# shellcheck disable=SC2086 # SUDO_MODE is '' or 'sudo -n'
|
|
101
|
+
$SUDO_MODE env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${missing[@]}" </dev/null \
|
|
102
|
+
|| { $SUDO_MODE apt-get update </dev/null && $SUDO_MODE env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${missing[@]}" </dev/null; } \
|
|
103
|
+
|| die "apt-get could not install ${missing[*]}"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
build_from_source() {
|
|
107
|
+
ensure_build_tools
|
|
108
|
+
local url="${DOWNLOAD_BASE}/archive/refs/tags/${WHISPER_TAG}.tar.gz"
|
|
109
|
+
log "building whisper.cpp ${WHISPER_TAG} from source (${url})"
|
|
110
|
+
curl -fsSL --retry 3 -o "${WORK}/src.tar.gz" "$url" || die "could not download ${url}"
|
|
111
|
+
mkdir -p "${WORK}/src"
|
|
112
|
+
tar -xzf "${WORK}/src.tar.gz" -C "${WORK}/src" --strip-components=1
|
|
113
|
+
cmake -S "${WORK}/src" -B "${WORK}/build" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DWHISPER_BUILD_TESTS=OFF >&2
|
|
114
|
+
cmake --build "${WORK}/build" --config Release --target whisper-cli -j "$BUILD_JOBS" >&2
|
|
115
|
+
local dest="${OPT_DIR}/whisper.cpp-${WHISPER_TAG}-src"
|
|
116
|
+
rm -rf "$dest"; mkdir -p "$dest"
|
|
117
|
+
cp "${WORK}/build/bin/whisper-cli" "${dest}/whisper-cli"
|
|
118
|
+
write_wrapper "${dest}/whisper-cli" "$dest"
|
|
119
|
+
smoke_test || die "the whisper-cli built from source does not run"
|
|
120
|
+
log "built whisper.cpp ${WHISPER_TAG} → ${BIN_DIR}/whisper-cli"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if [ "${WHISPER_CPP_SKIP_PREBUILT:-0}" != "1" ] && try_prebuilt; then
|
|
124
|
+
exit 0
|
|
125
|
+
fi
|
|
126
|
+
build_from_source
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for install-whisper-cpp.sh — run with: bash install-whisper-cpp.test.sh </dev/null
|
|
3
|
+
# Fakes uname (Linux x86_64), curl (serves a crafted release tarball) and
|
|
4
|
+
# sha256sum on an isolated PATH; nothing is downloaded or built. Exit 0 on pass.
|
|
5
|
+
set -u
|
|
6
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
SCRIPT="$HERE/install-whisper-cpp.sh"
|
|
8
|
+
PASS=0; FAIL=0
|
|
9
|
+
check() {
|
|
10
|
+
local name="$1"; local got="$2"; local want="$3"
|
|
11
|
+
if [ "$got" = "$want" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $name"; echo " want: $want"; echo " got: $got"; fi
|
|
12
|
+
}
|
|
13
|
+
BASH_BIN="${TEST_BASH:-$(command -v bash)}"
|
|
14
|
+
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
|
|
15
|
+
mkdir -p "$T/bin" "$T/pkg/whisper-bin-ubuntu-x64"
|
|
16
|
+
for tool in mktemp cat rm tar mkdir chmod cp cut dirname grep sed head; do
|
|
17
|
+
src="$(command -v "$tool" 2>/dev/null)" && ln -sf "$src" "$T/bin/$tool"
|
|
18
|
+
done
|
|
19
|
+
EXPECTED_SHA="$(grep '^SHA256_UBUNTU_X64=' "$SCRIPT" | cut -d'"' -f2)"
|
|
20
|
+
|
|
21
|
+
# The release tarball: whisper-cli that answers --help.
|
|
22
|
+
printf '#!/bin/sh\n[ "$1" = "--help" ] && echo "usage: whisper-cli" && exit 0\necho "ran $*"\n' > "$T/pkg/whisper-bin-ubuntu-x64/whisper-cli"
|
|
23
|
+
chmod 755 "$T/pkg/whisper-bin-ubuntu-x64/whisper-cli"
|
|
24
|
+
(cd "$T/pkg" && tar -czf "$T/release.tar.gz" whisper-bin-ubuntu-x64)
|
|
25
|
+
|
|
26
|
+
printf '#!/bin/sh\ncase "$1" in -s) echo Linux ;; -m) echo x86_64 ;; *) echo Linux ;; esac\n' > "$T/bin/uname"
|
|
27
|
+
printf '#!/bin/sh\necho 4\n' > "$T/bin/nproc"
|
|
28
|
+
# curl -fsSL --retry 3 -o <out> <url>: log the URL, copy the tarball.
|
|
29
|
+
cat > "$T/bin/curl" <<EOT
|
|
30
|
+
#!/bin/sh
|
|
31
|
+
while [ \$# -gt 0 ]; do case "\$1" in -o) out="\$2"; shift 2 ;; -*) shift ;; *) url="\$1"; shift ;; esac; done
|
|
32
|
+
echo "\$url" >> "$T/curl.log"
|
|
33
|
+
cp "$T/release.tar.gz" "\$out"
|
|
34
|
+
EOT
|
|
35
|
+
cat > "$T/bin/sha256sum" <<'EOT'
|
|
36
|
+
#!/bin/sh
|
|
37
|
+
echo "${FAKE_SHA} $1"
|
|
38
|
+
EOT
|
|
39
|
+
chmod 755 "$T/bin/"*
|
|
40
|
+
|
|
41
|
+
run() { env -i HOME="$T/home" PATH="$T/bin" CREWLY_HOME="$T/home/.crewly" CREWLY_SUDO=unavailable "$@" "$BASH_BIN" "$SCRIPT" </dev/null 2>"$T/err.log"; }
|
|
42
|
+
|
|
43
|
+
# 1. Prebuilt: checksum matches → extracted, wrapper written, smoke test passes.
|
|
44
|
+
run FAKE_SHA="$EXPECTED_SHA"; RC=$?
|
|
45
|
+
check "prebuilt: exit 0" "$RC" "0"
|
|
46
|
+
check "prebuilt: pinned release URL" "$(tail -n 1 "$T/curl.log")" "https://github.com/ggml-org/whisper.cpp/releases/download/v1.9.2/whisper-bin-ubuntu-x64.tar.gz"
|
|
47
|
+
check "prebuilt: wrapper is executable" "$([ -x "$T/home/.crewly/bin/whisper-cli" ] && echo yes)" "yes"
|
|
48
|
+
check "prebuilt: wrapper runs the extracted binary" "$(PATH="$T/bin" "$T/home/.crewly/bin/whisper-cli" -m x)" "ran -m x"
|
|
49
|
+
check "prebuilt: sets LD_LIBRARY_PATH to the bundle" "$(grep -c "LD_LIBRARY_PATH=\"$T/home/.crewly/opt/whisper.cpp-v1.9.2" "$T/home/.crewly/bin/whisper-cli")" "1"
|
|
50
|
+
|
|
51
|
+
# 2. Checksum mismatch → prebuilt rejected, source build needs tools; no sudo → clear failure.
|
|
52
|
+
rm -rf "$T/home"; : > "$T/curl.log"
|
|
53
|
+
run FAKE_SHA=0000; RC=$?
|
|
54
|
+
check "mismatch: exit 1" "$RC" "1"
|
|
55
|
+
grep -q "checksum mismatch for whisper-bin-ubuntu-x64.tar.gz" "$T/err.log" && OK=yes || OK=no
|
|
56
|
+
check "mismatch: says so" "$OK" "yes"
|
|
57
|
+
grep -q "building whisper.cpp needs: cmake build-essential. Install them (e.g. sudo apt-get install -y cmake build-essential)" "$T/err.log" && OK=yes || OK=no
|
|
58
|
+
check "no build tools + no sudo: tells the owner what to install" "$OK" "yes"
|
|
59
|
+
check "mismatch: no whisper-cli left behind" "$([ -e "$T/home/.crewly/bin/whisper-cli" ] && echo present || echo absent)" "absent"
|
|
60
|
+
|
|
61
|
+
# 3. Not Linux → refuses (macOS uses brew).
|
|
62
|
+
printf '#!/bin/sh\necho Darwin\n' > "$T/bin/uname"
|
|
63
|
+
run FAKE_SHA="$EXPECTED_SHA"; RC=$?
|
|
64
|
+
check "darwin: exit 1" "$RC" "1"
|
|
65
|
+
grep -q "this installer is for Linux" "$T/err.log" && OK=yes || OK=no
|
|
66
|
+
check "darwin: says brew is used there" "$OK" "yes"
|
|
67
|
+
|
|
68
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
69
|
+
[ "$FAIL" -eq 0 ]
|