@nac3/forge-cli 1.21.2 → 1.21.4
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/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +6 -1
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/tools/pending.d.ts.map +1 -1
- package/dist/chat/tools/pending.js +8 -2
- package/dist/chat/tools/pending.js.map +1 -1
- package/dist/chat/tools/urazuke.d.ts.map +1 -1
- package/dist/chat/tools/urazuke.js +7 -3
- package/dist/chat/tools/urazuke.js.map +1 -1
- package/dist/commands/pending.d.ts.map +1 -1
- package/dist/commands/pending.js +4 -2
- package/dist/commands/pending.js.map +1 -1
- package/dist/commands/urazuke.d.ts.map +1 -1
- package/dist/commands/urazuke.js +138 -10
- package/dist/commands/urazuke.js.map +1 -1
- package/dist/core/whats_new.d.ts.map +1 -1
- package/dist/core/whats_new.js +22 -0
- package/dist/core/whats_new.js.map +1 -1
- package/dist/urazuke/current.d.ts +2 -2
- package/dist/urazuke/current.d.ts.map +1 -1
- package/dist/urazuke/current.js +32 -12
- package/dist/urazuke/current.js.map +1 -1
- package/dist/urazuke/gate.d.ts +45 -7
- package/dist/urazuke/gate.d.ts.map +1 -1
- package/dist/urazuke/gate.js +93 -15
- package/dist/urazuke/gate.js.map +1 -1
- package/dist/urazuke/gates_data.d.ts +163 -0
- package/dist/urazuke/gates_data.d.ts.map +1 -1
- package/dist/urazuke/gates_data.js +73 -2
- package/dist/urazuke/gates_data.js.map +1 -1
- package/dist/urazuke/gates_ledger.d.ts +75 -0
- package/dist/urazuke/gates_ledger.d.ts.map +1 -0
- package/dist/urazuke/gates_ledger.js +183 -0
- package/dist/urazuke/gates_ledger.js.map +1 -0
- package/dist/urazuke/gates_pull.d.ts +97 -0
- package/dist/urazuke/gates_pull.d.ts.map +1 -0
- package/dist/urazuke/gates_pull.js +303 -0
- package/dist/urazuke/gates_pull.js.map +1 -0
- package/dist/urazuke/retro.d.ts.map +1 -1
- package/dist/urazuke/retro.js +4 -1
- package/dist/urazuke/retro.js.map +1 -1
- package/dist/urazuke/section.d.ts +2 -2
- package/dist/urazuke/section.d.ts.map +1 -1
- package/dist/urazuke/section.js +31 -6
- package/dist/urazuke/section.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workflow/orchestrator.d.ts +5 -0
- package/dist/workflow/orchestrator.d.ts.map +1 -1
- package/dist/workflow/orchestrator.js +17 -16
- package/dist/workflow/orchestrator.js.map +1 -1
- package/package.json +2 -2
- package/dist/chat/flow_layout.d.ts +0 -76
- package/dist/chat/flow_layout.d.ts.map +0 -1
- package/dist/chat/flow_layout.js +0 -247
- package/dist/chat/flow_layout.js.map +0 -1
- package/dist/commands/director.d.ts +0 -37
- package/dist/commands/director.d.ts.map +0 -1
- package/dist/commands/director.js +0 -290
- package/dist/commands/director.js.map +0 -1
- package/dist/commands/voice_setup.d.ts +0 -37
- package/dist/commands/voice_setup.d.ts.map +0 -1
- package/dist/commands/voice_setup.js +0 -308
- package/dist/commands/voice_setup.js.map +0 -1
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import crypto from 'node:crypto';
|
|
4
|
-
import { getAgentDefinition } from '../agents/store.js';
|
|
5
|
-
import { runAgent } from '../agents/runner.js';
|
|
6
|
-
import { recordIngest } from '../chat/ingest_session.js';
|
|
7
|
-
import { createRuntime } from '../agents/runtime.js';
|
|
8
|
-
function fileExtToFormat(filename) {
|
|
9
|
-
const ext = path.extname(filename).toLowerCase();
|
|
10
|
-
if (ext === '.md' || ext === '.markdown')
|
|
11
|
-
return 'markdown';
|
|
12
|
-
if (ext === '.txt')
|
|
13
|
-
return 'markdown'; /* parse as markdown */
|
|
14
|
-
if (ext === '.pdf')
|
|
15
|
-
return 'pdf';
|
|
16
|
-
if (ext === '.docx')
|
|
17
|
-
return 'docx';
|
|
18
|
-
return 'markdown'; /* best-effort default */
|
|
19
|
-
}
|
|
20
|
-
function buildDocFromText(filename, text) {
|
|
21
|
-
/* deterministic doc_id from file content -- 8-char sha1 hex slug. */
|
|
22
|
-
const hash = crypto.createHash('sha1').update(filename + ':' + text).digest('hex').slice(0, 12);
|
|
23
|
-
const slug = path.basename(filename).replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 60);
|
|
24
|
-
const id = slug + '-' + hash;
|
|
25
|
-
/* Split into sections per top-level heading. Fallback: one
|
|
26
|
-
* section with the whole text as a single paragraph block. */
|
|
27
|
-
const lines = text.split(/\r?\n/);
|
|
28
|
-
const sections = [];
|
|
29
|
-
let curHeading = '';
|
|
30
|
-
let curLevel = 0;
|
|
31
|
-
let curBlocks = [];
|
|
32
|
-
let sectionIdx = 0;
|
|
33
|
-
function flush() {
|
|
34
|
-
if (curBlocks.length === 0 && !curHeading)
|
|
35
|
-
return;
|
|
36
|
-
sections.push({
|
|
37
|
-
id: 'sec-' + sectionIdx,
|
|
38
|
-
heading: curHeading,
|
|
39
|
-
level: curLevel,
|
|
40
|
-
blocks: curBlocks.length > 0
|
|
41
|
-
? curBlocks.map((b) => ({ kind: 'paragraph', text: b }))
|
|
42
|
-
: [],
|
|
43
|
-
});
|
|
44
|
-
sectionIdx += 1;
|
|
45
|
-
curHeading = '';
|
|
46
|
-
curLevel = 0;
|
|
47
|
-
curBlocks = [];
|
|
48
|
-
}
|
|
49
|
-
for (const l of lines) {
|
|
50
|
-
const m = /^(#{1,6})\s+(.+)$/.exec(l.trim());
|
|
51
|
-
if (m) {
|
|
52
|
-
flush();
|
|
53
|
-
curHeading = m[2].trim();
|
|
54
|
-
curLevel = m[1].length;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (l.trim().length > 0) {
|
|
58
|
-
curBlocks.push(l.trim());
|
|
59
|
-
}
|
|
60
|
-
else if (curBlocks.length > 0) {
|
|
61
|
-
/* blank line -> next paragraph boundary; leave for next non-blank. */
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
flush();
|
|
65
|
-
if (sections.length === 0) {
|
|
66
|
-
sections.push({
|
|
67
|
-
id: 'sec-0', heading: '', level: 0,
|
|
68
|
-
blocks: [{ kind: 'paragraph', text }],
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
id,
|
|
73
|
-
filename: path.basename(filename),
|
|
74
|
-
format: fileExtToFormat(filename),
|
|
75
|
-
bytes: Buffer.byteLength(text, 'utf-8'),
|
|
76
|
-
title: sections[0].heading || path.basename(filename),
|
|
77
|
-
sections,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
export function registerDirectorCommand(program, deps = {}) {
|
|
81
|
-
program.command('director')
|
|
82
|
-
.description('Single-entry orchestrator: ingest a spec + dispatch the Director. Default is a smoke run (ingest only). Pass --real to fire the live LLM step.')
|
|
83
|
-
.argument('<spec>', 'path to a spec file (.md / .txt; pdf/docx follow-up)')
|
|
84
|
-
.option('--reason <r>', 'override the auto-generated trigger reason')
|
|
85
|
-
.option('--real', 'use the real LLM step (slice 15e factory) -- requires anthropic_api_key')
|
|
86
|
-
.option('--json', 'emit JSON instead of human-readable summary')
|
|
87
|
-
.action(async (specPath, opts) => {
|
|
88
|
-
/* Step 1: read + ingest the spec. */
|
|
89
|
-
let text;
|
|
90
|
-
try {
|
|
91
|
-
text = await fs.readFile(path.resolve(specPath), 'utf-8');
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
95
|
-
console.error('Failed to read spec at ' + JSON.stringify(specPath) + ': ' + msg);
|
|
96
|
-
process.exitCode = 1;
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const doc = buildDocFromText(specPath, text);
|
|
100
|
-
const session = recordIngest(doc);
|
|
101
|
-
/* Yuemail-dogfood fix #17: include the spec content in the
|
|
102
|
-
* Director's trigger_reason so the LLM has the actual
|
|
103
|
-
* requirements in its first turn. Without this, the
|
|
104
|
-
* Director only sees "spec doc_id=..." + the CLI --reason
|
|
105
|
-
* flag and starts asking the operator for a spec instead
|
|
106
|
-
* of delegating. The doc_id reference is kept in the
|
|
107
|
-
* header so the Director can quote it when delegating. */
|
|
108
|
-
const userReason = opts.reason || '';
|
|
109
|
-
const triggerReason = '## Task: orchestrate the implementation of the following spec.\n'
|
|
110
|
-
+ (userReason ? ('Operator note: ' + userReason + '\n\n') : '\n')
|
|
111
|
-
+ 'Spec doc_id: ' + session.doc_id + '\n'
|
|
112
|
-
+ 'Spec path: ' + specPath + '\n\n'
|
|
113
|
-
+ '--- BEGIN SPEC ---\n'
|
|
114
|
-
+ text
|
|
115
|
-
+ '\n--- END SPEC ---\n\n'
|
|
116
|
-
+ 'Per your YUEMAIL-DOGFOOD MANDATE: your first tool call MUST be '
|
|
117
|
-
+ 'yujin.agent.delegate({to_slug:"analyst", payload:<the spec above + "extract structured plan + tdd_first flag">, tokens_used:<n>}). '
|
|
118
|
-
+ 'Do NOT ask the operator for a spec -- it is included verbatim above.';
|
|
119
|
-
/* Step 2: ensure the Director is registered. */
|
|
120
|
-
const def = await getAgentDefinition('director');
|
|
121
|
-
if (!def) {
|
|
122
|
-
console.error('Director agent is not installed. Run `yf agent install --defaults` first.');
|
|
123
|
-
process.exitCode = 1;
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
/* Step 3: dispatch the Director. */
|
|
127
|
-
let stepFn;
|
|
128
|
-
let runtimeForDelegate;
|
|
129
|
-
if (opts.real) {
|
|
130
|
-
/* Yuemail-dogfood fix #7 + #12 + #13: construct a
|
|
131
|
-
* Runtime up-front so yujin.agent.delegate has a
|
|
132
|
-
* dispatcher to call. The default runner adapter inside
|
|
133
|
-
* runtime.ts passes (slug, reason) to runAgent, which
|
|
134
|
-
* expects (def, opts) -- so sub-agents end up running
|
|
135
|
-
* with a broken adapter, the run fails immediately, and
|
|
136
|
-
* the Director sees ok=false from the delegate.
|
|
137
|
-
*
|
|
138
|
-
* Fix #12: inject a runner that, given a slug, loads
|
|
139
|
-
* the agent definition + builds a real LLM step via
|
|
140
|
-
* makeRealStep + runs the agent through runAgent with
|
|
141
|
-
* the correct shape. This is what makes a delegated
|
|
142
|
-
* Analyst / Coder / Tester / Scaffolder run with the
|
|
143
|
-
* actual Claude API instead of a no-op stub.
|
|
144
|
-
*
|
|
145
|
-
* Fix #13: actually CALL start() on the runtime. The
|
|
146
|
-
* initial Fix #7 comment said "no start() needed for
|
|
147
|
-
* one-shot CLI" which was wrong: runtime.ts:325 and
|
|
148
|
-
* :413 throw "runtime not started -- call start() first"
|
|
149
|
-
* inside dispatchAgent + dispatchAgentSoft if running is
|
|
150
|
-
* false. Without start(), every delegate raises that
|
|
151
|
-
* error and the Director falls back to inline. */
|
|
152
|
-
const projectCwd = process.cwd();
|
|
153
|
-
runtimeForDelegate = createRuntime({
|
|
154
|
-
runner: async (subSlug, subReason) => {
|
|
155
|
-
const subDef = await getAgentDefinition(subSlug);
|
|
156
|
-
if (!subDef) {
|
|
157
|
-
throw new Error('delegate target agent not installed: ' + subSlug);
|
|
158
|
-
}
|
|
159
|
-
const { makeRealStep } = await import('../agents/cli_real.js');
|
|
160
|
-
const subStep = await makeRealStep({
|
|
161
|
-
def: subDef,
|
|
162
|
-
trigger_reason: subReason,
|
|
163
|
-
project_root: projectCwd,
|
|
164
|
-
project_name: 'Director CLI (delegate)',
|
|
165
|
-
forge_version: 'cli',
|
|
166
|
-
/* The sub-agent does not own a sub-runtime; it
|
|
167
|
-
* is a leaf in this dispatch path. If a sub-
|
|
168
|
-
* agent itself invokes agent.delegate, that
|
|
169
|
-
* call will go through runtime.dispatchAgent
|
|
170
|
-
* on the SAME runtime instance -- so the
|
|
171
|
-
* director's runtime is sufficient for the
|
|
172
|
-
* whole tree. */
|
|
173
|
-
runtime: runtimeForDelegate,
|
|
174
|
-
});
|
|
175
|
-
const { runAgent } = await import('../agents/runner.js');
|
|
176
|
-
return runAgent(subDef, {
|
|
177
|
-
trigger_kind: 'manual',
|
|
178
|
-
trigger_reason: subReason,
|
|
179
|
-
step: subStep,
|
|
180
|
-
persist: true,
|
|
181
|
-
/* Cap sub-agents at 20 iterations -- same as the
|
|
182
|
-
* Director itself. Prevents a single delegated
|
|
183
|
-
* worker from runawaying the budget. */
|
|
184
|
-
max_iterations: 50,
|
|
185
|
-
});
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
/* Yuemail-dogfood fix #13: start the runtime BEFORE the
|
|
189
|
-
* director step is built. runtime.ts gates dispatchAgent
|
|
190
|
-
* on `running` and throws "runtime not started" if it
|
|
191
|
-
* is invoked before start(). The dispatch loop is on a
|
|
192
|
-
* setTimeout that unrefs itself, so node will still
|
|
193
|
-
* exit cleanly when stop() is called at the end. */
|
|
194
|
-
runtimeForDelegate.start();
|
|
195
|
-
const builder = deps.makeRealStep ?? (async (args) => {
|
|
196
|
-
const { makeRealStep } = await import('../agents/cli_real.js');
|
|
197
|
-
return makeRealStep({
|
|
198
|
-
def: args.def,
|
|
199
|
-
trigger_reason: args.trigger_reason,
|
|
200
|
-
project_root: args.project_root,
|
|
201
|
-
project_name: args.project_name,
|
|
202
|
-
forge_version: args.forge_version,
|
|
203
|
-
...(args.runtime ? { runtime: args.runtime } : {}),
|
|
204
|
-
/* Yuemail-dogfood fix #18: 8192 cap for the Director.
|
|
205
|
-
* The spec inlined in trigger_reason (~9 KB) + graph
|
|
206
|
-
* snapshot + R-strict mandate + system prompt easily
|
|
207
|
-
* burn the 4096 default on a single turn of textual
|
|
208
|
-
* acknowledgement before the LLM emits its first
|
|
209
|
-
* tool_use block. Sub-agents stay at the default. */
|
|
210
|
-
max_tokens: 8192,
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
try {
|
|
214
|
-
stepFn = await builder({
|
|
215
|
-
def, trigger_reason: triggerReason,
|
|
216
|
-
project_root: process.cwd(), project_name: 'Director CLI', forge_version: 'cli',
|
|
217
|
-
...(runtimeForDelegate ? { runtime: runtimeForDelegate } : {}),
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
catch (err) {
|
|
221
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
222
|
-
console.error('Failed to build real LLM step: ' + msg);
|
|
223
|
-
if (err && typeof err === 'object' && err.code === 'no_claude_key') {
|
|
224
|
-
console.error('');
|
|
225
|
-
console.error('Tip: run `yf keys setup --provider anthropic --key <KEY>` to load a key.');
|
|
226
|
-
}
|
|
227
|
-
process.exitCode = 1;
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
stepFn = async () => ({
|
|
233
|
-
text: 'cli stub: ingest of "' + session.doc_id + '" recorded. Pass --real to dispatch Claude.',
|
|
234
|
-
tokens_in: 0, tokens_out: 0, tool_calls: 0, done: true,
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
const run = await runAgent(def, {
|
|
238
|
-
trigger_kind: 'manual',
|
|
239
|
-
trigger_reason: triggerReason,
|
|
240
|
-
step: stepFn,
|
|
241
|
-
persist: true,
|
|
242
|
-
/* alpha.59z.156 E2E lesson -- the runner default
|
|
243
|
-
* max_iterations=100 lets a chatty Director burn ~200k
|
|
244
|
-
* tokens / >$5 before any human notices. Cap at 20 for
|
|
245
|
-
* the CLI single-entry path; users running through the
|
|
246
|
-
* panel keep the original 100 via runtime.dispatchAgent
|
|
247
|
-
* which doesn't override this.
|
|
248
|
-
*
|
|
249
|
-
* Yuemail-dogfood fix #19: cap raised from 20 -> 40.
|
|
250
|
-
* Sprint #10 exit-2'd at iter 20 with 27 tool calls and
|
|
251
|
-
* an in-flight scaffolder roundtrip. A full TDD cycle
|
|
252
|
-
* needs ~5 delegates per slice (analyst once + 3 slices
|
|
253
|
-
* x [coder, scaffolder, tester]) so 20 is a hair short.
|
|
254
|
-
* 40 still bounds blast radius -- at ~$0.4/iter via fix
|
|
255
|
-
* #20 loop discipline, max sprint cost is ~$16. */
|
|
256
|
-
max_iterations: 40,
|
|
257
|
-
});
|
|
258
|
-
if (opts.json) {
|
|
259
|
-
console.log(JSON.stringify({ doc_id: session.doc_id, run }, null, 2));
|
|
260
|
-
process.exitCode = run.status === 'succeeded' ? 0 : 2;
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
console.log('Spec ingested:');
|
|
264
|
-
console.log(' Doc ID: ' + session.doc_id);
|
|
265
|
-
console.log(' Title: ' + doc.title);
|
|
266
|
-
console.log(' Sections: ' + doc.sections.length);
|
|
267
|
-
console.log(' Bytes: ' + doc.bytes);
|
|
268
|
-
console.log('');
|
|
269
|
-
console.log('Director run:');
|
|
270
|
-
console.log(' Run ID: ' + run.id);
|
|
271
|
-
console.log(' Status: ' + run.status + (run.kill_reason ? ' (' + run.kill_reason + ')' : ''));
|
|
272
|
-
console.log(' Trigger: ' + run.trigger_reason);
|
|
273
|
-
console.log(' Tokens: in=' + run.tokens_in + ' out=' + run.tokens_out);
|
|
274
|
-
console.log(' Tool calls: ' + run.tool_calls);
|
|
275
|
-
if (run.last_reply_summary) {
|
|
276
|
-
console.log(' Last reply: ' + run.last_reply_summary);
|
|
277
|
-
}
|
|
278
|
-
/* Yuemail-dogfood fix #13: stop the runtime we started so
|
|
279
|
-
* node can exit cleanly without waiting for the (unref'd)
|
|
280
|
-
* tick timer to drain. Best-effort -- never throw. */
|
|
281
|
-
if (runtimeForDelegate) {
|
|
282
|
-
try {
|
|
283
|
-
await runtimeForDelegate.stop();
|
|
284
|
-
}
|
|
285
|
-
catch { /* ignore */ }
|
|
286
|
-
}
|
|
287
|
-
process.exitCode = run.status === 'succeeded' ? 0 : 2;
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
//# sourceMappingURL=director.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"director.js","sourceRoot":"","sources":["../../src/commands/director.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `yf voice setup` -- interactive BYOK provider configuration.
|
|
3
|
-
*
|
|
4
|
-
* The user chooses one or more providers (OpenAI / Google /
|
|
5
|
-
* ElevenLabs), pastes the API keys, and this command:
|
|
6
|
-
*
|
|
7
|
-
* 1. Reads the current local provider keys from disk
|
|
8
|
-
* (~/.yujin-forge/provider_keys.json), if any.
|
|
9
|
-
* 2. Merges the new values on top.
|
|
10
|
-
* 3. Encrypts the merged payload with the SERVER_API_KEY
|
|
11
|
-
* from the vault (the same secret used for HMAC signing).
|
|
12
|
-
* 4. POSTs the encrypted blob to /v1/keys-sync/<handle> so
|
|
13
|
-
* the user's mobile + other paired devices pull it.
|
|
14
|
-
* 5. Writes the plaintext locally so subsequent voice calls
|
|
15
|
-
* can read it without re-prompting.
|
|
16
|
-
*
|
|
17
|
-
* Server never sees plaintext -- only the ciphertext blob.
|
|
18
|
-
*
|
|
19
|
-
* Subcommands:
|
|
20
|
-
* yf voice setup Interactive prompts.
|
|
21
|
-
* yf voice setup --show Print current local config (keys masked).
|
|
22
|
-
* yf voice setup --pull Pull the server copy + overwrite local.
|
|
23
|
-
* yf voice setup --provider openai --key sk-xxx
|
|
24
|
-
* Non-interactive single-provider set.
|
|
25
|
-
*
|
|
26
|
-
* ASCII-only.
|
|
27
|
-
*/
|
|
28
|
-
import type { Command } from 'commander';
|
|
29
|
-
export interface VoiceSetupOptions {
|
|
30
|
-
show?: boolean;
|
|
31
|
-
pull?: boolean;
|
|
32
|
-
provider?: string;
|
|
33
|
-
key?: string;
|
|
34
|
-
}
|
|
35
|
-
export declare function registerVoiceSetupCommand(parent: Command): void;
|
|
36
|
-
export declare function runVoiceSetup(opts: VoiceSetupOptions): Promise<void>;
|
|
37
|
-
//# sourceMappingURL=voice_setup.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"voice_setup.d.ts","sourceRoot":"","sources":["../../src/commands/voice_setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8HzC,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAU/D;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuK1E"}
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import prompts from 'prompts';
|
|
5
|
-
import { c, header } from '../ui/colors.js';
|
|
6
|
-
import { Vault } from '../vault/store.js';
|
|
7
|
-
import { configDir } from '../license/index.js';
|
|
8
|
-
import { readLicense } from '../core/polar.js';
|
|
9
|
-
import { signBearer, HITO4_BASE_URL } from '../license/hito4_client.js';
|
|
10
|
-
import { encryptUserKeys, decryptUserKeys, envelopeToBlob, envelopeFromBlob, } from '../core/keys_envelope.js';
|
|
11
|
-
const VAULT_SECRET_SLOT = 'yf_server_api_key';
|
|
12
|
-
const LOCAL_KEYS_FILE = 'provider_keys.json';
|
|
13
|
-
function localKeysPath() {
|
|
14
|
-
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
15
|
-
return path.join(home, '.yujin-forge', LOCAL_KEYS_FILE);
|
|
16
|
-
}
|
|
17
|
-
async function readLocalKeys() {
|
|
18
|
-
try {
|
|
19
|
-
const raw = await fs.readFile(localKeysPath(), 'utf-8');
|
|
20
|
-
const parsed = JSON.parse(raw);
|
|
21
|
-
if (parsed && parsed.v === 1)
|
|
22
|
-
return parsed;
|
|
23
|
-
}
|
|
24
|
-
catch { /* missing or malformed -- treat as empty */ }
|
|
25
|
-
return { v: 1, updated_at: '1970-01-01T00:00:00Z' };
|
|
26
|
-
}
|
|
27
|
-
async function writeLocalKeys(payload) {
|
|
28
|
-
const p = localKeysPath();
|
|
29
|
-
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
30
|
-
const tmp = p + '.tmp';
|
|
31
|
-
await fs.writeFile(tmp, JSON.stringify(payload, null, 2), 'utf-8');
|
|
32
|
-
await fs.rename(tmp, p);
|
|
33
|
-
}
|
|
34
|
-
function maskKey(k) {
|
|
35
|
-
if (!k)
|
|
36
|
-
return '(none)';
|
|
37
|
-
if (k.length <= 8)
|
|
38
|
-
return '****';
|
|
39
|
-
return k.slice(0, 4) + '...' + k.slice(-4);
|
|
40
|
-
}
|
|
41
|
-
async function loadServerSecret() {
|
|
42
|
-
const vault = await Vault.open({ configDir: configDir() });
|
|
43
|
-
if (!vault.has(VAULT_SECRET_SLOT)) {
|
|
44
|
-
throw new Error('voice setup: vault slot "' + VAULT_SECRET_SLOT + '" is empty. '
|
|
45
|
-
+ 'Run `yf vault set ' + VAULT_SECRET_SLOT + '` with the value '
|
|
46
|
-
+ 'provided at signup (or your tenant admin) before configuring '
|
|
47
|
-
+ 'voice providers.');
|
|
48
|
-
}
|
|
49
|
-
return vault.get(VAULT_SECRET_SLOT);
|
|
50
|
-
}
|
|
51
|
-
async function loadUserHandle() {
|
|
52
|
-
const lic = await readLicense();
|
|
53
|
-
if (!lic.user_handle) {
|
|
54
|
-
throw new Error('voice setup: no user_handle on the local license cache. '
|
|
55
|
-
+ 'Run `yf license activate --user-handle <you@example.com>` first.');
|
|
56
|
-
}
|
|
57
|
-
return lic.user_handle;
|
|
58
|
-
}
|
|
59
|
-
async function pushToServer(payload) {
|
|
60
|
-
const secret = await loadServerSecret();
|
|
61
|
-
const handle = await loadUserHandle();
|
|
62
|
-
const env = await encryptUserKeys(payload, secret);
|
|
63
|
-
const blob = envelopeToBlob(env);
|
|
64
|
-
const bearer = signBearer(secret, handle);
|
|
65
|
-
const url = HITO4_BASE_URL.replace(/\/+$/, '') + '/v1/keys-sync/' + encodeURIComponent(handle);
|
|
66
|
-
const r = await fetch(url, {
|
|
67
|
-
method: 'POST',
|
|
68
|
-
headers: {
|
|
69
|
-
Authorization: 'Bearer ' + bearer,
|
|
70
|
-
'content-type': 'application/json',
|
|
71
|
-
},
|
|
72
|
-
body: JSON.stringify({ blob, ts: payload.updated_at }),
|
|
73
|
-
});
|
|
74
|
-
if (!r.ok) {
|
|
75
|
-
let detail = '';
|
|
76
|
-
try {
|
|
77
|
-
detail = await r.text();
|
|
78
|
-
}
|
|
79
|
-
catch { /* */ }
|
|
80
|
-
throw new Error('voice setup: server push failed (HTTP ' + r.status + '): ' + detail.slice(0, 200));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async function pullFromServer() {
|
|
84
|
-
const secret = await loadServerSecret();
|
|
85
|
-
const handle = await loadUserHandle();
|
|
86
|
-
const bearer = signBearer(secret, handle);
|
|
87
|
-
const url = HITO4_BASE_URL.replace(/\/+$/, '') + '/v1/keys-sync/' + encodeURIComponent(handle);
|
|
88
|
-
const r = await fetch(url, {
|
|
89
|
-
method: 'GET',
|
|
90
|
-
headers: { Authorization: 'Bearer ' + bearer },
|
|
91
|
-
});
|
|
92
|
-
if (!r.ok) {
|
|
93
|
-
let detail = '';
|
|
94
|
-
try {
|
|
95
|
-
detail = await r.text();
|
|
96
|
-
}
|
|
97
|
-
catch { /* */ }
|
|
98
|
-
throw new Error('voice setup: server pull failed (HTTP ' + r.status + '): ' + detail.slice(0, 200));
|
|
99
|
-
}
|
|
100
|
-
const body = await r.json();
|
|
101
|
-
if (!body.blob)
|
|
102
|
-
return null;
|
|
103
|
-
const env = envelopeFromBlob(body.blob);
|
|
104
|
-
return decryptUserKeys(env, secret);
|
|
105
|
-
}
|
|
106
|
-
function printCurrent(p) {
|
|
107
|
-
header('Yujin Forge -- voice setup (current local)');
|
|
108
|
-
console.log('');
|
|
109
|
-
console.log(' OpenAI API key: ' + c.dim(maskKey(p.openai_api_key)));
|
|
110
|
-
console.log(' Google STT key: ' + c.dim(maskKey(p.google_stt_key)));
|
|
111
|
-
console.log(' Google TTS key: ' + c.dim(maskKey(p.google_tts_key)));
|
|
112
|
-
console.log(' ElevenLabs API key: ' + c.dim(maskKey(p.elevenlabs_api_key)));
|
|
113
|
-
console.log('');
|
|
114
|
-
console.log(' STT preference: ' + c.brand(p.stt_provider_preference ?? '(not set)'));
|
|
115
|
-
console.log(' TTS preference: ' + c.brand(p.tts_provider_preference ?? '(not set)'));
|
|
116
|
-
console.log(' STT language: ' + (p.stt_language ?? c.dim('(default)')));
|
|
117
|
-
console.log(' TTS voice: ' + (p.tts_voice ?? c.dim('(default)')));
|
|
118
|
-
console.log(' Last updated: ' + c.dim(p.updated_at));
|
|
119
|
-
}
|
|
120
|
-
export function registerVoiceSetupCommand(parent) {
|
|
121
|
-
parent
|
|
122
|
-
.command('setup')
|
|
123
|
-
.description('Configure voice provider API keys (OpenAI / Google / ElevenLabs). '
|
|
124
|
-
+ 'Encrypts + syncs to your other devices automatically.')
|
|
125
|
-
.option('--show', 'print current local config (keys masked)')
|
|
126
|
-
.option('--pull', 'pull the server copy + overwrite local')
|
|
127
|
-
.option('--provider <name>', 'non-interactive: set this provider')
|
|
128
|
-
.option('--key <value>', 'non-interactive: the API key value for --provider')
|
|
129
|
-
.action(async (opts) => { await runVoiceSetup(opts); });
|
|
130
|
-
}
|
|
131
|
-
export async function runVoiceSetup(opts) {
|
|
132
|
-
/* --show: print current + exit. */
|
|
133
|
-
if (opts.show) {
|
|
134
|
-
const current = await readLocalKeys();
|
|
135
|
-
printCurrent(current);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
/* --pull: server -> local. */
|
|
139
|
-
if (opts.pull) {
|
|
140
|
-
let pulled;
|
|
141
|
-
try {
|
|
142
|
-
pulled = await pullFromServer();
|
|
143
|
-
}
|
|
144
|
-
catch (err) {
|
|
145
|
-
console.error(c.error(err instanceof Error ? err.message : String(err)));
|
|
146
|
-
process.exitCode = 1;
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (!pulled) {
|
|
150
|
-
console.log(c.dim('Server has no provider keys for this user yet.'));
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
await writeLocalKeys(pulled);
|
|
154
|
-
header('Yujin Forge -- voice setup (pulled from server)');
|
|
155
|
-
console.log('');
|
|
156
|
-
printCurrent(pulled);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
/* --provider / --key: non-interactive single set. */
|
|
160
|
-
if (opts.provider || opts.key) {
|
|
161
|
-
if (!opts.provider || !opts.key) {
|
|
162
|
-
console.error(c.error('--provider and --key must be used together.'));
|
|
163
|
-
process.exitCode = 1;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const merged = await readLocalKeys();
|
|
167
|
-
const ts = new Date().toISOString();
|
|
168
|
-
const p = opts.provider.toLowerCase();
|
|
169
|
-
if (p === 'openai')
|
|
170
|
-
merged.openai_api_key = opts.key;
|
|
171
|
-
else if (p === 'google-stt')
|
|
172
|
-
merged.google_stt_key = opts.key;
|
|
173
|
-
else if (p === 'google-tts')
|
|
174
|
-
merged.google_tts_key = opts.key;
|
|
175
|
-
else if (p === 'elevenlabs')
|
|
176
|
-
merged.elevenlabs_api_key = opts.key;
|
|
177
|
-
else {
|
|
178
|
-
console.error(c.error('--provider must be one of: openai, google-stt, google-tts, elevenlabs'));
|
|
179
|
-
process.exitCode = 1;
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
merged.updated_at = ts;
|
|
183
|
-
try {
|
|
184
|
-
await pushToServer(merged);
|
|
185
|
-
await writeLocalKeys(merged);
|
|
186
|
-
header('Yujin Forge -- voice setup (saved)');
|
|
187
|
-
console.log('');
|
|
188
|
-
printCurrent(merged);
|
|
189
|
-
}
|
|
190
|
-
catch (err) {
|
|
191
|
-
console.error(c.error(err instanceof Error ? err.message : String(err)));
|
|
192
|
-
process.exitCode = 1;
|
|
193
|
-
}
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
/* Interactive flow. */
|
|
197
|
-
const current = await readLocalKeys();
|
|
198
|
-
header('Yujin Forge -- voice setup');
|
|
199
|
-
console.log('');
|
|
200
|
-
console.log(c.dim('Configure the API keys for your chosen voice providers. The keys are'));
|
|
201
|
-
console.log(c.dim('encrypted with your account secret + synced to your mobile + other'));
|
|
202
|
-
console.log(c.dim('paired devices automatically. The server never sees plaintext.'));
|
|
203
|
-
console.log('');
|
|
204
|
-
console.log(c.dim('Press Enter on any field to keep its current value.'));
|
|
205
|
-
console.log('');
|
|
206
|
-
const answers = await prompts([
|
|
207
|
-
{
|
|
208
|
-
type: 'multiselect',
|
|
209
|
-
name: 'providers',
|
|
210
|
-
message: 'Which providers do you want to configure?',
|
|
211
|
-
choices: [
|
|
212
|
-
{ title: 'OpenAI (Whisper for STT + tts-1 for TTS)', value: 'openai' },
|
|
213
|
-
{ title: 'Google Cloud (Speech-to-Text + Text-to-Speech)', value: 'google' },
|
|
214
|
-
{ title: 'ElevenLabs (TTS only -- higher quality voice)', value: 'elevenlabs' },
|
|
215
|
-
],
|
|
216
|
-
min: 1,
|
|
217
|
-
hint: 'Space to select, Enter to confirm.',
|
|
218
|
-
},
|
|
219
|
-
]);
|
|
220
|
-
if (!answers.providers || answers.providers.length === 0) {
|
|
221
|
-
console.log(c.dim('No providers selected. Aborting.'));
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
const merged = { ...current };
|
|
225
|
-
if (answers.providers.includes('openai')) {
|
|
226
|
-
const r = await prompts({
|
|
227
|
-
type: 'password',
|
|
228
|
-
name: 'k',
|
|
229
|
-
message: 'OpenAI API key (sk-...). Current: ' + maskKey(current.openai_api_key),
|
|
230
|
-
});
|
|
231
|
-
if (r.k)
|
|
232
|
-
merged.openai_api_key = r.k;
|
|
233
|
-
}
|
|
234
|
-
if (answers.providers.includes('google')) {
|
|
235
|
-
const sttR = await prompts({
|
|
236
|
-
type: 'password',
|
|
237
|
-
name: 'k',
|
|
238
|
-
message: 'Google Cloud Speech-to-Text API key. Current: ' + maskKey(current.google_stt_key),
|
|
239
|
-
});
|
|
240
|
-
if (sttR.k)
|
|
241
|
-
merged.google_stt_key = sttR.k;
|
|
242
|
-
const ttsR = await prompts({
|
|
243
|
-
type: 'password',
|
|
244
|
-
name: 'k',
|
|
245
|
-
message: 'Google Cloud Text-to-Speech API key (can be same as STT). Current: ' + maskKey(current.google_tts_key),
|
|
246
|
-
});
|
|
247
|
-
if (ttsR.k)
|
|
248
|
-
merged.google_tts_key = ttsR.k;
|
|
249
|
-
}
|
|
250
|
-
if (answers.providers.includes('elevenlabs')) {
|
|
251
|
-
const r = await prompts({
|
|
252
|
-
type: 'password',
|
|
253
|
-
name: 'k',
|
|
254
|
-
message: 'ElevenLabs API key. Current: ' + maskKey(current.elevenlabs_api_key),
|
|
255
|
-
});
|
|
256
|
-
if (r.k)
|
|
257
|
-
merged.elevenlabs_api_key = r.k;
|
|
258
|
-
}
|
|
259
|
-
/* Preferences (which provider to USE for each direction). */
|
|
260
|
-
const sttCandidates = [];
|
|
261
|
-
if (merged.openai_api_key)
|
|
262
|
-
sttCandidates.push({ title: 'OpenAI (Whisper)', value: 'openai' });
|
|
263
|
-
if (merged.google_stt_key)
|
|
264
|
-
sttCandidates.push({ title: 'Google', value: 'google' });
|
|
265
|
-
if (sttCandidates.length > 0) {
|
|
266
|
-
const pref = await prompts({
|
|
267
|
-
type: 'select',
|
|
268
|
-
name: 'p',
|
|
269
|
-
message: 'Preferred STT provider',
|
|
270
|
-
choices: sttCandidates,
|
|
271
|
-
initial: 0,
|
|
272
|
-
});
|
|
273
|
-
if (pref.p)
|
|
274
|
-
merged.stt_provider_preference = pref.p;
|
|
275
|
-
}
|
|
276
|
-
const ttsCandidates = [];
|
|
277
|
-
if (merged.openai_api_key)
|
|
278
|
-
ttsCandidates.push({ title: 'OpenAI (tts-1)', value: 'openai' });
|
|
279
|
-
if (merged.google_tts_key)
|
|
280
|
-
ttsCandidates.push({ title: 'Google', value: 'google' });
|
|
281
|
-
if (merged.elevenlabs_api_key)
|
|
282
|
-
ttsCandidates.push({ title: 'ElevenLabs', value: 'elevenlabs' });
|
|
283
|
-
if (ttsCandidates.length > 0) {
|
|
284
|
-
const pref = await prompts({
|
|
285
|
-
type: 'select',
|
|
286
|
-
name: 'p',
|
|
287
|
-
message: 'Preferred TTS provider',
|
|
288
|
-
choices: ttsCandidates,
|
|
289
|
-
initial: 0,
|
|
290
|
-
});
|
|
291
|
-
if (pref.p)
|
|
292
|
-
merged.tts_provider_preference = pref.p;
|
|
293
|
-
}
|
|
294
|
-
merged.updated_at = new Date().toISOString();
|
|
295
|
-
try {
|
|
296
|
-
await pushToServer(merged);
|
|
297
|
-
await writeLocalKeys(merged);
|
|
298
|
-
console.log('');
|
|
299
|
-
console.log(c.success('Voice config saved + synced.'));
|
|
300
|
-
console.log('');
|
|
301
|
-
printCurrent(merged);
|
|
302
|
-
}
|
|
303
|
-
catch (err) {
|
|
304
|
-
console.error(c.error(err instanceof Error ? err.message : String(err)));
|
|
305
|
-
process.exitCode = 1;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
//# sourceMappingURL=voice_setup.js.map
|