@neuvybe/jarvis 0.49.1 → 0.49.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -543,7 +543,7 @@ ${a}`:""}`}default:{let o=e?`${F("cyan",Qe)} `:"",a=e?F("cyan",Si):"";return`${
|
|
|
543
543
|
${a}
|
|
544
544
|
`}}}}).prompt();Nt();Fs();ec();var xu={brain:["text"],voice:["stt","tts"],memory:["embedding"],search:["search"]},So=["brain","voice","memory","search"],ac={brain:"Brain (text)",voice:"Voice (speech in + out)",memory:"Memory (embeddings)",search:"Search (web)"};function tZ(t){return ac[t].split(" (")[0]}var BR="brain",tDe=["text","stt","tts","embedding","search"];function rDe(t){return t==="text"}function xR(t){let e=new Set;for(let r of t)for(let i of xu[r])e.add(i);return tDe.filter(r=>e.has(r))}function nZ(t){let e=["brain"],r=t.routes;return(r?.stt||r?.tts)&&e.push("voice"),r?.embedding&&e.push("memory"),r?.search&&e.push("search"),e}function iZ(t){return Object.keys(t.routes??{}).length>0||Object.keys(t.secrets??{}).length>0}function sZ(t){let e=So.map(r=>{let i=xu[r].some(n=>!!t.routes?.[n]);return{value:r,label:`${i?"edit":"add"} ${ac[r]}`}});return e.push({value:"defaults",label:"edit defaults",hint:"modality, log level"}),e.push({value:"finish",label:"review & finish",hint:"show the summary and write once"}),e}var MI="not configured";function rZ(t,e){return t?`${t.provider}/${t.model} (${e(t.provider)})`:MI}function nDe(t,e,r){if(!t&&!e)return MI;if(t&&t.provider===e?.provider)return`${t.provider}/${t.model} + ${e.model} (${r(t.provider)})`;let i=(n,s)=>s?`${n} ${s.provider}/${s.model} (${r(s.provider)})`:`${n} ${MI}`;return`${i("stt",t)}, ${i("tts",e)}`}function oZ(t){let e=s=>t.secrets?.[s]?j.ok("key set"):j.warn("no key"),r={brain:rZ(t.routes?.text,e),voice:nDe(t.routes?.stt,t.routes?.tts,e),memory:rZ(t.routes?.embedding,e),search:t.routes?.search?`${t.routes.search.provider} (${e(t.routes.search.provider)})`:MI},i=Math.max(...So.map(s=>tZ(s).length+1)),n=So.map(s=>`${`${tZ(s)}:`.padEnd(i)} ${r[s]}`);return n.push(`defaults: modality=${t.defaults.modality}, logLevel=${t.defaults.logLevel}`),n}function aZ(t,e){if(e==="valid")return[];let r=rDe(t);return e==="invalid"?r?["retry","change"]:["retry","change","skip"]:r?["save","retry","change"]:["save","retry","change","skip"]}function QR(t){let e={},r=t.preserveGroups!==void 0?{...t.existing.secrets}:{};for(let s of t.routes)if(s.capability==="search")e.search={provider:s.provider};else{let o=t.existing.routes?.[s.capability],a=o?.provider===s.provider,c=s.voiceId??(a?o.voiceId:void 0);e[s.capability]={provider:s.provider,model:s.model,...a&&o.gateway!==void 0&&{gateway:o.gateway},...a&&o.url!==void 0&&{url:o.url},...c!==void 0&&{voiceId:c}}}for(let s of t.preserveGroups??[])for(let o of xu[s])if(e[o]===void 0)if(o==="search"){let a=t.existing.routes?.search;a!==void 0&&(e.search=a)}else{let a=t.existing.routes?.[o];a!==void 0&&(e[o]=a)}for(let s of Object.values(e)){let o=t.keys.get(s.provider),a=t.existing.secrets?.[s.provider];o!==void 0&&o.length>0?r[s.provider]=o:o===void 0&&a!==void 0&&(r[s.provider]=a)}let i=[...new Set([...So.flatMap(s=>xu[s]),...t.existing.wizard?.offered??[]])],n={version:t.existing.version,defaults:{logLevel:t.logLevel,modality:t.modality},upgrade:{auto:t.existing.upgrade.auto},wizard:{offered:i}};return Object.keys(e).length>0&&(n.routes=e),Object.keys(r).length>0&&(n.secrets=r),n}function cZ(t,e){return!!t.isTTY&&!!e.isTTY}var Gh=class extends Error{},lZ={save:{label:"save anyway",hint:"keep this key; verify later with jarvis doctor"},retry:{label:"re-enter the key"},change:{label:"choose a different provider"},skip:{label:"skip this capability",hint:"optional"}},uZ={ollama:"your ollama.com account"},LI=class{config;probe;catalogue;io;stdin;stdout;constructor(e){this.config=e.config,this.probe=e.probe,this.catalogue=e.catalogue,this.io=e.io??ct,this.stdin=e.stdin??process.stdin,this.stdout=e.stdout??process.stdout}async run(){if(!cZ(this.stdin,this.stdout))return this.printHeadlessGuidance(),1;try{return await this.wizard()}catch(e){if(e instanceof Gh)return z8("no changes written."),130;throw e}}loadExisting(){try{return{config:this.config.resolved(),corrupt:!1}}catch{return{config:jo.parse({}),corrupt:!0}}}async wizard(){let{config:e,corrupt:r}=this.loadExisting();return W8(j.bold("jarvis init")),r&&Tt.warn("the existing config.json is invalid; init will replace it with a fresh config."),!r&&iZ(e)?this.editWizard(e):this.freshWizard(e)}async freshWizard(e){RI(`Writes ${j.accent(Yt.resolve("config.json"))} (owner-only).`,"setup");let r=await this.pickCapabilities(e),i=r.includes("voice"),n=new Map,s=[];for(let c of xR(r)){let l=await this.configureCapability(c,e,n,i);l&&s.push(l)}let o=await this.pickModality(e.defaults.modality),a=await this.pickLogLevel(e.defaults.logLevel);return this.review(s,n,o,a),this.concludeRun(QR({existing:e,routes:s,keys:n,modality:o,logLevel:a}))}async editWizard(e){let r=new Map,i=new Map,n=e.defaults.modality,s=e.defaults.logLevel;for(let a=0;;a++){let c=this.pendingView(e,i,r,n,s),l=oZ(c).join(`
|
|
545
545
|
`);RI(a===0?`Writes ${j.accent(Yt.resolve("config.json"))} (owner-only).
|
|
546
|
-
${l}`:l,"setup");let A=this.ensure(await vo({message:"What do you want to change?",options:sZ(c)}));if(A==="finish"){if(c.routes?.text===void 0){Tt.warn("Brain (text) is required - add it before finishing.");continue}break}if(A==="defaults"){n=await this.pickModality(n),s=await this.pickLogLevel(s);continue}let d=!!(c.routes?.stt??c.routes?.tts);for(let h of xu[A]){let u=await this.configureCapability(h,c,r,d);if(u){let f=i.get(h);u.capability==="tts"&&u.voiceId===void 0&&f?.capability==="tts"&&f.provider===u.provider&&f.voiceId!==void 0&&(u.voiceId=f.voiceId),i.set(h,u)}}}let o=[];for(let a of xR(So)){let c=i.get(a);if(c)o.push(c);else if(a==="search"){let l=e.routes?.search;l&&o.push({capability:a,provider:l.provider})}else{let l=e.routes?.[a];l&&o.push({capability:a,provider:l.provider,model:l.model})}}return this.review(o,r,n,s,new Set(Object.keys(e.secrets??{}))),this.concludeRun(QR({existing:e,routes:[...i.values()],keys:r,modality:n,logLevel:s,preserveGroups:So}))}pendingView(e,r,i,n,s){let o={...e.routes};for(let c of r.values())if(c.capability==="search")o.search={provider:c.provider};else{let l=e.routes?.[c.capability],A=c.voiceId??(l?.provider===c.provider?l.voiceId:void 0);o[c.capability]={provider:c.provider,model:c.model,...A!==void 0&&{voiceId:A}}}let a={...e.secrets};for(let[c,l]of i)a[c]=l;return{...e,defaults:{modality:n,logLevel:s},routes:o,secrets:a}}async pickModality(e){return this.ensure(await vo({message:"Default modality",options:[{value:"voice",label:"voice",hint:"speak and listen when available"},{value:"text",label:"text",hint:"text-only"}],initialValue:e}))}async pickLogLevel(e){return this.ensure(await vo({message:"Log level",options:["debug","info","warn","error"].map(r=>({value:r,label:r})),initialValue:e}))}async concludeRun(e){if(!this.ensure(await NI({message:"Write this configuration?"})))throw new Gh;return this.config.write(e),Tt.success(`wrote ${Yt.resolve("config.json")}`),await NI({message:"Start Jarvis now?",initialValue:!1})===!0&&Tt.info("starting is not wired yet in v0.1 - run `jarvis` to begin a session."),J8(j.ok("done.")),0}async pickCapabilities(e){let r=this.ensure(await Z8({message:`Which capabilities do you want to set up? ${j.dim("(space to toggle, enter to confirm)")}`,options:[{value:"brain",label:ac.brain,hint:"required - the reasoning model"},{value:"voice",label:ac.voice,hint:"optional"},{value:"memory",label:ac.memory,hint:"optional"},{value:"search",label:ac.search,hint:"optional"}],initialValues:nZ(e),required:!1}));r.includes(BR)||Tt.warn("Brain (text) is required; keeping it selected.");let i=new Set(r);return i.add(BR),So.filter(n=>i.has(n))}async configureCapability(e,r,i,n){let s=this.probe.providersFor(e);if(s.length===0)return Tt.warn(`no providers available for ${e}; skipping.`),null;for(;;){let o=await this.pickProvider(e,s,r);if(!this.probe.requiresKey(o))Tt.info(`${o} needs no API key (a self-hosted server).`);else if(!i.has(o)){let l=await this.resolveKey(e,o,r,i);if(l==="change-provider")continue;if(l==="skip")return null}if(e==="search")return{capability:e,provider:o};let a=await this.pickModel(e,o,r,i,n),c=await this.maybePickVoice(e,o,r,i);return{capability:e,provider:o,model:a,...c!==void 0&&{voiceId:c}}}}async resolveKey(e,r,i,n){let s=await this.firstKey(r,i);for(;;){let o=await this.validateKey(r,s);if(o==="valid")return n.set(r,s),"keyed";let a=await this.pickAfterFailure(e,o);if(a==="save")return n.set(r,s),"keyed";if(a==="skip")return Tt.info(`skipped ${e} (optional).`),"skip";if(a==="change")return"change-provider";s=await this.promptPassword(r)}}async maybePickVoice(e,r,i,n){if(e!=="tts")return;let s=n.get(r);if(s===void 0)return;let o=await this.probe.listVoices(r,s);if(!o.ok){this.reportVoiceListFailure(r,o.reason,o.detail);return}let a=o.voices,c=i.routes?.tts,l=c?.provider===r?c.voiceId:void 0,A=u=>u!==void 0&&a.some(f=>f.voiceId===u),d=l!==void 0&&!A(l)?null:[l,vh].find(A)??a[0].voiceId;return this.ensure(await vo({message:`${e} voice`,options:[...a.map(u=>({value:u.voiceId,label:u.name,hint:u.category?`${u.voiceId} (${u.category})`:u.voiceId})),{value:null,label:"keep the current voice",hint:"change later with jarvis config set routes.tts.voiceId <id>"}],initialValue:d}))??void 0}reportVoiceListFailure(e,r,i){if(r!=="no_catalogue")if(r==="unauthorized")Tt.warn(`this ${e} key can't read its voice catalogue (missing an optional read scope - see "Provider API keys" in the README); keeping the current voice.`);else if(r==="empty")Tt.warn(`the ${e} voice catalogue came back empty; keeping the current voice.`);else{let n=i?` (${i})`:"";Tt.warn(`couldn't fetch the ${e} voice list${n}; keeping the current voice.`)}}async pickProvider(e,r,i){let n=i.routes?.[e]?.provider;return this.ensure(await vo({message:`${e} provider`,options:r.map(
|
|
546
|
+
${l}`:l,"setup");let A=this.ensure(await vo({message:"What do you want to change?",options:sZ(c)}));if(A==="finish"){if(c.routes?.text===void 0){Tt.warn("Brain (text) is required - add it before finishing.");continue}break}if(A==="defaults"){n=await this.pickModality(n),s=await this.pickLogLevel(s);continue}let d=!!(c.routes?.stt??c.routes?.tts);for(let h of xu[A]){let u=await this.configureCapability(h,c,r,d);if(u){let f=i.get(h);u.capability==="tts"&&u.voiceId===void 0&&f?.capability==="tts"&&f.provider===u.provider&&f.voiceId!==void 0&&(u.voiceId=f.voiceId),i.set(h,u)}}}let o=[];for(let a of xR(So)){let c=i.get(a);if(c)o.push(c);else if(a==="search"){let l=e.routes?.search;l&&o.push({capability:a,provider:l.provider})}else{let l=e.routes?.[a];l&&o.push({capability:a,provider:l.provider,model:l.model})}}return this.review(o,r,n,s,new Set(Object.keys(e.secrets??{}))),this.concludeRun(QR({existing:e,routes:[...i.values()],keys:r,modality:n,logLevel:s,preserveGroups:So}))}pendingView(e,r,i,n,s){let o={...e.routes};for(let c of r.values())if(c.capability==="search")o.search={provider:c.provider};else{let l=e.routes?.[c.capability],A=c.voiceId??(l?.provider===c.provider?l.voiceId:void 0);o[c.capability]={provider:c.provider,model:c.model,...A!==void 0&&{voiceId:A}}}let a={...e.secrets};for(let[c,l]of i)a[c]=l;return{...e,defaults:{modality:n,logLevel:s},routes:o,secrets:a}}async pickModality(e){return this.ensure(await vo({message:"Default modality",options:[{value:"voice",label:"voice",hint:"speak and listen when available"},{value:"text",label:"text",hint:"text-only"}],initialValue:e}))}async pickLogLevel(e){return this.ensure(await vo({message:"Log level",options:["debug","info","warn","error"].map(r=>({value:r,label:r})),initialValue:e}))}async concludeRun(e){if(!this.ensure(await NI({message:"Write this configuration?"})))throw new Gh;return this.config.write(e),Tt.success(`wrote ${Yt.resolve("config.json")}`),await NI({message:"Start Jarvis now?",initialValue:!1})===!0&&Tt.info("starting is not wired yet in v0.1 - run `jarvis` to begin a session."),J8(j.ok("done.")),0}async pickCapabilities(e){let r=this.ensure(await Z8({message:`Which capabilities do you want to set up? ${j.dim("(space to toggle, enter to confirm)")}`,options:[{value:"brain",label:ac.brain,hint:"required - the reasoning model"},{value:"voice",label:ac.voice,hint:"optional"},{value:"memory",label:ac.memory,hint:"optional"},{value:"search",label:ac.search,hint:"optional"}],initialValues:nZ(e),required:!1}));r.includes(BR)||Tt.warn("Brain (text) is required; keeping it selected.");let i=new Set(r);return i.add(BR),So.filter(n=>i.has(n))}async configureCapability(e,r,i,n){let s=this.probe.providersFor(e).filter(o=>e!=="stt"&&e!=="tts"||o==="elevenlabs");if(s.length===0)return Tt.warn(`no providers available for ${e}; skipping.`),null;for(;;){let o=await this.pickProvider(e,s,r);if(o===null)return null;if(!this.probe.requiresKey(o))Tt.info(`${o} needs no API key (a self-hosted server).`);else if(!i.has(o)){let l=await this.resolveKey(e,o,r,i);if(l==="change-provider")continue;if(l==="skip")return null}if(e==="search")return{capability:e,provider:o};let a=await this.pickModel(e,o,r,i,n),c=await this.maybePickVoice(e,o,r,i);return{capability:e,provider:o,model:a,...c!==void 0&&{voiceId:c}}}}async resolveKey(e,r,i,n){let s=await this.firstKey(r,i);for(;;){let o=await this.validateKey(r,s);if(o==="valid")return n.set(r,s),"keyed";let a=await this.pickAfterFailure(e,o);if(a==="save")return n.set(r,s),"keyed";if(a==="skip")return Tt.info(`skipped ${e} (optional).`),"skip";if(a==="change")return"change-provider";s=await this.promptPassword(r)}}async maybePickVoice(e,r,i,n){if(e!=="tts")return;let s=n.get(r);if(s===void 0)return;let o=await this.probe.listVoices(r,s);if(!o.ok){this.reportVoiceListFailure(r,o.reason,o.detail);return}let a=o.voices,c=i.routes?.tts,l=c?.provider===r?c.voiceId:void 0,A=u=>u!==void 0&&a.some(f=>f.voiceId===u),d=l!==void 0&&!A(l)?null:[l,vh].find(A)??a[0].voiceId;return this.ensure(await vo({message:`${e} voice`,options:[...a.map(u=>({value:u.voiceId,label:u.name,hint:u.category?`${u.voiceId} (${u.category})`:u.voiceId})),{value:null,label:"keep the current voice",hint:"change later with jarvis config set routes.tts.voiceId <id>"}],initialValue:d}))??void 0}reportVoiceListFailure(e,r,i){if(r!=="no_catalogue")if(r==="unauthorized")Tt.warn(`this ${e} key can't read its voice catalogue (missing an optional read scope - see "Provider API keys" in the README); keeping the current voice.`);else if(r==="empty")Tt.warn(`the ${e} voice catalogue came back empty; keeping the current voice.`);else{let n=i?` (${i})`:"";Tt.warn(`couldn't fetch the ${e} voice list${n}; keeping the current voice.`)}}async pickProvider(e,r,i){let n=i.routes?.[e]?.provider,s=(e==="stt"||e==="tts")&&n!==void 0&&!r.includes(n);return this.ensure(await vo({message:`${e} provider`,options:[...s?[{value:null,label:`keep current ${n}`,hint:"leave this route unchanged"}]:[],...r.map(o=>({value:o,label:o,...uZ[o]!==void 0&&{hint:uZ[o]}}))],initialValue:s?null:n&&r.includes(n)?n:void 0}))}async pickModel(e,r,i,n,s){let o=i.routes?.[e],a=o?.provider===r?o.model:void 0,c=a!==void 0&&a.trim().length>0?a:void 0,l=c??this.probe.defaultModel(r,e)??"",A=n.get(r),d=this.catalogue?await this.catalogue.models(r,e,{...A!==void 0&&{key:A}}):void 0;if(d===void 0||d.source==="prefill"||d.models.length===0)return this.editModelId(e,l);let h=new Set(d.models.map(v=>v.id)),u=e==="text"&&s&&d.recommendedFast!==void 0&&h.has(d.recommendedFast),f=u?d.recommendedFast:d.recommended,m=(v,S)=>{let O=[];return S!==void 0&&O.push(S),v===f&&O.push(u?"recommended for voice":"recommended"),v===c&&O.push("current"),O.length>0?O.join(", "):void 0},E=d.models.map(v=>{let S=m(v.id,v.label);return{value:v.id,label:v.id,...S!==void 0&&{hint:S}}});c!==void 0&&!h.has(c)&&E.push({value:c,label:c,hint:"current, not in the fetched list"}),E.push({value:null,label:"enter a different model id",hint:"free text"}),u&&Tt.message(j.dim("voice replies feel as fast as the model's first token"));let I=c??(f!==void 0&&h.has(f)?f:d.models[0].id),C=this.ensure(await vo({message:`${e} model`,options:E,initialValue:I}));if(C!==null)return C;let b=await this.editModelId(e,l);return h.has(b)||Tt.warn(`'${b}' is not in ${r}'s current list - saving anyway`),b}async editModelId(e,r){return this.ensure(await eZ({message:`${e} model`,initialValue:r,validate:n=>n?.trim()?void 0:"a model is required"})).trim()}async firstKey(e,r){let i=r.secrets?.[e];return i&&this.ensure(await NI({message:`Reuse the saved key for ${e}?`,initialValue:!0}))?i.trim():this.promptPassword(e)}async promptPassword(e){return this.ensure(await X8({message:`${e} API key`,validate:i=>i?.trim()?void 0:"a key is required"})).trim()}async validateKey(e,r){let i=K8();i.start(`validating ${e} key...`);let n=await this.probe.probe(e,r),s=n.detail?` (${n.detail})`:"";return n.ok?i.stop(j.ok(`${e} key valid`)):n.status==="invalid"?i.stop(j.fail(`${e} key rejected${s}`)):n.status==="unverifiable"?i.stop(j.warn(`could not verify ${e} here${s}`)):i.stop(j.warn(`could not validate ${e}${s}`)),n.status}async pickAfterFailure(e,r){let i=aZ(e,r);return this.ensure(await vo({message:`${e}: how do you want to proceed?`,options:i.map(n=>({value:n,label:lZ[n].label,hint:lZ[n].hint}))}))}review(e,r,i,n,s=new Set){let o=[];if(e.length===0)o.push("routes: none");else for(let a of e){let c=r.has(a.provider)||s.has(a.provider)?j.ok("key set"):j.warn("no key"),l=a.capability==="search"?a.provider:`${a.provider}/${a.model}`,A=a.capability!=="search"&&a.voiceId!==void 0?`, voice ${a.voiceId}`:"";o.push(`${a.capability}: ${l} (${c}${A})`)}o.push(`defaults: modality=${i}, logLevel=${n}`),RI(o.join(`
|
|
547
547
|
`),"review (secrets redacted)")}printHeadlessGuidance(){this.io.err(["jarvis init needs an interactive terminal.","","On a headless or non-interactive host:"," - run `jarvis init` over an interactive SSH session, or"," - set secrets and defaults non-interactively, e.g.:"," jarvis config set secrets.anthropic <key>"," jarvis config set defaults.modality text"," - relocate the home with the JARVIS_HOME env var if needed.","","Note: configuring a provider route (provider + model together) is interactive","in v0.1 - use `jarvis init` for that. Verify any setup with: jarvis doctor"].join(`
|
|
548
548
|
`)+`
|
|
549
549
|
`)}ensure(e){if(DI(e))throw new Gh;return e}};var iDe=["text","stt","tts","embedding"],FI=class{config;catalogue;probe;io;constructor(e){this.config=e.config,this.catalogue=e.catalogue,this.probe=e.probe,this.io=e.io??ct}async run(e={}){let r;try{r=this.config.resolved()}catch(o){return this.io.err(`error: ${oDe(o)}
|