catalyst-os 3.0.1 → 3.0.2
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/.catalyst/bin/install.js +13 -11
- package/.catalyst/voice/meeting-diy.html +24 -2
- package/package.json +1 -1
package/.catalyst/bin/install.js
CHANGED
|
@@ -149,18 +149,20 @@ function install() {
|
|
|
149
149
|
console.log(` ${green}✓${reset} Installed .claude/skills`);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (copyFile(
|
|
162
|
-
console.log(` ${green}✓${reset} Installed
|
|
152
|
+
// Framework-owned config: always refresh on (re)install.
|
|
153
|
+
if (copyFile(path.join(src, '.catalyst/spec-structure.yaml'), path.join(cwd, '.catalyst/spec-structure.yaml'))) {
|
|
154
|
+
console.log(` ${green}✓${reset} Installed .catalyst/spec-structure.yaml`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// User-owned config: create ONLY if missing — never clobber a customized
|
|
158
|
+
// project-config.yaml (branch settings, voice.language, agent_id, etc.) on update.
|
|
159
|
+
const pcDest = path.join(cwd, '.catalyst/main/project-config.yaml');
|
|
160
|
+
if (!fs.existsSync(pcDest)) {
|
|
161
|
+
if (copyFile(path.join(src, '.catalyst/main/project-config.yaml'), pcDest)) {
|
|
162
|
+
console.log(` ${green}✓${reset} Installed .catalyst/main/project-config.yaml`);
|
|
163
163
|
}
|
|
164
|
+
} else {
|
|
165
|
+
console.log(` ${dim}•${reset} Kept existing .catalyst/main/project-config.yaml`);
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
// Install voice runtime (zero-dependency local meeting daemon for /meet-spec)
|
|
@@ -113,13 +113,33 @@
|
|
|
113
113
|
notes.innerHTML = lastNotesHtml;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
let voices = [];
|
|
117
|
+
function loadVoices() { try { voices = speechSynthesis.getVoices() || []; } catch {} }
|
|
118
|
+
if ('speechSynthesis' in window) { loadVoices(); speechSynthesis.onvoiceschanged = loadVoices; }
|
|
119
|
+
function pickVoice() {
|
|
120
|
+
if (!voices.length) loadVoices();
|
|
121
|
+
const base = lang.split('-')[0].toLowerCase();
|
|
122
|
+
return voices.find(v => v.lang === lang)
|
|
123
|
+
|| voices.find(v => (v.lang || '').toLowerCase().startsWith(base))
|
|
124
|
+
|| voices[0] || null;
|
|
125
|
+
}
|
|
116
126
|
function speak(text) {
|
|
117
127
|
return new Promise((resolve) => {
|
|
118
|
-
if (!('speechSynthesis' in window)) return resolve();
|
|
128
|
+
if (!('speechSynthesis' in window) || !text) return resolve();
|
|
129
|
+
try { speechSynthesis.cancel(); } catch {}
|
|
119
130
|
const u = new SpeechSynthesisUtterance(text);
|
|
120
|
-
u.lang = lang;
|
|
131
|
+
u.lang = lang;
|
|
132
|
+
const v = pickVoice();
|
|
133
|
+
if (v) u.voice = v; // explicit voice → avoids silent "no voice for lang"
|
|
134
|
+
let done = false;
|
|
135
|
+
const finish = () => { if (done) return; done = true; clearInterval(ka); resolve(); };
|
|
136
|
+
u.onend = finish; u.onerror = finish;
|
|
137
|
+
// Chrome silently pauses long utterances (~15s); nudge it to keep going.
|
|
138
|
+
const ka = setInterval(() => { try { if (speechSynthesis.speaking) speechSynthesis.resume(); } catch {} }, 4000);
|
|
121
139
|
setOrb('speaking'); setStatus('Catalyst is speaking…');
|
|
122
140
|
speechSynthesis.speak(u);
|
|
141
|
+
// If speech never starts (blocked / no voice), don't hang the turn loop.
|
|
142
|
+
setTimeout(() => { if (!done && !speechSynthesis.speaking) finish(); }, 2500);
|
|
123
143
|
});
|
|
124
144
|
}
|
|
125
145
|
|
|
@@ -170,6 +190,8 @@
|
|
|
170
190
|
|
|
171
191
|
startBtn.onclick = () => {
|
|
172
192
|
startBtn.disabled = true; endBtn.disabled = false; chatRow.hidden = false;
|
|
193
|
+
// Unlock audio within the user gesture so TTS works later, and warm up voices.
|
|
194
|
+
if ('speechSynthesis' in window) { try { speechSynthesis.cancel(); speechSynthesis.resume(); loadVoices(); } catch {} }
|
|
173
195
|
recognition = buildRecognition();
|
|
174
196
|
if (!recognition) { setStatus('Speech needs Chrome. You can still type below.'); setOrb(''); return; }
|
|
175
197
|
startListening();
|