privateer-agent 0.12.56 → 0.12.57
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/extensions/privateer-speak.ts +246 -12
- package/extensions/speak-voices.data.ts +206 -0
- package/package.json +2 -2
|
@@ -32,17 +32,39 @@
|
|
|
32
32
|
// gates on hasUI, so harbor/daemon/ACP surfaces stay silent by design (this extension is
|
|
33
33
|
// manifest-only, like privateer-hints: buildMoat never includes it).
|
|
34
34
|
//
|
|
35
|
-
// VOICES.
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
35
|
+
// VOICES. Every TTS model the account can reach, each with the voices it really offers,
|
|
36
|
+
// comes from GET /api/agent/media/capabilities: its `speech.catalog` lists the models
|
|
37
|
+
// (name, provider, confidential or not, blocked by the account's ZDR setting or not),
|
|
38
|
+
// and `?ttsModel=<id>` returns one model's exact voice ids. Models blocked by the
|
|
39
|
+
// account's privacy setting are left out rather than offered to fail. Each voice is
|
|
40
|
+
// described in the app's words (speak-voices.data.ts, generated from the app's sourced
|
|
41
|
+
// table) and can be heard through /api/audio/voice-preview, which serves a fixed
|
|
42
|
+
// sentence from a cache shared across accounts: after the first listen, free.
|
|
43
|
+
//
|
|
44
|
+
// The account default (qwen3-tts, confidential) stays the default: a voice on it saves
|
|
45
|
+
// no model, so a change of default on the server carries users along. Picking a voice
|
|
46
|
+
// on another model saves that model, and it's sent as ttsModelId.
|
|
47
|
+
//
|
|
48
|
+
// Signed out, or the catalog unreachable: the qwen3-tts speakers below, so the picker
|
|
49
|
+
// is never empty. Tinfoil requires a voice server-side and applies "serena" when none is
|
|
50
|
+
// sent, so leaving voice unset is always safe.
|
|
40
51
|
import { join } from "node:path";
|
|
41
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
kokoroVoice,
|
|
54
|
+
languageName,
|
|
55
|
+
makePiSpeakExtension,
|
|
56
|
+
registerSpeechProvider,
|
|
57
|
+
registerTranscriptionProvider,
|
|
58
|
+
type SpeechAudio,
|
|
59
|
+
type TranscriptionModelInfo,
|
|
60
|
+
type VoiceInfo,
|
|
61
|
+
} from "privateer-speak";
|
|
42
62
|
import { apiRequest, hasCredentials } from "../src/auth/privateer.ts";
|
|
43
63
|
import { globalDir } from "../src/config/paths.ts";
|
|
64
|
+
import { VOICE_DESCRIPTIONS } from "./speak-voices.data.ts";
|
|
44
65
|
|
|
45
66
|
const QWEN3_TTS_VOICES = ["serena", "aiden", "dylan", "eric", "ono_anna", "ryan", "sohee", "uncle_fu", "vivian"];
|
|
67
|
+
const DEFAULT_MODEL = "qwen3-tts";
|
|
46
68
|
|
|
47
69
|
// Call an account audio endpoint and hand back the parsed JSON, throwing the server's
|
|
48
70
|
// own (person-readable, localized) message on failure — same policy as media.ts's
|
|
@@ -72,18 +94,163 @@ async function accountJson<T>(path: string, body: unknown, signal?: AbortSignal)
|
|
|
72
94
|
return (await res.json()) as T;
|
|
73
95
|
}
|
|
74
96
|
|
|
97
|
+
// ── the voice catalog ───────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The language a voice speaks, when its id or its maker says so. Same rules as the
|
|
101
|
+
* app's voiceLabels.ts: Voxtral's `de_male` / `en_paul_sad`, Aura-2's trailing code,
|
|
102
|
+
* MAI's leading tag, Kokoro's first letter, and Qwen's published native languages.
|
|
103
|
+
*/
|
|
104
|
+
const VOXTRAL_LANGUAGES = new Set(["ar", "de", "es", "fr", "hi", "it", "nl", "pt"]);
|
|
105
|
+
const ENGLISH_STYLES = new Set(["neutral", "casual", "cheerful", "american", "british"]);
|
|
106
|
+
const KOKORO_LANGUAGES: Record<string, string> = { a: "en-US", b: "en-GB", e: "es", f: "fr", h: "hi", i: "it", j: "ja", p: "pt-BR", z: "zh" };
|
|
107
|
+
const QWEN_NATIVE: Record<string, string> = {
|
|
108
|
+
serena: "zh", vivian: "zh", uncle_fu: "zh", dylan: "zh", eric: "zh", ryan: "en", aiden: "en", ono_anna: "ja", sohee: "ko",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export function voiceLanguage(id: string): string | undefined {
|
|
112
|
+
const two = /^([a-z]+)_(female|male)$/.exec(id);
|
|
113
|
+
if (two) {
|
|
114
|
+
if (VOXTRAL_LANGUAGES.has(two[1]!)) return languageName(two[1]!);
|
|
115
|
+
if (ENGLISH_STYLES.has(two[1]!)) return "English";
|
|
116
|
+
}
|
|
117
|
+
const kokoro = /^([abefhijpz])[fm]_/.exec(id);
|
|
118
|
+
if (kokoro) return languageName(KOKORO_LANGUAGES[kokoro[1]!]!);
|
|
119
|
+
const styled = /^([a-z]{2})_[a-z]+_[a-z]+$/.exec(id);
|
|
120
|
+
if (styled && (styled[1] === "en" || VOXTRAL_LANGUAGES.has(styled[1]!))) return languageName(styled[1]!);
|
|
121
|
+
const aura = /^(?:aura-2|flux)-.+-([a-z]{2})$/.exec(id);
|
|
122
|
+
if (aura) return languageName(aura[1]!);
|
|
123
|
+
const mai = /^([a-z]{2}-[A-Z]{2})-[^:]+:/.exec(id);
|
|
124
|
+
if (mai) return languageName(mai[1]!);
|
|
125
|
+
return QWEN_NATIVE[id] ? languageName(QWEN_NATIVE[id]!) : undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A readable name for ids that are really slugs: "aura-2-thalia-en" → "Thalia". */
|
|
129
|
+
function displayName(id: string): string | undefined {
|
|
130
|
+
const aura = /^(?:aura-2|flux)-(.+)-[a-z]{2}$/.exec(id);
|
|
131
|
+
if (aura) return aura[1]!.charAt(0).toUpperCase() + aura[1]!.slice(1);
|
|
132
|
+
const mai = /^[a-z]{2}-[A-Z]{2}-([^:]+):/.exec(id);
|
|
133
|
+
return mai?.[1];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface CatalogModel {
|
|
137
|
+
id: string;
|
|
138
|
+
name?: string;
|
|
139
|
+
provider?: string;
|
|
140
|
+
isZdr?: boolean;
|
|
141
|
+
isTee?: boolean;
|
|
142
|
+
blockedByZdr?: boolean;
|
|
143
|
+
voiceCount?: number;
|
|
144
|
+
}
|
|
145
|
+
interface SpeechCapabilities {
|
|
146
|
+
speech?: { model?: string; voices?: string[]; catalog?: CatalogModel[] };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const CLIP_FORMATS: Record<string, SpeechAudio["format"]> = {
|
|
150
|
+
"audio/mpeg": "mp3",
|
|
151
|
+
"audio/mp3": "mp3",
|
|
152
|
+
"audio/wav": "wav",
|
|
153
|
+
"audio/x-wav": "wav",
|
|
154
|
+
"audio/ogg": "ogg",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** A voice as the picker shows it: described, with its language, model and a sample. */
|
|
158
|
+
function voiceInfo(id: string, model: CatalogModel | undefined, modelId: string): VoiceInfo {
|
|
159
|
+
// Kokoro's id states name and gender outright; the app's table adds the author's grade.
|
|
160
|
+
const kokoro = /^[abefhijpz][fm]_/.test(id) ? kokoroVoice(id, modelId) : undefined;
|
|
161
|
+
const description = kokoro?.description ?? VOICE_DESCRIPTIONS[id];
|
|
162
|
+
const language = voiceLanguage(id);
|
|
163
|
+
const name = kokoro?.name ?? displayName(id);
|
|
164
|
+
return {
|
|
165
|
+
id,
|
|
166
|
+
model: modelId,
|
|
167
|
+
...(model?.name ? { modelName: model.name } : {}),
|
|
168
|
+
...(name ? { name } : {}),
|
|
169
|
+
...(description ? { description } : {}),
|
|
170
|
+
...(language ? { language } : {}),
|
|
171
|
+
// The enclave is attested by Privateer's server, which then forwards the text: the
|
|
172
|
+
// shield says so ("TEE · attested by Privateer"), not that this terminal checked it.
|
|
173
|
+
...(model
|
|
174
|
+
? model.isTee
|
|
175
|
+
? { privacy: "confidential" as const, attestedBy: "Privateer" }
|
|
176
|
+
: { privacy: model.isZdr ? ("zdr" as const) : ("standard" as const) }
|
|
177
|
+
: {}),
|
|
178
|
+
preview: async (signal?: AbortSignal): Promise<SpeechAudio> => {
|
|
179
|
+
const { audioBase64, mimeType } = await accountJson<{ audioBase64?: string; mimeType?: string }>(
|
|
180
|
+
"/api/audio/voice-preview",
|
|
181
|
+
{ ttsModelId: modelId, voice: id, locale: "en" },
|
|
182
|
+
signal,
|
|
183
|
+
);
|
|
184
|
+
if (!audioBase64) throw new Error("Privateer returned no sample");
|
|
185
|
+
return { data: Buffer.from(audioBase64, "base64"), format: CLIP_FORMATS[mimeType ?? ""] ?? "mp3" };
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function capabilities(ttsModel?: string): Promise<SpeechCapabilities> {
|
|
191
|
+
const res = await apiRequest(`/api/agent/media/capabilities${ttsModel ? `?ttsModel=${encodeURIComponent(ttsModel)}` : ""}`, {
|
|
192
|
+
method: "GET",
|
|
193
|
+
signal: AbortSignal.timeout(10_000),
|
|
194
|
+
});
|
|
195
|
+
if (!res.ok) throw new Error(`Privateer returned ${res.status}`);
|
|
196
|
+
return (await res.json()) as SpeechCapabilities;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Read once per session: the catalog changes on deploys, not between keypresses.
|
|
200
|
+
let catalog: Promise<VoiceInfo[]> | undefined;
|
|
201
|
+
let accountDefault: string | undefined;
|
|
202
|
+
|
|
203
|
+
function loadCatalog(): Promise<VoiceInfo[]> {
|
|
204
|
+
return (catalog ??= (async () => {
|
|
205
|
+
const top = await capabilities();
|
|
206
|
+
accountDefault = top.speech?.model;
|
|
207
|
+
const models = (top.speech?.catalog ?? []).filter((m) => !m.blockedByZdr);
|
|
208
|
+
// The default model first, then the rest as the server sorts them.
|
|
209
|
+
models.sort((a, b) => Number(b.id === accountDefault) - Number(a.id === accountDefault));
|
|
210
|
+
const lists = await Promise.all(
|
|
211
|
+
models.map(async (m) => {
|
|
212
|
+
const ids = m.id === accountDefault ? (top.speech?.voices ?? []) : m.voiceCount ? ((await capabilities(m.id).catch(() => ({}))) as SpeechCapabilities).speech?.voices ?? [] : [];
|
|
213
|
+
// A model whose voices are prose, not a list: its default voice is still usable.
|
|
214
|
+
return ids.length ? ids.map((id) => voiceInfo(id, m, m.id)) : [{ ...voiceInfo("", m, m.id), name: "default voice", preview: undefined }];
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
return lists.flat();
|
|
218
|
+
})().catch((e) => {
|
|
219
|
+
catalog = undefined; // try again next time the picker opens
|
|
220
|
+
throw e;
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const fallbackVoices = (): VoiceInfo[] =>
|
|
225
|
+
QWEN3_TTS_VOICES.map((id) => ({
|
|
226
|
+
...voiceInfo(id, undefined, DEFAULT_MODEL),
|
|
227
|
+
modelName: "Qwen3 TTS",
|
|
228
|
+
privacy: "confidential" as const,
|
|
229
|
+
attestedBy: "Privateer",
|
|
230
|
+
}));
|
|
231
|
+
|
|
75
232
|
registerSpeechProvider(
|
|
76
233
|
{
|
|
77
234
|
id: "privateer",
|
|
78
|
-
label: "Privateer
|
|
235
|
+
label: "Privateer",
|
|
79
236
|
privacy: "confidential",
|
|
237
|
+
attestedBy: "Privateer",
|
|
80
238
|
defaultVoice: QWEN3_TTS_VOICES[0],
|
|
81
|
-
|
|
239
|
+
model: () => accountDefault ?? DEFAULT_MODEL,
|
|
240
|
+
voices: async () => {
|
|
241
|
+
if (!hasCredentials()) return fallbackVoices();
|
|
242
|
+
try {
|
|
243
|
+
const voices = await loadCatalog();
|
|
244
|
+
return voices.length ? voices : fallbackVoices();
|
|
245
|
+
} catch {
|
|
246
|
+
return fallbackVoices();
|
|
247
|
+
}
|
|
248
|
+
},
|
|
82
249
|
available: () => hasCredentials(),
|
|
83
|
-
fetchSpeech: async (text, { voice, signal }) => {
|
|
250
|
+
fetchSpeech: async (text, { voice, model, signal }) => {
|
|
84
251
|
const { audioBase64 } = await accountJson<{ audioBase64?: string }>(
|
|
85
252
|
"/api/audio/speech",
|
|
86
|
-
{ text, ...(voice ? { voice } : {}) },
|
|
253
|
+
{ text, ...(voice ? { voice } : {}), ...(model ? { ttsModelId: model } : {}) },
|
|
87
254
|
signal,
|
|
88
255
|
);
|
|
89
256
|
if (!audioBase64) throw new Error("Privateer returned no audio");
|
|
@@ -93,18 +260,85 @@ registerSpeechProvider(
|
|
|
93
260
|
{ preferWhen: () => hasCredentials() },
|
|
94
261
|
);
|
|
95
262
|
|
|
263
|
+
// ── transcription models ────────────────────────────────────────────────────
|
|
264
|
+
//
|
|
265
|
+
// The account's STT catalog is GET /api/models/openrouter?action=stt: OpenRouter's
|
|
266
|
+
// ZDR-covered transcription models plus the NEAR AI and Tinfoil enclave ones, each with
|
|
267
|
+
// isTee / isZdr. A model id goes back as sttModelId.
|
|
268
|
+
//
|
|
269
|
+
// THE DEFAULT IS ASKED FOR BY NAME. The server's own default (DEFAULT_STT_MODEL) is a
|
|
270
|
+
// deployment setting and today an OpenRouter ZDR model, not an enclave, so sending no
|
|
271
|
+
// model would let this provider's "confidential" badge describe something it doesn't
|
|
272
|
+
// control. Unless you pick otherwise, transcription asks for Tinfoil's enclave Whisper.
|
|
273
|
+
const CONFIDENTIAL_STT = "tinfoil/whisper-large-v3-turbo";
|
|
274
|
+
|
|
275
|
+
interface SttCatalogModel {
|
|
276
|
+
id: string;
|
|
277
|
+
name?: string;
|
|
278
|
+
description?: string;
|
|
279
|
+
isTee?: boolean;
|
|
280
|
+
isZdr?: boolean;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The catalog's first sentence, short enough for a row. */
|
|
284
|
+
function oneLine(text: string | undefined): string | undefined {
|
|
285
|
+
const first = text?.split(/(?<=\.)\s/)[0]?.trim().replace(/\.$/, "");
|
|
286
|
+
if (!first) return undefined;
|
|
287
|
+
return first.length > 90 ? `${first.slice(0, 89)}…` : first;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let sttCatalog: Promise<TranscriptionModelInfo[]> | undefined;
|
|
291
|
+
function loadSttCatalog(): Promise<TranscriptionModelInfo[]> {
|
|
292
|
+
return (sttCatalog ??= (async () => {
|
|
293
|
+
const res = await apiRequest("/api/models/openrouter?action=stt", { method: "GET", signal: AbortSignal.timeout(10_000) });
|
|
294
|
+
if (!res.ok) throw new Error(`Privateer returned ${res.status}`);
|
|
295
|
+
const { models = [] } = (await res.json()) as { models?: SttCatalogModel[] };
|
|
296
|
+
const described = models.map((m): TranscriptionModelInfo => {
|
|
297
|
+
const about = oneLine(m.description);
|
|
298
|
+
return {
|
|
299
|
+
id: m.id,
|
|
300
|
+
...(m.name ? { name: m.name } : {}),
|
|
301
|
+
...(about ? { description: about } : {}),
|
|
302
|
+
...(m.isTee
|
|
303
|
+
? { privacy: "confidential" as const, attestedBy: "Privateer" }
|
|
304
|
+
: { privacy: m.isZdr ? ("zdr" as const) : ("standard" as const) }),
|
|
305
|
+
};
|
|
306
|
+
});
|
|
307
|
+
// Enclave models first: they're what the default is and what the badge promises.
|
|
308
|
+
return described.sort((a, b) => Number(b.privacy === "confidential") - Number(a.privacy === "confidential"));
|
|
309
|
+
})().catch((e) => {
|
|
310
|
+
sttCatalog = undefined;
|
|
311
|
+
throw e;
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
|
|
96
315
|
registerTranscriptionProvider(
|
|
97
316
|
{
|
|
98
317
|
id: "privateer",
|
|
99
|
-
label: "Privateer
|
|
318
|
+
label: "Privateer",
|
|
100
319
|
privacy: "confidential",
|
|
320
|
+
attestedBy: "Privateer",
|
|
321
|
+
defaultModel: () => CONFIDENTIAL_STT,
|
|
322
|
+
models: async () => {
|
|
323
|
+
const fallback: TranscriptionModelInfo[] = [
|
|
324
|
+
{ id: CONFIDENTIAL_STT, name: "Whisper Large v3 Turbo", privacy: "confidential", attestedBy: "Privateer" },
|
|
325
|
+
];
|
|
326
|
+
if (!hasCredentials()) return fallback;
|
|
327
|
+
try {
|
|
328
|
+
const models = await loadSttCatalog();
|
|
329
|
+
return models.length ? models : fallback;
|
|
330
|
+
} catch {
|
|
331
|
+
return fallback;
|
|
332
|
+
}
|
|
333
|
+
},
|
|
101
334
|
available: () => hasCredentials(),
|
|
102
|
-
transcribe: async (audio, { language, signal }) => {
|
|
335
|
+
transcribe: async (audio, { language, model, signal }) => {
|
|
103
336
|
const { text } = await accountJson<{ text?: string }>(
|
|
104
337
|
"/api/audio/transcribe",
|
|
105
338
|
{
|
|
106
339
|
audioBase64: Buffer.from(audio.data).toString("base64"),
|
|
107
340
|
format: audio.format,
|
|
341
|
+
sttModelId: model ?? CONFIDENTIAL_STT,
|
|
108
342
|
...(language ? { language } : {}),
|
|
109
343
|
},
|
|
110
344
|
signal,
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-speak-voices.mjs from the Privateer app's voiceProfiles.ts.
|
|
2
|
+
// Do not edit by hand: change the app's table and regenerate. Every entry is the
|
|
3
|
+
// provider's own published description of the voice (see that file's sources).
|
|
4
|
+
export const VOICE_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
5
|
+
"aura-2-amalthea-en": "female · young · Filipino · engaging, natural, cheerful · for casual chat",
|
|
6
|
+
"aura-2-andromeda-en": "female · adult · American · casual, expressive, comfortable · for customer service, phone menus",
|
|
7
|
+
"aura-2-apollo-en": "male · adult · American · confident, comfortable, casual · for casual chat",
|
|
8
|
+
"aura-2-arcas-en": "male · adult · American · natural, smooth, clear, comfortable · for customer service, casual chat",
|
|
9
|
+
"aura-2-aries-en": "male · adult · American · warm, energetic, caring · for casual chat",
|
|
10
|
+
"aura-2-asteria-en": "female · adult · American · clear, confident, knowledgeable, energetic · for advertising",
|
|
11
|
+
"aura-2-athena-en": "female · mature · American · calm, smooth, professional · for storytelling",
|
|
12
|
+
"aura-2-atlas-en": "male · mature · American · enthusiastic, confident, approachable, friendly · for advertising",
|
|
13
|
+
"aura-2-aurora-en": "female · adult · American · cheerful, expressive, energetic · for interview",
|
|
14
|
+
"aura-2-callista-en": "female · adult · American · clear, energetic, professional, smooth · for phone menus",
|
|
15
|
+
"aura-2-cora-en": "female · adult · American · smooth, melodic, caring · for storytelling",
|
|
16
|
+
"aura-2-cordelia-en": "female · young · American · approachable, warm, polite · for storytelling",
|
|
17
|
+
"aura-2-delia-en": "female · young · American · casual, friendly, cheerful, breathy · for interview",
|
|
18
|
+
"aura-2-draco-en": "male · adult · British · warm, approachable, trustworthy, baritone · for storytelling",
|
|
19
|
+
"aura-2-electra-en": "female · adult · American · professional, engaging, knowledgeable · for phone menus, advertising, customer service",
|
|
20
|
+
"aura-2-harmonia-en": "female · adult · American · empathetic, clear, calm, confident · for customer service",
|
|
21
|
+
"aura-2-helena-en": "female · adult · American · caring, natural, positive, friendly, raspy · for phone menus, casual chat",
|
|
22
|
+
"aura-2-hera-en": "female · adult · American · smooth, warm, professional · for informative",
|
|
23
|
+
"aura-2-hermes-en": "male · adult · American · expressive, engaging, professional · for informative",
|
|
24
|
+
"aura-2-hyperion-en": "male · adult · Australian · caring, warm, empathetic · for interview",
|
|
25
|
+
"aura-2-iris-en": "female · young · American · cheerful, positive, approachable · for phone menus, advertising, customer service",
|
|
26
|
+
"aura-2-janus-en": "female · adult · American · southern, smooth, trustworthy · for storytelling",
|
|
27
|
+
"aura-2-juno-en": "female · adult · American · natural, engaging, melodic, breathy · for interview",
|
|
28
|
+
"aura-2-jupiter-en": "male · adult · American · expressive, knowledgeable, baritone · for informative",
|
|
29
|
+
"aura-2-luna-en": "female · young · American · friendly, natural, engaging · for phone menus",
|
|
30
|
+
"aura-2-mars-en": "male · adult · American · smooth, patient, trustworthy, baritone · for customer service",
|
|
31
|
+
"aura-2-minerva-en": "female · adult · American · positive, friendly, natural · for storytelling",
|
|
32
|
+
"aura-2-neptune-en": "male · adult · American · professional, patient, polite · for customer service",
|
|
33
|
+
"aura-2-odysseus-en": "male · adult · American · calm, smooth, comfortable, professional · for advertising",
|
|
34
|
+
"aura-2-ophelia-en": "female · adult · American · expressive, enthusiastic, cheerful · for interview",
|
|
35
|
+
"aura-2-orion-en": "male · adult · American · approachable, comfortable, calm, polite · for informative",
|
|
36
|
+
"aura-2-orpheus-en": "male · adult · American · professional, clear, confident, trustworthy · for customer service, storytelling",
|
|
37
|
+
"aura-2-pandora-en": "female · adult · British · smooth, calm, melodic, breathy · for phone menus, informative",
|
|
38
|
+
"aura-2-phoebe-en": "female · adult · American · energetic, warm, casual · for customer service",
|
|
39
|
+
"aura-2-pluto-en": "male · adult · American · smooth, calm, empathetic, baritone · for interview, storytelling",
|
|
40
|
+
"aura-2-saturn-en": "male · adult · American · knowledgeable, confident, baritone · for customer service",
|
|
41
|
+
"aura-2-selene-en": "female · adult · American · expressive, engaging, energetic · for informative",
|
|
42
|
+
"aura-2-thalia-en": "female · adult · American · clear, confident, energetic, enthusiastic · for casual chat, customer service, phone menus",
|
|
43
|
+
"aura-2-theia-en": "female · adult · Australian · expressive, polite, sincere · for informative",
|
|
44
|
+
"aura-2-vesta-en": "female · adult · American · natural, expressive, patient, empathetic · for customer service, interview, storytelling",
|
|
45
|
+
"aura-2-zeus-en": "male · adult · American · deep, trustworthy, smooth · for phone menus",
|
|
46
|
+
"aura-2-agustina-es": "female · adult · Peninsular · calm, clear, expressive, knowledgeable, professional · for interview, casual chat",
|
|
47
|
+
"aura-2-alvaro-es": "male · adult · Peninsular · calm, professional, clear, knowledgeable, approachable · for interview, customer service",
|
|
48
|
+
"aura-2-antonia-es": "female · adult · Argentine · approachable, enthusiastic, friendly, natural, professional · for customer service, interview, casual chat",
|
|
49
|
+
"aura-2-aquila-es": "male · adult · Latin American · expressive, enthusiastic, confident, casual, comfortable · for casual chat, informative",
|
|
50
|
+
"aura-2-carina-es": "female · adult · Peninsular · professional, raspy, energetic, breathy, confident · for interview, customer service, phone menus",
|
|
51
|
+
"aura-2-celeste-es": "female · young · Colombian · clear, energetic, positive, friendly, enthusiastic · for casual chat, advertising, phone menus",
|
|
52
|
+
"aura-2-diana-es": "female · adult · Peninsular · professional, confident, expressive, polite, knowledgeable · for storytelling, advertising",
|
|
53
|
+
"aura-2-estrella-es": "female · mature · Mexican · approachable, natural, calm, comfortable, expressive · for casual chat, interview",
|
|
54
|
+
"aura-2-gloria-es": "female · young · Colombian · casual, clear, expressive, natural, smooth · for customer service, casual chat",
|
|
55
|
+
"aura-2-javier-es": "male · adult · Mexican · approachable, professional, friendly, comfortable, calm · for casual chat, phone menus, storytelling",
|
|
56
|
+
"aura-2-luciano-es": "male · adult · Mexican · charismatic, cheerful, energetic, expressive, friendly · for customer service, casual chat",
|
|
57
|
+
"aura-2-nestor-es": "male · adult · Peninsular · calm, professional, approachable, clear, confident · for casual chat, customer service",
|
|
58
|
+
"aura-2-olivia-es": "female · adult · Mexican · breathy, calm, casual, expressive, warm · for customer service, casual chat",
|
|
59
|
+
"aura-2-selena-es": "female · young · Latin American · approachable, casual, friendly, calm, positive · for customer service, informative",
|
|
60
|
+
"aura-2-silvia-es": "female · adult · Peninsular · charismatic, clear, expressive, natural, warm · for customer service, casual chat",
|
|
61
|
+
"aura-2-sirio-es": "male · adult · Mexican · calm, professional, comfortable, empathetic, baritone · for casual chat, interview",
|
|
62
|
+
"aura-2-valerio-es": "male · adult · Mexican · deep, knowledgeable, natural, polite, professional · for customer service, informative",
|
|
63
|
+
"aura-2-beatrix-nl": "female · adult · cheerful, enthusiastic, friendly, trustworthy, warm · for customer service",
|
|
64
|
+
"aura-2-cornelia-nl": "female · adult · approachable, friendly, polite, positive, warm · for customer service",
|
|
65
|
+
"aura-2-daphne-nl": "female · adult · calm, clear, confident, professional, smooth · for healthcare, interview, casual chat, audiobook",
|
|
66
|
+
"aura-2-hestia-nl": "female · adult · approachable, caring, expressive, friendly, knowledgeable · for customer service",
|
|
67
|
+
"aura-2-lars-nl": "male · adult · breathy, casual, comfortable, sincere, trustworthy · for customer service",
|
|
68
|
+
"aura-2-leda-nl": "female · adult · caring, comfortable, empathetic, friendly, sincere · for sales",
|
|
69
|
+
"aura-2-rhea-nl": "female · adult · caring, knowledgeable, positive, smooth, warm · for customer service",
|
|
70
|
+
"aura-2-roman-nl": "male · adult · calm, casual, deep, natural, patient · for customer service",
|
|
71
|
+
"aura-2-sander-nl": "male · adult · calm, clear, deep, professional, smooth · for customer service",
|
|
72
|
+
"aura-2-agathe-fr": "female · adult · charismatic, cheerful, enthusiastic, friendly, natural · for customer service",
|
|
73
|
+
"aura-2-hector-fr": "male · adult · confident, empathetic, expressive, friendly, patient · for customer service",
|
|
74
|
+
"aura-2-aurelia-de": "female · young · approachable, casual, comfortable, natural, sincere · for healthcare, customer service, sales, financial services",
|
|
75
|
+
"aura-2-elara-de": "female · adult · calm, clear, natural, patient, trustworthy · for healthcare, customer service, sales, financial services",
|
|
76
|
+
"aura-2-fabian-de": "male · mature · confident, knowledgeable, natural, polite, professional · for healthcare, customer service, sales, financial services",
|
|
77
|
+
"aura-2-julius-de": "male · adult · casual, cheerful, engaging, expressive, friendly · for healthcare, customer service, sales, financial services",
|
|
78
|
+
"aura-2-kara-de": "female · young · caring, empathetic, expressive, professional, warm · for healthcare, customer service, sales, financial services",
|
|
79
|
+
"aura-2-lara-de": "female · young · caring, cheerful, empathetic, expressive, warm · for healthcare, customer service, sales, financial services",
|
|
80
|
+
"aura-2-viktoria-de": "female · adult · charismatic, cheerful, enthusiastic, friendly, warm · for healthcare, customer service, sales, financial services",
|
|
81
|
+
"aura-2-cesare-it": "male · adult · clear, empathetic, knowledgeable, natural, smooth · for casual chat, customer service, interview, phone menus",
|
|
82
|
+
"aura-2-cinzia-it": "female · mature · approachable, friendly, smooth, trustworthy, warm · for customer service, interview, narration",
|
|
83
|
+
"aura-2-demetra-it": "female · adult · calm, comfortable, patient · for casual chat, interview, narration",
|
|
84
|
+
"aura-2-dionisio-it": "male · adult · confident, engaging, friendly, melodic, positive · for interview, casual chat, customer service",
|
|
85
|
+
"aura-2-elio-it": "male · adult · breathy, calm, professional, smooth, trustworthy · for interview, casual chat, customer service",
|
|
86
|
+
"aura-2-flavio-it": "male · adult · confident, deep, empathetic, professional, trustworthy · for casual chat, interview, customer service",
|
|
87
|
+
"aura-2-livia-it": "female · adult · approachable, cheerful, clear, engaging, expressive · for customer service, interview, audiobook",
|
|
88
|
+
"aura-2-maia-it": "female · young · caring, energetic, expressive, professional, warm · for interview, casual chat, customer service",
|
|
89
|
+
"aura-2-melia-it": "female · adult · clear, comfortable, engaging, friendly, natural · for casual chat, customer service, interview",
|
|
90
|
+
"aura-2-ama-ja": "female · adult · casual, comfortable, confident, knowledgeable, natural · for interview, phone menus",
|
|
91
|
+
"aura-2-ebisu-ja": "male · young · calm, deep, natural, patient, sincere · for casual chat, customer service",
|
|
92
|
+
"aura-2-fujin-ja": "male · adult · calm, confident, knowledgeable, professional, smooth · for interview, casual chat, phone menus",
|
|
93
|
+
"aura-2-izanami-ja": "female · adult · approachable, clear, knowledgeable, polite, professional · for casual chat, customer service, interview, phone menus",
|
|
94
|
+
"aura-2-uzume-ja": "female · young · approachable, clear, polite, professional, trustworthy · for customer service, interview, phone menus, commercial",
|
|
95
|
+
"Zephyr": "bright",
|
|
96
|
+
"Puck": "upbeat",
|
|
97
|
+
"Charon": "informative",
|
|
98
|
+
"Kore": "firm",
|
|
99
|
+
"Fenrir": "excitable",
|
|
100
|
+
"Leda": "youthful",
|
|
101
|
+
"Orus": "firm",
|
|
102
|
+
"Aoede": "breezy",
|
|
103
|
+
"Callirrhoe": "easy-going",
|
|
104
|
+
"Autonoe": "bright",
|
|
105
|
+
"Enceladus": "breathy",
|
|
106
|
+
"Iapetus": "clear",
|
|
107
|
+
"Umbriel": "easy-going",
|
|
108
|
+
"Algieba": "smooth",
|
|
109
|
+
"Despina": "smooth",
|
|
110
|
+
"Erinome": "clear",
|
|
111
|
+
"Algenib": "gravelly",
|
|
112
|
+
"Rasalgethi": "informative",
|
|
113
|
+
"Laomedeia": "upbeat",
|
|
114
|
+
"Achernar": "soft",
|
|
115
|
+
"Alnilam": "firm",
|
|
116
|
+
"Schedar": "even",
|
|
117
|
+
"Gacrux": "mature",
|
|
118
|
+
"Pulcherrima": "forward",
|
|
119
|
+
"Achird": "friendly",
|
|
120
|
+
"Zubenelgenubi": "casual",
|
|
121
|
+
"Vindemiatrix": "gentle",
|
|
122
|
+
"Sadachbia": "lively",
|
|
123
|
+
"Sadaltager": "knowledgeable",
|
|
124
|
+
"Sulafat": "warm",
|
|
125
|
+
"Rachel": "female · young · American · calm · for narration",
|
|
126
|
+
"Sarah": "female · young · American · confident, warm, professional · for news",
|
|
127
|
+
"Laura": "female · young · American · sunny, quirky, upbeat · for social media",
|
|
128
|
+
"Charlie": "male · young · Australian · confident, energetic, natural · for casual chat",
|
|
129
|
+
"George": "male · adult · British · calm, resonant, warm · for audiobook, narration",
|
|
130
|
+
"Callum": "male · adult · Transatlantic · gravelly, intense · for characters",
|
|
131
|
+
"River": "neutral · adult · American · relaxed · for narration, casual chat",
|
|
132
|
+
"Liam": "male · young · American · warm, energetic · for social media",
|
|
133
|
+
"Charlotte": "female · young · Swedish · sensual, raspy · for characters",
|
|
134
|
+
"Alice": "female · adult · British · clear, engaging · for e-learning",
|
|
135
|
+
"Matilda": "female · adult · American · professional, warm · for narration",
|
|
136
|
+
"Will": "male · young · American · friendly · for social media",
|
|
137
|
+
"Jessica": "female · young · American · playful, expressive · for casual chat",
|
|
138
|
+
"Eric": "male · adult · American · smooth · for casual chat",
|
|
139
|
+
"Chris": "male · adult · American · natural, down-to-earth · for casual chat",
|
|
140
|
+
"Brian": "male · adult · American · resonant, comforting, deep · for narration",
|
|
141
|
+
"Daniel": "male · adult · British · authoritative, clear · for news",
|
|
142
|
+
"Lily": "female · adult · British · velvety, warm · for narration, news",
|
|
143
|
+
"Bill": "male · mature · American · friendly, comforting · for advertising",
|
|
144
|
+
"serena": "female · young · warm, gentle",
|
|
145
|
+
"vivian": "female · young · bright, edgy",
|
|
146
|
+
"ono_anna": "female · playful, light",
|
|
147
|
+
"uncle_fu": "male · mature · deep, mellow",
|
|
148
|
+
"dylan": "male · young · Beijing · clear, natural",
|
|
149
|
+
"eric": "male · Sichuan · lively, husky, bright",
|
|
150
|
+
"ryan": "male · dynamic, rhythmic",
|
|
151
|
+
"aiden": "male · American · sunny, clear",
|
|
152
|
+
"conversational_a": "for casual chat",
|
|
153
|
+
"conversational_b": "for casual chat",
|
|
154
|
+
"read_speech_a": "for narration",
|
|
155
|
+
"read_speech_b": "for narration",
|
|
156
|
+
"read_speech_c": "for narration",
|
|
157
|
+
"read_speech_d": "for narration",
|
|
158
|
+
"af_heart": "grade A",
|
|
159
|
+
"af_bella": "grade A-",
|
|
160
|
+
"af_nicole": "grade B-",
|
|
161
|
+
"bf_emma": "grade B-",
|
|
162
|
+
"ff_siwis": "grade B-",
|
|
163
|
+
"af_aoede": "grade C+",
|
|
164
|
+
"af_kore": "grade C+",
|
|
165
|
+
"af_sarah": "grade C+",
|
|
166
|
+
"am_fenrir": "grade C+",
|
|
167
|
+
"am_michael": "grade C+",
|
|
168
|
+
"am_puck": "grade C+",
|
|
169
|
+
"jf_alpha": "grade C+",
|
|
170
|
+
"af_alloy": "grade C",
|
|
171
|
+
"af_nova": "grade C",
|
|
172
|
+
"bf_isabella": "grade C",
|
|
173
|
+
"bm_fable": "grade C",
|
|
174
|
+
"bm_george": "grade C",
|
|
175
|
+
"hf_alpha": "grade C",
|
|
176
|
+
"hf_beta": "grade C",
|
|
177
|
+
"hm_omega": "grade C",
|
|
178
|
+
"hm_psi": "grade C",
|
|
179
|
+
"if_sara": "grade C",
|
|
180
|
+
"im_nicola": "grade C",
|
|
181
|
+
"jf_gongitsune": "grade C",
|
|
182
|
+
"jf_tebukuro": "grade C",
|
|
183
|
+
"af_sky": "grade C-",
|
|
184
|
+
"jf_nezumi": "grade C-",
|
|
185
|
+
"jm_kumo": "grade C-",
|
|
186
|
+
"bm_lewis": "grade D+",
|
|
187
|
+
"af_jessica": "grade D",
|
|
188
|
+
"af_river": "grade D",
|
|
189
|
+
"am_echo": "grade D",
|
|
190
|
+
"am_eric": "grade D",
|
|
191
|
+
"am_liam": "grade D",
|
|
192
|
+
"am_onyx": "grade D",
|
|
193
|
+
"bf_alice": "grade D",
|
|
194
|
+
"bf_lily": "grade D",
|
|
195
|
+
"bm_daniel": "grade D",
|
|
196
|
+
"zf_xiaobei": "grade D",
|
|
197
|
+
"zf_xiaoni": "grade D",
|
|
198
|
+
"zf_xiaoxiao": "grade D",
|
|
199
|
+
"zf_xiaoyi": "grade D",
|
|
200
|
+
"zm_yunjian": "grade D",
|
|
201
|
+
"zm_yunxi": "grade D",
|
|
202
|
+
"zm_yunxia": "grade D",
|
|
203
|
+
"zm_yunyang": "grade D",
|
|
204
|
+
"am_santa": "grade D-",
|
|
205
|
+
"am_adam": "grade F+",
|
|
206
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "privateer-agent",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.57",
|
|
4
4
|
"description": "Privacy-first terminal coding agent — bring your own model across 20 providers (Anthropic, OpenAI, OpenRouter, Google, local Ollama…). Safe-by-default permissions, MCP, sub-agents, workflows, and verifiable TEE inference. Built on the Pi toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"pi-privacy": "0.14.1",
|
|
87
87
|
"pi-subagents": "0.70.0",
|
|
88
88
|
"picomatch": "4.0.5",
|
|
89
|
-
"privateer-speak": "0.
|
|
89
|
+
"privateer-speak": "0.6.0",
|
|
90
90
|
"privateer-workflow": "0.1.0",
|
|
91
91
|
"tinfoil": "1.1.11",
|
|
92
92
|
"tsx": "4.23.0",
|