@x-otto/cli 0.0.1-alpha.14-debug → 0.0.1-alpha.16-debug
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/build-id-CIFJCmlj.js +3 -0
- package/dist/build-id-CIFJCmlj.js.map +1 -0
- package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/meta.json +1 -1
- package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/plugin.cjs +16 -6
- package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-completions.ts +12 -5
- package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-responses.ts +4 -1
- package/dist/command-dispatcher-DibEV53Q.js +17 -0
- package/dist/command-dispatcher-DibEV53Q.js.map +1 -0
- package/dist/extension-plugin-D7gnpllF.js +7 -0
- package/dist/extension-plugin-D7gnpllF.js.map +1 -0
- package/dist/extension-plugin-fPDz7XlG.js +2 -0
- package/dist/{feedback-Dek5sP3-.js → feedback-BVl4sRah.js} +2 -2
- package/dist/{feedback-Dek5sP3-.js.map → feedback-BVl4sRah.js.map} +1 -1
- package/dist/feedback-DAY-1bJ0.js +2 -0
- package/dist/index.js +10 -12
- package/dist/index.js.map +1 -1
- package/dist/model-auth-BxHBaDQx.js +2 -0
- package/dist/{model-auth-ByWCZNza.js → model-auth-Dih25xOB.js} +2 -2
- package/dist/{model-auth-ByWCZNza.js.map → model-auth-Dih25xOB.js.map} +1 -1
- package/dist/parse-cli-Dr-Fl0on.js +3 -0
- package/dist/{parse-cli-BG6_Q42O.js.map → parse-cli-Dr-Fl0on.js.map} +1 -1
- package/dist/{version-4RG5w_9n.js → version-Cd8HZ7ua.js} +2 -2
- package/dist/{version-4RG5w_9n.js.map → version-Cd8HZ7ua.js.map} +1 -1
- package/package.json +2 -2
- package/dist/build-id-CxPaemCF.js +0 -3
- package/dist/build-id-CxPaemCF.js.map +0 -1
- package/dist/command-dispatcher-ZigPcRaU.js +0 -13
- package/dist/command-dispatcher-ZigPcRaU.js.map +0 -1
- package/dist/extension-plugin-BfPbcuo6.js +0 -2
- package/dist/extension-plugin-D76MKjOB.js +0 -7
- package/dist/extension-plugin-D76MKjOB.js.map +0 -1
- package/dist/feedback-6JROHQgm.js +0 -2
- package/dist/model-auth-CQaQ5KuC.js +0 -2
- package/dist/parse-cli-BG6_Q42O.js +0 -3
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{i as e}from"./extension-plugin-D76MKjOB.js";import{openBrowser as t}from"@x-otto/shared";import{i18next as n}from"@x-otto/tui";import{getProviderDescriptor as r}from"@x-otto/ai";const i=`__otto_model_login__`,a=`__otto_model_setkey__`,o=`__otto_model_grant_capability__`,s=`__otto_model_install_plugin__`;async function c(t,r,c){let u=t[0];if(u===`refresh`||u===`--refresh`){let t=await r.app.refreshModels();r.state.models=r.app.modelRegistry.getAll().filter(e=>e.id!==`__otto_setup__`).map(e=>({id:e.id,name:e.name,provider:e.provider,api:e.api,strengths:e.strengths??[],contextWindow:e.contextWindow}));for(let e of t)for(let t of e.availableIds??[])r.state.availableModelIds.add(t);r.callbacks.persistAvailable();let i=Math.max(...t.map(e=>e.provider.length),0),a=e=>e.padEnd(i),o={},s=t.map((e,t)=>e.status===`ok`?(o[t]=`success`,n.t(`cmd.modelRefreshLineOk`,{provider:a(e.provider),added:e.added,kept:e.kept,total:e.total})):e.status===`skipped`&&e.reason===`not authenticated`?(o[t]=`muted`,n.t(`cmd.modelRefreshLineMuted`,{provider:a(e.provider),reason:n.t(`cmd.modelRefreshReasonNotAuthenticated`)})):e.reason?.includes(`capability not granted`)?(o[t]=`warning`,n.t(`cmd.modelRefreshLineWarning`,{provider:a(e.provider),reason:n.t(`cmd.modelRefreshReasonPendingCapability`)})):(o[t]=`warning`,n.t(`cmd.modelRefreshLineErr`,{provider:a(e.provider),status:e.status,reason:e.reason}))),c=t.some(e=>e.status===`skipped`&&e.reason?.includes(`capability not granted`));r.tui.modal.openModal({kind:`info`,title:n.t(`cmd.titleModels`),lines:[...s,``,n.t(`cmd.modelRefreshHint`)],action:c?{kind:`extension`,run:()=>{e([],r)}}:void 0,lineTones:o});return}if(u){let e=r.state.models.find(e=>e.id===u||e.name===u);if(!e){let e=u.includes(`/`)?u.split(`/`)[0]:u;if(!r.state.models.some(t=>t.provider.toLowerCase()===e.toLowerCase())&&/^[a-z0-9][\w-]*$/i.test(e)){r.tui.modal.openModal({kind:`select`,title:n.t(`cmd.modelDelegateProviderTitle`,{cand:e}),options:[{label:n.t(`cmd.modelDelegateProviderYes`,{cand:e}),value:`yes`},{label:n.t(`cmd.commonNo`),value:`no`}],onSelect:t=>{if(t!==`yes`)return;let i=`为 "${e}" 实现一个 provider 适配器:参照 packages/ai/src 下现有 provider 的实现,接 model-fetch 的 resolveModelsRequest、注册进 provider/model registry,并写测试跑绿。`;try{r.app.agentJobs.start({title:`write provider: ${e}`,prompt:i,sessionId:r.state.session.id,origin:`user`}),r.tui.info.showToast(n.t(`cmd.titleDelegated`),n.t(`cmd.modelProviderDelegated`,{cand:e}))}catch(e){r.tui.messages.pushMessage({role:`command_echo`,command:`/model`,status:`error`,summary:n.t(`cmd.delegateFailed`,{err:e instanceof Error?e.message:String(e)})})}},onCancel:()=>{}});return}r.tui.info.showToast(n.t(`cmd.titleModel`),n.t(`cmd.modelNotFoundUse`,{arg:u}));return}r.tui.info.showToast(n.t(`cmd.titleModel`),await r.callbacks.applyModel(e.id)),r.state.session.model?.id===e.id&&c?.afterSwitch?.();return}r.state.models=r.app.modelRegistry.getAll().filter(e=>e.id!==`__otto_setup__`).map(e=>({id:e.id,name:e.name,provider:e.provider,api:e.api,strengths:e.strengths??[],contextWindow:e.contextWindow}));let d=e=>r.state.availableModelIds.has(e),f=[...new Set(r.state.models.map(e=>r.callbacks.apiCredentialId(e.api??``,e.provider)))],p=new Map(await Promise.all(f.map(async e=>[e,await r.app.authStore.getAuthSource(e).catch(()=>null)]))),m=e=>{let t=r.callbacks.apiCredentialId(e.api??``,e.provider),i=p.get(t);if(!i)return``;let a=n.t(`pref.authModeLabel`,{context:i});return`${a===i?``:` · `}${a}`},h=[...r.state.models].sort((e,t)=>Number(d(t.id))-Number(d(e.id))||e.provider.localeCompare(t.provider)||e.name.localeCompare(t.name)),g=h.length>0?Math.max(...h.map(e=>e.name.length),8):8,_=e=>e?e>=1e6?`${(e/1e6).toFixed(1)}M`:`${Math.round(e/1e3)}k`:``,v=e=>{let t=d(e.id),n=e.id===r.state.currentModelId?`●`:` `,i=_(e.contextWindow),a=`${n} ${e.name.padEnd(g)}`+(i?` ${i}`:``),o=[m(e).replace(/^ · /,``),e.strengths.length>0?e.strengths.join(`/`):``].filter(Boolean).join(` `);return{isAvail:t,label:a,detail:o&&` ${o}`}},y=r.app.settings.get(`recent_models`)??[],b=new Map(r.state.models.map(e=>[e.id,e])),x=y.map(e=>b.get(e)).filter(e=>e!==void 0).map(e=>{let{isAvail:t,label:r,detail:i}=v(e);return{label:r,value:e.id,disabled:!t,group:n.t(`cmd.modelRecentGroup`),detail:i||void 0,searchText:e.name}}),S=r.state.availableModelIds.size===0,C=!r.app.modelRegistry.hasAnyPluginSourcedModel()&&S,w=(r.app.pendingProviderCapabilities??[]).length,T=[];C&&w>0?(T.push(n.t(`model.noProviderPlugin`)),T.push(n.t(`model.pendingCapabilities`,{count:w}))):C?T.push(n.t(`model.noProviderPlugin`)):S&&T.push(n.t(`model.noneAvailable`)),!C&&w>0&&T.push(n.t(`model.pendingCapabilities`,{count:w})),r.tui.modal.openModal({kind:`select`,searchable:!0,maxVisible:13,title:S?n.t(`cmd.modelSelectTitle`):n.t(`cmd.modelSelectTitleAvail`),subtitle:T.length>0?T.join(` · `):void 0,subtitleTone:w>0?`warning`:`default`,subtitleActions:C||S||w>0?[...C?[{label:n.t(`model.goInstallPlugin`),value:s}]:S?[{label:n.t(`model.goLogin`),value:i},{label:n.t(`model.setKey`),value:a}]:[],...w>0?[{label:n.t(`model.goGrantCapability`),value:o}]:[]]:void 0,options:[...x,...h.map(e=>{let{isAvail:t,label:n,detail:r}=v(e);return{label:n,value:e.id,disabled:!t,group:e.provider,detail:r||void 0,searchText:e.name}})],onSelect:async t=>{if(t===i){await l([],r,{method:`oauth`});return}if(t===a){await l([],r,{method:`key`});return}if(t===o){await e([],r);return}if(t===s){let{EXTENSION_ALIASES:e}=await import(`./extension-C3AP3KxC.js`),{runPluginInstall:t}=await import(`./extension-plugin-BfPbcuo6.js`),i=Object.entries(e).filter(([,e])=>e.capabilities.includes(`provider`)).map(([e])=>({label:e,value:e}));if(i.length===1){await t(i[0].value,r);return}r.tui.modal.openModal({kind:`select`,title:n.t(`cmd.modelSelectTitle`),options:i.map(e=>({label:e.label,value:e.value})),onSelect:async e=>{await t(e,r)},onCancel:()=>{}});return}r.tui.info.showToast(n.t(`cmd.titleModel`),await r.callbacks.applyModel(t)),r.state.session.model?.id===t&&c?.afterSwitch?.()}})}async function l(e,i,a){if(e.includes(`--service`))return d(e,i);let o=i.app.authStore,s=e=>new Promise(t=>i.tui.modal.openModal({kind:`select`,...e,title:e.title??n.t(`cmd.commonSelect`),detailPlacement:`inline`,onSelect:t,onCancel:()=>t(null)})),c=o.listProviders();if(c.length===0){i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginNoProviders`));return}let l=e[0]||null,u=e=>o.oauth.get(e)?.name??r(e)?.name??i.callbacks.PROVIDER_DISPLAY_NAMES[e]??e,f=e=>u(e),p=e=>e?n.t(`pref.authModeLabel`,{context:e}):n.t(`cmd.loginNotSet`),m=(e,t)=>{let n=p(t);return u(e).toLowerCase()===e.toLowerCase()?n:`[${e}] · ${n}`};if(!l){let e=await Promise.all(c.map(e=>o.getAuthSource(e)));l=await s({title:n.t(`cmd.loginPickProvider`),options:c.map((t,n)=>({label:f(t),value:t,detail:m(t,e[n]??null)}))})}if(!l)return;let h=o.oauth.get(l)?.name??l,g=!!o.oauth.get(l),_=`key`;if(a?.method)_=a.method===`oauth`&&g?`oauth`:`key`;else if(g){let e=await s({title:n.t(`cmd.loginAuthMethod`,{name:h}),options:[{label:n.t(`cmd.loginMethodOauth`),value:`oauth`},{label:n.t(`cmd.loginMethodKey`),value:`key`}]});if(!e)return;_=e}if(_===`key`){let e=await i.tui.modal.openModal({kind:`input`,title:n.t(`cmd.loginKeyTitle`,{provider:l}),placeholder:n.t(`cmd.loginKeyPlaceholder`),mask:!0});if(!e||!e.trim()){i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginCancelledNoKey`));return}await o.setCredentialKey(l,e.trim()),await o.save(),i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginKeySaved`,{provider:l})),await i.callbacks.refreshModelsNow(e=>i.tui.info.showToast(n.t(`cmd.titleModels`),e));return}try{let e={};await o.login(l,{onAuth:r=>{if(e=r,r.url)if(t(r.url))i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginOauthBrowserOpened`));else{let e=[n.t(`cmd.loginOauthUrlAuthorize`),`\n${r.url}`];r.instructions&&e.push(`\n${r.instructions}`),i.tui.modal.openModal({kind:`info`,title:n.t(`cmd.loginOauthTitle`,{provider:l}),lines:e})}if(r.code){let e=[n.t(`cmd.loginOauthCodeIntro`)];r.url&&e.push(`\n${r.url}`),e.push(`\n${n.t(`cmd.loginOauthCode`,{code:r.code})}`),r.instructions&&e.push(`\n${r.instructions}`),i.tui.modal.openModal({kind:`info`,title:n.t(`cmd.loginOauthTitle`,{provider:l}),lines:e})}},onProgress:e=>i.tui.info.showToast(n.t(`cmd.titleLogin`),e),onPrompt:async e=>await i.tui.modal.openModal({kind:`input`,title:e.message,placeholder:e.placeholder})??``,onManualCode:async r=>{let a=[];return e.url&&(a.push(n.t(`cmd.loginOauthUrlAuthorize`)),a.push(` `),a.push(e.url)),e.instructions&&(a.length>0&&a.push(` `),a.push(e.instructions)),await i.tui.modal.openModal({kind:`input`,title:n.t(`cmd.loginOauthPasteCode`,{provider:l}),description:a.length>0?a:void 0,placeholder:`code#state`,signal:r,actionButton:e.url?{label:n.t(`cmd.loginOauthOpenBrowserButton`),onPress:()=>{t(e.url)&&i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginOauthBrowserOpened`))}}:void 0})??``}}),i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginSuccess`,{provider:l})),await i.callbacks.refreshModelsNow(e=>i.tui.info.showToast(n.t(`cmd.titleModels`),e))}catch(e){i.tui.messages.pushMessage({role:`command_echo`,command:`/auth`,status:`error`,summary:n.t(`cmd.loginFailed`,{msg:e.message})})}}async function u(e,t){if(e.includes(`--service`))return f(e,t);let i=t.app.authStore,a=e[0]||null;if(!a){let e=i.listProviders(),o=await Promise.all(e.map(e=>i.getAuthSource(e))),s=e.filter((e,t)=>o[t]===`oauth`||o[t]===`api_key`);if(s.length===0){t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.logoutNoStored`));return}a=await new Promise(a=>t.tui.modal.openModal({kind:`select`,title:n.t(`cmd.logoutPickProvider`),detailPlacement:`inline`,options:s.map(a=>{let s=i.oauth.get(a)?.name??r(a)?.name??t.callbacks.PROVIDER_DISPLAY_NAMES[a]??a,c=o[e.indexOf(a)];return{label:s.toLowerCase()===a.toLowerCase()?s:`${s} [${a}]`,value:a,detail:c?n.t(`pref.authModeLabel`,{context:c}):void 0}}),onSelect:a,onCancel:()=>a(null)}))}a&&(await i.logout(a),t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.logoutSuccess`,{provider:a})))}async function d(e,t){let r=e.indexOf(`--service`),i=e.indexOf(`--token`);if(r===-1||i===-1){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginUsage`));return}let a=e[r+1],o=e[i+1];if(!a||!o){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginNeedValues`));return}let s=a.replace(/\/+$/,``);try{let e=await fetch(`${s}/auth/login`,{method:`POST`,headers:{Authorization:`Bearer ${o}`,Accept:`application/json`}});if(!e.ok){let r=(await e.json().catch(()=>({}))).error??e.statusText;t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginFailed`,{err:String(r)}));return}let r=await e.json();if(typeof r.user_id!=`string`||typeof r.token!=`string`){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginBadResponse`));return}let{OTTO_IDENTITY_PATH:i,isShadowModeEnabled:a}=await import(`@x-otto/env`);if(a()){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginSuccess`,{url:s,user:r.user_id.slice(0,12)}));return}let{mkdir:c}=await import(`node:fs/promises`),{dirname:l}=await import(`node:path`),{atomicWriteFile:u}=await import(`@x-otto/persistence`),d={userId:r.user_id,token:r.token,serviceUrl:s,loginAt:Date.now()};await c(l(i),{recursive:!0}),await u(i,JSON.stringify(d,null,2)),t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginSuccess`,{url:s,user:r.user_id.slice(0,12)}))}catch(e){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginFailed`,{err:e instanceof Error?e.message:String(e)}))}}async function f(e,t){try{let{OTTO_IDENTITY_PATH:e}=await import(`@x-otto/env`),{unlink:r}=await import(`node:fs/promises`);await r(e),t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.serviceLogoutRemoved`))}catch(e){if(e.code===`ENOENT`){t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.serviceLogoutNotFound`));return}t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.serviceLogoutFailed`,{err:e instanceof Error?e.message:String(e)}))}}export{f as a,d as i,c as n,l as r,u as t};
|
|
3
|
-
//# sourceMappingURL=model-auth-
|
|
2
|
+
import{i as e}from"./extension-plugin-D7gnpllF.js";import{openBrowser as t}from"@x-otto/shared";import{i18next as n}from"@x-otto/tui";import{getProviderDescriptor as r}from"@x-otto/ai";const i=`__otto_model_login__`,a=`__otto_model_setkey__`,o=`__otto_model_grant_capability__`,s=`__otto_model_install_plugin__`;async function c(t,r,c){let u=t[0];if(u===`refresh`||u===`--refresh`){let t=await r.app.refreshModels();r.state.models=r.app.modelRegistry.getAll().filter(e=>e.id!==`__otto_setup__`).map(e=>({id:e.id,name:e.name,provider:e.provider,api:e.api,strengths:e.strengths??[],contextWindow:e.contextWindow}));for(let e of t)for(let t of e.availableIds??[])r.state.availableModelIds.add(t);r.callbacks.persistAvailable();let i=Math.max(...t.map(e=>e.provider.length),0),a=e=>e.padEnd(i),o={},s=t.map((e,t)=>e.status===`ok`?(o[t]=`success`,n.t(`cmd.modelRefreshLineOk`,{provider:a(e.provider),added:e.added,kept:e.kept,total:e.total})):e.status===`skipped`&&e.reason===`not authenticated`?(o[t]=`muted`,n.t(`cmd.modelRefreshLineMuted`,{provider:a(e.provider),reason:n.t(`cmd.modelRefreshReasonNotAuthenticated`)})):e.reason?.includes(`capability not granted`)?(o[t]=`warning`,n.t(`cmd.modelRefreshLineWarning`,{provider:a(e.provider),reason:n.t(`cmd.modelRefreshReasonPendingCapability`)})):(o[t]=`warning`,n.t(`cmd.modelRefreshLineErr`,{provider:a(e.provider),status:e.status,reason:e.reason}))),c=t.some(e=>e.status===`skipped`&&e.reason?.includes(`capability not granted`));r.tui.modal.openModal({kind:`info`,title:n.t(`cmd.titleModels`),lines:[...s,``,n.t(`cmd.modelRefreshHint`)],action:c?{kind:`extension`,run:()=>{e([],r)}}:void 0,lineTones:o});return}if(u){let e=r.state.models.find(e=>e.id===u||e.name===u);if(!e){let e=u.includes(`/`)?u.split(`/`)[0]:u;if(!r.state.models.some(t=>t.provider.toLowerCase()===e.toLowerCase())&&/^[a-z0-9][\w-]*$/i.test(e)){r.tui.modal.openModal({kind:`select`,title:n.t(`cmd.modelDelegateProviderTitle`,{cand:e}),options:[{label:n.t(`cmd.modelDelegateProviderYes`,{cand:e}),value:`yes`},{label:n.t(`cmd.commonNo`),value:`no`}],onSelect:t=>{if(t!==`yes`)return;let i=`为 "${e}" 实现一个 provider 适配器:参照 packages/ai/src 下现有 provider 的实现,接 model-fetch 的 resolveModelsRequest、注册进 provider/model registry,并写测试跑绿。`;try{r.app.agentJobs.start({title:`write provider: ${e}`,prompt:i,sessionId:r.state.session.id,origin:`user`}),r.tui.info.showToast(n.t(`cmd.titleDelegated`),n.t(`cmd.modelProviderDelegated`,{cand:e}))}catch(e){r.tui.messages.pushMessage({role:`command_echo`,command:`/model`,status:`error`,summary:n.t(`cmd.delegateFailed`,{err:e instanceof Error?e.message:String(e)})})}},onCancel:()=>{}});return}r.tui.info.showToast(n.t(`cmd.titleModel`),n.t(`cmd.modelNotFoundUse`,{arg:u}));return}r.tui.info.showToast(n.t(`cmd.titleModel`),await r.callbacks.applyModel(e.id)),r.state.session.model?.id===e.id&&c?.afterSwitch?.();return}r.state.models=r.app.modelRegistry.getAll().filter(e=>e.id!==`__otto_setup__`).map(e=>({id:e.id,name:e.name,provider:e.provider,api:e.api,strengths:e.strengths??[],contextWindow:e.contextWindow}));let d=e=>r.state.availableModelIds.has(e),f=[...new Set(r.state.models.map(e=>r.callbacks.apiCredentialId(e.api??``,e.provider)))],p=new Map(await Promise.all(f.map(async e=>[e,await r.app.authStore.getAuthSource(e).catch(()=>null)]))),m=e=>{let t=r.callbacks.apiCredentialId(e.api??``,e.provider),i=p.get(t);if(!i)return``;let a=n.t(`pref.authModeLabel`,{context:i});return`${a===i?``:` · `}${a}`},h=[...r.state.models].sort((e,t)=>Number(d(t.id))-Number(d(e.id))||e.provider.localeCompare(t.provider)||e.name.localeCompare(t.name)),g=h.length>0?Math.max(...h.map(e=>e.name.length),8):8,_=e=>e?e>=1e6?`${(e/1e6).toFixed(1)}M`:`${Math.round(e/1e3)}k`:``,v=e=>{let t=d(e.id),n=e.id===r.state.currentModelId?`●`:` `,i=_(e.contextWindow),a=`${n} ${e.name.padEnd(g)}`+(i?` ${i}`:``),o=[m(e).replace(/^ · /,``),e.strengths.length>0?e.strengths.join(`/`):``].filter(Boolean).join(` `);return{isAvail:t,label:a,detail:o&&` ${o}`}},y=r.app.settings.get(`recent_models`)??[],b=new Map(r.state.models.map(e=>[e.id,e])),x=y.map(e=>b.get(e)).filter(e=>e!==void 0).map(e=>{let{isAvail:t,label:r,detail:i}=v(e);return{label:r,value:e.id,disabled:!t,group:n.t(`cmd.modelRecentGroup`),detail:i||void 0,searchText:e.name}}),S=r.state.availableModelIds.size===0,C=!r.app.modelRegistry.hasAnyPluginSourcedModel()&&S,w=(r.app.pendingProviderCapabilities??[]).length,T=[];C&&w>0?(T.push(n.t(`model.noProviderPlugin`)),T.push(n.t(`model.pendingCapabilities`,{count:w}))):C?T.push(n.t(`model.noProviderPlugin`)):S&&T.push(n.t(`model.noneAvailable`)),!C&&w>0&&T.push(n.t(`model.pendingCapabilities`,{count:w})),r.tui.modal.openModal({kind:`select`,searchable:!0,maxVisible:13,title:S?n.t(`cmd.modelSelectTitle`):n.t(`cmd.modelSelectTitleAvail`),subtitle:T.length>0?T.join(` · `):void 0,subtitleTone:w>0?`warning`:`default`,subtitleActions:C||S||w>0?[...C?[{label:n.t(`model.goInstallPlugin`),value:s}]:S?[{label:n.t(`model.goLogin`),value:i},{label:n.t(`model.setKey`),value:a}]:[],...w>0?[{label:n.t(`model.goGrantCapability`),value:o}]:[]]:void 0,options:[...x,...h.map(e=>{let{isAvail:t,label:n,detail:r}=v(e);return{label:n,value:e.id,disabled:!t,group:e.provider,detail:r||void 0,searchText:e.name}})],onSelect:async t=>{if(t===i){await l([],r,{method:`oauth`});return}if(t===a){await l([],r,{method:`key`});return}if(t===o){await e([],r);return}if(t===s){let{EXTENSION_ALIASES:e}=await import(`./extension-C3AP3KxC.js`),{runPluginInstall:t}=await import(`./extension-plugin-fPDz7XlG.js`),i=Object.entries(e).filter(([,e])=>e.capabilities.includes(`provider`)).map(([e])=>({label:e,value:e}));if(i.length===1){await t(i[0].value,r);return}r.tui.modal.openModal({kind:`select`,title:n.t(`cmd.modelSelectTitle`),options:i.map(e=>({label:e.label,value:e.value})),onSelect:async e=>{await t(e,r)},onCancel:()=>{}});return}r.tui.info.showToast(n.t(`cmd.titleModel`),await r.callbacks.applyModel(t)),r.state.session.model?.id===t&&c?.afterSwitch?.()}})}async function l(e,i,a){if(e.includes(`--service`))return d(e,i);let o=i.app.authStore,s=e=>new Promise(t=>i.tui.modal.openModal({kind:`select`,...e,title:e.title??n.t(`cmd.commonSelect`),detailPlacement:`inline`,onSelect:t,onCancel:()=>t(null)})),c=o.listProviders();if(c.length===0){i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginNoProviders`));return}let l=e[0]||null,u=e=>o.oauth.get(e)?.name??r(e)?.name??i.callbacks.PROVIDER_DISPLAY_NAMES[e]??e,f=e=>u(e),p=e=>e?n.t(`pref.authModeLabel`,{context:e}):n.t(`cmd.loginNotSet`),m=(e,t)=>{let n=p(t);return u(e).toLowerCase()===e.toLowerCase()?n:`[${e}] · ${n}`};if(!l){let e=await Promise.all(c.map(e=>o.getAuthSource(e)));l=await s({title:n.t(`cmd.loginPickProvider`),options:c.map((t,n)=>({label:f(t),value:t,detail:m(t,e[n]??null)}))})}if(!l)return;let h=o.oauth.get(l)?.name??l,g=!!o.oauth.get(l),_=`key`;if(a?.method)_=a.method===`oauth`&&g?`oauth`:`key`;else if(g){let e=await s({title:n.t(`cmd.loginAuthMethod`,{name:h}),options:[{label:n.t(`cmd.loginMethodOauth`),value:`oauth`},{label:n.t(`cmd.loginMethodKey`),value:`key`}]});if(!e)return;_=e}if(_===`key`){let e=await i.tui.modal.openModal({kind:`input`,title:n.t(`cmd.loginKeyTitle`,{provider:l}),placeholder:n.t(`cmd.loginKeyPlaceholder`),mask:!0});if(!e||!e.trim()){i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginCancelledNoKey`));return}await o.setCredentialKey(l,e.trim()),await o.save(),i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginKeySaved`,{provider:l})),await i.callbacks.refreshModelsNow(e=>i.tui.info.showToast(n.t(`cmd.titleModels`),e));return}try{let e={};await o.login(l,{onAuth:r=>{if(e=r,r.url)if(t(r.url))i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginOauthBrowserOpened`));else{let e=[n.t(`cmd.loginOauthUrlAuthorize`),`\n${r.url}`];r.instructions&&e.push(`\n${r.instructions}`),i.tui.modal.openModal({kind:`info`,title:n.t(`cmd.loginOauthTitle`,{provider:l}),lines:e})}if(r.code){let e=[n.t(`cmd.loginOauthCodeIntro`)];r.url&&e.push(`\n${r.url}`),e.push(`\n${n.t(`cmd.loginOauthCode`,{code:r.code})}`),r.instructions&&e.push(`\n${r.instructions}`),i.tui.modal.openModal({kind:`info`,title:n.t(`cmd.loginOauthTitle`,{provider:l}),lines:e})}},onProgress:e=>i.tui.info.showToast(n.t(`cmd.titleLogin`),e),onPrompt:async e=>await i.tui.modal.openModal({kind:`input`,title:e.message,placeholder:e.placeholder})??``,onManualCode:async r=>{let a=[];return e.url&&(a.push(n.t(`cmd.loginOauthUrlAuthorize`)),a.push(` `),a.push(e.url)),e.instructions&&(a.length>0&&a.push(` `),a.push(e.instructions)),await i.tui.modal.openModal({kind:`input`,title:n.t(`cmd.loginOauthPasteCode`,{provider:l}),description:a.length>0?a:void 0,placeholder:`code#state`,signal:r,actionButton:e.url?{label:n.t(`cmd.loginOauthOpenBrowserButton`),onPress:()=>{t(e.url)&&i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginOauthBrowserOpened`))}}:void 0})??``}}),i.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.loginSuccess`,{provider:l})),await i.callbacks.refreshModelsNow(e=>i.tui.info.showToast(n.t(`cmd.titleModels`),e))}catch(e){i.tui.messages.pushMessage({role:`command_echo`,command:`/auth`,status:`error`,summary:n.t(`cmd.loginFailed`,{msg:e.message})})}}async function u(e,t){if(e.includes(`--service`))return f(e,t);let i=t.app.authStore,a=e[0]||null;if(!a){let e=i.listProviders(),o=await Promise.all(e.map(e=>i.getAuthSource(e))),s=e.filter((e,t)=>o[t]===`oauth`||o[t]===`api_key`);if(s.length===0){t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.logoutNoStored`));return}a=await new Promise(a=>t.tui.modal.openModal({kind:`select`,title:n.t(`cmd.logoutPickProvider`),detailPlacement:`inline`,options:s.map(a=>{let s=i.oauth.get(a)?.name??r(a)?.name??t.callbacks.PROVIDER_DISPLAY_NAMES[a]??a,c=o[e.indexOf(a)];return{label:s.toLowerCase()===a.toLowerCase()?s:`${s} [${a}]`,value:a,detail:c?n.t(`pref.authModeLabel`,{context:c}):void 0}}),onSelect:a,onCancel:()=>a(null)}))}a&&(await i.logout(a),t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.logoutSuccess`,{provider:a})))}async function d(e,t){let r=e.indexOf(`--service`),i=e.indexOf(`--token`);if(r===-1||i===-1){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginUsage`));return}let a=e[r+1],o=e[i+1];if(!a||!o){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginNeedValues`));return}let s=a.replace(/\/+$/,``);try{let e=await fetch(`${s}/auth/login`,{method:`POST`,headers:{Authorization:`Bearer ${o}`,Accept:`application/json`}});if(!e.ok){let r=(await e.json().catch(()=>({}))).error??e.statusText;t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginFailed`,{err:String(r)}));return}let r=await e.json();if(typeof r.user_id!=`string`||typeof r.token!=`string`){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginBadResponse`));return}let{OTTO_IDENTITY_PATH:i,isShadowModeEnabled:a}=await import(`@x-otto/env`);if(a()){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginSuccess`,{url:s,user:r.user_id.slice(0,12)}));return}let{mkdir:c}=await import(`node:fs/promises`),{dirname:l}=await import(`node:path`),{atomicWriteFile:u}=await import(`@x-otto/persistence`),d={userId:r.user_id,token:r.token,serviceUrl:s,loginAt:Date.now()};await c(l(i),{recursive:!0}),await u(i,JSON.stringify(d,null,2)),t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginSuccess`,{url:s,user:r.user_id.slice(0,12)}))}catch(e){t.tui.info.showToast(n.t(`cmd.titleLogin`),n.t(`cmd.serviceLoginFailed`,{err:e instanceof Error?e.message:String(e)}))}}async function f(e,t){try{let{OTTO_IDENTITY_PATH:e}=await import(`@x-otto/env`),{unlink:r}=await import(`node:fs/promises`);await r(e),t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.serviceLogoutRemoved`))}catch(e){if(e.code===`ENOENT`){t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.serviceLogoutNotFound`));return}t.tui.info.showToast(n.t(`cmd.titleLogout`),n.t(`cmd.serviceLogoutFailed`,{err:e instanceof Error?e.message:String(e)}))}}export{f as a,d as i,c as n,l as r,u as t};
|
|
3
|
+
//# sourceMappingURL=model-auth-Dih25xOB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-auth-ByWCZNza.js","names":[],"sources":["../src/commands/handlers/model-auth.ts"],"sourcesContent":["/**\n * handlers/model-auth.ts — 模型选择与认证指令处理\n *\n * /model — 选择/切换/刷新模型\n * /auth — 交互式 provider 授权(RFC-274:/login 收敛为账号语义,provider 授权走 /auth)\n * /logout — 清除已存凭证\n */\nimport { i18next } from '@x-otto/tui'\nimport { openBrowser } from '@x-otto/shared'\nimport { getProviderDescriptor } from '@x-otto/ai'\n\nimport { handleExtension } from './extension-plugin'\nimport type { CliContext } from '../../command-dispatcher'\n\n/** /model 副标题逃生口按钮的哨兵 value(区别于真实模型 id)。 */\nconst MODEL_LOGIN_SENTINEL = '__otto_model_login__'\nconst MODEL_SETKEY_SENTINEL = '__otto_model_setkey__'\n/** RFC-125 D5:因待授权高危能力被跳过装载的 provider 插件——直达 /extension 授权入口。 */\nconst MODEL_GRANT_CAPABILITY_SENTINEL = '__otto_model_grant_capability__'\n/** RFC-126 M1:零 provider 插件安装时 subtitleActions 逃生口——安装 provider 插件(M1 占位 toast,M2-04 接线到 runPluginInstall)。 */\nconst MODEL_INSTALL_PLUGIN_SENTINEL = '__otto_model_install_plugin__'\n\n/**\n * /model [<id>|refresh] | /m\n * No arg → interactive picker.\n * refresh → pull available models from each authenticated provider.\n * <id> → find by id/name & switch.\n */\nexport async function handleModel(\n args: string[],\n ctx: CliContext,\n opts?: { afterSwitch?: () => void },\n): Promise<void> {\n const arg = args[0]\n\n if (arg === 'refresh' || arg === '--refresh') {\n const results = await ctx.app.refreshModels()\n ctx.state.models = ctx.app.modelRegistry\n .getAll()\n .filter(m => m.id !== '__otto_setup__')\n .map((m) => ({\n id: m.id,\n name: m.name,\n provider: m.provider,\n api: m.api,\n strengths: m.strengths ?? [],\n contextWindow: m.contextWindow,\n }))\n for (const r of results)\n for (const id of r.availableIds ?? [])\n ctx.state.availableModelIds.add(id)\n ctx.callbacks.persistAvailable()\n // 三态视觉分离(真实用户反馈:四行状态视觉等重,分不清\"好消息/需要处理/无需关心\"):\n // ok → success(✓ 绿);skipped+未认证 → muted(弱化灰,正常状态非错误);\n // skipped+能力待授权 / failed → warning(⚠ 琥珀,需要用户处理)。\n // provider 名等宽对齐成一列,扫描更快。\n const maxProviderLen = Math.max(...results.map((r) => r.provider.length), 0)\n const pad = (s: string) => s.padEnd(maxProviderLen)\n const lineTones: Record<number, 'success' | 'muted' | 'warning'> = {}\n const lines = results.map((r, i) => {\n if (r.status === 'ok') {\n lineTones[i] = 'success'\n return i18next.t('cmd.modelRefreshLineOk', { provider: pad(r.provider), added: r.added, kept: r.kept, total: r.total })\n }\n if (r.status === 'skipped' && r.reason === 'not authenticated') {\n lineTones[i] = 'muted'\n return i18next.t('cmd.modelRefreshLineMuted', { provider: pad(r.provider), reason: i18next.t('cmd.modelRefreshReasonNotAuthenticated') })\n }\n if (r.reason?.includes('capability not granted')) {\n lineTones[i] = 'warning'\n return i18next.t('cmd.modelRefreshLineWarning', { provider: pad(r.provider), reason: i18next.t('cmd.modelRefreshReasonPendingCapability') })\n }\n lineTones[i] = 'warning'\n return i18next.t('cmd.modelRefreshLineErr', { provider: pad(r.provider), status: r.status, reason: r.reason })\n })\n // 回归修复(真实用户反馈:\"这里需要一个 dialog 承载信息\"):此前用 pushMessage 把结果\n // 塞进对话转录(伪装成一条 assistant 消息),既污染对话历史又无法承载可操作按钮。\n // 改为 openInfo 只读 dialog(同 /status /usage /help 等既有模式);若结果里存在\n // \"provider capability not granted\"(model-catalog-manager.ts 的插件待授权分支),\n // 追加 [ 去授权 ] 按钮直达 /extension(而非让用户对着一段静态文字不知道下一步做什么)。\n const hasPendingCapability = results.some(\n (r) => r.status === 'skipped' && r.reason?.includes('capability not granted'),\n )\n ctx.tui.modal.openModal({\n kind: 'info',\n title: i18next.t('cmd.titleModels'),\n lines: [...lines, '', i18next.t('cmd.modelRefreshHint')],\n action: hasPendingCapability\n ? { kind: 'extension', run: () => { void handleExtension([], ctx) } }\n : undefined,\n lineTones,\n })\n return\n }\n\n if (arg) {\n const target = ctx.state.models.find(\n (m) => m.id === arg || m.name === arg,\n )\n if (!target) {\n const cand = arg.includes('/') ? arg.split('/')[0]! : arg\n const knownProvider = ctx.state.models.some(\n (m) => m.provider.toLowerCase() === cand.toLowerCase(),\n )\n if (!knownProvider && /^[a-z0-9][\\w-]*$/i.test(cand)) {\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.modelDelegateProviderTitle', { cand: cand }),\n options: [\n {\n label: i18next.t('cmd.modelDelegateProviderYes', { cand: cand }),\n value: 'yes',\n },\n { label: i18next.t('cmd.commonNo'), value: 'no' },\n ],\n onSelect: (v: string) => {\n if (v !== 'yes') return\n const directive =\n `为 \"${cand}\" 实现一个 provider 适配器:参照 packages/ai/src 下现有 provider 的实现,` +\n `接 model-fetch 的 resolveModelsRequest、注册进 provider/model registry,并写测试跑绿。`\n try {\n ctx.app.agentJobs.start({\n title: `write provider: ${cand}`,\n prompt: directive,\n sessionId: ctx.state.session.id,\n origin: 'user',\n })\n ctx.tui.info.showToast(\n i18next.t('cmd.titleDelegated'),\n i18next.t('cmd.modelProviderDelegated', { cand: cand }),\n )\n } catch (e) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/model',\n status: 'error',\n summary: i18next.t('cmd.delegateFailed', { err: e instanceof Error ? e.message : String(e) }),\n })\n }\n },\n onCancel: () => {},\n })\n return\n }\n ctx.tui.info.showToast(i18next.t('cmd.titleModel'), i18next.t('cmd.modelNotFoundUse', { arg: arg }))\n return\n }\n // RFC-090 M1(T3):页脚由 applyModel 权威刷(成功→已提交模型 / 未命中→重置回真实模型),\n // 删抢先写,收敛到单一写入者。\n ctx.tui.info.showToast(i18next.t('cmd.titleModel'), await ctx.callbacks.applyModel(target.id))\n // 仅在切换真正生效(session.model 已变为目标)时才触发 afterSwitch(如错误重试卡的 retry)。\n // applyModel 对占位模型/registry 未命中会返回说明文案但不改 session.model——此时无条件\n // afterSwitch 会用旧模型重投,表现为\"选了切换模型但没切换成功\"(用户报告的 bug 根因)。\n if (ctx.state.session.model?.id === target.id) opts?.afterSwitch?.()\n return\n }\n\n ctx.state.models = ctx.app.modelRegistry\n .getAll()\n .filter(m => m.id !== '__otto_setup__')\n .map((m) => ({\n id: m.id,\n name: m.name,\n provider: m.provider,\n api: m.api,\n strengths: m.strengths ?? [],\n contextWindow: m.contextWindow,\n }))\n const avail = (id: string) => ctx.state.availableModelIds.has(id)\n const credIds = [\n ...new Set(\n ctx.state.models.map((m) =>\n ctx.callbacks.apiCredentialId(m.api ?? '', m.provider),\n ),\n ),\n ]\n const modeByCred = new Map(\n await Promise.all(\n credIds.map(\n async (c) =>\n [\n c,\n await ctx.app.authStore.getAuthSource(c).catch(() => null),\n ] as const,\n ),\n ),\n )\n const authTag = (m: { api?: string; provider: string }): string => {\n const cred = ctx.callbacks.apiCredentialId(m.api ?? '', m.provider)\n const mode = modeByCred.get(cred)\n if (!mode) return ''\n const label = i18next.t('pref.authModeLabel', { context: mode })\n const prefix = label === mode ? '' : ' · '\n return `${prefix}${label}`\n }\n const sorted = [...ctx.state.models].sort(\n (a, b) =>\n Number(avail(b.id)) - Number(avail(a.id)) ||\n a.provider.localeCompare(b.provider) ||\n a.name.localeCompare(b.name),\n )\n const maxNameLen = sorted.length > 0 ? Math.max(...sorted.map((m) => m.name.length), 8) : 8\n const fmtWindow = (cw?: number): string => {\n if (!cw) return ''\n return cw >= 1_000_000\n ? `${(cw / 1_000_000).toFixed(1)}M`\n : `${Math.round(cw / 1000)}k`\n }\n const buildOption = (m: (typeof sorted)[number]) => {\n const isAvail = avail(m.id)\n const mark = m.id === ctx.state.currentModelId ? '●' : ' '\n const window = fmtWindow(m.contextWindow)\n const label = `${mark} ${m.name.padEnd(maxNameLen)}` +\n (window ? ` ${window}` : '')\n const modeLabel = authTag(m).replace(/^ · /, '')\n const strengthsStr = m.strengths.length > 0\n ? m.strengths.join('/')\n : ''\n const detail = [modeLabel, strengthsStr].filter(Boolean).join(' ')\n // SelectDialog 的 detail 行本身只缩进到条目状态符号列;/model 的详情应与模型名列对齐。\n return { isAvail, label, detail: detail ? ` ${detail}` : detail }\n }\n // 「最近使用」分组(MRU,最多 5 条,recent_models 持久化真源):置顶展示,按最近顺序排列,\n // 只收录仍在当前 models 目录中的 id(避免下线/改名模型残留幽灵项);为空则整段不渲染。\n const recentIds = ctx.app.settings.get('recent_models') ?? []\n const modelById = new Map(ctx.state.models.map((m) => [m.id, m] as const))\n const recentOptions = recentIds\n .map((id) => modelById.get(id))\n .filter((m): m is (typeof sorted)[number] => m !== undefined)\n .map((m) => {\n const { isAvail, label, detail } = buildOption(m)\n return {\n label,\n value: m.id,\n disabled: !isAvail,\n group: i18next.t('cmd.modelRecentGroup'),\n detail: detail || undefined,\n // RFC-125 review 修复:label 含状态标记/padding/上下文窗口后缀等渲染装饰,\n // 模糊搜索改评分纯模型名,避免短 query 命中装饰字符产生假阳性。\n searchText: m.name,\n }\n })\n const noneAvailable = ctx.state.availableModelIds.size === 0\n // RFC-126 附带修复:零插件判定用 ModelRegistry.hasAnyPluginSourcedModel()——\n // 只有插件声明的 provider 模型才被标记为 plugin-sourced,未装插件时 models 真正为空,\n // zeroPlugins 判定正确触发。\n // 合取 noneAvailable:零插件但恰好其它凭据可用(如 copilot 已登录)时不该提示\"装插件\"。\n const zeroPlugins = !ctx.app.modelRegistry.hasAnyPluginSourcedModel() && noneAvailable\n const pendingCapabilityCount = (ctx.app.pendingProviderCapabilities ?? []).length\n const subtitleParts: string[] = []\n if (zeroPlugins && pendingCapabilityCount > 0) {\n // 有待授权插件 → 已安装但能力未被授予,优先告知「待授权」而非「尚未安装」\n subtitleParts.push(i18next.t('model.noProviderPlugin'))\n subtitleParts.push(i18next.t('model.pendingCapabilities', { count: pendingCapabilityCount }))\n } else if (zeroPlugins) {\n subtitleParts.push(i18next.t('model.noProviderPlugin'))\n } else if (noneAvailable) {\n subtitleParts.push(i18next.t('model.noneAvailable'))\n }\n if (!zeroPlugins && pendingCapabilityCount > 0) subtitleParts.push(i18next.t('model.pendingCapabilities', { count: pendingCapabilityCount }))\n ctx.tui.modal.openModal({\n kind: 'select',\n searchable: true,\n maxVisible: 13,\n title: noneAvailable\n ? i18next.t('cmd.modelSelectTitle')\n : i18next.t('cmd.modelSelectTitleAvail'),\n subtitle: subtitleParts.length > 0 ? subtitleParts.join(' · ') : undefined,\n subtitleTone: pendingCapabilityCount > 0 ? 'warning' : 'default',\n // 零插件:仅 [安装 provider 插件] 按钮([登录]/[配置凭证] 此时无效,不展示);\n // 无可用模型:内联 [登录]/[配置凭证] 逃生口直达 /auth;\n // 有待授权 provider 插件:追加 [去授权] 逃生口直达 /extension(RFC-125 D5)。\n subtitleActions:\n zeroPlugins || noneAvailable || pendingCapabilityCount > 0\n ? [\n ...(zeroPlugins\n ? [{ label: i18next.t('model.goInstallPlugin'), value: MODEL_INSTALL_PLUGIN_SENTINEL }]\n : noneAvailable\n ? [\n { label: i18next.t('model.goLogin'), value: MODEL_LOGIN_SENTINEL },\n { label: i18next.t('model.setKey'), value: MODEL_SETKEY_SENTINEL },\n ]\n : []),\n ...(pendingCapabilityCount > 0\n ? [{ label: i18next.t('model.goGrantCapability'), value: MODEL_GRANT_CAPABILITY_SENTINEL }]\n : []),\n ]\n : undefined,\n options: [\n ...recentOptions,\n ...sorted.map((m) => {\n const { isAvail, label, detail } = buildOption(m)\n return {\n label,\n value: m.id,\n disabled: !isAvail,\n group: m.provider,\n detail: detail || undefined,\n searchText: m.name,\n }\n }),\n ],\n onSelect: async (id: string) => {\n if (id === MODEL_LOGIN_SENTINEL) {\n await handleProviderAuth([], ctx, { method: 'oauth' })\n return\n }\n if (id === MODEL_SETKEY_SENTINEL) {\n await handleProviderAuth([], ctx, { method: 'key' })\n return\n }\n if (id === MODEL_GRANT_CAPABILITY_SENTINEL) {\n await handleExtension([], ctx)\n return\n }\n if (id === MODEL_INSTALL_PLUGIN_SENTINEL) {\n // RFC-126 M2-04:占位 toast → 展示 provider 插件列表,选中后调用 runPluginInstall。\n const { EXTENSION_ALIASES } = await import('../extension')\n const { runPluginInstall } = await import('./extension-plugin')\n const providerEntries = Object.entries(EXTENSION_ALIASES)\n .filter(([, entry]) => entry.capabilities.includes('provider'))\n .map(([alias]) => ({ label: alias, value: alias }))\n if (providerEntries.length === 1) {\n await runPluginInstall(providerEntries[0]!.value, ctx)\n return\n }\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.modelSelectTitle'),\n options: providerEntries.map((e) => ({\n label: e.label,\n value: e.value,\n })),\n onSelect: async (v: string) => {\n await runPluginInstall(v, ctx)\n },\n onCancel: () => {},\n })\n return\n }\n // RFC-090 M1(T3):页脚由 applyModel 权威刷,删抢先写与仅为其服务的 model 查找。\n ctx.tui.info.showToast(i18next.t('cmd.titleModel'), await ctx.callbacks.applyModel(id))\n // 仅切换真正生效才 afterSwitch(错误重试卡「切换模型」根因修复:占位/未命中模型\n // 不改 session.model,无条件 retry 会用旧模型重投=假装切换成功)。\n if (ctx.state.session.model?.id === id) opts?.afterSwitch?.()\n },\n })\n}\n\n/**\n * /auth [<provider>] | /login [<provider>] (deprecated alias)\n * Interactive provider authentication: pick provider → choose method (OAuth / API key).\n *\n * RFC-274 §0.1:/login 收敛为 Otto/Aniwei 账号登录语义,provider 授权走 /auth。\n * /login 作为 deprecated alias 保留,行为等同 /auth。\n */\nexport async function handleProviderAuth(\n args: string[],\n ctx: CliContext,\n opts?: { method?: 'oauth' | 'key' },\n): Promise<void> {\n if (args.includes('--service')) {\n return handleServiceLogin(args, ctx)\n }\n\n const authStore = ctx.app.authStore\n const pickOne = (req: {\n title?: string\n options: { label: string; value: string; detail?: string }[]\n }) =>\n new Promise<string | null>((resolve) =>\n ctx.tui.modal.openModal({\n kind: 'select',\n ...req,\n title: req.title ?? i18next.t('cmd.commonSelect'),\n // RFC-292 D1:provider detail 是 订阅/密钥 这类短徽章——与 label 同行左对齐,\n // 不再占用独立一行(method 选择弹窗无 detail,inline 对其零视觉影响)。\n detailPlacement: 'inline',\n onSelect: resolve,\n onCancel: () => resolve(null),\n }),\n )\n\n const known = authStore.listProviders()\n if (known.length === 0) {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginNoProviders'))\n return\n }\n let provider: string | null = args[0] || null\n // 回归修复(真实用户反馈 \"/login 需要根据插件来显示,不是固定的\"):展示名优先级改为\n // OAuth provider 自带名(`oauth.get(p)?.name`,装载器现已从 provider entry 的 `name`\n // 字段回填,见 provider-loader.ts oauthRefDisplayNames)→ provider descriptor 的\n // `name` 字段(`getProviderDescriptor`,插件声明式 provider 装载时同步注册,覆盖非\n // OAuth 场景如 DeepSeek 纯 API-key)→ 硬编码表兜底(仅剩极少数历史遗留场景)→裸 id。\n // 新增插件 provider 只要在 manifest 里声明 `name` 字段即可自动展示,不再需要改宿主代码\n // 手工加表项(RFC-105\"宿主对具体插件零认知\"原则)。\n const displayName = (p: string): string =>\n authStore.oauth.get(p)?.name ??\n getProviderDescriptor(p)?.name ??\n ctx.callbacks.PROVIDER_DISPLAY_NAMES[p] ??\n p\n const providerLabel = (p: string): string => displayName(p)\n // 认证来源 → 本地化用户面文案:复用 `authModeLabel`(oauth→订阅、api_key/env→key),\n // 未设置回退 `loginNotSet`。\n const sourceLabel = (src: string | null): string =>\n src ? i18next.t('pref.authModeLabel', { context: src }) : i18next.t('cmd.loginNotSet')\n const providerDetail = (p: string, src: string | null): string => {\n const source = sourceLabel(src)\n // 展示名与 provider id 语义等同(大小写无关)时省略冗余 `[id]`,第二行只留认证来源,\n // 文字左对齐(不再有 `[id] · ` 前缀把来源推到右侧)。\n return displayName(p).toLowerCase() === p.toLowerCase()\n ? source\n : `[${p}] · ${source}`\n }\n\n if (!provider) {\n const sources = await Promise.all(\n known.map((p: string) => authStore.getAuthSource(p)),\n )\n provider = await pickOne({\n title: i18next.t('cmd.loginPickProvider'),\n options: known.map((p: string, i: number) => ({\n label: providerLabel(p),\n value: p,\n detail: providerDetail(p, sources[i] ?? null),\n })),\n })\n }\n if (!provider) return\n\n const oauthName = authStore.oauth.get(provider)?.name ?? provider\n const oauthCapable = !!authStore.oauth.get(provider)\n let method = 'key'\n if (opts?.method) {\n // 调用方预选方式(如 /model 的 [登录]→oauth / [配置凭证]→key):\n // oauth 仅在 provider 支持时生效,否则回退 key;跳过方式选择弹窗。\n method = opts.method === 'oauth' && oauthCapable ? 'oauth' : 'key'\n } else if (oauthCapable) {\n const m = await pickOne({\n title: i18next.t('cmd.loginAuthMethod', { name: oauthName }),\n options: [\n { label: i18next.t('cmd.loginMethodOauth'), value: 'oauth' },\n { label: i18next.t('cmd.loginMethodKey'), value: 'key' },\n ],\n })\n if (!m) return\n method = m\n }\n\n if (method === 'key') {\n const key = await ctx.tui.modal.openModal({\n kind: 'input',\n title: i18next.t('cmd.loginKeyTitle', { provider: provider }),\n placeholder: i18next.t('cmd.loginKeyPlaceholder'),\n mask: true,\n })\n if (!key || !key.trim()) {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginCancelledNoKey'))\n return\n }\n await authStore.setCredentialKey(provider, key.trim())\n await authStore.save()\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginKeySaved', { provider: provider }))\n await ctx.callbacks.refreshModelsNow((msg) => ctx.tui.info.showToast(i18next.t('cmd.titleModels'), msg))\n return\n }\n\n try {\n let authInfo: { url?: string; code?: string; instructions?: string } = {}\n await authStore.login(provider, {\n onAuth: (info: { url?: string; code?: string; instructions?: string }) => {\n authInfo = info\n // 回归修复(真实用户反馈:\"/login 选择oauth 之后没有打开浏览器授权\"):\n // openBrowser() 按其自身文档\"失败静默吞错,返回 boolean\"——此前返回值被完全丢弃,\n // 失败时(如 `open`/`xdg-open` 不在 PATH、沙箱环境、spawn 权限问题)用户没有任何\n // 反馈。且此前只有 `info.code` 存在(device-flow 手动码场景)才会调用 openInfo\n // 兜底展示 URL——PKCE 流程(Anthropic/OpenAI Codex 走的正是这条)从不设置 `.code`\n // (见 templates.ts PKCEOAuthTemplate 的 onAuth 调用点),故 openBrowser 静默失败时\n // 用户在 TUI 里完全看不到授权 URL,无法手动复制打开——表现正是\"选了 oauth 但没反应\"。\n // 修复:openBrowser 成功 → toast 提示已打开浏览器(复用既有 loginOauthBrowserOpened\n // 文案,此前在 i18n 表里定义但从未被引用);失败或无浏览器可打开 → 兜底弹 openInfo\n // 展示可手动复制的 URL(不再仅限 info.code 存在时才展示)。\n if (info.url) {\n const opened = openBrowser(info.url)\n if (opened) {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginOauthBrowserOpened'))\n } else {\n const lines = [i18next.t('cmd.loginOauthUrlAuthorize'), `\\n${info.url}`]\n if (info.instructions) lines.push(`\\n${info.instructions}`)\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.loginOauthTitle', { provider: provider }), lines })\n }\n }\n if (info.code) {\n const lines = [i18next.t('cmd.loginOauthCodeIntro')]\n if (info.url) lines.push(`\\n${info.url}`)\n lines.push(`\\n${i18next.t('cmd.loginOauthCode', { code: info.code })}`)\n if (info.instructions) lines.push(`\\n${info.instructions}`)\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.loginOauthTitle', { provider: provider }), lines })\n }\n },\n onProgress: (m: string) => ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), m),\n onPrompt: async (p: { message: string; placeholder?: string }) =>\n (await ctx.tui.modal.openModal({\n kind: 'input',\n title: p.message,\n placeholder: p.placeholder,\n })) ?? '',\n onManualCode: async (signal?: AbortSignal) => {\n const description: string[] = []\n if (authInfo.url) {\n description.push(i18next.t('cmd.loginOauthUrlAuthorize'))\n description.push(' ')\n description.push(authInfo.url)\n }\n if (authInfo.instructions) {\n if (description.length > 0) description.push(' ')\n description.push(authInfo.instructions)\n }\n return (\n (await ctx.tui.modal.openModal({\n kind: 'input',\n title: i18next.t('cmd.loginOauthPasteCode', { provider: provider }),\n description: description.length > 0 ? description : undefined,\n placeholder: 'code#state',\n signal,\n actionButton: authInfo.url\n ? {\n label: i18next.t('cmd.loginOauthOpenBrowserButton'),\n onPress: () => {\n const opened = openBrowser(authInfo.url!)\n if (opened) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.loginOauthBrowserOpened'),\n )\n }\n },\n }\n : undefined,\n })) ?? ''\n )\n },\n })\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginSuccess', { provider: provider }))\n await ctx.callbacks.refreshModelsNow((msg) => ctx.tui.info.showToast(i18next.t('cmd.titleModels'), msg))\n } catch (e) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/auth',\n status: 'error',\n summary: i18next.t('cmd.loginFailed', { msg: (e as Error).message }),\n })\n }\n}\n\n/**\n * /logout [<provider>] — Clear stored credentials for a provider.\n * Env-sourced credentials cannot be removed and are not listed.\n */\nexport async function handleLogout(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n if (args.includes('--service')) {\n return handleServiceLogout(args, ctx)\n }\n\n const authStore = ctx.app.authStore\n let provider: string | null = args[0] || null\n if (!provider) {\n const known = authStore.listProviders()\n const sources = await Promise.all(\n known.map((p: string) => authStore.getAuthSource(p)),\n )\n const removable = known.filter(\n (_: string, i: number) =>\n sources[i] === 'oauth' || sources[i] === 'api_key',\n )\n if (removable.length === 0) {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogout'), i18next.t('cmd.logoutNoStored'))\n return\n }\n provider = await new Promise<string | null>((resolve) =>\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.logoutPickProvider'),\n // RFC-292 D1:provider 展示名/id 与认证来源(订阅/密钥)拆成 label + detail 两个\n // 字段、detailPlacement:'inline' 同行左对齐渲染——此前把来源拼进 label 字符串本身\n // (`${name} [${p}] · ${src}`),与 /login 视觉不一致且无法参与列对齐。\n detailPlacement: 'inline',\n options: removable.map((p: string) => {\n const name =\n authStore.oauth.get(p)?.name ??\n getProviderDescriptor(p)?.name ??\n ctx.callbacks.PROVIDER_DISPLAY_NAMES[p] ??\n p\n const src = sources[known.indexOf(p)]\n const label = name.toLowerCase() === p.toLowerCase() ? name : `${name} [${p}]`\n // src 恒为 'oauth'|'api_key'(上游 removable 过滤已保证,见上方 filter),\n // i18next.t 不会收到 null/undefined context——若未来 removable 过滤条件放宽\n // (如纳入 'env'),需同步检查 pref.authModeLabel 是否有对应 context 分支,\n // 否则会静默产出空字符串 detail(i18next 无匹配 context 时退化到基础键插值)。\n const detail = src ? i18next.t('pref.authModeLabel', { context: src }) : undefined\n return { label, value: p, detail }\n }),\n onSelect: resolve,\n onCancel: () => resolve(null),\n }),\n )\n }\n if (!provider) return\n await authStore.logout(provider)\n ctx.tui.info.showToast(i18next.t('cmd.titleLogout'), i18next.t('cmd.logoutSuccess', { provider: provider }))\n}\n\n/**\n * RFC-052 M71a:/auth --service <url> --token <val>\n *\n * 登录 otto 远端服务拿到 user identity(独立于 provider API 凭据)。\n *\n * 流程:POST /auth/login → 本地写 ~/.otto/identity.json。\n * 仅 CLI flag 驱动,非交互式。\n */\nexport async function handleServiceLogin(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const serviceIdx = args.indexOf('--service')\n const tokenIdx = args.indexOf('--token')\n\n if (serviceIdx === -1 || tokenIdx === -1) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginUsage'),\n )\n return\n }\n\n const serviceUrl = args[serviceIdx + 1]\n const token = args[tokenIdx + 1]\n\n if (!serviceUrl || !token) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginNeedValues'),\n )\n return\n }\n\n const url = serviceUrl.replace(/\\/+$/, '')\n\n try {\n const resp = await fetch(`${url}/auth/login`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/json',\n },\n })\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({})) as Record<string, unknown>\n const err = (body as Record<string, unknown>).error ?? resp.statusText\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.serviceLoginFailed', { err: String(err) }))\n return\n }\n\n const data = (await resp.json()) as { user_id?: unknown; token?: unknown }\n\n if (typeof data.user_id !== 'string' || typeof data.token !== 'string') {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.serviceLoginBadResponse'))\n return\n }\n\n const { OTTO_IDENTITY_PATH, isShadowModeEnabled } = await import('@x-otto/env')\n // RFC-345 §D3:影子态不写 identity.json——登录身份只在本会话内存生效、退出即失。\n if (isShadowModeEnabled()) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginSuccess', { url: url, user: data.user_id.slice(0, 12) }),\n )\n return\n }\n const { mkdir } = await import('node:fs/promises')\n const { dirname } = await import('node:path')\n const { atomicWriteFile } = await import('@x-otto/persistence')\n\n const identity = {\n userId: data.user_id,\n token: data.token,\n serviceUrl: url,\n loginAt: Date.now(),\n }\n\n // RFC-303 D7(M2 T303-16):identity.json 此前用裸 writeFile 覆盖写,非原子——\n // 改用 atomicWriteFile(temp+fsync+rename),跨进程读者永不看到半写文件。\n await mkdir(dirname(OTTO_IDENTITY_PATH), { recursive: true })\n await atomicWriteFile(OTTO_IDENTITY_PATH, JSON.stringify(identity, null, 2))\n\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginSuccess', { url: url, user: data.user_id.slice(0, 12) }),\n )\n } catch (e) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginFailed', { err: e instanceof Error ? e.message : String(e) }),\n )\n }\n}\n\n/**\n * RFC-052 M71a:/logout --service\n *\n * 清除本地 ~/.otto/identity.json。\n */\nexport async function handleServiceLogout(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n try {\n const { OTTO_IDENTITY_PATH } = await import('@x-otto/env')\n const { unlink } = await import('node:fs/promises')\n\n await unlink(OTTO_IDENTITY_PATH)\n ctx.tui.info.showToast(i18next.t('cmd.titleLogout'), i18next.t('cmd.serviceLogoutRemoved'))\n } catch (e) {\n const code = (e as NodeJS.ErrnoException).code\n if (code === 'ENOENT') {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogout'), i18next.t('cmd.serviceLogoutNotFound'))\n return\n }\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogout'),\n i18next.t('cmd.serviceLogoutFailed', { err: e instanceof Error ? e.message : String(e) }),\n )\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"model-auth-Dih25xOB.js","names":[],"sources":["../src/commands/handlers/model-auth.ts"],"sourcesContent":["/**\n * handlers/model-auth.ts — 模型选择与认证指令处理\n *\n * /model — 选择/切换/刷新模型\n * /auth — 交互式 provider 授权(RFC-274:/login 收敛为账号语义,provider 授权走 /auth)\n * /logout — 清除已存凭证\n */\nimport { i18next } from '@x-otto/tui'\nimport { openBrowser } from '@x-otto/shared'\nimport { getProviderDescriptor } from '@x-otto/ai'\n\nimport { handleExtension } from './extension-plugin'\nimport type { CliContext } from '../../command-dispatcher'\n\n/** /model 副标题逃生口按钮的哨兵 value(区别于真实模型 id)。 */\nconst MODEL_LOGIN_SENTINEL = '__otto_model_login__'\nconst MODEL_SETKEY_SENTINEL = '__otto_model_setkey__'\n/** RFC-125 D5:因待授权高危能力被跳过装载的 provider 插件——直达 /extension 授权入口。 */\nconst MODEL_GRANT_CAPABILITY_SENTINEL = '__otto_model_grant_capability__'\n/** RFC-126 M1:零 provider 插件安装时 subtitleActions 逃生口——安装 provider 插件(M1 占位 toast,M2-04 接线到 runPluginInstall)。 */\nconst MODEL_INSTALL_PLUGIN_SENTINEL = '__otto_model_install_plugin__'\n\n/**\n * /model [<id>|refresh] | /m\n * No arg → interactive picker.\n * refresh → pull available models from each authenticated provider.\n * <id> → find by id/name & switch.\n */\nexport async function handleModel(\n args: string[],\n ctx: CliContext,\n opts?: { afterSwitch?: () => void },\n): Promise<void> {\n const arg = args[0]\n\n if (arg === 'refresh' || arg === '--refresh') {\n const results = await ctx.app.refreshModels()\n ctx.state.models = ctx.app.modelRegistry\n .getAll()\n .filter(m => m.id !== '__otto_setup__')\n .map((m) => ({\n id: m.id,\n name: m.name,\n provider: m.provider,\n api: m.api,\n strengths: m.strengths ?? [],\n contextWindow: m.contextWindow,\n }))\n for (const r of results)\n for (const id of r.availableIds ?? [])\n ctx.state.availableModelIds.add(id)\n ctx.callbacks.persistAvailable()\n // 三态视觉分离(真实用户反馈:四行状态视觉等重,分不清\"好消息/需要处理/无需关心\"):\n // ok → success(✓ 绿);skipped+未认证 → muted(弱化灰,正常状态非错误);\n // skipped+能力待授权 / failed → warning(⚠ 琥珀,需要用户处理)。\n // provider 名等宽对齐成一列,扫描更快。\n const maxProviderLen = Math.max(...results.map((r) => r.provider.length), 0)\n const pad = (s: string) => s.padEnd(maxProviderLen)\n const lineTones: Record<number, 'success' | 'muted' | 'warning'> = {}\n const lines = results.map((r, i) => {\n if (r.status === 'ok') {\n lineTones[i] = 'success'\n return i18next.t('cmd.modelRefreshLineOk', { provider: pad(r.provider), added: r.added, kept: r.kept, total: r.total })\n }\n if (r.status === 'skipped' && r.reason === 'not authenticated') {\n lineTones[i] = 'muted'\n return i18next.t('cmd.modelRefreshLineMuted', { provider: pad(r.provider), reason: i18next.t('cmd.modelRefreshReasonNotAuthenticated') })\n }\n if (r.reason?.includes('capability not granted')) {\n lineTones[i] = 'warning'\n return i18next.t('cmd.modelRefreshLineWarning', { provider: pad(r.provider), reason: i18next.t('cmd.modelRefreshReasonPendingCapability') })\n }\n lineTones[i] = 'warning'\n return i18next.t('cmd.modelRefreshLineErr', { provider: pad(r.provider), status: r.status, reason: r.reason })\n })\n // 回归修复(真实用户反馈:\"这里需要一个 dialog 承载信息\"):此前用 pushMessage 把结果\n // 塞进对话转录(伪装成一条 assistant 消息),既污染对话历史又无法承载可操作按钮。\n // 改为 openInfo 只读 dialog(同 /status /usage /help 等既有模式);若结果里存在\n // \"provider capability not granted\"(model-catalog-manager.ts 的插件待授权分支),\n // 追加 [ 去授权 ] 按钮直达 /extension(而非让用户对着一段静态文字不知道下一步做什么)。\n const hasPendingCapability = results.some(\n (r) => r.status === 'skipped' && r.reason?.includes('capability not granted'),\n )\n ctx.tui.modal.openModal({\n kind: 'info',\n title: i18next.t('cmd.titleModels'),\n lines: [...lines, '', i18next.t('cmd.modelRefreshHint')],\n action: hasPendingCapability\n ? { kind: 'extension', run: () => { void handleExtension([], ctx) } }\n : undefined,\n lineTones,\n })\n return\n }\n\n if (arg) {\n const target = ctx.state.models.find(\n (m) => m.id === arg || m.name === arg,\n )\n if (!target) {\n const cand = arg.includes('/') ? arg.split('/')[0]! : arg\n const knownProvider = ctx.state.models.some(\n (m) => m.provider.toLowerCase() === cand.toLowerCase(),\n )\n if (!knownProvider && /^[a-z0-9][\\w-]*$/i.test(cand)) {\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.modelDelegateProviderTitle', { cand: cand }),\n options: [\n {\n label: i18next.t('cmd.modelDelegateProviderYes', { cand: cand }),\n value: 'yes',\n },\n { label: i18next.t('cmd.commonNo'), value: 'no' },\n ],\n onSelect: (v: string) => {\n if (v !== 'yes') return\n const directive =\n `为 \"${cand}\" 实现一个 provider 适配器:参照 packages/ai/src 下现有 provider 的实现,` +\n `接 model-fetch 的 resolveModelsRequest、注册进 provider/model registry,并写测试跑绿。`\n try {\n ctx.app.agentJobs.start({\n title: `write provider: ${cand}`,\n prompt: directive,\n sessionId: ctx.state.session.id,\n origin: 'user',\n })\n ctx.tui.info.showToast(\n i18next.t('cmd.titleDelegated'),\n i18next.t('cmd.modelProviderDelegated', { cand: cand }),\n )\n } catch (e) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/model',\n status: 'error',\n summary: i18next.t('cmd.delegateFailed', { err: e instanceof Error ? e.message : String(e) }),\n })\n }\n },\n onCancel: () => {},\n })\n return\n }\n ctx.tui.info.showToast(i18next.t('cmd.titleModel'), i18next.t('cmd.modelNotFoundUse', { arg: arg }))\n return\n }\n // RFC-090 M1(T3):页脚由 applyModel 权威刷(成功→已提交模型 / 未命中→重置回真实模型),\n // 删抢先写,收敛到单一写入者。\n ctx.tui.info.showToast(i18next.t('cmd.titleModel'), await ctx.callbacks.applyModel(target.id))\n // 仅在切换真正生效(session.model 已变为目标)时才触发 afterSwitch(如错误重试卡的 retry)。\n // applyModel 对占位模型/registry 未命中会返回说明文案但不改 session.model——此时无条件\n // afterSwitch 会用旧模型重投,表现为\"选了切换模型但没切换成功\"(用户报告的 bug 根因)。\n if (ctx.state.session.model?.id === target.id) opts?.afterSwitch?.()\n return\n }\n\n ctx.state.models = ctx.app.modelRegistry\n .getAll()\n .filter(m => m.id !== '__otto_setup__')\n .map((m) => ({\n id: m.id,\n name: m.name,\n provider: m.provider,\n api: m.api,\n strengths: m.strengths ?? [],\n contextWindow: m.contextWindow,\n }))\n const avail = (id: string) => ctx.state.availableModelIds.has(id)\n const credIds = [\n ...new Set(\n ctx.state.models.map((m) =>\n ctx.callbacks.apiCredentialId(m.api ?? '', m.provider),\n ),\n ),\n ]\n const modeByCred = new Map(\n await Promise.all(\n credIds.map(\n async (c) =>\n [\n c,\n await ctx.app.authStore.getAuthSource(c).catch(() => null),\n ] as const,\n ),\n ),\n )\n const authTag = (m: { api?: string; provider: string }): string => {\n const cred = ctx.callbacks.apiCredentialId(m.api ?? '', m.provider)\n const mode = modeByCred.get(cred)\n if (!mode) return ''\n const label = i18next.t('pref.authModeLabel', { context: mode })\n const prefix = label === mode ? '' : ' · '\n return `${prefix}${label}`\n }\n const sorted = [...ctx.state.models].sort(\n (a, b) =>\n Number(avail(b.id)) - Number(avail(a.id)) ||\n a.provider.localeCompare(b.provider) ||\n a.name.localeCompare(b.name),\n )\n const maxNameLen = sorted.length > 0 ? Math.max(...sorted.map((m) => m.name.length), 8) : 8\n const fmtWindow = (cw?: number): string => {\n if (!cw) return ''\n return cw >= 1_000_000\n ? `${(cw / 1_000_000).toFixed(1)}M`\n : `${Math.round(cw / 1000)}k`\n }\n const buildOption = (m: (typeof sorted)[number]) => {\n const isAvail = avail(m.id)\n const mark = m.id === ctx.state.currentModelId ? '●' : ' '\n const window = fmtWindow(m.contextWindow)\n const label = `${mark} ${m.name.padEnd(maxNameLen)}` +\n (window ? ` ${window}` : '')\n const modeLabel = authTag(m).replace(/^ · /, '')\n const strengthsStr = m.strengths.length > 0\n ? m.strengths.join('/')\n : ''\n const detail = [modeLabel, strengthsStr].filter(Boolean).join(' ')\n // SelectDialog 的 detail 行本身只缩进到条目状态符号列;/model 的详情应与模型名列对齐。\n return { isAvail, label, detail: detail ? ` ${detail}` : detail }\n }\n // 「最近使用」分组(MRU,最多 5 条,recent_models 持久化真源):置顶展示,按最近顺序排列,\n // 只收录仍在当前 models 目录中的 id(避免下线/改名模型残留幽灵项);为空则整段不渲染。\n const recentIds = ctx.app.settings.get('recent_models') ?? []\n const modelById = new Map(ctx.state.models.map((m) => [m.id, m] as const))\n const recentOptions = recentIds\n .map((id) => modelById.get(id))\n .filter((m): m is (typeof sorted)[number] => m !== undefined)\n .map((m) => {\n const { isAvail, label, detail } = buildOption(m)\n return {\n label,\n value: m.id,\n disabled: !isAvail,\n group: i18next.t('cmd.modelRecentGroup'),\n detail: detail || undefined,\n // RFC-125 review 修复:label 含状态标记/padding/上下文窗口后缀等渲染装饰,\n // 模糊搜索改评分纯模型名,避免短 query 命中装饰字符产生假阳性。\n searchText: m.name,\n }\n })\n const noneAvailable = ctx.state.availableModelIds.size === 0\n // RFC-126 附带修复:零插件判定用 ModelRegistry.hasAnyPluginSourcedModel()——\n // 只有插件声明的 provider 模型才被标记为 plugin-sourced,未装插件时 models 真正为空,\n // zeroPlugins 判定正确触发。\n // 合取 noneAvailable:零插件但恰好其它凭据可用(如 copilot 已登录)时不该提示\"装插件\"。\n const zeroPlugins = !ctx.app.modelRegistry.hasAnyPluginSourcedModel() && noneAvailable\n const pendingCapabilityCount = (ctx.app.pendingProviderCapabilities ?? []).length\n const subtitleParts: string[] = []\n if (zeroPlugins && pendingCapabilityCount > 0) {\n // 有待授权插件 → 已安装但能力未被授予,优先告知「待授权」而非「尚未安装」\n subtitleParts.push(i18next.t('model.noProviderPlugin'))\n subtitleParts.push(i18next.t('model.pendingCapabilities', { count: pendingCapabilityCount }))\n } else if (zeroPlugins) {\n subtitleParts.push(i18next.t('model.noProviderPlugin'))\n } else if (noneAvailable) {\n subtitleParts.push(i18next.t('model.noneAvailable'))\n }\n if (!zeroPlugins && pendingCapabilityCount > 0) subtitleParts.push(i18next.t('model.pendingCapabilities', { count: pendingCapabilityCount }))\n ctx.tui.modal.openModal({\n kind: 'select',\n searchable: true,\n maxVisible: 13,\n title: noneAvailable\n ? i18next.t('cmd.modelSelectTitle')\n : i18next.t('cmd.modelSelectTitleAvail'),\n subtitle: subtitleParts.length > 0 ? subtitleParts.join(' · ') : undefined,\n subtitleTone: pendingCapabilityCount > 0 ? 'warning' : 'default',\n // 零插件:仅 [安装 provider 插件] 按钮([登录]/[配置凭证] 此时无效,不展示);\n // 无可用模型:内联 [登录]/[配置凭证] 逃生口直达 /auth;\n // 有待授权 provider 插件:追加 [去授权] 逃生口直达 /extension(RFC-125 D5)。\n subtitleActions:\n zeroPlugins || noneAvailable || pendingCapabilityCount > 0\n ? [\n ...(zeroPlugins\n ? [{ label: i18next.t('model.goInstallPlugin'), value: MODEL_INSTALL_PLUGIN_SENTINEL }]\n : noneAvailable\n ? [\n { label: i18next.t('model.goLogin'), value: MODEL_LOGIN_SENTINEL },\n { label: i18next.t('model.setKey'), value: MODEL_SETKEY_SENTINEL },\n ]\n : []),\n ...(pendingCapabilityCount > 0\n ? [{ label: i18next.t('model.goGrantCapability'), value: MODEL_GRANT_CAPABILITY_SENTINEL }]\n : []),\n ]\n : undefined,\n options: [\n ...recentOptions,\n ...sorted.map((m) => {\n const { isAvail, label, detail } = buildOption(m)\n return {\n label,\n value: m.id,\n disabled: !isAvail,\n group: m.provider,\n detail: detail || undefined,\n searchText: m.name,\n }\n }),\n ],\n onSelect: async (id: string) => {\n if (id === MODEL_LOGIN_SENTINEL) {\n await handleProviderAuth([], ctx, { method: 'oauth' })\n return\n }\n if (id === MODEL_SETKEY_SENTINEL) {\n await handleProviderAuth([], ctx, { method: 'key' })\n return\n }\n if (id === MODEL_GRANT_CAPABILITY_SENTINEL) {\n await handleExtension([], ctx)\n return\n }\n if (id === MODEL_INSTALL_PLUGIN_SENTINEL) {\n // RFC-126 M2-04:占位 toast → 展示 provider 插件列表,选中后调用 runPluginInstall。\n const { EXTENSION_ALIASES } = await import('../extension')\n const { runPluginInstall } = await import('./extension-plugin')\n const providerEntries = Object.entries(EXTENSION_ALIASES)\n .filter(([, entry]) => entry.capabilities.includes('provider'))\n .map(([alias]) => ({ label: alias, value: alias }))\n if (providerEntries.length === 1) {\n await runPluginInstall(providerEntries[0]!.value, ctx)\n return\n }\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.modelSelectTitle'),\n options: providerEntries.map((e) => ({\n label: e.label,\n value: e.value,\n })),\n onSelect: async (v: string) => {\n await runPluginInstall(v, ctx)\n },\n onCancel: () => {},\n })\n return\n }\n // RFC-090 M1(T3):页脚由 applyModel 权威刷,删抢先写与仅为其服务的 model 查找。\n ctx.tui.info.showToast(i18next.t('cmd.titleModel'), await ctx.callbacks.applyModel(id))\n // 仅切换真正生效才 afterSwitch(错误重试卡「切换模型」根因修复:占位/未命中模型\n // 不改 session.model,无条件 retry 会用旧模型重投=假装切换成功)。\n if (ctx.state.session.model?.id === id) opts?.afterSwitch?.()\n },\n })\n}\n\n/**\n * /auth [<provider>] | /login [<provider>] (deprecated alias)\n * Interactive provider authentication: pick provider → choose method (OAuth / API key).\n *\n * RFC-274 §0.1:/login 收敛为 Otto/Aniwei 账号登录语义,provider 授权走 /auth。\n * /login 作为 deprecated alias 保留,行为等同 /auth。\n */\nexport async function handleProviderAuth(\n args: string[],\n ctx: CliContext,\n opts?: { method?: 'oauth' | 'key' },\n): Promise<void> {\n if (args.includes('--service')) {\n return handleServiceLogin(args, ctx)\n }\n\n const authStore = ctx.app.authStore\n const pickOne = (req: {\n title?: string\n options: { label: string; value: string; detail?: string }[]\n }) =>\n new Promise<string | null>((resolve) =>\n ctx.tui.modal.openModal({\n kind: 'select',\n ...req,\n title: req.title ?? i18next.t('cmd.commonSelect'),\n // RFC-292 D1:provider detail 是 订阅/密钥 这类短徽章——与 label 同行左对齐,\n // 不再占用独立一行(method 选择弹窗无 detail,inline 对其零视觉影响)。\n detailPlacement: 'inline',\n onSelect: resolve,\n onCancel: () => resolve(null),\n }),\n )\n\n const known = authStore.listProviders()\n if (known.length === 0) {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginNoProviders'))\n return\n }\n let provider: string | null = args[0] || null\n // 回归修复(真实用户反馈 \"/login 需要根据插件来显示,不是固定的\"):展示名优先级改为\n // OAuth provider 自带名(`oauth.get(p)?.name`,装载器现已从 provider entry 的 `name`\n // 字段回填,见 provider-loader.ts oauthRefDisplayNames)→ provider descriptor 的\n // `name` 字段(`getProviderDescriptor`,插件声明式 provider 装载时同步注册,覆盖非\n // OAuth 场景如 DeepSeek 纯 API-key)→ 硬编码表兜底(仅剩极少数历史遗留场景)→裸 id。\n // 新增插件 provider 只要在 manifest 里声明 `name` 字段即可自动展示,不再需要改宿主代码\n // 手工加表项(RFC-105\"宿主对具体插件零认知\"原则)。\n const displayName = (p: string): string =>\n authStore.oauth.get(p)?.name ??\n getProviderDescriptor(p)?.name ??\n ctx.callbacks.PROVIDER_DISPLAY_NAMES[p] ??\n p\n const providerLabel = (p: string): string => displayName(p)\n // 认证来源 → 本地化用户面文案:复用 `authModeLabel`(oauth→订阅、api_key/env→key),\n // 未设置回退 `loginNotSet`。\n const sourceLabel = (src: string | null): string =>\n src ? i18next.t('pref.authModeLabel', { context: src }) : i18next.t('cmd.loginNotSet')\n const providerDetail = (p: string, src: string | null): string => {\n const source = sourceLabel(src)\n // 展示名与 provider id 语义等同(大小写无关)时省略冗余 `[id]`,第二行只留认证来源,\n // 文字左对齐(不再有 `[id] · ` 前缀把来源推到右侧)。\n return displayName(p).toLowerCase() === p.toLowerCase()\n ? source\n : `[${p}] · ${source}`\n }\n\n if (!provider) {\n const sources = await Promise.all(\n known.map((p: string) => authStore.getAuthSource(p)),\n )\n provider = await pickOne({\n title: i18next.t('cmd.loginPickProvider'),\n options: known.map((p: string, i: number) => ({\n label: providerLabel(p),\n value: p,\n detail: providerDetail(p, sources[i] ?? null),\n })),\n })\n }\n if (!provider) return\n\n const oauthName = authStore.oauth.get(provider)?.name ?? provider\n const oauthCapable = !!authStore.oauth.get(provider)\n let method = 'key'\n if (opts?.method) {\n // 调用方预选方式(如 /model 的 [登录]→oauth / [配置凭证]→key):\n // oauth 仅在 provider 支持时生效,否则回退 key;跳过方式选择弹窗。\n method = opts.method === 'oauth' && oauthCapable ? 'oauth' : 'key'\n } else if (oauthCapable) {\n const m = await pickOne({\n title: i18next.t('cmd.loginAuthMethod', { name: oauthName }),\n options: [\n { label: i18next.t('cmd.loginMethodOauth'), value: 'oauth' },\n { label: i18next.t('cmd.loginMethodKey'), value: 'key' },\n ],\n })\n if (!m) return\n method = m\n }\n\n if (method === 'key') {\n const key = await ctx.tui.modal.openModal({\n kind: 'input',\n title: i18next.t('cmd.loginKeyTitle', { provider: provider }),\n placeholder: i18next.t('cmd.loginKeyPlaceholder'),\n mask: true,\n })\n if (!key || !key.trim()) {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginCancelledNoKey'))\n return\n }\n await authStore.setCredentialKey(provider, key.trim())\n await authStore.save()\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginKeySaved', { provider: provider }))\n await ctx.callbacks.refreshModelsNow((msg) => ctx.tui.info.showToast(i18next.t('cmd.titleModels'), msg))\n return\n }\n\n try {\n let authInfo: { url?: string; code?: string; instructions?: string } = {}\n await authStore.login(provider, {\n onAuth: (info: { url?: string; code?: string; instructions?: string }) => {\n authInfo = info\n // 回归修复(真实用户反馈:\"/login 选择oauth 之后没有打开浏览器授权\"):\n // openBrowser() 按其自身文档\"失败静默吞错,返回 boolean\"——此前返回值被完全丢弃,\n // 失败时(如 `open`/`xdg-open` 不在 PATH、沙箱环境、spawn 权限问题)用户没有任何\n // 反馈。且此前只有 `info.code` 存在(device-flow 手动码场景)才会调用 openInfo\n // 兜底展示 URL——PKCE 流程(Anthropic/OpenAI Codex 走的正是这条)从不设置 `.code`\n // (见 templates.ts PKCEOAuthTemplate 的 onAuth 调用点),故 openBrowser 静默失败时\n // 用户在 TUI 里完全看不到授权 URL,无法手动复制打开——表现正是\"选了 oauth 但没反应\"。\n // 修复:openBrowser 成功 → toast 提示已打开浏览器(复用既有 loginOauthBrowserOpened\n // 文案,此前在 i18n 表里定义但从未被引用);失败或无浏览器可打开 → 兜底弹 openInfo\n // 展示可手动复制的 URL(不再仅限 info.code 存在时才展示)。\n if (info.url) {\n const opened = openBrowser(info.url)\n if (opened) {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginOauthBrowserOpened'))\n } else {\n const lines = [i18next.t('cmd.loginOauthUrlAuthorize'), `\\n${info.url}`]\n if (info.instructions) lines.push(`\\n${info.instructions}`)\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.loginOauthTitle', { provider: provider }), lines })\n }\n }\n if (info.code) {\n const lines = [i18next.t('cmd.loginOauthCodeIntro')]\n if (info.url) lines.push(`\\n${info.url}`)\n lines.push(`\\n${i18next.t('cmd.loginOauthCode', { code: info.code })}`)\n if (info.instructions) lines.push(`\\n${info.instructions}`)\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.loginOauthTitle', { provider: provider }), lines })\n }\n },\n onProgress: (m: string) => ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), m),\n onPrompt: async (p: { message: string; placeholder?: string }) =>\n (await ctx.tui.modal.openModal({\n kind: 'input',\n title: p.message,\n placeholder: p.placeholder,\n })) ?? '',\n onManualCode: async (signal?: AbortSignal) => {\n const description: string[] = []\n if (authInfo.url) {\n description.push(i18next.t('cmd.loginOauthUrlAuthorize'))\n description.push(' ')\n description.push(authInfo.url)\n }\n if (authInfo.instructions) {\n if (description.length > 0) description.push(' ')\n description.push(authInfo.instructions)\n }\n return (\n (await ctx.tui.modal.openModal({\n kind: 'input',\n title: i18next.t('cmd.loginOauthPasteCode', { provider: provider }),\n description: description.length > 0 ? description : undefined,\n placeholder: 'code#state',\n signal,\n actionButton: authInfo.url\n ? {\n label: i18next.t('cmd.loginOauthOpenBrowserButton'),\n onPress: () => {\n const opened = openBrowser(authInfo.url!)\n if (opened) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.loginOauthBrowserOpened'),\n )\n }\n },\n }\n : undefined,\n })) ?? ''\n )\n },\n })\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.loginSuccess', { provider: provider }))\n await ctx.callbacks.refreshModelsNow((msg) => ctx.tui.info.showToast(i18next.t('cmd.titleModels'), msg))\n } catch (e) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/auth',\n status: 'error',\n summary: i18next.t('cmd.loginFailed', { msg: (e as Error).message }),\n })\n }\n}\n\n/**\n * /logout [<provider>] — Clear stored credentials for a provider.\n * Env-sourced credentials cannot be removed and are not listed.\n */\nexport async function handleLogout(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n if (args.includes('--service')) {\n return handleServiceLogout(args, ctx)\n }\n\n const authStore = ctx.app.authStore\n let provider: string | null = args[0] || null\n if (!provider) {\n const known = authStore.listProviders()\n const sources = await Promise.all(\n known.map((p: string) => authStore.getAuthSource(p)),\n )\n const removable = known.filter(\n (_: string, i: number) =>\n sources[i] === 'oauth' || sources[i] === 'api_key',\n )\n if (removable.length === 0) {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogout'), i18next.t('cmd.logoutNoStored'))\n return\n }\n provider = await new Promise<string | null>((resolve) =>\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.logoutPickProvider'),\n // RFC-292 D1:provider 展示名/id 与认证来源(订阅/密钥)拆成 label + detail 两个\n // 字段、detailPlacement:'inline' 同行左对齐渲染——此前把来源拼进 label 字符串本身\n // (`${name} [${p}] · ${src}`),与 /login 视觉不一致且无法参与列对齐。\n detailPlacement: 'inline',\n options: removable.map((p: string) => {\n const name =\n authStore.oauth.get(p)?.name ??\n getProviderDescriptor(p)?.name ??\n ctx.callbacks.PROVIDER_DISPLAY_NAMES[p] ??\n p\n const src = sources[known.indexOf(p)]\n const label = name.toLowerCase() === p.toLowerCase() ? name : `${name} [${p}]`\n // src 恒为 'oauth'|'api_key'(上游 removable 过滤已保证,见上方 filter),\n // i18next.t 不会收到 null/undefined context——若未来 removable 过滤条件放宽\n // (如纳入 'env'),需同步检查 pref.authModeLabel 是否有对应 context 分支,\n // 否则会静默产出空字符串 detail(i18next 无匹配 context 时退化到基础键插值)。\n const detail = src ? i18next.t('pref.authModeLabel', { context: src }) : undefined\n return { label, value: p, detail }\n }),\n onSelect: resolve,\n onCancel: () => resolve(null),\n }),\n )\n }\n if (!provider) return\n await authStore.logout(provider)\n ctx.tui.info.showToast(i18next.t('cmd.titleLogout'), i18next.t('cmd.logoutSuccess', { provider: provider }))\n}\n\n/**\n * RFC-052 M71a:/auth --service <url> --token <val>\n *\n * 登录 otto 远端服务拿到 user identity(独立于 provider API 凭据)。\n *\n * 流程:POST /auth/login → 本地写 ~/.otto/identity.json。\n * 仅 CLI flag 驱动,非交互式。\n */\nexport async function handleServiceLogin(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const serviceIdx = args.indexOf('--service')\n const tokenIdx = args.indexOf('--token')\n\n if (serviceIdx === -1 || tokenIdx === -1) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginUsage'),\n )\n return\n }\n\n const serviceUrl = args[serviceIdx + 1]\n const token = args[tokenIdx + 1]\n\n if (!serviceUrl || !token) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginNeedValues'),\n )\n return\n }\n\n const url = serviceUrl.replace(/\\/+$/, '')\n\n try {\n const resp = await fetch(`${url}/auth/login`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/json',\n },\n })\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({})) as Record<string, unknown>\n const err = (body as Record<string, unknown>).error ?? resp.statusText\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.serviceLoginFailed', { err: String(err) }))\n return\n }\n\n const data = (await resp.json()) as { user_id?: unknown; token?: unknown }\n\n if (typeof data.user_id !== 'string' || typeof data.token !== 'string') {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogin'), i18next.t('cmd.serviceLoginBadResponse'))\n return\n }\n\n const { OTTO_IDENTITY_PATH, isShadowModeEnabled } = await import('@x-otto/env')\n // RFC-345 §D3:影子态不写 identity.json——登录身份只在本会话内存生效、退出即失。\n if (isShadowModeEnabled()) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginSuccess', { url: url, user: data.user_id.slice(0, 12) }),\n )\n return\n }\n const { mkdir } = await import('node:fs/promises')\n const { dirname } = await import('node:path')\n const { atomicWriteFile } = await import('@x-otto/persistence')\n\n const identity = {\n userId: data.user_id,\n token: data.token,\n serviceUrl: url,\n loginAt: Date.now(),\n }\n\n // RFC-303 D7(M2 T303-16):identity.json 此前用裸 writeFile 覆盖写,非原子——\n // 改用 atomicWriteFile(temp+fsync+rename),跨进程读者永不看到半写文件。\n await mkdir(dirname(OTTO_IDENTITY_PATH), { recursive: true })\n await atomicWriteFile(OTTO_IDENTITY_PATH, JSON.stringify(identity, null, 2))\n\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginSuccess', { url: url, user: data.user_id.slice(0, 12) }),\n )\n } catch (e) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogin'),\n i18next.t('cmd.serviceLoginFailed', { err: e instanceof Error ? e.message : String(e) }),\n )\n }\n}\n\n/**\n * RFC-052 M71a:/logout --service\n *\n * 清除本地 ~/.otto/identity.json。\n */\nexport async function handleServiceLogout(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n try {\n const { OTTO_IDENTITY_PATH } = await import('@x-otto/env')\n const { unlink } = await import('node:fs/promises')\n\n await unlink(OTTO_IDENTITY_PATH)\n ctx.tui.info.showToast(i18next.t('cmd.titleLogout'), i18next.t('cmd.serviceLogoutRemoved'))\n } catch (e) {\n const code = (e as NodeJS.ErrnoException).code\n if (code === 'ENOENT') {\n ctx.tui.info.showToast(i18next.t('cmd.titleLogout'), i18next.t('cmd.serviceLogoutNotFound'))\n return\n }\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLogout'),\n i18next.t('cmd.serviceLogoutFailed', { err: e instanceof Error ? e.message : String(e) }),\n )\n }\n}\n"],"mappings":";yLAeA,MAAM,EAAuB,uBACvB,EAAwB,wBAExB,EAAkC,kCAElC,EAAgC,gCAQtC,eAAsB,EACpB,EACA,EACA,EACe,CACf,IAAM,EAAM,EAAK,GAEjB,GAAI,IAAQ,WAAa,IAAQ,YAAa,CAC5C,IAAM,EAAU,MAAM,EAAI,IAAI,eAAe,CAC7C,EAAI,MAAM,OAAS,EAAI,IAAI,cACxB,QAAQ,CACR,OAAO,GAAK,EAAE,KAAO,iBAAiB,CACtC,IAAK,IAAO,CACX,GAAI,EAAE,GACN,KAAM,EAAE,KACR,SAAU,EAAE,SACZ,IAAK,EAAE,IACP,UAAW,EAAE,WAAa,EAAE,CAC5B,cAAe,EAAE,cAClB,EAAE,CACL,IAAK,IAAM,KAAK,EACd,IAAK,IAAM,KAAM,EAAE,cAAgB,EAAE,CACnC,EAAI,MAAM,kBAAkB,IAAI,EAAG,CACvC,EAAI,UAAU,kBAAkB,CAKhC,IAAM,EAAiB,KAAK,IAAI,GAAG,EAAQ,IAAK,GAAM,EAAE,SAAS,OAAO,CAAE,EAAE,CACtE,EAAO,GAAc,EAAE,OAAO,EAAe,CAC7C,EAA6D,EAAE,CAC/D,EAAQ,EAAQ,KAAK,EAAG,IACxB,EAAE,SAAW,MACf,EAAU,GAAK,UACR,EAAQ,EAAE,yBAA0B,CAAE,SAAU,EAAI,EAAE,SAAS,CAAE,MAAO,EAAE,MAAO,KAAM,EAAE,KAAM,MAAO,EAAE,MAAO,CAAC,EAErH,EAAE,SAAW,WAAa,EAAE,SAAW,qBACzC,EAAU,GAAK,QACR,EAAQ,EAAE,4BAA6B,CAAE,SAAU,EAAI,EAAE,SAAS,CAAE,OAAQ,EAAQ,EAAE,yCAAyC,CAAE,CAAC,EAEvI,EAAE,QAAQ,SAAS,yBAAyB,EAC9C,EAAU,GAAK,UACR,EAAQ,EAAE,8BAA+B,CAAE,SAAU,EAAI,EAAE,SAAS,CAAE,OAAQ,EAAQ,EAAE,0CAA0C,CAAE,CAAC,GAE9I,EAAU,GAAK,UACR,EAAQ,EAAE,0BAA2B,CAAE,SAAU,EAAI,EAAE,SAAS,CAAE,OAAQ,EAAE,OAAQ,OAAQ,EAAE,OAAQ,CAAC,EAC9G,CAMI,EAAuB,EAAQ,KAClC,GAAM,EAAE,SAAW,WAAa,EAAE,QAAQ,SAAS,yBAAyB,CAC9E,CACD,EAAI,IAAI,MAAM,UAAU,CACtB,KAAM,OACN,MAAO,EAAQ,EAAE,kBAAkB,CACnC,MAAO,CAAC,GAAG,EAAO,GAAI,EAAQ,EAAE,uBAAuB,CAAC,CACxD,OAAQ,EACJ,CAAE,KAAM,YAAa,QAAW,CAAO,EAAgB,EAAE,CAAE,EAAI,EAAI,CACnE,IAAA,GACJ,YACD,CAAC,CACF,OAGF,GAAI,EAAK,CACP,IAAM,EAAS,EAAI,MAAM,OAAO,KAC7B,GAAM,EAAE,KAAO,GAAO,EAAE,OAAS,EACnC,CACD,GAAI,CAAC,EAAQ,CACX,IAAM,EAAO,EAAI,SAAS,IAAI,CAAG,EAAI,MAAM,IAAI,CAAC,GAAM,EAItD,GAAI,CAHkB,EAAI,MAAM,OAAO,KACpC,GAAM,EAAE,SAAS,aAAa,GAAK,EAAK,aAAa,CACvD,EACqB,oBAAoB,KAAK,EAAK,CAAE,CACpD,EAAI,IAAI,MAAM,UAAU,CACtB,KAAM,SACN,MAAO,EAAQ,EAAE,iCAAkC,CAAQ,OAAM,CAAC,CAClE,QAAS,CACP,CACE,MAAO,EAAQ,EAAE,+BAAgC,CAAQ,OAAM,CAAC,CAChE,MAAO,MACR,CACD,CAAE,MAAO,EAAQ,EAAE,eAAe,CAAE,MAAO,KAAM,CAClD,CACD,SAAW,GAAc,CACvB,GAAI,IAAM,MAAO,OACjB,IAAM,EACJ,MAAM,EAAK,kIAEb,GAAI,CACF,EAAI,IAAI,UAAU,MAAM,CACtB,MAAO,mBAAmB,IAC1B,OAAQ,EACR,UAAW,EAAI,MAAM,QAAQ,GAC7B,OAAQ,OACT,CAAC,CACF,EAAI,IAAI,KAAK,UACX,EAAQ,EAAE,qBAAqB,CAC/B,EAAQ,EAAE,6BAA8B,CAAQ,OAAM,CAAC,CACxD,OACM,EAAG,CACV,EAAI,IAAI,SAAS,YAAY,CAC3B,KAAM,eACN,QAAS,SACT,OAAQ,QACR,QAAS,EAAQ,EAAE,qBAAsB,CAAE,IAAK,aAAa,MAAQ,EAAE,QAAU,OAAO,EAAE,CAAE,CAAC,CAC9F,CAAC,GAGN,aAAgB,GACjB,CAAC,CACF,OAEF,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAQ,EAAE,uBAAwB,CAAO,MAAK,CAAC,CAAC,CACpG,OAIF,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,MAAM,EAAI,UAAU,WAAW,EAAO,GAAG,CAAC,CAI1F,EAAI,MAAM,QAAQ,OAAO,KAAO,EAAO,IAAI,GAAM,eAAe,CACpE,OAGF,EAAI,MAAM,OAAS,EAAI,IAAI,cACxB,QAAQ,CACR,OAAO,GAAK,EAAE,KAAO,iBAAiB,CACtC,IAAK,IAAO,CACX,GAAI,EAAE,GACN,KAAM,EAAE,KACR,SAAU,EAAE,SACZ,IAAK,EAAE,IACP,UAAW,EAAE,WAAa,EAAE,CAC5B,cAAe,EAAE,cAClB,EAAE,CACL,IAAM,EAAS,GAAe,EAAI,MAAM,kBAAkB,IAAI,EAAG,CAC3D,EAAU,CACd,GAAG,IAAI,IACL,EAAI,MAAM,OAAO,IAAK,GACpB,EAAI,UAAU,gBAAgB,EAAE,KAAO,GAAI,EAAE,SAAS,CACvD,CACF,CACF,CACK,EAAa,IAAI,IACrB,MAAM,QAAQ,IACZ,EAAQ,IACN,KAAO,IACL,CACE,EACA,MAAM,EAAI,IAAI,UAAU,cAAc,EAAE,CAAC,UAAY,KAAK,CAC3D,CACJ,CACF,CACF,CACK,EAAW,GAAkD,CACjE,IAAM,EAAO,EAAI,UAAU,gBAAgB,EAAE,KAAO,GAAI,EAAE,SAAS,CAC7D,EAAO,EAAW,IAAI,EAAK,CACjC,GAAI,CAAC,EAAM,MAAO,GAClB,IAAM,EAAQ,EAAQ,EAAE,qBAAsB,CAAE,QAAS,EAAM,CAAC,CAEhE,MAAO,GADQ,IAAU,EAAO,GAAK,QAClB,KAEf,EAAS,CAAC,GAAG,EAAI,MAAM,OAAO,CAAC,MAClC,EAAG,IACF,OAAO,EAAM,EAAE,GAAG,CAAC,CAAG,OAAO,EAAM,EAAE,GAAG,CAAC,EACzC,EAAE,SAAS,cAAc,EAAE,SAAS,EACpC,EAAE,KAAK,cAAc,EAAE,KAAK,CAC/B,CACK,EAAa,EAAO,OAAS,EAAI,KAAK,IAAI,GAAG,EAAO,IAAK,GAAM,EAAE,KAAK,OAAO,CAAE,EAAE,CAAG,EACpF,EAAa,GACZ,EACE,GAAM,IACT,IAAI,EAAK,KAAW,QAAQ,EAAE,CAAC,GAC/B,GAAG,KAAK,MAAM,EAAK,IAAK,CAAC,GAHb,GAKZ,EAAe,GAA+B,CAClD,IAAM,EAAU,EAAM,EAAE,GAAG,CACrB,EAAO,EAAE,KAAO,EAAI,MAAM,eAAiB,IAAM,IACjD,EAAS,EAAU,EAAE,cAAc,CACnC,EAAQ,GAAG,EAAK,GAAG,EAAE,KAAK,OAAO,EAAW,IAC/C,EAAS,KAAK,IAAW,IAKtB,EAAS,CAJG,EAAQ,EAAE,CAAC,QAAQ,OAAQ,GAAG,CAC3B,EAAE,UAAU,OAAS,EACtC,EAAE,UAAU,KAAK,IAAI,CACrB,GACoC,CAAC,OAAO,QAAQ,CAAC,KAAK,KAAK,CAEnE,MAAO,CAAE,UAAS,QAAO,OAAQ,GAAS,KAAK,IAAmB,EAI9D,EAAY,EAAI,IAAI,SAAS,IAAI,gBAAgB,EAAI,EAAE,CACvD,EAAY,IAAI,IAAI,EAAI,MAAM,OAAO,IAAK,GAAM,CAAC,EAAE,GAAI,EAAE,CAAU,CAAC,CACpE,EAAgB,EACnB,IAAK,GAAO,EAAU,IAAI,EAAG,CAAC,CAC9B,OAAQ,GAAoC,IAAM,IAAA,GAAU,CAC5D,IAAK,GAAM,CACV,GAAM,CAAE,UAAS,QAAO,UAAW,EAAY,EAAE,CACjD,MAAO,CACL,QACA,MAAO,EAAE,GACT,SAAU,CAAC,EACX,MAAO,EAAQ,EAAE,uBAAuB,CACxC,OAAQ,GAAU,IAAA,GAGlB,WAAY,EAAE,KACf,EACD,CACE,EAAgB,EAAI,MAAM,kBAAkB,OAAS,EAKrD,EAAc,CAAC,EAAI,IAAI,cAAc,0BAA0B,EAAI,EACnE,GAA0B,EAAI,IAAI,6BAA+B,EAAE,EAAE,OACrE,EAA0B,EAAE,CAC9B,GAAe,EAAyB,GAE1C,EAAc,KAAK,EAAQ,EAAE,yBAAyB,CAAC,CACvD,EAAc,KAAK,EAAQ,EAAE,4BAA6B,CAAE,MAAO,EAAwB,CAAC,CAAC,EACpF,EACT,EAAc,KAAK,EAAQ,EAAE,yBAAyB,CAAC,CAC9C,GACT,EAAc,KAAK,EAAQ,EAAE,sBAAsB,CAAC,CAElD,CAAC,GAAe,EAAyB,GAAG,EAAc,KAAK,EAAQ,EAAE,4BAA6B,CAAE,MAAO,EAAwB,CAAC,CAAC,CAC7I,EAAI,IAAI,MAAM,UAAU,CACtB,KAAM,SACN,WAAY,GACZ,WAAY,GACZ,MAAO,EACH,EAAQ,EAAE,uBAAuB,CACjC,EAAQ,EAAE,4BAA4B,CAC1C,SAAU,EAAc,OAAS,EAAI,EAAc,KAAK,QAAQ,CAAG,IAAA,GACnE,aAAc,EAAyB,EAAI,UAAY,UAIvD,gBACE,GAAe,GAAiB,EAAyB,EACrD,CACE,GAAI,EACA,CAAC,CAAE,MAAO,EAAQ,EAAE,wBAAwB,CAAE,MAAO,EAA+B,CAAC,CACrF,EACE,CACE,CAAE,MAAO,EAAQ,EAAE,gBAAgB,CAAE,MAAO,EAAsB,CAClE,CAAE,MAAO,EAAQ,EAAE,eAAe,CAAE,MAAO,EAAuB,CACnE,CACD,EAAE,CACR,GAAI,EAAyB,EACzB,CAAC,CAAE,MAAO,EAAQ,EAAE,0BAA0B,CAAE,MAAO,EAAiC,CAAC,CACzF,EAAE,CACP,CACD,IAAA,GACN,QAAS,CACP,GAAG,EACH,GAAG,EAAO,IAAK,GAAM,CACnB,GAAM,CAAE,UAAS,QAAO,UAAW,EAAY,EAAE,CACjD,MAAO,CACL,QACA,MAAO,EAAE,GACT,SAAU,CAAC,EACX,MAAO,EAAE,SACT,OAAQ,GAAU,IAAA,GAClB,WAAY,EAAE,KACf,EACD,CACH,CACD,SAAU,KAAO,IAAe,CAC9B,GAAI,IAAO,EAAsB,CAC/B,MAAM,EAAmB,EAAE,CAAE,EAAK,CAAE,OAAQ,QAAS,CAAC,CACtD,OAEF,GAAI,IAAO,EAAuB,CAChC,MAAM,EAAmB,EAAE,CAAE,EAAK,CAAE,OAAQ,MAAO,CAAC,CACpD,OAEF,GAAI,IAAO,EAAiC,CAC1C,MAAM,EAAgB,EAAE,CAAE,EAAI,CAC9B,OAEF,GAAI,IAAO,EAA+B,CAExC,GAAM,CAAE,qBAAsB,MAAM,OAAO,2BACrC,CAAE,oBAAqB,MAAM,OAAO,kCACpC,EAAkB,OAAO,QAAQ,EAAkB,CACtD,QAAQ,EAAG,KAAW,EAAM,aAAa,SAAS,WAAW,CAAC,CAC9D,KAAK,CAAC,MAAY,CAAE,MAAO,EAAO,MAAO,EAAO,EAAE,CACrD,GAAI,EAAgB,SAAW,EAAG,CAChC,MAAM,EAAiB,EAAgB,GAAI,MAAO,EAAI,CACtD,OAEF,EAAI,IAAI,MAAM,UAAU,CACtB,KAAM,SACN,MAAO,EAAQ,EAAE,uBAAuB,CACxC,QAAS,EAAgB,IAAK,IAAO,CACnC,MAAO,EAAE,MACT,MAAO,EAAE,MACV,EAAE,CACH,SAAU,KAAO,IAAc,CAC7B,MAAM,EAAiB,EAAG,EAAI,EAEhC,aAAgB,GACjB,CAAC,CACF,OAGF,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,MAAM,EAAI,UAAU,WAAW,EAAG,CAAC,CAGnF,EAAI,MAAM,QAAQ,OAAO,KAAO,GAAI,GAAM,eAAe,EAEhE,CAAC,CAUJ,eAAsB,EACpB,EACA,EACA,EACe,CACf,GAAI,EAAK,SAAS,YAAY,CAC5B,OAAO,EAAmB,EAAM,EAAI,CAGtC,IAAM,EAAY,EAAI,IAAI,UACpB,EAAW,GAIf,IAAI,QAAwB,GAC1B,EAAI,IAAI,MAAM,UAAU,CACtB,KAAM,SACN,GAAG,EACH,MAAO,EAAI,OAAS,EAAQ,EAAE,mBAAmB,CAGjD,gBAAiB,SACjB,SAAU,EACV,aAAgB,EAAQ,KAAK,CAC9B,CAAC,CACH,CAEG,EAAQ,EAAU,eAAe,CACvC,GAAI,EAAM,SAAW,EAAG,CACtB,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAQ,EAAE,uBAAuB,CAAC,CACtF,OAEF,IAAI,EAA0B,EAAK,IAAM,KAQnC,EAAe,GACnB,EAAU,MAAM,IAAI,EAAE,EAAE,MACxB,EAAsB,EAAE,EAAE,MAC1B,EAAI,UAAU,uBAAuB,IACrC,EACI,EAAiB,GAAsB,EAAY,EAAE,CAGrD,EAAe,GACnB,EAAM,EAAQ,EAAE,qBAAsB,CAAE,QAAS,EAAK,CAAC,CAAG,EAAQ,EAAE,kBAAkB,CAClF,GAAkB,EAAW,IAA+B,CAChE,IAAM,EAAS,EAAY,EAAI,CAG/B,OAAO,EAAY,EAAE,CAAC,aAAa,GAAK,EAAE,aAAa,CACnD,EACA,IAAI,EAAE,QAAQ,KAGpB,GAAI,CAAC,EAAU,CACb,IAAM,EAAU,MAAM,QAAQ,IAC5B,EAAM,IAAK,GAAc,EAAU,cAAc,EAAE,CAAC,CACrD,CACD,EAAW,MAAM,EAAQ,CACvB,MAAO,EAAQ,EAAE,wBAAwB,CACzC,QAAS,EAAM,KAAK,EAAW,KAAe,CAC5C,MAAO,EAAc,EAAE,CACvB,MAAO,EACP,OAAQ,EAAe,EAAG,EAAQ,IAAM,KAAK,CAC9C,EAAE,CACJ,CAAC,CAEJ,GAAI,CAAC,EAAU,OAEf,IAAM,EAAY,EAAU,MAAM,IAAI,EAAS,EAAE,MAAQ,EACnD,EAAe,CAAC,CAAC,EAAU,MAAM,IAAI,EAAS,CAChD,EAAS,MACb,GAAI,GAAM,OAGR,EAAS,EAAK,SAAW,SAAW,EAAe,QAAU,cACpD,EAAc,CACvB,IAAM,EAAI,MAAM,EAAQ,CACtB,MAAO,EAAQ,EAAE,sBAAuB,CAAE,KAAM,EAAW,CAAC,CAC5D,QAAS,CACP,CAAE,MAAO,EAAQ,EAAE,uBAAuB,CAAE,MAAO,QAAS,CAC5D,CAAE,MAAO,EAAQ,EAAE,qBAAqB,CAAE,MAAO,MAAO,CACzD,CACF,CAAC,CACF,GAAI,CAAC,EAAG,OACR,EAAS,EAGX,GAAI,IAAW,MAAO,CACpB,IAAM,EAAM,MAAM,EAAI,IAAI,MAAM,UAAU,CACxC,KAAM,QACN,MAAO,EAAQ,EAAE,oBAAqB,CAAY,WAAU,CAAC,CAC7D,YAAa,EAAQ,EAAE,0BAA0B,CACjD,KAAM,GACP,CAAC,CACF,GAAI,CAAC,GAAO,CAAC,EAAI,MAAM,CAAE,CACvB,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAQ,EAAE,0BAA0B,CAAC,CACzF,OAEF,MAAM,EAAU,iBAAiB,EAAU,EAAI,MAAM,CAAC,CACtD,MAAM,EAAU,MAAM,CACtB,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAQ,EAAE,oBAAqB,CAAY,WAAU,CAAC,CAAC,CAC3G,MAAM,EAAI,UAAU,iBAAkB,GAAQ,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,kBAAkB,CAAE,EAAI,CAAC,CACxG,OAGF,GAAI,CACF,IAAI,EAAmE,EAAE,CACzE,MAAM,EAAU,MAAM,EAAU,CAC9B,OAAS,GAAiE,CAYxE,GAXA,EAAW,EAWP,EAAK,IAEP,GADe,EAAY,EAAK,IAAI,CAElC,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAQ,EAAE,8BAA8B,CAAC,KACxF,CACL,IAAM,EAAQ,CAAC,EAAQ,EAAE,6BAA6B,CAAE,KAAK,EAAK,MAAM,CACpE,EAAK,cAAc,EAAM,KAAK,KAAK,EAAK,eAAe,CAC3D,EAAI,IAAI,MAAM,UAAU,CAAE,KAAM,OAAQ,MAAO,EAAQ,EAAE,sBAAuB,CAAY,WAAU,CAAC,CAAE,QAAO,CAAC,CAGrH,GAAI,EAAK,KAAM,CACb,IAAM,EAAQ,CAAC,EAAQ,EAAE,0BAA0B,CAAC,CAChD,EAAK,KAAK,EAAM,KAAK,KAAK,EAAK,MAAM,CACzC,EAAM,KAAK,KAAK,EAAQ,EAAE,qBAAsB,CAAE,KAAM,EAAK,KAAM,CAAC,GAAG,CACnE,EAAK,cAAc,EAAM,KAAK,KAAK,EAAK,eAAe,CAC3D,EAAI,IAAI,MAAM,UAAU,CAAE,KAAM,OAAQ,MAAO,EAAQ,EAAE,sBAAuB,CAAY,WAAU,CAAC,CAAE,QAAO,CAAC,GAGrH,WAAa,GAAc,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAE,CACjF,SAAU,KAAO,IACd,MAAM,EAAI,IAAI,MAAM,UAAU,CAC7B,KAAM,QACN,MAAO,EAAE,QACT,YAAa,EAAE,YAChB,CAAC,EAAK,GACT,aAAc,KAAO,IAAyB,CAC5C,IAAM,EAAwB,EAAE,CAUhC,OATI,EAAS,MACX,EAAY,KAAK,EAAQ,EAAE,6BAA6B,CAAC,CACzD,EAAY,KAAK,IAAI,CACrB,EAAY,KAAK,EAAS,IAAI,EAE5B,EAAS,eACP,EAAY,OAAS,GAAG,EAAY,KAAK,IAAI,CACjD,EAAY,KAAK,EAAS,aAAa,EAGtC,MAAM,EAAI,IAAI,MAAM,UAAU,CAC7B,KAAM,QACN,MAAO,EAAQ,EAAE,0BAA2B,CAAY,WAAU,CAAC,CACnE,YAAa,EAAY,OAAS,EAAI,EAAc,IAAA,GACpD,YAAa,aACb,SACA,aAAc,EAAS,IACnB,CACE,MAAO,EAAQ,EAAE,kCAAkC,CACnD,YAAe,CACE,EAAY,EAAS,IAAK,EAEvC,EAAI,IAAI,KAAK,UACX,EAAQ,EAAE,iBAAiB,CAC3B,EAAQ,EAAE,8BAA8B,CACzC,EAGN,CACD,IAAA,GACL,CAAC,EAAK,IAGZ,CAAC,CACF,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAQ,EAAE,mBAAoB,CAAY,WAAU,CAAC,CAAC,CAC1G,MAAM,EAAI,UAAU,iBAAkB,GAAQ,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,kBAAkB,CAAE,EAAI,CAAC,OACjG,EAAG,CACV,EAAI,IAAI,SAAS,YAAY,CAC3B,KAAM,eACN,QAAS,QACT,OAAQ,QACR,QAAS,EAAQ,EAAE,kBAAmB,CAAE,IAAM,EAAY,QAAS,CAAC,CACrE,CAAC,EAQN,eAAsB,EACpB,EACA,EACe,CACf,GAAI,EAAK,SAAS,YAAY,CAC5B,OAAO,EAAoB,EAAM,EAAI,CAGvC,IAAM,EAAY,EAAI,IAAI,UACtB,EAA0B,EAAK,IAAM,KACzC,GAAI,CAAC,EAAU,CACb,IAAM,EAAQ,EAAU,eAAe,CACjC,EAAU,MAAM,QAAQ,IAC5B,EAAM,IAAK,GAAc,EAAU,cAAc,EAAE,CAAC,CACrD,CACK,EAAY,EAAM,QACrB,EAAW,IACV,EAAQ,KAAO,SAAW,EAAQ,KAAO,UAC5C,CACD,GAAI,EAAU,SAAW,EAAG,CAC1B,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,kBAAkB,CAAE,EAAQ,EAAE,qBAAqB,CAAC,CACrF,OAEF,EAAW,MAAM,IAAI,QAAwB,GAC3C,EAAI,IAAI,MAAM,UAAU,CACtB,KAAM,SACN,MAAO,EAAQ,EAAE,yBAAyB,CAI1C,gBAAiB,SACjB,QAAS,EAAU,IAAK,GAAc,CACpC,IAAM,EACJ,EAAU,MAAM,IAAI,EAAE,EAAE,MACxB,EAAsB,EAAE,EAAE,MAC1B,EAAI,UAAU,uBAAuB,IACrC,EACI,EAAM,EAAQ,EAAM,QAAQ,EAAE,EAOpC,MAAO,CAAE,MANK,EAAK,aAAa,GAAK,EAAE,aAAa,CAAG,EAAO,GAAG,EAAK,IAAI,EAAE,GAM5D,MAAO,EAAG,OADX,EAAM,EAAQ,EAAE,qBAAsB,CAAE,QAAS,EAAK,CAAC,CAAG,IAAA,GACvC,EAClC,CACF,SAAU,EACV,aAAgB,EAAQ,KAAK,CAC9B,CAAC,CACH,CAEE,IACL,MAAM,EAAU,OAAO,EAAS,CAChC,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,kBAAkB,CAAE,EAAQ,EAAE,oBAAqB,CAAY,WAAU,CAAC,CAAC,EAW9G,eAAsB,EACpB,EACA,EACe,CACf,IAAM,EAAa,EAAK,QAAQ,YAAY,CACtC,EAAW,EAAK,QAAQ,UAAU,CAExC,GAAI,IAAe,IAAM,IAAa,GAAI,CACxC,EAAI,IAAI,KAAK,UACX,EAAQ,EAAE,iBAAiB,CAC3B,EAAQ,EAAE,wBAAwB,CACnC,CACD,OAGF,IAAM,EAAa,EAAK,EAAa,GAC/B,EAAQ,EAAK,EAAW,GAE9B,GAAI,CAAC,GAAc,CAAC,EAAO,CACzB,EAAI,IAAI,KAAK,UACX,EAAQ,EAAE,iBAAiB,CAC3B,EAAQ,EAAE,6BAA6B,CACxC,CACD,OAGF,IAAM,EAAM,EAAW,QAAQ,OAAQ,GAAG,CAE1C,GAAI,CACF,IAAM,EAAO,MAAM,MAAM,GAAG,EAAI,aAAc,CAC5C,OAAQ,OACR,QAAS,CACP,cAAe,UAAU,IACzB,OAAQ,mBACT,CACF,CAAC,CAEF,GAAI,CAAC,EAAK,GAAI,CAEZ,IAAM,GADO,MAAM,EAAK,MAAM,CAAC,WAAa,EAAE,EAAE,EACF,OAAS,EAAK,WAC5D,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAQ,EAAE,yBAA0B,CAAE,IAAK,OAAO,EAAI,CAAE,CAAC,CAAC,CAC9G,OAGF,IAAM,EAAQ,MAAM,EAAK,MAAM,CAE/B,GAAI,OAAO,EAAK,SAAY,UAAY,OAAO,EAAK,OAAU,SAAU,CACtE,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,iBAAiB,CAAE,EAAQ,EAAE,8BAA8B,CAAC,CAC7F,OAGF,GAAM,CAAE,qBAAoB,uBAAwB,MAAM,OAAO,eAEjE,GAAI,GAAqB,CAAE,CACzB,EAAI,IAAI,KAAK,UACX,EAAQ,EAAE,iBAAiB,CAC3B,EAAQ,EAAE,0BAA2B,CAAO,MAAK,KAAM,EAAK,QAAQ,MAAM,EAAG,GAAG,CAAE,CAAC,CACpF,CACD,OAEF,GAAM,CAAE,SAAU,MAAM,OAAO,oBACzB,CAAE,WAAY,MAAM,OAAO,aAC3B,CAAE,mBAAoB,MAAM,OAAO,uBAEnC,EAAW,CACf,OAAQ,EAAK,QACb,MAAO,EAAK,MACZ,WAAY,EACZ,QAAS,KAAK,KAAK,CACpB,CAID,MAAM,EAAM,EAAQ,EAAmB,CAAE,CAAE,UAAW,GAAM,CAAC,CAC7D,MAAM,EAAgB,EAAoB,KAAK,UAAU,EAAU,KAAM,EAAE,CAAC,CAE5E,EAAI,IAAI,KAAK,UACX,EAAQ,EAAE,iBAAiB,CAC3B,EAAQ,EAAE,0BAA2B,CAAO,MAAK,KAAM,EAAK,QAAQ,MAAM,EAAG,GAAG,CAAE,CAAC,CACpF,OACM,EAAG,CACV,EAAI,IAAI,KAAK,UACX,EAAQ,EAAE,iBAAiB,CAC3B,EAAQ,EAAE,yBAA0B,CAAE,IAAK,aAAa,MAAQ,EAAE,QAAU,OAAO,EAAE,CAAE,CAAC,CACzF,EASL,eAAsB,EACpB,EACA,EACe,CACf,GAAI,CACF,GAAM,CAAE,sBAAuB,MAAM,OAAO,eACtC,CAAE,UAAW,MAAM,OAAO,oBAEhC,MAAM,EAAO,EAAmB,CAChC,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,kBAAkB,CAAE,EAAQ,EAAE,2BAA2B,CAAC,OACpF,EAAG,CAEV,GADc,EAA4B,OAC7B,SAAU,CACrB,EAAI,IAAI,KAAK,UAAU,EAAQ,EAAE,kBAAkB,CAAE,EAAQ,EAAE,4BAA4B,CAAC,CAC5F,OAEF,EAAI,IAAI,KAAK,UACX,EAAQ,EAAE,kBAAkB,CAC5B,EAAQ,EAAE,0BAA2B,CAAE,IAAK,aAAa,MAAQ,EAAE,QAAU,OAAO,EAAE,CAAE,CAAC,CAC1F"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{nt as e}from"./command-dispatcher-DibEV53Q.js";import{t}from"./version-Cd8HZ7ua.js";import{resolve as n}from"node:path";var r=e({SUB_COMMAND_NAMES:()=>i,getHelpLines:()=>c,parseCLI:()=>s});const i=new Set([`run`,`serve`,`daemon`,`doctor`,`config`,`migrate`,`auth`,`observe`,`debug`,`time-travel`,`mcp`,`persistenced`,`remote-persistence-server`,`extension`,`ps`,`feedback`]),a=new Map([{flags:[`--print`,`-p`],type:`mode`,value:`print`},{flags:[`--interactive`,`-i`],type:`mode`,value:`interactive`},{flags:[`--json`],type:`mode`,value:`json`},{flags:[`--model`,`-m`],type:`string`,key:`model`},{flags:[`--config`,`-c`],type:`string`,key:`configPath`},{flags:[`--project`,`-d`],type:`path`,key:`projectDir`},{flags:[`--quiet`,`-q`],type:`boolean`,key:`quiet`},{flags:[`--verbose`],type:`boolean`,key:`verbose`},{flags:[`--max-tool-turns`],type:`number`,key:`maxToolTurns`},{flags:[`--max-tool-turn-extensions`],type:`number`,key:`maxToolTurnExtensions`},{flags:[`--prompt-token-budget`],type:`number`,key:`promptTokenBudget`},{flags:[`--prompt-time-budget`],type:`number`,key:`promptTimeBudgetMin`},{flags:[`--session-cost-budget`],type:`number`,key:`sessionCostBudget`},{flags:[`--no-stall-detection`],type:`boolean`,key:`noStallDetection`},{flags:[`--stall-window-turns`],type:`number`,key:`stallWindowTurns`},{flags:[`--stall-repeat-threshold`],type:`number`,key:`stallRepeatThreshold`},{flags:[`--continue`],type:`string`,key:`continueSession`},{flags:[`--attach`],type:`string`,key:`attachSession`},{flags:[`--force-write-lease`],type:`boolean`,key:`forceWriteLease`},{flags:[`--memory-restart-grace`],type:`boolean`,key:`memoryRestartGrace`},{flags:[`--resume-prompt`],type:`string`,key:`resumePrompt`},{flags:[`--session-url`],type:`string`,key:`sessionUrl`},{flags:[`--session-token`],type:`string`,key:`sessionToken`},{flags:[`--id`],type:`string`,key:`workspaceId`},{flags:[`--shadow`],type:`boolean`,key:`shadow`},{flags:[`--help`,`-h`],type:`boolean`,key:`showHelp`},{flags:[`--version`,`-v`],type:`action`,handler:l}].flatMap(e=>e.flags.map(t=>[t,e])));var o=class{i;constructor(e){this.args=e,this.i=0}current(){return this.args[this.i]}next(){return this.args[++this.i]}advance(){this.i++}skip(e){this.i+=e}pos(){return this.i}};function s(e){let t=e.slice(2),r=new o(t),s={mode:`interactive`,projectDir:process.cwd(),verbose:!1,quiet:!1},c=r.current(),l,u=``,d,f;c&&i.has(c)?(l=c,u=t.slice(1).join(` `),r.skip(1),l===`run`&&(s.prompt=t.slice(1).filter(e=>!e.startsWith(`-`)).join(` `),s.mode=`print`)):c&&!c.startsWith(`-`)&&(d=c,f=t.slice(1));let p=s.prompt?[s.prompt]:[];for(;r.current()!==void 0;){let e=r.current();if(e===`--inspect`){s.inspect=!0,r.advance();continue}if(e?.startsWith(`--inspect=`)){let t=Number(e.slice(10));s.inspect=Number.isInteger(t)&&t>0?t:!0,r.advance();continue}if(e?.startsWith(`--restart-generation=`)){let t=Number(e.slice(21));Number.isInteger(t)&&t>0&&(s.restartGeneration=t),r.advance();continue}let t=a.get(e);if(t){switch(r.advance(),t.type){case`boolean`:s[t.key]=!0;break;case`mode`:s.mode=t.value;break;case`string`:s[t.key]=r.current(),r.advance();break;case`number`:s[t.key]=Number(r.current()),r.advance();break;case`path`:s[t.key]=n(r.current()??process.cwd()),r.advance();break;case`action`:t.handler();break}continue}e&&!e.startsWith(`-`)&&!l&&(s.prompt===void 0&&s.mode===`interactive`&&(s.mode=`print`),p.push(e)),r.advance()}return p.length>0&&(s.prompt=p.join(` `)),{options:s,subCommand:l,subCommandArgs:u,unresolvedCommandName:d,unresolvedCommandArgs:f}}function c(){return'(otto - AI 助理命令行工具((用法:( otto [提示词] 使用提示词启动(print 模式)( otto 进入交互式 TUI 模式( otto --json "query" JSON 输出模式((选项:( -i, --interactive Interactive 模式(默认)( -p, --print Print 模式(非交互)( --json JSON 输出模式( -m, --model <id> 指定模型( -c, --config <path> 配置文件路径( -d, --project <dir> 项目目录( --verbose 输出详细日志( -q, --quiet 只输出 error 日志( --shadow 影子模式:otto 自有状态(凭据/会话/记忆/教训/配置/trace/日志)本次会话零磁盘写入(不保证 provider 服务端日志与 shell/OS 层痕迹;RFC-345)( --max-tool-turns <n> 单个用户回合最大工具回合数(默认 120)( --max-tool-turn-extensions <n> 触顶后压缩续跑的扩展次数(默认主会话 3 / 子 agent 0)( --prompt-token-budget <n> 单回合输出 token 预算(配置后轮次上限 ×4 退位安全阀;RFC-104)( --prompt-time-budget <min> 单回合墙钟时间预算(分钟)( --session-cost-budget <usd> 会话累计成本软预算(美元,超过一次性提醒不阻断;RFC-204)( --no-stall-detection 关闭早期停滞检测(默认开启;RFC-203)( --stall-window-turns <n> 停滞检测滑动窗口大小(需与 --stall-repeat-threshold 同时提供,默认 8)( --stall-repeat-threshold <n> 停滞检测同签名重复阈值(需与 --stall-window-turns 同时提供,默认 4)( --continue <id> 继续已有会话( --session-url <url> 远端会话持久化地址(如 http://host:3001/sessions)( --session-token <token> 远端会话持久化 Bearer token( --inspect[=port] 启用 Node.js inspector(默认: 9229)( -h, --help 显示帮助( --version, -v 显示版本((命令:( otto run <prompt> 执行一次性任务( otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)( otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)( otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)( otto auth status 查看各 provider 凭据来源( otto auth logout <provider> 注销并清除凭据( otto config export [--project] [-o <path>] 导出配置(RFC-219)( otto config import <file> [--dry-run] [--scope global|project] [--no-plugins] [--plugins-only] [--yes] 导入配置( otto config restore [--list] 撤销最近一次导入( otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)( otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)( otto observe <action> <id> 观测面板(summary / timeline / spans)( otto debug <action> [...] 调试面板(breakpoints / points / state)( otto time-travel <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)( otto mcp login <name> MCP server OAuth 登录( otto mcp logout <name> 登出 MCP server( otto mcp list 列出已认证的 MCP server( otto extension create <name> 生成插件骨架(命令 + 面板 + manifest,开箱即用)( otto extension install <name> [--yes] 安装首方/自定义插件(anthropic-provider / deepseek-provider / browser-use / …)( otto extension update [name] 检查并重装已过期的已安装 skill/plugin( otto persistenced --dir <path> --token <token> [...flags] 启动独立持久化服务(storage + events + sync)( otto remote-persistence-server [...flags] 启动最小化远程持久化服务(5 REST 端点)( otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)((面板命令默认连 `otto serve` 实例(http://127.0.0.1:8417,OTTO_SERVICE_URL/--remote 可覆盖)。'.split(`(`)}function l(){process.stdout.write(`otto ${t()}\n`),process.exit(0)}export{s as n,r,i as t};
|
|
3
|
+
//# sourceMappingURL=parse-cli-Dr-Fl0on.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-cli-BG6_Q42O.js","names":[],"sources":["../src/parse-cli.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\nimport type { CLIOptions, RunMode } from './types'\nimport { getVersion } from './version'\n\nexport type SubCommand = 'run' | 'serve' | 'daemon' | 'doctor' | 'config' | 'migrate' | 'auth' | 'observe' | 'debug' | 'time-travel' | 'mcp' | 'persistenced' | 'remote-persistence-server' | 'extension' | 'ps' | 'feedback'\n\nexport interface ParsedCLI {\n options: CLIOptions\n /** 未命中任何内建子命令时为 `undefined`(而非 `null` 哨兵——与 SUB_COMMAND_NAMES 的\n * 元素类型保持一致,消费方无需 `.filter(n => n !== null)` 剔除哨兵值)。 */\n subCommand: SubCommand | undefined\n subCommandArgs: string\n /**\n * RFC-112 D2:内建 `SUB_COMMAND_NAMES` 未命中的第一个位置参数(候选插件 CLI 命令名,\n * 如 `otto feishu ...` 里的 `\"feishu\"`)。`undefined` 表示第一个参数已命中内建命令,\n * 或压根没有位置参数。**不改变** `options.prompt`/`subCommand` 的既有构建逻辑——\n * 这两者仍然按\"当作 prompt\"的旧行为完整计算好,供插件命令名查无匹配时零改动回退。\n */\n unresolvedCommandName?: string\n /**\n * 与 `unresolvedCommandName` 配对:该候选名之后的**原始** args(未做 flag 过滤,含 `-h`\n * 等字面量),只在插件命令表命中该名字时使用(透传给插件可执行文件)。\n */\n unresolvedCommandArgs?: string[]\n}\n\n/** RFC-112 D2:导出供 `cli.ts` 构建插件 CLI 命令表时用作保留字集合(插件不得覆盖)。 */\nexport const SUB_COMMAND_NAMES = new Set<SubCommand>([\n 'run', 'serve', 'daemon', 'doctor', 'config', 'migrate', 'auth', 'observe', 'debug', 'time-travel', 'mcp', 'persistenced', 'remote-persistence-server', 'extension', 'ps', 'feedback',\n])\n\ntype OptionEntry =\n | { flags: string[]; type: 'boolean'; key: keyof CLIOptions }\n | { flags: string[]; type: 'mode'; value: RunMode }\n | { flags: string[]; type: 'string'; key: keyof CLIOptions }\n | { flags: string[]; type: 'number'; key: keyof CLIOptions }\n | { flags: string[]; type: 'path'; key: keyof CLIOptions }\n | { flags: string[]; type: 'action'; handler: () => void }\n\nconst OPTIONS: OptionEntry[] = [\n { flags: ['--print', '-p'], type: 'mode', value: 'print' },\n { flags: ['--interactive', '-i'], type: 'mode', value: 'interactive' },\n { flags: ['--json'], type: 'mode', value: 'json' },\n { flags: ['--model', '-m'], type: 'string', key: 'model' },\n { flags: ['--config', '-c'], type: 'string', key: 'configPath' },\n { flags: ['--project', '-d'], type: 'path', key: 'projectDir' },\n { flags: ['--quiet', '-q'], type: 'boolean', key: 'quiet' },\n { flags: ['--verbose'], type: 'boolean', key: 'verbose' },\n { flags: ['--max-tool-turns'], type: 'number', key: 'maxToolTurns' },\n { flags: ['--max-tool-turn-extensions'], type: 'number', key: 'maxToolTurnExtensions' },\n { flags: ['--prompt-token-budget'], type: 'number', key: 'promptTokenBudget' },\n { flags: ['--prompt-time-budget'], type: 'number', key: 'promptTimeBudgetMin' },\n { flags: ['--session-cost-budget'], type: 'number', key: 'sessionCostBudget' },\n { flags: ['--no-stall-detection'], type: 'boolean', key: 'noStallDetection' },\n { flags: ['--stall-window-turns'], type: 'number', key: 'stallWindowTurns' },\n { flags: ['--stall-repeat-threshold'], type: 'number', key: 'stallRepeatThreshold' },\n { flags: ['--continue'], type: 'string', key: 'continueSession' },\n { flags: ['--attach'], type: 'string', key: 'attachSession' },\n { flags: ['--force-write-lease'], type: 'boolean', key: 'forceWriteLease' },\n // RFC-115 M3(P1-1 修正):仅由 cli.ts 的内存治理触发重启 chaperone 附加,不对外文档化——\n // 区分\"内存压力触发的自动重启\"与\"用户手动 otto --continue\",前者才应起算 D5 宽限期。\n { flags: ['--memory-restart-grace'], type: 'boolean', key: 'memoryRestartGrace' },\n // RFC-115 M4:仅由 cli.ts 的内存治理触发重启 chaperone 附加,不对外文档化——重启前\n // 会话正忙时的续跑原文,子进程启动后自动重新提交为续跑轮。\n { flags: ['--resume-prompt'], type: 'string', key: 'resumePrompt' },\n { flags: ['--session-url'], type: 'string', key: 'sessionUrl' },\n { flags: ['--session-token'], type: 'string', key: 'sessionToken' },\n { flags: ['--id'], type: 'string', key: 'workspaceId' },\n { flags: ['--shadow'], type: 'boolean', key: 'shadow' },\n { flags: ['--help', '-h'], type: 'boolean', key: 'showHelp' },\n { flags: ['--version', '-v'], type: 'action', handler: printVersion },\n]\n\nconst OPTION_LOOKUP: Map<string, OptionEntry> = new Map(\n OPTIONS.flatMap(e => e.flags.map(f => [f, e] as const)),\n)\n\nclass ArgCursor {\n private i: number\n constructor(private args: string[]) {\n this.i = 0\n }\n current(): string | undefined { return this.args[this.i] }\n next(): string | undefined { return this.args[++this.i] }\n advance(): void { this.i++ }\n skip(count: number): void { this.i += count }\n pos(): number { return this.i }\n}\n\nexport function parseCLI(argv: string[]): ParsedCLI {\n const args = argv.slice(2)\n const cursor = new ArgCursor(args)\n\n const options: CLIOptions = {\n mode: 'interactive',\n projectDir: process.cwd(),\n verbose: false,\n quiet: false,\n }\n\n const first = cursor.current()\n let subCommand: SubCommand | undefined\n let subCommandArgs = ''\n let unresolvedCommandName: string | undefined\n let unresolvedCommandArgs: string[] | undefined\n if (first && SUB_COMMAND_NAMES.has(first as SubCommand)) {\n subCommand = first as SubCommand\n subCommandArgs = args.slice(1).join(' ')\n cursor.skip(1)\n\n if (subCommand === 'run') {\n options.prompt = args.slice(1).filter(a => !a.startsWith('-')).join(' ')\n options.mode = 'print'\n }\n } else if (first && !first.startsWith('-')) {\n // RFC-112 D2:第一个位置参数不是内建命令,也不以 `-` 开头(不是 flag)——\n // 记录为候选插件 CLI 命令名,供 cli.ts 查插件表二次判定;不改变下方现有的\n // \"当作 prompt\" 逻辑(该逻辑仍会照常把 first 纳入 promptParts,回退路径零改动)。\n unresolvedCommandName = first\n unresolvedCommandArgs = args.slice(1)\n }\n\n let promptParts: string[] = options.prompt ? [options.prompt] : []\n\n while (cursor.current() !== undefined) {\n const arg = cursor.current()!\n\n if (arg === '--inspect') {\n options.inspect = true\n cursor.advance()\n continue\n }\n if (arg?.startsWith('--inspect=')) {\n const port = Number(arg.slice('--inspect='.length))\n options.inspect = Number.isInteger(port) && port > 0 ? port : true\n cursor.advance()\n continue\n }\n // RFC-196 M3:重启代际计数(chaperone 重启 spawn 注入,不文档化——内部可观测标记,\n // 供 ≥3 代告警与 otto ps GEN 列展示;不驱动任何行为分支)。\n if (arg?.startsWith('--restart-generation=')) {\n const gen = Number(arg.slice('--restart-generation='.length))\n if (Number.isInteger(gen) && gen > 0) options.restartGeneration = gen\n cursor.advance()\n continue\n }\n\n const entry = OPTION_LOOKUP.get(arg)\n if (entry) {\n cursor.advance()\n switch (entry.type) {\n case 'boolean':\n ;(options as unknown as Record<string, unknown>)[entry.key] = true\n break\n case 'mode':\n options.mode = entry.value\n break\n case 'string':\n ;(options as unknown as Record<string, unknown>)[entry.key] = cursor.current()\n cursor.advance()\n break\n case 'number':\n ;(options as unknown as Record<string, unknown>)[entry.key] = Number(cursor.current())\n cursor.advance()\n break\n case 'path':\n ;(options as unknown as Record<string, unknown>)[entry.key] = resolve(cursor.current() ?? process.cwd())\n cursor.advance()\n break\n case 'action':\n entry.handler()\n break\n }\n continue\n }\n\n if (arg && !arg.startsWith('-') && !subCommand) {\n if (options.prompt === undefined && options.mode === 'interactive') {\n options.mode = 'print'\n }\n promptParts.push(arg)\n }\n cursor.advance()\n }\n\n if (promptParts.length > 0) {\n options.prompt = promptParts.join(' ')\n }\n\n return { options, subCommand, subCommandArgs, unresolvedCommandName, unresolvedCommandArgs }\n}\n\n/** 帮助文本行(供 plain-text printHelp 与 TUI InfoDialog 共用)。 */\nexport function getHelpLines(): string[] {\n return [\n '',\n 'otto - AI 助理命令行工具',\n '',\n '用法:',\n ' otto [提示词] 使用提示词启动(print 模式)',\n ' otto 进入交互式 TUI 模式',\n ' otto --json \"query\" JSON 输出模式',\n '',\n '选项:',\n ' -i, --interactive Interactive 模式(默认)',\n ' -p, --print Print 模式(非交互)',\n ' --json JSON 输出模式',\n ' -m, --model <id> 指定模型',\n ' -c, --config <path> 配置文件路径',\n ' -d, --project <dir> 项目目录',\n ' --verbose 输出详细日志',\n ' -q, --quiet 只输出 error 日志',\n ' --shadow 影子模式:otto 自有状态(凭据/会话/记忆/教训/配置/trace/日志)本次会话零磁盘写入(不保证 provider 服务端日志与 shell/OS 层痕迹;RFC-345)',\n ' --max-tool-turns <n> 单个用户回合最大工具回合数(默认 120)',\n ' --max-tool-turn-extensions <n> 触顶后压缩续跑的扩展次数(默认主会话 3 / 子 agent 0)',\n ' --prompt-token-budget <n> 单回合输出 token 预算(配置后轮次上限 ×4 退位安全阀;RFC-104)',\n ' --prompt-time-budget <min> 单回合墙钟时间预算(分钟)',\n ' --session-cost-budget <usd> 会话累计成本软预算(美元,超过一次性提醒不阻断;RFC-204)',\n ' --no-stall-detection 关闭早期停滞检测(默认开启;RFC-203)',\n ' --stall-window-turns <n> 停滞检测滑动窗口大小(需与 --stall-repeat-threshold 同时提供,默认 8)',\n ' --stall-repeat-threshold <n> 停滞检测同签名重复阈值(需与 --stall-window-turns 同时提供,默认 4)',\n ' --continue <id> 继续已有会话',\n ' --session-url <url> 远端会话持久化地址(如 http://host:3001/sessions)',\n ' --session-token <token> 远端会话持久化 Bearer token',\n ' --inspect[=port] 启用 Node.js inspector(默认: 9229)',\n ' -h, --help 显示帮助',\n ' --version, -v 显示版本',\n '',\n '命令:',\n ' otto run <prompt> 执行一次性任务',\n ' otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)',\n ' otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)',\n ' otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)',\n ' otto auth status 查看各 provider 凭据来源',\n ' otto auth logout <provider> 注销并清除凭据',\n ' otto config export [--project] [-o <path>] 导出配置(RFC-219)',\n ' otto config import <file> [--dry-run] [--scope global|project] [--no-plugins] [--plugins-only] [--yes] 导入配置',\n ' otto config restore [--list] 撤销最近一次导入',\n ' otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)',\n ' otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)',\n ' otto observe <action> <id> 观测面板(summary / timeline / spans)',\n ' otto debug <action> [...] 调试面板(breakpoints / points / state)',\n ' otto time-travel <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)',\n ' otto mcp login <name> MCP server OAuth 登录',\n ' otto mcp logout <name> 登出 MCP server',\n ' otto mcp list 列出已认证的 MCP server',\n ' otto extension create <name> 生成插件骨架(命令 + 面板 + manifest,开箱即用)',\n ' otto extension install <name> [--yes] 安装首方/自定义插件(anthropic-provider / deepseek-provider / browser-use / …)',\n ' otto extension update [name] 检查并重装已过期的已安装 skill/plugin',\n ' otto persistenced --dir <path> --token <token> [...flags] 启动独立持久化服务(storage + events + sync)',\n ' otto remote-persistence-server [...flags] 启动最小化远程持久化服务(5 REST 端点)',\n ' otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)',\n '',\n '面板命令默认连 `otto serve` 实例(http://127.0.0.1:8417,OTTO_SERVICE_URL/--remote 可覆盖)。',\n ]\n}\n\nfunction printVersion(): void {\n process.stdout.write(`otto ${getVersion()}\\n`)\n process.exit(0)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parse-cli-Dr-Fl0on.js","names":[],"sources":["../src/parse-cli.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\nimport type { CLIOptions, RunMode } from './types'\nimport { getVersion } from './version'\n\nexport type SubCommand = 'run' | 'serve' | 'daemon' | 'doctor' | 'config' | 'migrate' | 'auth' | 'observe' | 'debug' | 'time-travel' | 'mcp' | 'persistenced' | 'remote-persistence-server' | 'extension' | 'ps' | 'feedback'\n\nexport interface ParsedCLI {\n options: CLIOptions\n /** 未命中任何内建子命令时为 `undefined`(而非 `null` 哨兵——与 SUB_COMMAND_NAMES 的\n * 元素类型保持一致,消费方无需 `.filter(n => n !== null)` 剔除哨兵值)。 */\n subCommand: SubCommand | undefined\n subCommandArgs: string\n /**\n * RFC-112 D2:内建 `SUB_COMMAND_NAMES` 未命中的第一个位置参数(候选插件 CLI 命令名,\n * 如 `otto feishu ...` 里的 `\"feishu\"`)。`undefined` 表示第一个参数已命中内建命令,\n * 或压根没有位置参数。**不改变** `options.prompt`/`subCommand` 的既有构建逻辑——\n * 这两者仍然按\"当作 prompt\"的旧行为完整计算好,供插件命令名查无匹配时零改动回退。\n */\n unresolvedCommandName?: string\n /**\n * 与 `unresolvedCommandName` 配对:该候选名之后的**原始** args(未做 flag 过滤,含 `-h`\n * 等字面量),只在插件命令表命中该名字时使用(透传给插件可执行文件)。\n */\n unresolvedCommandArgs?: string[]\n}\n\n/** RFC-112 D2:导出供 `cli.ts` 构建插件 CLI 命令表时用作保留字集合(插件不得覆盖)。 */\nexport const SUB_COMMAND_NAMES = new Set<SubCommand>([\n 'run', 'serve', 'daemon', 'doctor', 'config', 'migrate', 'auth', 'observe', 'debug', 'time-travel', 'mcp', 'persistenced', 'remote-persistence-server', 'extension', 'ps', 'feedback',\n])\n\ntype OptionEntry =\n | { flags: string[]; type: 'boolean'; key: keyof CLIOptions }\n | { flags: string[]; type: 'mode'; value: RunMode }\n | { flags: string[]; type: 'string'; key: keyof CLIOptions }\n | { flags: string[]; type: 'number'; key: keyof CLIOptions }\n | { flags: string[]; type: 'path'; key: keyof CLIOptions }\n | { flags: string[]; type: 'action'; handler: () => void }\n\nconst OPTIONS: OptionEntry[] = [\n { flags: ['--print', '-p'], type: 'mode', value: 'print' },\n { flags: ['--interactive', '-i'], type: 'mode', value: 'interactive' },\n { flags: ['--json'], type: 'mode', value: 'json' },\n { flags: ['--model', '-m'], type: 'string', key: 'model' },\n { flags: ['--config', '-c'], type: 'string', key: 'configPath' },\n { flags: ['--project', '-d'], type: 'path', key: 'projectDir' },\n { flags: ['--quiet', '-q'], type: 'boolean', key: 'quiet' },\n { flags: ['--verbose'], type: 'boolean', key: 'verbose' },\n { flags: ['--max-tool-turns'], type: 'number', key: 'maxToolTurns' },\n { flags: ['--max-tool-turn-extensions'], type: 'number', key: 'maxToolTurnExtensions' },\n { flags: ['--prompt-token-budget'], type: 'number', key: 'promptTokenBudget' },\n { flags: ['--prompt-time-budget'], type: 'number', key: 'promptTimeBudgetMin' },\n { flags: ['--session-cost-budget'], type: 'number', key: 'sessionCostBudget' },\n { flags: ['--no-stall-detection'], type: 'boolean', key: 'noStallDetection' },\n { flags: ['--stall-window-turns'], type: 'number', key: 'stallWindowTurns' },\n { flags: ['--stall-repeat-threshold'], type: 'number', key: 'stallRepeatThreshold' },\n { flags: ['--continue'], type: 'string', key: 'continueSession' },\n { flags: ['--attach'], type: 'string', key: 'attachSession' },\n { flags: ['--force-write-lease'], type: 'boolean', key: 'forceWriteLease' },\n // RFC-115 M3(P1-1 修正):仅由 cli.ts 的内存治理触发重启 chaperone 附加,不对外文档化——\n // 区分\"内存压力触发的自动重启\"与\"用户手动 otto --continue\",前者才应起算 D5 宽限期。\n { flags: ['--memory-restart-grace'], type: 'boolean', key: 'memoryRestartGrace' },\n // RFC-115 M4:仅由 cli.ts 的内存治理触发重启 chaperone 附加,不对外文档化——重启前\n // 会话正忙时的续跑原文,子进程启动后自动重新提交为续跑轮。\n { flags: ['--resume-prompt'], type: 'string', key: 'resumePrompt' },\n { flags: ['--session-url'], type: 'string', key: 'sessionUrl' },\n { flags: ['--session-token'], type: 'string', key: 'sessionToken' },\n { flags: ['--id'], type: 'string', key: 'workspaceId' },\n { flags: ['--shadow'], type: 'boolean', key: 'shadow' },\n { flags: ['--help', '-h'], type: 'boolean', key: 'showHelp' },\n { flags: ['--version', '-v'], type: 'action', handler: printVersion },\n]\n\nconst OPTION_LOOKUP: Map<string, OptionEntry> = new Map(\n OPTIONS.flatMap(e => e.flags.map(f => [f, e] as const)),\n)\n\nclass ArgCursor {\n private i: number\n constructor(private args: string[]) {\n this.i = 0\n }\n current(): string | undefined { return this.args[this.i] }\n next(): string | undefined { return this.args[++this.i] }\n advance(): void { this.i++ }\n skip(count: number): void { this.i += count }\n pos(): number { return this.i }\n}\n\nexport function parseCLI(argv: string[]): ParsedCLI {\n const args = argv.slice(2)\n const cursor = new ArgCursor(args)\n\n const options: CLIOptions = {\n mode: 'interactive',\n projectDir: process.cwd(),\n verbose: false,\n quiet: false,\n }\n\n const first = cursor.current()\n let subCommand: SubCommand | undefined\n let subCommandArgs = ''\n let unresolvedCommandName: string | undefined\n let unresolvedCommandArgs: string[] | undefined\n if (first && SUB_COMMAND_NAMES.has(first as SubCommand)) {\n subCommand = first as SubCommand\n subCommandArgs = args.slice(1).join(' ')\n cursor.skip(1)\n\n if (subCommand === 'run') {\n options.prompt = args.slice(1).filter(a => !a.startsWith('-')).join(' ')\n options.mode = 'print'\n }\n } else if (first && !first.startsWith('-')) {\n // RFC-112 D2:第一个位置参数不是内建命令,也不以 `-` 开头(不是 flag)——\n // 记录为候选插件 CLI 命令名,供 cli.ts 查插件表二次判定;不改变下方现有的\n // \"当作 prompt\" 逻辑(该逻辑仍会照常把 first 纳入 promptParts,回退路径零改动)。\n unresolvedCommandName = first\n unresolvedCommandArgs = args.slice(1)\n }\n\n let promptParts: string[] = options.prompt ? [options.prompt] : []\n\n while (cursor.current() !== undefined) {\n const arg = cursor.current()!\n\n if (arg === '--inspect') {\n options.inspect = true\n cursor.advance()\n continue\n }\n if (arg?.startsWith('--inspect=')) {\n const port = Number(arg.slice('--inspect='.length))\n options.inspect = Number.isInteger(port) && port > 0 ? port : true\n cursor.advance()\n continue\n }\n // RFC-196 M3:重启代际计数(chaperone 重启 spawn 注入,不文档化——内部可观测标记,\n // 供 ≥3 代告警与 otto ps GEN 列展示;不驱动任何行为分支)。\n if (arg?.startsWith('--restart-generation=')) {\n const gen = Number(arg.slice('--restart-generation='.length))\n if (Number.isInteger(gen) && gen > 0) options.restartGeneration = gen\n cursor.advance()\n continue\n }\n\n const entry = OPTION_LOOKUP.get(arg)\n if (entry) {\n cursor.advance()\n switch (entry.type) {\n case 'boolean':\n ;(options as unknown as Record<string, unknown>)[entry.key] = true\n break\n case 'mode':\n options.mode = entry.value\n break\n case 'string':\n ;(options as unknown as Record<string, unknown>)[entry.key] = cursor.current()\n cursor.advance()\n break\n case 'number':\n ;(options as unknown as Record<string, unknown>)[entry.key] = Number(cursor.current())\n cursor.advance()\n break\n case 'path':\n ;(options as unknown as Record<string, unknown>)[entry.key] = resolve(cursor.current() ?? process.cwd())\n cursor.advance()\n break\n case 'action':\n entry.handler()\n break\n }\n continue\n }\n\n if (arg && !arg.startsWith('-') && !subCommand) {\n if (options.prompt === undefined && options.mode === 'interactive') {\n options.mode = 'print'\n }\n promptParts.push(arg)\n }\n cursor.advance()\n }\n\n if (promptParts.length > 0) {\n options.prompt = promptParts.join(' ')\n }\n\n return { options, subCommand, subCommandArgs, unresolvedCommandName, unresolvedCommandArgs }\n}\n\n/** 帮助文本行(供 plain-text printHelp 与 TUI InfoDialog 共用)。 */\nexport function getHelpLines(): string[] {\n return [\n '',\n 'otto - AI 助理命令行工具',\n '',\n '用法:',\n ' otto [提示词] 使用提示词启动(print 模式)',\n ' otto 进入交互式 TUI 模式',\n ' otto --json \"query\" JSON 输出模式',\n '',\n '选项:',\n ' -i, --interactive Interactive 模式(默认)',\n ' -p, --print Print 模式(非交互)',\n ' --json JSON 输出模式',\n ' -m, --model <id> 指定模型',\n ' -c, --config <path> 配置文件路径',\n ' -d, --project <dir> 项目目录',\n ' --verbose 输出详细日志',\n ' -q, --quiet 只输出 error 日志',\n ' --shadow 影子模式:otto 自有状态(凭据/会话/记忆/教训/配置/trace/日志)本次会话零磁盘写入(不保证 provider 服务端日志与 shell/OS 层痕迹;RFC-345)',\n ' --max-tool-turns <n> 单个用户回合最大工具回合数(默认 120)',\n ' --max-tool-turn-extensions <n> 触顶后压缩续跑的扩展次数(默认主会话 3 / 子 agent 0)',\n ' --prompt-token-budget <n> 单回合输出 token 预算(配置后轮次上限 ×4 退位安全阀;RFC-104)',\n ' --prompt-time-budget <min> 单回合墙钟时间预算(分钟)',\n ' --session-cost-budget <usd> 会话累计成本软预算(美元,超过一次性提醒不阻断;RFC-204)',\n ' --no-stall-detection 关闭早期停滞检测(默认开启;RFC-203)',\n ' --stall-window-turns <n> 停滞检测滑动窗口大小(需与 --stall-repeat-threshold 同时提供,默认 8)',\n ' --stall-repeat-threshold <n> 停滞检测同签名重复阈值(需与 --stall-window-turns 同时提供,默认 4)',\n ' --continue <id> 继续已有会话',\n ' --session-url <url> 远端会话持久化地址(如 http://host:3001/sessions)',\n ' --session-token <token> 远端会话持久化 Bearer token',\n ' --inspect[=port] 启用 Node.js inspector(默认: 9229)',\n ' -h, --help 显示帮助',\n ' --version, -v 显示版本',\n '',\n '命令:',\n ' otto run <prompt> 执行一次性任务',\n ' otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)',\n ' otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)',\n ' otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)',\n ' otto auth status 查看各 provider 凭据来源',\n ' otto auth logout <provider> 注销并清除凭据',\n ' otto config export [--project] [-o <path>] 导出配置(RFC-219)',\n ' otto config import <file> [--dry-run] [--scope global|project] [--no-plugins] [--plugins-only] [--yes] 导入配置',\n ' otto config restore [--list] 撤销最近一次导入',\n ' otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)',\n ' otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)',\n ' otto observe <action> <id> 观测面板(summary / timeline / spans)',\n ' otto debug <action> [...] 调试面板(breakpoints / points / state)',\n ' otto time-travel <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)',\n ' otto mcp login <name> MCP server OAuth 登录',\n ' otto mcp logout <name> 登出 MCP server',\n ' otto mcp list 列出已认证的 MCP server',\n ' otto extension create <name> 生成插件骨架(命令 + 面板 + manifest,开箱即用)',\n ' otto extension install <name> [--yes] 安装首方/自定义插件(anthropic-provider / deepseek-provider / browser-use / …)',\n ' otto extension update [name] 检查并重装已过期的已安装 skill/plugin',\n ' otto persistenced --dir <path> --token <token> [...flags] 启动独立持久化服务(storage + events + sync)',\n ' otto remote-persistence-server [...flags] 启动最小化远程持久化服务(5 REST 端点)',\n ' otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)',\n '',\n '面板命令默认连 `otto serve` 实例(http://127.0.0.1:8417,OTTO_SERVICE_URL/--remote 可覆盖)。',\n ]\n}\n\nfunction printVersion(): void {\n process.stdout.write(`otto ${getVersion()}\\n`)\n process.exit(0)\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{n as e}from"./build-id-
|
|
3
|
-
//# sourceMappingURL=version-
|
|
2
|
+
import{n as e}from"./build-id-CIFJCmlj.js";import{createRequire as t}from"node:module";const n=t(import.meta.url);function r(){return`${n(`../package.json`).version??`0.0.0`} (build ${e})`}export{r as t};
|
|
3
|
+
//# sourceMappingURL=version-Cd8HZ7ua.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-
|
|
1
|
+
{"version":3,"file":"version-Cd8HZ7ua.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["import { createRequire } from 'node:module'\n\nimport { BUILD_ID } from './build-id'\n\nconst require = createRequire(import.meta.url)\n\n/**\n * 版本号 + build ID。\n * 格式:`0.1.0 (build 2072)`。build ID = git rev-list --count HEAD,随每次构建自增。\n */\nexport function getVersion(): string {\n const pkg = require('../package.json') as { version?: string }\n const ver = pkg.version ?? '0.0.0'\n return `${ver} (build ${BUILD_ID})`\n}\n"],"mappings":";uFAIA,MAAM,EAAU,EAAc,OAAO,KAAK,IAAI,CAM9C,SAAgB,GAAqB,CAGnC,MAAO,GAFK,EAAQ,kBAAkB,CACtB,SAAW,QACb,UAAU,EAAS"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x-otto/cli",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.16-debug",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@x-otto/shared": "0.1.0-alpha.1",
|
|
37
37
|
"@x-otto/tools": "0.0.1-alpha.0",
|
|
38
38
|
"@x-otto/trace-view": "0.0.1-alpha.0",
|
|
39
|
-
"@x-otto/tui": "0.0.1-alpha.
|
|
39
|
+
"@x-otto/tui": "0.0.1-alpha.7",
|
|
40
40
|
"react": "^19.2.4"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"build-id-CxPaemCF.js","names":[],"sources":["../src/build-id.ts"],"sourcesContent":["// auto-generated by scripts/gen-build-id.mjs — DO NOT EDIT\nexport const BUILD_ID = 4624\nexport const BUILD_BRANCH = 'dev'\nexport const BUILD_HASH = 'e7a1d7b3a'\n"],"mappings":";AACA,MAAa,EAAW,KAEX,EAAa"}
|