@praeko/studio 0.1.0 → 0.2.0

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.
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
3
+
4
+ // ../voice/bin/account.ts
5
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
6
+ import { dirname as dirname2 } from "node:path";
7
+ import { createInterface } from "node:readline";
8
+
9
+ // ../voice/src/index.ts
10
+ import { ElevenLabsClient as ElevenLabsClient2 } from "@elevenlabs/elevenlabs-js";
11
+
12
+ // ../voice/src/config.ts
13
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { execFileSync } from "node:child_process";
15
+ import { homedir } from "node:os";
16
+ import { join, dirname, resolve } from "node:path";
17
+
18
+ // ../voice/src/defaults.ts
19
+ var DEFAULT_VOICES = {
20
+ accounts: { elevenlabs: ["principal"] },
21
+ voices: {
22
+ "draft-silent": { provider: "estimated", wordsPerMin: 190 },
23
+ "my-voice": { provider: "elevenlabs", model: "eleven_v4", ids: {}, aligner: "scribe" },
24
+ "local-voice": { provider: "voicestudio", model: "", id: "", language: "pt-BR", settings: {}, aligner: "voicestudio" }
25
+ },
26
+ strategy: { elevenlabs: "failover-balance" },
27
+ quality: { draft: "draft-silent", local: "local-voice", final: "my-voice" },
28
+ voicestudio: { base: "http://127.0.0.1:3900" }
29
+ };
30
+
31
+ // ../voice/src/config.ts
32
+ var userConfigDir = () => process.env.PRAEKO_STUDIO_CONFIG || join(homedir(), ".config", "praeko", "studio");
33
+ var userVoicesFile = () => join(userConfigDir(), "voices.json");
34
+ var secretsFile = () => process.env.PRAEKO_SECRETS || join(homedir(), ".config", "praeko", "secrets.json");
35
+ var KEYCHAIN_SERVICE = "praeko";
36
+ var keychainActive = () => process.platform === "darwin" && !process.env.PRAEKO_NO_KEYCHAIN;
37
+ function projectRoot(from = process.cwd()) {
38
+ let d = resolve(from);
39
+ for (; ; ) {
40
+ if (existsSync(join(d, "brand.json"))) return d;
41
+ const p = dirname(d);
42
+ if (p === d) return null;
43
+ d = p;
44
+ }
45
+ }
46
+ var projectVoicesFile = (from) => {
47
+ const r = projectRoot(from);
48
+ return r ? join(r, "voices.json") : null;
49
+ };
50
+ var writableVoicesFile = (from) => projectVoicesFile(from) || userVoicesFile();
51
+ var readJson = (file) => {
52
+ try {
53
+ return JSON.parse(readFileSync(file, "utf8"));
54
+ } catch (e) {
55
+ throw new Error(`${file}: invalid JSON (${e.message})`);
56
+ }
57
+ };
58
+ var merge = (base, extra) => ({
59
+ ...base,
60
+ ...extra,
61
+ accounts: { ...base.accounts, ...extra.accounts },
62
+ voices: { ...base.voices, ...extra.voices },
63
+ strategy: { ...base.strategy, ...extra.strategy },
64
+ quality: { ...base.quality, ...extra.quality },
65
+ voicestudio: { ...base.voicestudio, ...extra.voicestudio }
66
+ });
67
+ function loadConfig(file) {
68
+ if (file) return merge({ accounts: {}, voices: {}, strategy: {} }, readJson(file));
69
+ let c = structuredClone(DEFAULT_VOICES);
70
+ for (const f of [userVoicesFile(), projectVoicesFile()]) if (f && existsSync(f)) c = merge(c, readJson(f));
71
+ return c;
72
+ }
73
+ var keyEnvNames = (ref) => [`PRAEKO_STUDIO_KEY_${ref.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`, ...ref === "elevenlabs/principal" ? ["ELEVENLABS_API_KEY"] : [], ...ref === "praeko/main" ? ["PRAEKO_KEY"] : []];
74
+ var canonical = (ref) => ref === "praeko/main" ? "praeko/chave" : ref;
75
+ var readSecrets = () => {
76
+ try {
77
+ return existsSync(secretsFile()) ? JSON.parse(readFileSync(secretsFile(), "utf8")) : {};
78
+ } catch {
79
+ return {};
80
+ }
81
+ };
82
+ function getKey(ref) {
83
+ for (const n of keyEnvNames(ref)) if (process.env[n]) return process.env[n];
84
+ const refs = [.../* @__PURE__ */ new Set([canonical(ref), ref])];
85
+ if (keychainActive()) for (const r of refs) {
86
+ try {
87
+ const k = execFileSync("security", ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", r, "-w"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
88
+ if (k) return k;
89
+ } catch {
90
+ }
91
+ }
92
+ const secrets = readSecrets();
93
+ for (const r of refs) if (secrets[r]) return secrets[r];
94
+ return null;
95
+ }
96
+ function storeKey(ref, key) {
97
+ ref = canonical(ref);
98
+ if (keychainActive()) {
99
+ execFileSync("security", ["add-generic-password", "-U", "-s", KEYCHAIN_SERVICE, "-a", ref, "-w", key], { stdio: ["ignore", "ignore", "pipe"] });
100
+ return "keychain";
101
+ }
102
+ const f = secretsFile();
103
+ mkdirSync(dirname(f), { recursive: true });
104
+ const s = readSecrets();
105
+ s[ref] = key;
106
+ writeFileSync(f, JSON.stringify(s, null, 2) + "\n", { mode: 384 });
107
+ if (process.platform !== "win32") chmodSync(f, 384);
108
+ return "file";
109
+ }
110
+ var maskKey = (k) => k ? `${k.slice(0, 3)}\u2026${k.slice(-2)}` : "(no key)";
111
+
112
+ // ../voice/src/providers/elevenlabs.ts
113
+ import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
114
+ function createElevenLabs({ key }) {
115
+ const client = new ElevenLabsClient({ apiKey: key });
116
+ return {
117
+ name: "elevenlabs",
118
+ /** `{ text, id, model, settings }` → mp3 Buffer */
119
+ async generate({ text, id, model, settings = {} }) {
120
+ const audio = await client.textToSpeech.convert(id, {
121
+ text,
122
+ modelId: model || "eleven_v4",
123
+ languageCode: "pt",
124
+ outputFormat: "mp3_44100_128",
125
+ voiceSettings: { stability: settings.stability ?? 0.5, similarityBoost: settings.similarity ?? 0.75, style: settings.style ?? 0, useSpeakerBoost: true, speed: settings.speed ?? 1 }
126
+ });
127
+ const chunks = [];
128
+ for await (const c of audio) chunks.push(c);
129
+ return Buffer.concat(chunks);
130
+ },
131
+ /** Characters used, limit, remaining, plan and reset date. */
132
+ async balance() {
133
+ const r = await fetch("https://api.elevenlabs.io/v1/user/subscription", { headers: { "xi-api-key": key } });
134
+ if (!r.ok) {
135
+ const e = new Error(`balance: HTTP ${r.status}`);
136
+ e.statusCode = r.status;
137
+ throw e;
138
+ }
139
+ const d = await r.json();
140
+ return { used: d.character_count, limit: d.character_limit, remaining: d.character_limit - d.character_count, plan: d.tier, resets: d.next_character_count_reset_unix ? new Date(d.next_character_count_reset_unix * 1e3) : null };
141
+ }
142
+ };
143
+ }
144
+
145
+ // ../voice/src/router.ts
146
+ async function balances({ config = loadConfig(), create } = {}) {
147
+ const factory = create || ((_a, key) => createElevenLabs({ key }));
148
+ const out = [];
149
+ const el = config.accounts?.elevenlabs;
150
+ for (const account of Array.isArray(el) ? el : []) {
151
+ const key = getKey(`elevenlabs/${account}`);
152
+ if (!key) {
153
+ out.push({ account, error: "no key" });
154
+ continue;
155
+ }
156
+ try {
157
+ out.push({ account, ...await factory(account, key).balance() });
158
+ } catch (e) {
159
+ out.push({ account, error: e.message });
160
+ }
161
+ }
162
+ return out;
163
+ }
164
+
165
+ // ../voice/src/alignment.ts
166
+ var NUM = new Set("zero um uma dois duas tr\xEAs tres quatro cinco seis sete oito nove dez onze doze treze catorze quatorze quinze dezesseis dezessete dezoito dezenove vinte trinta quarenta cinquenta sessenta setenta oitenta noventa cem cento duzentos duzentas trezentos trezentas quatrocentos quatrocentas quinhentos quinhentas seiscentos seiscentas setecentos setecentas oitocentos oitocentas novecentos novecentas mil milh\xE3o milh\xF5es bilh\xE3o bilh\xF5es meio meia primeiro primeira".split(" "));
167
+
168
+ // ../voice/bin/account.ts
169
+ var a = process.argv.slice(2);
170
+ var cmd = a[0];
171
+ var name = a[1];
172
+ var opt = (k) => {
173
+ const i = a.indexOf("--" + k);
174
+ return i >= 0 ? a[i + 1] : void 0;
175
+ };
176
+ var fmt = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
177
+ async function readKey() {
178
+ const fromEnv = opt("from-env");
179
+ if (fromEnv) return process.env[fromEnv];
180
+ if (!process.stdin.isTTY) return await new Promise((ok) => {
181
+ let s = "";
182
+ process.stdin.on("data", (d) => s += d);
183
+ process.stdin.on("end", () => ok(s));
184
+ });
185
+ return await new Promise((ok) => {
186
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
187
+ rl._writeToOutput = (s) => {
188
+ if (s.includes("\n") || s.startsWith("Key")) process.stdout.write(s);
189
+ };
190
+ rl.question(`Key for elevenlabs/${name}: `, (k) => {
191
+ rl.close();
192
+ console.log();
193
+ ok(k);
194
+ });
195
+ });
196
+ }
197
+ async function api(path, key) {
198
+ const r = await fetch(`https://api.elevenlabs.io${path}`, { headers: { "xi-api-key": key }, signal: AbortSignal.timeout(2e4) });
199
+ if (!r.ok) {
200
+ let msg = "";
201
+ try {
202
+ const d = await r.json();
203
+ msg = d?.detail?.message || d?.detail?.status || JSON.stringify(d).slice(0, 160);
204
+ } catch {
205
+ }
206
+ const e = new Error(`${path}: HTTP ${r.status}${msg ? ` \u2014 ${msg}` : ""}`);
207
+ e.status = r.status;
208
+ throw e;
209
+ }
210
+ return r.json();
211
+ }
212
+ if (cmd === "list") {
213
+ const cfg = loadConfig();
214
+ const s = await balances({ config: cfg });
215
+ for (const c of s) console.log(`${c.account.padEnd(12)} ${"error" in c ? c.error : `${fmt(c.remaining)} left of ${fmt(c.limit)} (${c.plan}), resets ${c.resets?.toLocaleDateString("en-US")}`}`);
216
+ const seen = /* @__PURE__ */ new Map();
217
+ for (const c of s) {
218
+ if ("error" in c) continue;
219
+ const k = `${c.used}|${c.limit}|${c.resets?.getTime()}`;
220
+ seen.set(k, [...seen.get(k) || [], c.account]);
221
+ }
222
+ for (const [, l] of seen) if (l.length > 1) console.log(`WARNING: ${l.join(" and ")} show the SAME balance: they are probably the same workspace/plan (credits are shared).`);
223
+ } else if (cmd === "add" && name) {
224
+ if (!/^[a-z0-9][a-z0-9_-]*$/i.test(name)) {
225
+ console.error("name: use letters, numbers, - or _");
226
+ process.exit(2);
227
+ }
228
+ const key = (await readKey() || "").trim();
229
+ if (!key) {
230
+ console.error("empty key");
231
+ process.exit(1);
232
+ }
233
+ const alias = opt("voice") || "my-voice";
234
+ const cfg = loadConfig();
235
+ if (!cfg.voices[alias]) {
236
+ console.error(`voice "${alias}" does not exist in voices.json`);
237
+ process.exit(1);
238
+ }
239
+ let sub = null, voices = null, subError = null, voiceError = null;
240
+ try {
241
+ sub = await api("/v1/user/subscription", key);
242
+ } catch (e) {
243
+ subError = e;
244
+ }
245
+ try {
246
+ voices = (await api("/v1/voices", key)).voices || [];
247
+ } catch (e) {
248
+ voiceError = e;
249
+ }
250
+ if (subError && voiceError) {
251
+ console.error(`ElevenLabs rejected the key (${maskKey(key)}):
252
+ ${subError.message}
253
+ ${voiceError.message}
254
+ Check: (1) the key was copied whole, without spaces; (2) it was created in the right workspace; (3) if restricted, it has the permissions Voices (read), User (read), Text to Speech and Speech to Text.`);
255
+ process.exit(1);
256
+ }
257
+ if (sub) {
258
+ console.log(`account ${name}: plan ${sub.tier}, ${fmt(sub.character_limit - sub.character_count)} left of ${fmt(sub.character_limit)}, resets ${new Date(sub.next_character_count_reset_unix * 1e3).toLocaleDateString("en-US")} (key ${maskKey(key)})`);
259
+ for (const c of await balances({ config: cfg })) if (!("error" in c) && c.used === sub.character_count && c.limit === sub.character_limit) console.log(`WARNING: the balance is identical to account "${c.account}": it may be the SAME workspace (shared credits), and failover would not help.`);
260
+ } else {
261
+ console.log(`NOTE: the key cannot read the balance (${subError?.message}). It works for generating voice, but balance failover will not work for this account: enable the "User" (read) permission on the key so Praeko Studio can see the credits.`);
262
+ }
263
+ voices = voices || [];
264
+ const clones = voices.filter((v) => ["cloned", "professional"].includes(v.category));
265
+ console.log(`${voices.length} voices on this key; clones: ${clones.length ? clones.map((v) => `${v.name} (${v.voice_id})`).join(", ") : "none"}${voiceError ? ` (voice list unavailable: ${voiceError.message})` : ""}`);
266
+ let id = opt("voice-id");
267
+ if (!id) {
268
+ const candidates = clones.filter((v) => new RegExp(alias, "i").test(v.name));
269
+ if (candidates.length === 1) id = candidates[0].voice_id;
270
+ else {
271
+ console.error(candidates.length ? `several clones look like "${alias}": repeat with --voice-id <id>` : `no clone named "${alias}" on this key: create the clone in that workspace/account or repeat with --voice-id <id>`);
272
+ process.exit(1);
273
+ }
274
+ }
275
+ console.log(`voice "${alias}" on this account: ${id}`);
276
+ if (a.includes("--dry-run")) {
277
+ console.log("(dry run: nothing was stored)");
278
+ process.exit(0);
279
+ }
280
+ const where = storeKey(`elevenlabs/${name}`, key);
281
+ const target = writableVoicesFile();
282
+ mkdirSync2(dirname2(target), { recursive: true });
283
+ const raw = existsSync2(target) ? JSON.parse(readFileSync2(target, "utf8")) : {};
284
+ raw.accounts ||= {};
285
+ raw.accounts.elevenlabs ||= [];
286
+ if (!raw.accounts.elevenlabs.includes(name)) raw.accounts.elevenlabs.push(name);
287
+ raw.voices ||= {};
288
+ raw.voices[alias] = { ...cfg.voices[alias], ...raw.voices[alias] || {} };
289
+ raw.voices[alias].ids = { ...raw.voices[alias].ids || {}, [name]: id };
290
+ writeFileSync2(target, JSON.stringify(raw, null, 2) + "\n");
291
+ console.log(`done: key stored (${where}, elevenlabs/${name}) and id in ${target}. Failover order: ${raw.accounts.elevenlabs.join(" \u2192 ")}`);
292
+ } else {
293
+ console.error("usage: account.ts list | account.ts add <name> [--from-env VAR] [--voice my-voice] [--voice-id ID] [--dry-run]");
294
+ process.exit(2);
295
+ }
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
3
+
4
+ // ../voice/src/index.ts
5
+ import { ElevenLabsClient as ElevenLabsClient2 } from "@elevenlabs/elevenlabs-js";
6
+
7
+ // ../voice/src/config.ts
8
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { execFileSync } from "node:child_process";
10
+ import { homedir } from "node:os";
11
+ import { join, dirname, resolve } from "node:path";
12
+
13
+ // ../voice/src/defaults.ts
14
+ var DEFAULT_VOICES = {
15
+ accounts: { elevenlabs: ["principal"] },
16
+ voices: {
17
+ "draft-silent": { provider: "estimated", wordsPerMin: 190 },
18
+ "my-voice": { provider: "elevenlabs", model: "eleven_v4", ids: {}, aligner: "scribe" },
19
+ "local-voice": { provider: "voicestudio", model: "", id: "", language: "pt-BR", settings: {}, aligner: "voicestudio" }
20
+ },
21
+ strategy: { elevenlabs: "failover-balance" },
22
+ quality: { draft: "draft-silent", local: "local-voice", final: "my-voice" },
23
+ voicestudio: { base: "http://127.0.0.1:3900" }
24
+ };
25
+
26
+ // ../voice/src/config.ts
27
+ var userConfigDir = () => process.env.PRAEKO_STUDIO_CONFIG || join(homedir(), ".config", "praeko", "studio");
28
+ var userVoicesFile = () => join(userConfigDir(), "voices.json");
29
+ var secretsFile = () => process.env.PRAEKO_SECRETS || join(homedir(), ".config", "praeko", "secrets.json");
30
+ var KEYCHAIN_SERVICE = "praeko";
31
+ var keychainActive = () => process.platform === "darwin" && !process.env.PRAEKO_NO_KEYCHAIN;
32
+ function projectRoot(from = process.cwd()) {
33
+ let d = resolve(from);
34
+ for (; ; ) {
35
+ if (existsSync(join(d, "brand.json"))) return d;
36
+ const p = dirname(d);
37
+ if (p === d) return null;
38
+ d = p;
39
+ }
40
+ }
41
+ var projectVoicesFile = (from) => {
42
+ const r = projectRoot(from);
43
+ return r ? join(r, "voices.json") : null;
44
+ };
45
+ var readJson = (file) => {
46
+ try {
47
+ return JSON.parse(readFileSync(file, "utf8"));
48
+ } catch (e) {
49
+ throw new Error(`${file}: invalid JSON (${e.message})`);
50
+ }
51
+ };
52
+ var merge = (base, extra) => ({
53
+ ...base,
54
+ ...extra,
55
+ accounts: { ...base.accounts, ...extra.accounts },
56
+ voices: { ...base.voices, ...extra.voices },
57
+ strategy: { ...base.strategy, ...extra.strategy },
58
+ quality: { ...base.quality, ...extra.quality },
59
+ voicestudio: { ...base.voicestudio, ...extra.voicestudio }
60
+ });
61
+ function loadConfig(file) {
62
+ if (file) return merge({ accounts: {}, voices: {}, strategy: {} }, readJson(file));
63
+ let c = structuredClone(DEFAULT_VOICES);
64
+ for (const f of [userVoicesFile(), projectVoicesFile()]) if (f && existsSync(f)) c = merge(c, readJson(f));
65
+ return c;
66
+ }
67
+ var keyEnvNames = (ref) => [`PRAEKO_STUDIO_KEY_${ref.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`, ...ref === "elevenlabs/principal" ? ["ELEVENLABS_API_KEY"] : [], ...ref === "praeko/main" ? ["PRAEKO_KEY"] : []];
68
+ var canonical = (ref) => ref === "praeko/main" ? "praeko/chave" : ref;
69
+ var readSecrets = () => {
70
+ try {
71
+ return existsSync(secretsFile()) ? JSON.parse(readFileSync(secretsFile(), "utf8")) : {};
72
+ } catch {
73
+ return {};
74
+ }
75
+ };
76
+ function getKey(ref) {
77
+ for (const n of keyEnvNames(ref)) if (process.env[n]) return process.env[n];
78
+ const refs = [.../* @__PURE__ */ new Set([canonical(ref), ref])];
79
+ if (keychainActive()) for (const r of refs) {
80
+ try {
81
+ const k = execFileSync("security", ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", r, "-w"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
82
+ if (k) return k;
83
+ } catch {
84
+ }
85
+ }
86
+ const secrets = readSecrets();
87
+ for (const r of refs) if (secrets[r]) return secrets[r];
88
+ return null;
89
+ }
90
+
91
+ // ../voice/src/providers/elevenlabs.ts
92
+ import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
93
+ function createElevenLabs({ key }) {
94
+ const client = new ElevenLabsClient({ apiKey: key });
95
+ return {
96
+ name: "elevenlabs",
97
+ /** `{ text, id, model, settings }` → mp3 Buffer */
98
+ async generate({ text, id, model, settings = {} }) {
99
+ const audio = await client.textToSpeech.convert(id, {
100
+ text,
101
+ modelId: model || "eleven_v4",
102
+ languageCode: "pt",
103
+ outputFormat: "mp3_44100_128",
104
+ voiceSettings: { stability: settings.stability ?? 0.5, similarityBoost: settings.similarity ?? 0.75, style: settings.style ?? 0, useSpeakerBoost: true, speed: settings.speed ?? 1 }
105
+ });
106
+ const chunks = [];
107
+ for await (const c of audio) chunks.push(c);
108
+ return Buffer.concat(chunks);
109
+ },
110
+ /** Characters used, limit, remaining, plan and reset date. */
111
+ async balance() {
112
+ const r = await fetch("https://api.elevenlabs.io/v1/user/subscription", { headers: { "xi-api-key": key } });
113
+ if (!r.ok) {
114
+ const e = new Error(`balance: HTTP ${r.status}`);
115
+ e.statusCode = r.status;
116
+ throw e;
117
+ }
118
+ const d = await r.json();
119
+ return { used: d.character_count, limit: d.character_limit, remaining: d.character_limit - d.character_count, plan: d.tier, resets: d.next_character_count_reset_unix ? new Date(d.next_character_count_reset_unix * 1e3) : null };
120
+ }
121
+ };
122
+ }
123
+
124
+ // ../voice/src/router.ts
125
+ async function balances({ config = loadConfig(), create } = {}) {
126
+ const factory = create || ((_a, key) => createElevenLabs({ key }));
127
+ const out = [];
128
+ const el = config.accounts?.elevenlabs;
129
+ for (const account of Array.isArray(el) ? el : []) {
130
+ const key = getKey(`elevenlabs/${account}`);
131
+ if (!key) {
132
+ out.push({ account, error: "no key" });
133
+ continue;
134
+ }
135
+ try {
136
+ out.push({ account, ...await factory(account, key).balance() });
137
+ } catch (e) {
138
+ out.push({ account, error: e.message });
139
+ }
140
+ }
141
+ return out;
142
+ }
143
+
144
+ // ../voice/src/alignment.ts
145
+ var NUM = new Set("zero um uma dois duas tr\xEAs tres quatro cinco seis sete oito nove dez onze doze treze catorze quatorze quinze dezesseis dezessete dezoito dezenove vinte trinta quarenta cinquenta sessenta setenta oitenta noventa cem cento duzentos duzentas trezentos trezentas quatrocentos quatrocentas quinhentos quinhentas seiscentos seiscentas setecentos setecentas oitocentos oitocentas novecentos novecentas mil milh\xE3o milh\xF5es bilh\xE3o bilh\xF5es meio meia primeiro primeira".split(" "));
146
+
147
+ // ../voice/bin/balance.ts
148
+ var fmt = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
149
+ for (const s of await balances()) {
150
+ if ("error" in s) console.log(`${s.account.padEnd(10)} ${s.error}`);
151
+ else console.log(`${s.account.padEnd(10)} ${fmt(s.remaining)} left of ${fmt(s.limit)} (${s.plan})${s.resets ? ", resets on " + s.resets.toLocaleDateString("en-US") : ""}`);
152
+ }
@@ -1,81 +1,84 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
3
3
 
4
- // ../voz/src/index.mjs
4
+ // ../voice/bin/config.ts
5
+ import { createInterface } from "node:readline";
6
+
7
+ // ../voice/src/index.ts
5
8
  import { ElevenLabsClient as ElevenLabsClient2 } from "@elevenlabs/elevenlabs-js";
6
9
 
7
- // ../voz/src/config.mjs
10
+ // ../voice/src/config.ts
8
11
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
12
  import { execFileSync } from "node:child_process";
10
13
  import { homedir } from "node:os";
11
14
  import { join, dirname, resolve } from "node:path";
12
- var arquivoSegredos = () => process.env.PRAEKO_SEGREDOS || join(homedir(), ".config", "praeko", "segredos.json");
13
- var SERVICO_CHAVEIRO = "praeko";
14
- var chaveiroAtivo = () => process.platform === "darwin" && !process.env.PRAEKO_SEM_CHAVEIRO;
15
- var canonica = (ref2) => ref2 === "praeko/principal" ? "praeko/chave" : ref2;
16
- var lerSegredos = () => {
15
+ var secretsFile = () => process.env.PRAEKO_SECRETS || join(homedir(), ".config", "praeko", "secrets.json");
16
+ var KEYCHAIN_SERVICE = "praeko";
17
+ var keychainActive = () => process.platform === "darwin" && !process.env.PRAEKO_NO_KEYCHAIN;
18
+ var canonical = (ref2) => ref2 === "praeko/main" ? "praeko/chave" : ref2;
19
+ var readSecrets = () => {
17
20
  try {
18
- return existsSync(arquivoSegredos()) ? JSON.parse(readFileSync(arquivoSegredos(), "utf8")) : {};
21
+ return existsSync(secretsFile()) ? JSON.parse(readFileSync(secretsFile(), "utf8")) : {};
19
22
  } catch {
20
23
  return {};
21
24
  }
22
25
  };
23
- function guardarChave(ref2, chave) {
24
- ref2 = canonica(ref2);
25
- if (chaveiroAtivo()) {
26
- execFileSync("security", ["add-generic-password", "-U", "-s", SERVICO_CHAVEIRO, "-a", ref2, "-w", chave], { stdio: ["ignore", "ignore", "pipe"] });
27
- return "chaveiro";
26
+ function storeKey(ref2, key) {
27
+ ref2 = canonical(ref2);
28
+ if (keychainActive()) {
29
+ execFileSync("security", ["add-generic-password", "-U", "-s", KEYCHAIN_SERVICE, "-a", ref2, "-w", key], { stdio: ["ignore", "ignore", "pipe"] });
30
+ return "keychain";
28
31
  }
29
- const f = arquivoSegredos();
32
+ const f = secretsFile();
30
33
  mkdirSync(dirname(f), { recursive: true });
31
- const s = lerSegredos();
32
- s[ref2] = chave;
34
+ const s = readSecrets();
35
+ s[ref2] = key;
33
36
  writeFileSync(f, JSON.stringify(s, null, 2) + "\n", { mode: 384 });
34
37
  if (process.platform !== "win32") chmodSync(f, 384);
35
- return "arquivo";
38
+ return "file";
36
39
  }
37
- var mascarar = (k) => k ? `${k.slice(0, 3)}\u2026${k.slice(-2)}` : "(sem chave)";
40
+ var maskKey = (k) => k ? `${k.slice(0, 3)}\u2026${k.slice(-2)}` : "(no key)";
38
41
 
39
- // ../voz/src/provedores/elevenlabs.mjs
42
+ // ../voice/src/providers/elevenlabs.ts
40
43
  import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
41
44
 
42
- // ../voz/src/alinhamento.mjs
45
+ // ../voice/src/alignment.ts
43
46
  var NUM = new Set("zero um uma dois duas tr\xEAs tres quatro cinco seis sete oito nove dez onze doze treze catorze quatorze quinze dezesseis dezessete dezoito dezenove vinte trinta quarenta cinquenta sessenta setenta oitenta noventa cem cento duzentos duzentas trezentos trezentas quatrocentos quatrocentas quinhentos quinhentas seiscentos seiscentas setecentos setecentas oitocentos oitocentas novecentos novecentas mil milh\xE3o milh\xF5es bilh\xE3o bilh\xF5es meio meia primeiro primeira".split(" "));
44
47
 
45
- // ../voz/bin/config.mjs
46
- import { createInterface } from "node:readline";
48
+ // ../voice/bin/config.ts
47
49
  var a = process.argv.slice(2);
48
50
  var [cmd, ref] = a;
49
51
  var opt = (k) => {
50
52
  const i = a.indexOf("--" + k);
51
53
  return i >= 0 ? a[i + 1] : void 0;
52
54
  };
53
- if (cmd !== "chave" || !ref) {
54
- console.error("uso: config.mjs chave <elevenlabs/nome> [--de-env VAR]");
55
+ if (cmd !== "key" || !ref) {
56
+ console.error("usage: config.ts key <elevenlabs/name> [--from-env VAR]");
55
57
  process.exit(2);
56
58
  }
57
- var salvar = (k) => {
58
- const chave = (k || "").trim();
59
- if (!chave) {
60
- console.error("chave vazia: nada foi guardado");
59
+ var save = (k) => {
60
+ const key = (k || "").trim();
61
+ if (!key) {
62
+ console.error("empty key: nothing was stored");
61
63
  process.exit(1);
62
64
  }
63
- const onde = guardarChave(ref, chave);
64
- console.log(`guardada (${onde}, ${ref}): ${mascarar(chave)}`);
65
+ const where = storeKey(ref, key);
66
+ console.log(`stored (${where}, ${ref}): ${maskKey(key)}`);
65
67
  };
66
- if (opt("de-env")) salvar(process.env[opt("de-env")]);
68
+ var fromEnv = opt("from-env");
69
+ if (fromEnv) save(process.env[fromEnv]);
67
70
  else if (!process.stdin.isTTY) {
68
71
  let s = "";
69
72
  process.stdin.on("data", (d) => s += d);
70
- process.stdin.on("end", () => salvar(s));
73
+ process.stdin.on("end", () => save(s));
71
74
  } else {
72
75
  const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
73
76
  rl._writeToOutput = (s) => {
74
- if (s.includes("\n") || s.startsWith("Chave")) process.stdout.write(s);
77
+ if (s.includes("\n") || s.startsWith("Key")) process.stdout.write(s);
75
78
  };
76
- rl.question(`Chave de ${ref}: `, (k) => {
79
+ rl.question(`Key for ${ref}: `, (k) => {
77
80
  rl.close();
78
81
  console.log();
79
- salvar(k);
82
+ save(k);
80
83
  });
81
84
  }