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.
@@ -149,18 +149,20 @@ function install() {
149
149
  console.log(` ${green}✓${reset} Installed .claude/skills`);
150
150
  }
151
151
 
152
- // Install config files
153
- const configs = [
154
- ['.catalyst/spec-structure.yaml', '.catalyst/spec-structure.yaml'],
155
- ['.catalyst/main/project-config.yaml', '.catalyst/main/project-config.yaml']
156
- ];
157
-
158
- for (const [srcFile, destFile] of configs) {
159
- const srcPath = path.join(src, srcFile);
160
- const destPath = path.join(cwd, destFile);
161
- if (copyFile(srcPath, destPath)) {
162
- console.log(` ${green}✓${reset} Installed ${destFile}`);
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; u.onend = resolve; u.onerror = resolve;
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catalyst-os",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "scripts": {
5
5
  "postinstall": "node .catalyst/bin/install.js",
6
6
  "validate": "node .catalyst/bin/validate-artifacts.js"