@swttch/extend-kit 0.10.0 → 0.10.1

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/README.ko.md CHANGED
@@ -64,7 +64,7 @@ swttch-ext api frame delete --slug 3627498c-4893-4ce8-bfaf-524f7b041c1d --yes
64
64
  echo "터미널에서 읽어 줍니다." | swttch-ext tts --language ko | play -t raw -r 16000 -e signed -b 16 -c 1 -
65
65
  ```
66
66
 
67
- 옵션: `--language <코드>`(`ko` 같은 힌트), `--speed <0.7~1.5>`, `--voice <이름>`. 다 읽으면 0으로, 실패하면 1로 끝나며 표준 오류에 한 줄 JSON(`{"ok":false,"error":{"code","message","fatal"}}`)을 씁니다. 프로세스를 끄면 읽기가 멈춥니다.
67
+ 옵션: `--language <코드>`(`ko` 같은 힌트), `--speed <0.7~1.5>`, `--voice <이름>`. 따로 정하지 않은 것은 Claude 계정의 음성 설정(데스크탑 앱에서 고른 목소리·속도·언어. `/api/oauth/account/settings`에서 읽고, 아무것도 다시 쓰지 않습니다)을 따르므로, 데스크탑에서 고른 목소리가 여기서도 들립니다. `ko-balgeun`처럼 한 언어용 계정 목소리는 다른 언어의 글에서는 쓰지 않습니다. 다 읽으면 0으로, 실패하면 1로 끝나며 표준 오류에 한 줄 JSON(`{"ok":false,"error":{"code","message","fatal"}}`)을 씁니다. 프로세스를 끄면 읽기가 멈춥니다.
68
68
 
69
69
  ### 이 버전이 할 수 있는 일 알아보기
70
70
 
package/README.md CHANGED
@@ -64,7 +64,7 @@ Reads the text on stdin aloud with the same voice service the Claude desktop app
64
64
  echo "Hello from the terminal." | swttch-ext tts | play -t raw -r 16000 -e signed -b 16 -c 1 -
65
65
  ```
66
66
 
67
- Options: `--language <code>` (a hint such as `ko`), `--speed <0.7-1.5>`, `--voice <name>`. It exits 0 when everything was read and 1 on failure, with one JSON line on stderr: `{"ok":false,"error":{"code","message","fatal"}}`. Killing the process stops the reading.
67
+ Options: `--language <code>` (a hint such as `ko`), `--speed <0.7-1.5>`, `--voice <name>`. What you do not name comes from your Claude account's own voice settings (the voice, speed and language chosen in the Claude desktop app, read from `/api/oauth/account/settings`; nothing is written back), so a voice picked there is the voice heard here. An account voice made for one language (such as `ko-balgeun`) is left out for text in another. It exits 0 when everything was read and 1 on failure, with one JSON line on stderr: `{"ok":false,"error":{"code","message","fatal"}}`. Killing the process stops the reading.
68
68
 
69
69
  ### Finding out what this version can do
70
70
 
package/dist/ext/tts.d.ts CHANGED
@@ -8,6 +8,9 @@ export declare const TTS_CAPABILITY = "tts";
8
8
  * ── stderr ── on failure, one JSON line: {"ok":false,"error":{"code","message","fatal"}}.
9
9
  * ── exit ─── 0 when the service said everything, 1 when it failed. Killing the process stops the reading.
10
10
  *
11
+ * What the caller does not name comes from the Claude account's own voice settings (the voice, speed and language
12
+ * chosen in the desktop app), so a voice picked there is the voice heard here.
13
+ *
11
14
  * A terminal user can drive it by hand (macOS):
12
15
  *
13
16
  * echo "Hello" | swttch-ext tts | play -t raw -r 16000 -e signed -b 16 -c 1 -
package/dist/ext/tts.js CHANGED
@@ -1,4 +1,4 @@
1
- import { openTextToSpeechStream } from '../tts/index.js';
1
+ import { openTextToSpeechStream, readAccountVoiceSettings, chooseVoiceOptions } from '../tts/index.js';
2
2
  /** The name `--capabilities` lists for this command, so the plugin can tell a kit that has it from one that does not. */
3
3
  export const TTS_CAPABILITY = 'tts';
4
4
  /**
@@ -9,6 +9,9 @@ export const TTS_CAPABILITY = 'tts';
9
9
  * ── stderr ── on failure, one JSON line: {"ok":false,"error":{"code","message","fatal"}}.
10
10
  * ── exit ─── 0 when the service said everything, 1 when it failed. Killing the process stops the reading.
11
11
  *
12
+ * What the caller does not name comes from the Claude account's own voice settings (the voice, speed and language
13
+ * chosen in the desktop app), so a voice picked there is the voice heard here.
14
+ *
12
15
  * A terminal user can drive it by hand (macOS):
13
16
  *
14
17
  * echo "Hello" | swttch-ext tts | play -t raw -r 16000 -e signed -b 16 -c 1 -
@@ -21,7 +24,8 @@ export async function runTextToSpeech(options) {
21
24
  for await (const chunk of process.stdin)
22
25
  chunks.push(Buffer.from(chunk));
23
26
  const text = Buffer.concat(chunks).toString('utf8');
24
- const speed = options.speed === undefined || options.speed === '' ? undefined : Number(options.speed);
27
+ const askedSpeed = options.speed === undefined || options.speed === '' ? undefined : Number(options.speed);
28
+ const chosen = chooseVoiceOptions({ voice: options.voice || undefined, speed: askedSpeed, language: options.language || undefined }, await readAccountVoiceSettings());
25
29
  return new Promise((resolve) => {
26
30
  const stopOnPipeClosed = () => {
27
31
  // The reader went away (the plugin stopped listening): there is nobody to read to.
@@ -37,7 +41,7 @@ export async function runTextToSpeech(options) {
37
41
  process.stderr.write(`${JSON.stringify({ ok: false, error: { code: 'tts_failed', message, fatal: info?.fatal ?? false } })}\n`);
38
42
  resolve(1);
39
43
  },
40
- }, { language: options.language || undefined, voice: options.voice || undefined, speed }).catch((err) => {
44
+ }, chosen).catch((err) => {
41
45
  const message = err instanceof Error ? err.message : String(err);
42
46
  process.stderr.write(`${JSON.stringify({ ok: false, error: { code: 'not_logged_in', message, fatal: true } })}\n`);
43
47
  resolve(1);
@@ -1 +1 @@
1
- {"version":3,"file":"tts.js","sourceRoot":"","sources":["../../src/ext/tts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,yHAAyH;AACzH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC;AAEpC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAA8D;IAClG,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;QAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC,CAAC;IACnF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEpD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAEtG,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;QACrC,MAAM,gBAAgB,GAAG,GAAS,EAAE;YAClC,mFAAmF;YACnF,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,CAAC,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAE7C,MAAM,MAAM,GAAG,sBAAsB,CACnC,IAAI,EACJ;YACE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;YACD,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;YACxB,OAAO,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;gBACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;gBAChI,OAAO,CAAC,CAAC,CAAC,CAAC;YACb,CAAC;SACF,EACD,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,SAAS,EAAE,KAAK,EAAE,CACtF,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACvB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YACnH,OAAO,CAAC,CAAC,CAAC,CAAC;YACX,OAAO,SAAkB,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"tts.js","sourceRoot":"","sources":["../../src/ext/tts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,yHAAyH;AACzH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAA8D;IAClG,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;QAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC,CAAC;IACnF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEpD,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3G,MAAM,MAAM,GAAG,kBAAkB,CAC/B,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS,EAAE,EACjG,MAAM,wBAAwB,EAAE,CACjC,CAAC;IAEF,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;QACrC,MAAM,gBAAgB,GAAG,GAAS,EAAE;YAClC,mFAAmF;YACnF,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,CAAC,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAE7C,MAAM,MAAM,GAAG,sBAAsB,CACnC,IAAI,EACJ;YACE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;YACD,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;YACxB,OAAO,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;gBACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;gBAChI,OAAO,CAAC,CAAC,CAAC,CAAC;YACb,CAAC;SACF,EACD,MAAM,CACP,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACvB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YACnH,OAAO,CAAC,CAAC,CAAC,CAAC;YACX,OAAO,SAAkB,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,39 @@
1
+ /** What the Claude account says about how replies are read aloud (the voice settings of the desktop app and claude.ai). */
2
+ export interface AccountVoiceSettings {
3
+ /** A voice id such as "buttery" or "ko-balgeun". */
4
+ voice?: string;
5
+ /** Playback speed. */
6
+ speed?: number;
7
+ /** The language the account reads in, such as "ko". */
8
+ language?: string;
9
+ }
10
+ /**
11
+ * The voice, speed and language chosen in the Claude desktop app, as the account stores them.
12
+ *
13
+ * Those three are account settings, not per-device ones: choosing a "Bright" Korean voice in the desktop app writes
14
+ * `voice_preference` to the account, so reading the account gives the same voice here without a second setting to
15
+ * keep in step. Nothing is written back.
16
+ *
17
+ * Answers an empty object when the account cannot be read for any reason: reading aloud then uses the service's own
18
+ * defaults rather than failing because a preference was out of reach.
19
+ */
20
+ export declare function readAccountVoiceSettings(token?: string): Promise<AccountVoiceSettings>;
21
+ /** The three voice fields of an account settings document, each only when it is of the right kind. */
22
+ export declare function pickVoiceSettings(settings: Record<string, unknown>): AccountVoiceSettings;
23
+ /**
24
+ * What to ask the service for, given what the caller said and what the account chose.
25
+ *
26
+ * What the caller named wins. Otherwise the account's speed applies. The account's voice applies only when it can
27
+ * read the text: a voice made for one language (`ko-balgeun`) is left out for text in another, since the service
28
+ * would read that text in a voice that cannot pronounce it. The language hint is the caller's detection of the
29
+ * text first, and the account's language only when the caller could not tell.
30
+ */
31
+ export declare function chooseVoiceOptions(asked: {
32
+ voice?: string;
33
+ speed?: number;
34
+ language?: string;
35
+ }, account: AccountVoiceSettings): {
36
+ voice?: string;
37
+ speed?: number;
38
+ language?: string;
39
+ };
@@ -0,0 +1,60 @@
1
+ import { getCredentials, getAccessToken } from '../battery/auth/index.js';
2
+ import { getJson } from '../battery/api/http.js';
3
+ import { SPEECH_SPEED_RANGE } from './stream.js';
4
+ const ACCOUNT_SETTINGS_URL = 'https://api.anthropic.com/api/oauth/account/settings';
5
+ /**
6
+ * The voice, speed and language chosen in the Claude desktop app, as the account stores them.
7
+ *
8
+ * Those three are account settings, not per-device ones: choosing a "Bright" Korean voice in the desktop app writes
9
+ * `voice_preference` to the account, so reading the account gives the same voice here without a second setting to
10
+ * keep in step. Nothing is written back.
11
+ *
12
+ * Answers an empty object when the account cannot be read for any reason: reading aloud then uses the service's own
13
+ * defaults rather than failing because a preference was out of reach.
14
+ */
15
+ export async function readAccountVoiceSettings(token) {
16
+ try {
17
+ const accessToken = token ?? getAccessToken(await getCredentials());
18
+ const response = await getJson(ACCOUNT_SETTINGS_URL, { Authorization: `Bearer ${accessToken}`, 'anthropic-beta': 'oauth-2025-04-20', 'x-app': 'swttch' }, { deadlineMs: 8000 });
19
+ if (!response.ok || typeof response.body !== 'object' || response.body === null)
20
+ return {};
21
+ return pickVoiceSettings(response.body);
22
+ }
23
+ catch {
24
+ return {};
25
+ }
26
+ }
27
+ /** The three voice fields of an account settings document, each only when it is of the right kind. */
28
+ export function pickVoiceSettings(settings) {
29
+ const voice = typeof settings.voice_preference === 'string' && settings.voice_preference.trim() !== '' ? settings.voice_preference.trim() : undefined;
30
+ const speed = typeof settings.voice_speed === 'number' && settings.voice_speed >= SPEECH_SPEED_RANGE.min && settings.voice_speed <= SPEECH_SPEED_RANGE.max
31
+ ? settings.voice_speed
32
+ : undefined;
33
+ const language = typeof settings.voice_language_code === 'string' && settings.voice_language_code.trim() !== '' ? settings.voice_language_code.trim() : undefined;
34
+ return { voice, speed, language };
35
+ }
36
+ /** "ko-balgeun" is read for Korean text; "buttery", which names no language, for any. */
37
+ function voiceLanguage(voice) {
38
+ const at = voice.indexOf('-');
39
+ return at > 0 ? voice.slice(0, at).toLowerCase() : undefined;
40
+ }
41
+ /**
42
+ * What to ask the service for, given what the caller said and what the account chose.
43
+ *
44
+ * What the caller named wins. Otherwise the account's speed applies. The account's voice applies only when it can
45
+ * read the text: a voice made for one language (`ko-balgeun`) is left out for text in another, since the service
46
+ * would read that text in a voice that cannot pronounce it. The language hint is the caller's detection of the
47
+ * text first, and the account's language only when the caller could not tell.
48
+ */
49
+ export function chooseVoiceOptions(asked, account) {
50
+ const language = asked.language ?? account.language;
51
+ const textLanguage = (asked.language ?? account.language)?.split('-')[0].toLowerCase();
52
+ let voice = asked.voice;
53
+ if (voice === undefined && account.voice !== undefined) {
54
+ const needs = voiceLanguage(account.voice);
55
+ if (needs === undefined || textLanguage === undefined || needs === textLanguage)
56
+ voice = account.voice;
57
+ }
58
+ return { voice, speed: asked.speed ?? account.speed, language };
59
+ }
60
+ //# sourceMappingURL=accountVoice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accountVoice.js","sourceRoot":"","sources":["../../src/tts/accountVoice.ts"],"names":[],"mappings":"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"}
@@ -5,3 +5,5 @@
5
5
  */
6
6
  export { openTextToSpeechStream, isTextToSpeechAvailable, splitForSpeech, SPEECH_AUDIO_FORMAT, SPEECH_SPEED_RANGE } from './stream.js';
7
7
  export type { TextToSpeechOptions, TextToSpeechHandlers, TextToSpeechStream } from './stream.js';
8
+ export { readAccountVoiceSettings, chooseVoiceOptions, pickVoiceSettings } from './accountVoice.js';
9
+ export type { AccountVoiceSettings } from './accountVoice.js';
package/dist/tts/index.js CHANGED
@@ -4,4 +4,5 @@
4
4
  * Outside the plugin bundle for the same reason as dictation: it sends the account's OAuth token.
5
5
  */
6
6
  export { openTextToSpeechStream, isTextToSpeechAvailable, splitForSpeech, SPEECH_AUDIO_FORMAT, SPEECH_SPEED_RANGE } from './stream.js';
7
+ export { readAccountVoiceSettings, chooseVoiceOptions, pickVoiceSettings } from './accountVoice.js';
7
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tts/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,cAAc,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tts/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,cAAc,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEvI,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swttch/extend-kit",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Companion tools for Swttch that live outside the plugin bundle: Claude Code account/usage SDK, the ccb CLI, speech-to-text and text-to-speech.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",