@snelusha/noto 2.0.0-beta.1 → 2.0.0-beta.3

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.
Files changed (3) hide show
  1. package/README.md +9 -5
  2. package/dist/index.js +19 -19
  3. package/package.json +13 -13
package/README.md CHANGED
@@ -104,21 +104,25 @@ Specify a model to use (overrides config file and environment variable):
104
104
  noto --model gemini-2.5-flash
105
105
  ```
106
106
 
107
- Retrieve the previously generated commit message:
107
+ Retrieve and commit with the previously generated commit message (default behavior):
108
108
 
109
109
  ```bash
110
110
  noto prev
111
111
  ```
112
112
 
113
- Amend the previously generated commit message:
113
+ Preview the previously generated commit message without committing:
114
114
 
115
115
  ```bash
116
- noto prev --amend --edit # or simply: noto prev --amend -e
116
+ noto prev --preview # or simply noto prev -p
117
117
  ```
118
118
 
119
- > Note: When using the `--amend` flag with the noto prev command, the `--edit` (`-e`) flag is also required. This combination will allow you to modify (amend) the previous commit message before applying it.
119
+ Amend the last commit with the previously generated commit message:
120
120
 
121
- Note: `--preview` and `--copy` can also be used with the `noto prev` command. `--edit` applies only when using `noto prev --amend`.
121
+ ```bash
122
+ noto prev --amend
123
+ ```
124
+
125
+ Note: `--preview` and `--copy` can also be used with the `noto prev` command. When using `--preview`, the message is shown without prompting for editing. Without `--preview`, the command will prompt you to edit the message before committing (or amending).
122
126
 
123
127
  Switch between branches in your git repo with an interactive prompt:
124
128
 
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import{createCli as Ft}from"trpc-cli";var G="@snelusha/noto",C="2.0.0-beta.1";import bt from"node:fs/promises";import{initTRPC as vt}from"@trpc/server";import*as U from"@clack/prompts";import b from"picocolors";import pe from"dedent";import rt from"simple-git";var k=rt(),Y=async()=>{return k.checkIsRepo()},H=async()=>{try{return await k.revparse(["--show-toplevel"])}catch{return null}};var ye=async(o=20,t=!1)=>{try{let a=t?{maxCount:o,"--no-merges":null}:{maxCount:o};return(await k.log(a)).all.map((r)=>r.message)}catch{return null}};var we=async()=>{try{return k.diff(["--cached","--",":!*.lock"])}catch{return null}},L=async(o,t)=>{try{let a=t?{"--amend":null}:void 0,{summary:{changes:e}}=await k.commit(o,void 0,a);return Boolean(e)}catch{return!1}},be=async()=>{try{let o=await k.push();return o.update||o.pushed&&o.pushed.length>0}catch{return!1}},ve=async()=>{try{return(await k.branch()).current}catch{return null}},xe=async(o)=>{try{let t=await k.branch();return o?t.all:Object.keys(t.branches).filter((a)=>!a.startsWith("remotes/"))}catch{return null}},ne=async(o)=>{try{return await k.checkout(o,{}),!0}catch{return!1}},se=async(o)=>{try{return await k.checkoutLocalBranch(o),!0}catch{return!1}};import*as N from"node:fs";import*as j from"node:path";import*as Me from"node:child_process";import ce from"node:process";import it from"semver";var B=it.prerelease(C)!==null;async function ke(){let o=ce.argv[1];if(!o)return{packageManager:"unknown",isGlobal:!1};let t=ce.cwd();try{let a=N.realpathSync(o).replace(/\\/g,"/"),e=t.replace(/\\/g,"/");if(await Y()&&e&&a.startsWith(e)&&!a.includes("/node_modules/"))return{packageManager:"unknown",isGlobal:!1,updateMessage:'Running from a local git clone. Please update with "git pull".'};if(a.includes("/.npm/_npx")||a.includes("/npm/_npx"))return{packageManager:"npx",isGlobal:!1,updateMessage:"Running via npx, update not applicable."};if(ce.platform==="darwin")try{return Me.execSync('brew list -1 | grep -q "^noto$"',{stdio:"ignore"}),{packageManager:"homebrew",isGlobal:!0,updateMessage:'Installed via Homebrew. Please update with "brew upgrade".'}}catch{}if(a.includes("/pnpm/dlx")||a.includes("/pnpm-cache/dlx"))return{packageManager:"pnpx",isGlobal:!1,updateMessage:"Running via pnpx, update not applicable."};if(a.includes("/pnpm/global"))return{packageManager:"pnpm",isGlobal:!0,updateCommand:"pnpm add -g @snelusha/noto@latest",updateMessage:"Please run pnpm add -g @snelusha/noto@latest to update"};if(a.includes("/.yarn/global"))return{packageManager:"yarn",isGlobal:!0,updateCommand:"yarn global add @snelusha/noto@latest",updateMessage:"Please run yarn global add @snelusha/noto@latest to update"};if(a.includes("/bunx"))return{packageManager:"bunx",isGlobal:!1,updateMessage:"Running via bunx, update not applicable."};if(a.includes("/.bun/install/global"))return{packageManager:"bun",isGlobal:!0,updateCommand:"bun add -g @snelusha/noto@latest",updateMessage:"Please run bun add -g @snelusha/noto@latest to update"};if(e&&a.startsWith(`${e}/node_modules`)){let n="npm";if(N.existsSync(j.join(t,"yarn.lock")))n="yarn";else if(N.existsSync(j.join(t,"pnpm-lock.yaml")))n="pnpm";else if(N.existsSync(j.join(t,"bun.lockb"))||N.existsSync(j.join(t,"bun.lock")))n="bun";return{packageManager:n,isGlobal:!1,updateMessage:"Locally installed. Please update via your project's package.json."}}let s="npm install -g @snelusha/noto@latest";return{packageManager:"npm",isGlobal:!0,updateCommand:s,updateMessage:`Please run ${s} to update`}}catch{return{packageManager:"unknown",isGlobal:!1}}}import ct from"node:os";import Ce from"node:path";import{z}from"zod";import X from"node:fs/promises";import nt from"node:path";function J(o){let{schema:t,path:a}=o;return class e{static storagePath=a;static storage={};static async load(){try{await X.access(e.storagePath);let r=await X.readFile(e.storagePath,"utf-8"),s=r?JSON.parse(r):{},n=t.safeParse(s);e.storage=n.success?n.data:{}}catch{e.storage={}}return e.storage}static async save(){try{let r=nt.dirname(e.storagePath);await X.mkdir(r,{recursive:!0});let s=JSON.stringify(e.storage,null,2);await X.writeFile(e.storagePath,s,"utf-8")}catch{}}static async update(r){try{await e.load();let s=await r(e.storage),n=t.safeParse(s);if(n.success)e.storage=n.data,await e.save()}catch{}return e.storage}static async get(){return await e.load(),JSON.parse(JSON.stringify(e.storage))}static async clear(){e.storage={},await e.save()}static get path(){return e.storagePath}static set path(r){e.storagePath=r}static get raw(){return e.storage}}}import{z as st}from"zod";var Pe=st.enum(["gemini-2.5-flash","gemini-2.5-flash-lite","gemini-2.5-pro","gemini-3-flash-preview","gemini-3-pro-preview"]);var lt=z.object({llm:z.object({apiKey:z.string().optional(),model:Pe.optional().or(z.string())}).optional(),lastGeneratedMessage:z.string().optional()}),m=J({path:Ce.resolve(Ce.join(ct.homedir(),".config","noto"),".notorc"),schema:lt});async function Ae(){try{let o=await m.get();await m.update(()=>o)}catch{}}import pt from"node:fs/promises";import D from"node:path";import{fileURLToPath as mt}from"node:url";var Re=(o)=>o instanceof URL?mt(o):o;async function Ie(o,t={}){let a=D.resolve(Re(t.cwd??"")),{root:e}=D.parse(a);t.stopAt=D.resolve(Re(t.stopAt??e));let r=D.isAbsolute(o);while(a){let s=r?o:D.join(a,o);try{let n=await pt.stat(s);if(t.type===void 0||t.type==="file"&&n.isFile()||t.type==="directory"&&n.isDirectory())return s}catch{}if(a===t.stopAt||a===e)break;a=D.dirname(a)}}var Q=async()=>{let o=await H();return await Ie(".noto/commit-prompt.md",{stopAt:o||process.cwd(),type:"file"})};import*as Ue from"@clack/prompts";import Ne from"picocolors";import yt from"dedent";import w from"semver";import q from"latest-version";import dt from"node:os";import $e from"node:path";import{z as R}from"zod";var ut=R.object({commitGenerationCache:R.record(R.string(),R.string()).optional(),update:R.object({timestamp:R.number(),current:R.string(),latest:R.string()}).optional()}),I=J({path:$e.resolve($e.join(dt.homedir(),".cache","noto"),"cache"),schema:ut});var gt=86400000;function ft(o,t){if(!o||!t)return o||t||null;let a=w.parse(t),e=w.parse(o);if(!a||!e)return o||t||null;return w.gt(o,t)?o:t}async function le(o=!1,t=!1,a="auto"){let e=(await I.get()).update;if(!t&&e){if(w.valid(e.current)&&w.valid(e.latest)){let s=w.gte(e.current,e.latest),n=Date.now()-e.timestamp<gt;if(s||n)return{latest:e.latest,current:e.current,timestamp:e.timestamp}}}try{let r;if(a==="stable")r=await q(G);else if(a==="beta")r=await q(G,{version:"beta"});else r=B?ft(...await Promise.all([q(G,{version:"beta"}),q(G)]))??C:await q(G);let s={latest:r,current:C,timestamp:Date.now()};if(o)await ht(s);return s}catch{if(e)return{latest:e.latest,current:e.current,timestamp:e.timestamp};return{latest:C,current:C,timestamp:Date.now()}}}async function ht(o){if(!o)return;await I.update((t)=>({...t,update:o&&{timestamp:o.timestamp,latest:o.latest,current:o.current}}))}async function Z(o=!1,t=!1,a="auto"){let e=await le(o,t,a);if(w.valid(e.current)&&w.valid(e.latest)){if(B&&a==="stable"&&e.current!==e.latest)return e;if(!w.gte(e.current,e.latest))return e}return null}async function wt(){let o=await Z();if(o)Ue.log.warn(yt`A new version of noto is available: ${Ne.dim(o.current)} → ${Ne.green(o.latest)}
3
- Please run \`noto upgrade\` to update`.trim())}var i=async(o,t=!0)=>{if(t)await wt();await new Promise((a)=>setTimeout(a,1)),console.log(),process.exit(o)};var T=vt.meta().create({defaultMeta:{intro:!0,authRequired:!0,repoRequired:!0,diffRequired:!1,promptRequired:!1}}),Te=T.middleware(async(o)=>{let{meta:t,next:a}=o,e=await m.get(),r=process.env.NOTO_API_KEY||e.llm?.apiKey;if(t?.authRequired&&!r)return U.log.error(pe`${b.red("noto api key is missing.")}
4
- ${b.dim(`run ${b.cyan("`noto config key`")} to set it up.`)}`),await i(1);return a()}),Ee=T.middleware(async(o)=>{let{meta:t,next:a}=o,e=await Y();if(t?.repoRequired&&!e)return U.log.error(pe`${b.red("no git repository found in cwd.")}
5
- ${b.dim(`run ${b.cyan("`git init`")} to initialize a new repository.`)}`),await i(1);let r=e&&await we();if(t?.diffRequired&&!r)return U.log.error(pe`${b.red("no staged changes found.")}
6
- ${b.dim(`run ${b.cyan("`git add <file>`")} or ${b.cyan("`git add .`")} to stage changes.`)}`),await i(1);let s=null;if(t?.promptRequired){let n=await Q();if(n)try{s=await bt.readFile(n,"utf-8")}catch{}}return a({ctx:{noto:{prompt:s},git:{isRepository:e,diff:r}}})}),x=T.procedure.use((o)=>{let{meta:t,next:a}=o;if(t?.intro)if(console.log(),B)U.intro(`${b.bgGreen(b.black(" @snelusha/noto [Prerelease] "))}`);else U.intro(`${b.bgCyan(b.black(" @snelusha/noto "))}`);return a()}),Go=x.use(Te),ee=x.use(Ee),te=x.use(Te).use(Ee);import{z as F}from"zod";import*as c from"@clack/prompts";import u from"picocolors";import xt from"clipboardy";var Se=ee.meta({description:"checkout a branch"}).input(F.object({copy:F.boolean().meta({description:"copy the selected branch to clipboard",alias:"c"}),create:F.union([F.boolean(),F.string()]).optional().meta({description:"create a new branch",alias:"b"}),branch:F.string().optional().meta({positional:!0})})).mutation(async(o)=>{let{input:t}=o,a=await xe();if(!a)return c.log.error("failed to fetch branches"),await i(1);let e=await ve(),r=typeof t.create==="string"?t.create:t.branch;if((t.create===!0||typeof t.create==="string")&&r){if(a.includes(r))return c.log.error(`branch ${u.red(r)} already exists in the repository`),await i(1);if(!await se(r))return c.log.error(`failed to create and checkout ${u.bold(r)}`),await i(1);return c.log.success(`created and checked out ${u.green(r)}`),await i(0)}if(r){if(!a.includes(r)){c.log.error(`branch ${u.red(r)} does not exist in the repository`);let O=await c.confirm({message:`do you want to create branch ${u.green(r)}?`});if(c.isCancel(O))return c.log.error("aborted"),await i(1);if(O){if(!await se(r))return c.log.error(`failed to create and checkout ${u.bold(r)}`),await i(1);return c.log.success(`created and checked out ${u.green(r)}`),await i(0)}return await i(1)}if(r===e)return c.log.error(`${u.red("already on branch")} ${u.green(r)}`),await i(1);if(!await ne(r))return c.log.error(`failed to checkout ${u.bold(r)}`),await i(1);return c.log.success(`checked out ${u.green(r)}`),await i(0)}if(a.length===0)return c.log.error("no branches found in the repository"),await i(1);let n=await c.select({message:"select a branch to checkout",options:a.map((l)=>({value:l,label:u.bold(l===e?u.green(l):l),hint:l===e?"current branch":void 0})),initialValue:e});if(c.isCancel(n))return c.log.error("nothing selected!"),await i(1);if(!n)return c.log.error("no branch selected"),await i(1);if(t.copy)return xt.writeSync(n),c.log.success(`copied ${u.green(n)} to clipboard`),await i(0);if(n===e)return c.log.error(`${u.red("already on branch")}`),await i(1);if(!await ne(n))return c.log.error(`failed to checkout ${u.bold(n)}`),await i(1);c.log.success(`checked out ${u.green(n)}`),await i(0)});import{z as Mt}from"zod";import*as M from"@clack/prompts";import me from"picocolors";var Oe=x.meta({description:"configure noto api key"}).input(Mt.string().optional().describe("apiKey")).mutation(async(o)=>{let{input:t}=o,a=t;if((await m.get()).llm?.apiKey){let e=await M.confirm({message:"noto api key already configured, do you want to update it?"});if(M.isCancel(e)||!e)return M.log.error(me.red("nothing changed!")),await i(1)}if(!a){let e=await M.text({message:"enter your noto api key"});if(M.isCancel(e))return M.log.error(me.red("nothing changed!")),await i(1);a=e}await m.update((e)=>({...e,llm:{...e.llm,apiKey:a}})),M.log.success(me.green("noto api key configured!")),await i(0)});import*as E from"@clack/prompts";import De from"picocolors";import{createGoogleGenerativeAI as kt}from"@ai-sdk/google";var V=kt({apiKey:process.env.NOTO_API_KEY||(await m.get()).llm?.apiKey||"api-key"}),Ge="gemini-2.5-flash-lite",oe={"gemini-2.5-flash":V("gemini-2.5-flash"),"gemini-2.5-flash-lite":V("gemini-2.5-flash-lite"),"gemini-2.5-pro":V("gemini-2.5-pro"),"gemini-3-flash-preview":V("gemini-3-flash-preview"),"gemini-3-pro-preview":V("gemini-3-pro-preview")},de=Object.keys(oe),ue=async(o)=>{let t;if(o)if(o=o.trim(),de.includes(o))t=o;else t=void 0;let a=process.env.NOTO_MODEL;if(!t&&a)if(a=a.trim(),de.includes(a))t=a;else t=void 0;if(!t){let e=(await m.get()).llm?.model;if(e&&de.includes(e))t=e}if(!t)t=Ge,await m.update((e)=>({...e,llm:{...e.llm,model:Ge}}));return oe[t]};var Fe=x.meta({description:"configure model"}).mutation(async()=>{let o=await E.select({message:"select a model",initialValue:(await m.get()).llm?.model,options:Object.keys(oe).map((t)=>({label:t,value:t}))});if(E.isCancel(o))return E.log.error(De.red("nothing changed!")),await i(1);await m.update((t)=>({...t,llm:{...t.llm,model:o}})),E.log.success(De.green("model configured!")),await i(0)});import*as S from"@clack/prompts";import Le from"picocolors";var je=x.meta({description:"reset the configuration"}).mutation(async()=>{let o=await S.confirm({message:"are you sure you want to reset the configuration?"});if(S.isCancel(o)||!o)return S.log.error(Le.red("nothing changed!")),await i(1);await m.clear(),S.log.success(Le.green("configuration reset!")),await i(0)});var Be=T.router({key:Oe,model:Fe,reset:je});import{z as _}from"zod";import*as f from"@clack/prompts";import g from"picocolors";import ze from"dedent";import Pt from"clipboardy";var qe=ee.meta({description:"access the last generated commit",repoRequired:!1}).input(_.object({copy:_.boolean().meta({description:"copy the last commit to clipboard",alias:"c"}),apply:_.boolean().meta({description:"commit the last generated message",alias:"a"}),edit:_.boolean().meta({description:"edit the last generated commit message",alias:"e"}),amend:_.boolean().meta({description:"amend the last commit with the last message"})})).mutation(async(o)=>{let{input:t,ctx:a}=o,e=(await m.get()).lastGeneratedMessage;if(!e)return f.log.error(g.red("no previous commit message found")),await i(1);let{edit:r,amend:s}=t;if(s&&!r)return f.log.error(g.red("the --amend option requires the --edit option")),await i(1);if(f.log.step(r?g.white(e):g.green(e)),r){let n=await f.text({message:"edit the last generated commit message",initialValue:e,placeholder:e});if(f.isCancel(n))return f.log.error(g.red("nothing changed!")),await i(1);e=n,await m.update((h)=>({...h,lastGeneratedMessage:n})),f.log.step(g.green(e))}if(t.copy)Pt.writeSync(e),f.log.step(g.dim("copied last generated commit message to clipboard"));if(t.apply||s){if(!a.git.isRepository)return f.log.error(ze`${g.red("no git repository found in cwd.")}
7
- ${g.dim(`run ${g.cyan("`git init`")} to initialize a new repository.`)}`),await i(1);if(!a.git.diff&&!s)return f.log.error(ze`${g.red("no staged changes found.")}
8
- ${g.dim(`run ${g.cyan("`git add <file>`")} or ${g.cyan("`git add .`")} to stage changes.`)}`),await i(1);if(await L(e,s))f.log.step(g.dim("commit successful"));else f.log.error(g.red("failed to commit changes"))}return await i(0)});import He from"node:fs/promises";import{z as re}from"zod";import*as d from"@clack/prompts";import P from"picocolors";import K from"dedent";import{generateObject as We,wrapLanguageModel as At}from"ai";import ae from"zod";import W from"dedent";import _e from"superjson";import Ct from"node:crypto";function Ve(o){let t=Buffer.from(o,"utf-8"),a=Buffer.from(`blob ${t.length}\x00`,"utf-8");return Ct.createHash("sha1").update(a).update(t).digest("hex")}var Rt=W`
2
+ import{createCli as Lt}from"trpc-cli";var G="@snelusha/noto",C="2.0.0-beta.3";import vt from"node:fs/promises";import{initTRPC as xt}from"@trpc/server";import*as U from"@clack/prompts";import w from"picocolors";import pe from"dedent";import it from"simple-git";var k=it(),K=async()=>{return k.checkIsRepo()},Y=async()=>{try{return await k.revparse(["--show-toplevel"])}catch{return null}};var ye=async(o=20,t=!1)=>{try{let a=t?{maxCount:o,"--no-merges":null}:{maxCount:o};return(await k.log(a)).all.map((r)=>r.message)}catch{return null}};var we=async()=>{try{return k.diff(["--cached","--",":!*.lock"])}catch{return null}},L=async(o,t)=>{try{let a=t?{"--amend":null}:void 0,{summary:{changes:e}}=await k.commit(o,void 0,a);return Boolean(e)}catch{return!1}},be=async()=>{try{let o=await k.push();return o.update||o.pushed&&o.pushed.length>0}catch{return!1}},ve=async()=>{try{return(await k.branch()).current}catch{return null}},xe=async(o)=>{try{let t=await k.branch();return o?t.all:Object.keys(t.branches).filter((a)=>!a.startsWith("remotes/"))}catch{return null}},ne=async(o)=>{try{return await k.checkout(o,{}),!0}catch{return!1}},se=async(o)=>{try{return await k.checkoutLocalBranch(o),!0}catch{return!1}};import*as N from"node:fs";import*as j from"node:path";import*as Me from"node:child_process";import ce from"node:process";import nt from"semver";var B=nt.prerelease(C)!==null;async function ke(){let o=ce.argv[1];if(!o)return{packageManager:"unknown",isGlobal:!1};let t=ce.cwd();try{let a=N.realpathSync(o).replace(/\\/g,"/"),e=t.replace(/\\/g,"/");if(await K()&&e&&a.startsWith(e)&&!a.includes("/node_modules/"))return{packageManager:"unknown",isGlobal:!1,updateMessage:'Running from a local git clone. Please update with "git pull".'};if(a.includes("/.npm/_npx")||a.includes("/npm/_npx"))return{packageManager:"npx",isGlobal:!1,updateMessage:"Running via npx, update not applicable."};if(ce.platform==="darwin")try{return Me.execSync('brew list -1 | grep -q "^noto$"',{stdio:"ignore"}),{packageManager:"homebrew",isGlobal:!0,updateMessage:'Installed via Homebrew. Please update with "brew upgrade".'}}catch{}if(a.includes("/pnpm/dlx")||a.includes("/pnpm-cache/dlx"))return{packageManager:"pnpx",isGlobal:!1,updateMessage:"Running via pnpx, update not applicable."};if(a.includes("/pnpm/global"))return{packageManager:"pnpm",isGlobal:!0,updateCommand:"pnpm add -g @snelusha/noto@latest",updateMessage:"Please run pnpm add -g @snelusha/noto@latest to update"};if(a.includes("/.yarn/global"))return{packageManager:"yarn",isGlobal:!0,updateCommand:"yarn global add @snelusha/noto@latest",updateMessage:"Please run yarn global add @snelusha/noto@latest to update"};if(a.includes("/bunx"))return{packageManager:"bunx",isGlobal:!1,updateMessage:"Running via bunx, update not applicable."};if(a.includes("/.bun/install/global"))return{packageManager:"bun",isGlobal:!0,updateCommand:"bun add -g @snelusha/noto@latest",updateMessage:"Please run bun add -g @snelusha/noto@latest to update"};if(e&&a.startsWith(`${e}/node_modules`)){let n="npm";if(N.existsSync(j.join(t,"yarn.lock")))n="yarn";else if(N.existsSync(j.join(t,"pnpm-lock.yaml")))n="pnpm";else if(N.existsSync(j.join(t,"bun.lockb"))||N.existsSync(j.join(t,"bun.lock")))n="bun";return{packageManager:n,isGlobal:!1,updateMessage:"Locally installed. Please update via your project's package.json."}}let s="npm install -g @snelusha/noto@latest";return{packageManager:"npm",isGlobal:!0,updateCommand:s,updateMessage:`Please run ${s} to update`}}catch{return{packageManager:"unknown",isGlobal:!1}}}import lt from"node:os";import Ce from"node:path";import{z}from"zod";import H from"node:fs/promises";import st from"node:path";function X(o){let{schema:t,path:a}=o;return class e{static storagePath=a;static storage={};static async load(){try{await H.access(e.storagePath);let r=await H.readFile(e.storagePath,"utf-8"),s=r?JSON.parse(r):{},n=t.safeParse(s);e.storage=n.success?n.data:{}}catch{e.storage={}}return e.storage}static async save(){try{let r=st.dirname(e.storagePath);await H.mkdir(r,{recursive:!0});let s=JSON.stringify(e.storage,null,2);await H.writeFile(e.storagePath,s,"utf-8")}catch{}}static async update(r){try{await e.load();let s=await r(e.storage),n=t.safeParse(s);if(n.success)e.storage=n.data,await e.save()}catch{}return e.storage}static async get(){return await e.load(),JSON.parse(JSON.stringify(e.storage))}static async clear(){e.storage={},await e.save()}static get path(){return e.storagePath}static set path(r){e.storagePath=r}static get raw(){return e.storage}}}import{z as ct}from"zod";var Pe=ct.enum(["gemini-2.5-flash","gemini-2.5-flash-lite","gemini-2.5-pro","gemini-3-flash-preview","gemini-3-pro-preview"]);var pt=z.object({llm:z.object({apiKey:z.string().optional(),model:Pe.optional().or(z.string())}).optional(),lastGeneratedMessage:z.string().optional()}),m=X({path:Ce.resolve(Ce.join(lt.homedir(),".config","noto"),".notorc"),schema:pt});async function Ae(){try{let o=await m.get();await m.update(()=>o)}catch{}}import mt from"node:fs/promises";import D from"node:path";import{fileURLToPath as dt}from"node:url";var Re=(o)=>o instanceof URL?dt(o):o;async function Ie(o,t={}){let a=D.resolve(Re(t.cwd??"")),{root:e}=D.parse(a);t.stopAt=D.resolve(Re(t.stopAt??e));let r=D.isAbsolute(o);while(a){let s=r?o:D.join(a,o);try{let n=await mt.stat(s);if(t.type===void 0||t.type==="file"&&n.isFile()||t.type==="directory"&&n.isDirectory())return s}catch{}if(a===t.stopAt||a===e)break;a=D.dirname(a)}}var J=async()=>{let o=await Y();return await Ie(".noto/commit-prompt.md",{stopAt:o||process.cwd(),type:"file"})};import*as Ue from"@clack/prompts";import Ne from"picocolors";import wt from"dedent";import y from"semver";import q from"latest-version";import ut from"node:os";import $e from"node:path";import{z as R}from"zod";var gt=R.object({commitGenerationCache:R.record(R.string(),R.string()).optional(),update:R.object({timestamp:R.number(),current:R.string(),latest:R.string()}).optional()}),I=X({path:$e.resolve($e.join(ut.homedir(),".cache","noto"),"cache"),schema:gt});var ft=86400000;function ht(o,t){if(!o||!t)return o||t||null;let a=y.parse(t),e=y.parse(o);if(!a||!e)return o||t||null;return y.gt(o,t)?o:t}async function le(o=!1,t=!1,a="auto"){let e=(await I.get()).update;if(!t&&e){if(y.valid(e.current)&&y.valid(e.latest)){let s=y.gte(e.current,e.latest),n=Date.now()-e.timestamp<ft;if(s||n)return{latest:e.latest,current:e.current,timestamp:e.timestamp}}}try{let r;if(a==="stable")r=await q(G);else if(a==="beta")r=await q(G,{version:"beta"});else r=B?ht(...await Promise.all([q(G,{version:"beta"}),q(G)]))??C:await q(G);let s={latest:r,current:C,timestamp:Date.now()};if(o)await yt(s);return s}catch{if(e)return{latest:e.latest,current:e.current,timestamp:e.timestamp};return{latest:C,current:C,timestamp:Date.now()}}}async function yt(o){if(!o)return;await I.update((t)=>({...t,update:o&&{timestamp:o.timestamp,latest:o.latest,current:o.current}}))}async function Q(o=!1,t=!1,a="auto"){let e=await le(o,t,a);if(y.valid(e.current)&&y.valid(e.latest)){if(B&&a==="stable"&&e.current!==e.latest)return e;if(!y.gte(e.current,e.latest))return e}return null}async function bt(){let o=await Q();if(o)Ue.log.warn(wt`A new version of noto is available: ${Ne.dim(o.current)} → ${Ne.green(o.latest)}
3
+ Please run \`noto upgrade\` to update`.trim())}var i=async(o,t=!0)=>{if(t)await bt();await new Promise((a)=>setTimeout(a,1)),console.log(),process.exit(o)};var T=xt.meta().create({defaultMeta:{intro:!0,authRequired:!0,repoRequired:!0,diffRequired:!1,promptRequired:!1}}),Te=T.middleware(async(o)=>{let{meta:t,next:a}=o,e=await m.get(),r=process.env.NOTO_API_KEY||e.llm?.apiKey;if(t?.authRequired&&!r)return U.log.error(pe`${w.red("noto api key is missing.")}
4
+ ${w.dim(`run ${w.cyan("`noto config key`")} to set it up.`)}`),await i(1);return a()}),Ee=T.middleware(async(o)=>{let{meta:t,next:a}=o,e=await K();if(t?.repoRequired&&!e)return U.log.error(pe`${w.red("no git repository found in cwd.")}
5
+ ${w.dim(`run ${w.cyan("`git init`")} to initialize a new repository.`)}`),await i(1);let r=e&&await we();if(t?.diffRequired&&!r)return U.log.error(pe`${w.red("no staged changes found.")}
6
+ ${w.dim(`run ${w.cyan("`git add <file>`")} or ${w.cyan("`git add .`")} to stage changes.`)}`),await i(1);let s=null;if(t?.promptRequired){let n=await J();if(n)try{s=await vt.readFile(n,"utf-8")}catch{}}return a({ctx:{noto:{prompt:s},git:{isRepository:e,diff:r}}})}),x=T.procedure.use((o)=>{let{meta:t,next:a}=o;if(t?.intro)if(console.log(),B)U.intro(`${w.bgGreen(w.black(" @snelusha/noto [Prerelease] "))}`);else U.intro(`${w.bgCyan(w.black(" @snelusha/noto "))}`);return a()}),Do=x.use(Te),Z=x.use(Ee),ee=x.use(Te).use(Ee);import{z as F}from"zod";import*as c from"@clack/prompts";import u from"picocolors";import Mt from"clipboardy";var Se=Z.meta({description:"checkout a branch"}).input(F.object({copy:F.boolean().meta({description:"copy the selected branch to clipboard",alias:"c"}),create:F.union([F.boolean(),F.string()]).optional().meta({description:"create a new branch",alias:"b"}),branch:F.string().optional().meta({positional:!0})})).mutation(async(o)=>{let{input:t}=o,a=await xe();if(!a)return c.log.error("failed to fetch branches"),await i(1);let e=await ve(),r=typeof t.create==="string"?t.create:t.branch;if((t.create===!0||typeof t.create==="string")&&r){if(a.includes(r))return c.log.error(`branch ${u.red(r)} already exists in the repository`),await i(1);if(!await se(r))return c.log.error(`failed to create and checkout ${u.bold(r)}`),await i(1);return c.log.success(`created and checked out ${u.green(r)}`),await i(0)}if(r){if(!a.includes(r)){c.log.error(`branch ${u.red(r)} does not exist in the repository`);let O=await c.confirm({message:`do you want to create branch ${u.green(r)}?`});if(c.isCancel(O))return c.log.error("aborted"),await i(1);if(O){if(!await se(r))return c.log.error(`failed to create and checkout ${u.bold(r)}`),await i(1);return c.log.success(`created and checked out ${u.green(r)}`),await i(0)}return await i(1)}if(r===e)return c.log.error(`${u.red("already on branch")} ${u.green(r)}`),await i(1);if(!await ne(r))return c.log.error(`failed to checkout ${u.bold(r)}`),await i(1);return c.log.success(`checked out ${u.green(r)}`),await i(0)}if(a.length===0)return c.log.error("no branches found in the repository"),await i(1);let n=await c.select({message:"select a branch to checkout",options:a.map((l)=>({value:l,label:u.bold(l===e?u.green(l):l),hint:l===e?"current branch":void 0})),initialValue:e});if(c.isCancel(n))return c.log.error("nothing selected!"),await i(1);if(!n)return c.log.error("no branch selected"),await i(1);if(t.copy)return Mt.writeSync(n),c.log.success(`copied ${u.green(n)} to clipboard`),await i(0);if(n===e)return c.log.error(`${u.red("already on branch")}`),await i(1);if(!await ne(n))return c.log.error(`failed to checkout ${u.bold(n)}`),await i(1);c.log.success(`checked out ${u.green(n)}`),await i(0)});import{z as kt}from"zod";import*as M from"@clack/prompts";import me from"picocolors";var Oe=x.meta({description:"configure noto api key"}).input(kt.string().optional().describe("apiKey")).mutation(async(o)=>{let{input:t}=o,a=t;if((await m.get()).llm?.apiKey){let e=await M.confirm({message:"noto api key already configured, do you want to update it?"});if(M.isCancel(e)||!e)return M.log.error(me.red("nothing changed!")),await i(1)}if(!a){let e=await M.text({message:"enter your noto api key"});if(M.isCancel(e))return M.log.error(me.red("nothing changed!")),await i(1);a=e}await m.update((e)=>({...e,llm:{...e.llm,apiKey:a}})),M.log.success(me.green("noto api key configured!")),await i(0)});import*as E from"@clack/prompts";import De from"picocolors";import{createGoogleGenerativeAI as Pt}from"@ai-sdk/google";var V=Pt({apiKey:process.env.NOTO_API_KEY||(await m.get()).llm?.apiKey||"api-key"}),Ge="gemini-2.5-flash-lite",te={"gemini-2.5-flash":V("gemini-2.5-flash"),"gemini-2.5-flash-lite":V("gemini-2.5-flash-lite"),"gemini-2.5-pro":V("gemini-2.5-pro"),"gemini-3-flash-preview":V("gemini-3-flash-preview"),"gemini-3-pro-preview":V("gemini-3-pro-preview")},de=Object.keys(te),ue=async(o)=>{let t;if(o)if(o=o.trim(),de.includes(o))t=o;else t=void 0;let a=process.env.NOTO_MODEL;if(!t&&a)if(a=a.trim(),de.includes(a))t=a;else t=void 0;if(!t){let e=(await m.get()).llm?.model;if(e&&de.includes(e))t=e}if(!t)t=Ge,await m.update((e)=>({...e,llm:{...e.llm,model:Ge}}));return te[t]};var Fe=x.meta({description:"configure model"}).mutation(async()=>{let o=await E.select({message:"select a model",initialValue:(await m.get()).llm?.model,options:Object.keys(te).map((t)=>({label:t,value:t}))});if(E.isCancel(o))return E.log.error(De.red("nothing changed!")),await i(1);await m.update((t)=>({...t,llm:{...t.llm,model:o}})),E.log.success(De.green("model configured!")),await i(0)});import*as S from"@clack/prompts";import Le from"picocolors";var je=x.meta({description:"reset the configuration"}).mutation(async()=>{let o=await S.confirm({message:"are you sure you want to reset the configuration?"});if(S.isCancel(o)||!o)return S.log.error(Le.red("nothing changed!")),await i(1);await m.clear(),S.log.success(Le.green("configuration reset!")),await i(0)});var Be=T.router({key:Oe,model:Fe,reset:je});import{z as oe}from"zod";import*as f from"@clack/prompts";import g from"picocolors";import ze from"dedent";import Ct from"clipboardy";var qe=Z.meta({description:"access the last generated commit",repoRequired:!1}).input(oe.object({copy:oe.boolean().meta({description:"copy the last commit to clipboard",alias:"c"}),preview:oe.boolean().meta({description:"preview the last generated message without committing",alias:"p"}),amend:oe.boolean().meta({description:"amend the last commit with the last message"})})).mutation(async(o)=>{let{input:t,ctx:a}=o,e=(await m.get()).lastGeneratedMessage;if(!e)return f.log.error(g.red("no previous commit message found")),await i(1);let r=t.amend;if(t.preview)f.log.step(g.green(e));else{f.log.step(g.white(e));let s=await f.text({message:"edit the last generated commit message",initialValue:e,placeholder:e});if(f.isCancel(s))return f.log.error(g.red("nothing changed!")),await i(1);e=s,f.log.step(g.green(e))}if(t.copy)Ct.writeSync(e),f.log.step(g.dim("copied last generated commit message to clipboard"));if(!t.preview){if(!a.git.isRepository)return f.log.error(ze`${g.red("no git repository found in cwd.")}
7
+ ${g.dim(`run ${g.cyan("`git init`")} to initialize a new repository.`)}`),await i(1);if(!a.git.diff&&!r)return f.log.error(ze`${g.red("no staged changes found.")}
8
+ ${g.dim(`run ${g.cyan("`git add <file>`")} or ${g.cyan("`git add .`")} to stage changes.`)}`),await i(1);if(await m.update((n)=>({...n,lastGeneratedMessage:e})),await L(e,r))f.log.step(g.dim("commit successful"));else f.log.error(g.red("failed to commit changes"))}return await i(0)});import Xe from"node:fs/promises";import{z as re}from"zod";import*as d from"@clack/prompts";import P from"picocolors";import W from"dedent";import{generateText as We,Output as Ke,wrapLanguageModel as Rt}from"ai";import ae from"zod";import _ from"dedent";import _e from"superjson";import At from"node:crypto";function Ve(o){let t=Buffer.from(o,"utf-8"),a=Buffer.from(`blob ${t.length}\x00`,"utf-8");return At.createHash("sha1").update(a).update(t).digest("hex")}var It=_`
9
9
  # System Instruction for Noto
10
10
 
11
11
  You are a Git commit message generator for the \`noto\` CLI tool. Your role is to analyze staged code changes and generate clear, single-line commit messages that follow the user's established style.
@@ -76,7 +76,7 @@ Or whatever format matches the user's guidelines. **Must be single-line only.**
76
76
  - Don't use generic or vague descriptions
77
77
 
78
78
  **Remember:** Your output becomes permanent git history. Generate commit messages that are clear, accurate, and consistent with the user's established patterns.
79
- `,It=W`
79
+ `,$t=_`
80
80
  # Commit Message Guidelines
81
81
 
82
82
  ## Format
@@ -148,7 +148,7 @@ For breaking changes, add \`!\` after the type/scope:
148
148
  ## Additional Notes
149
149
  - If a commit addresses a specific issue, you can reference it in the description (e.g., \`fix: resolve memory leak (fixes #123)\`)
150
150
  - Each commit should represent a single logical change
151
- - Write commits as if completing the sentence: "If applied, this commit will..."`,$t=W`
151
+ - Write commits as if completing the sentence: "If applied, this commit will..."`,Nt=_`
152
152
  You are a commit style analyzer. Analyze the provided commit history and generate a personalized style guide that will be used to generate future commit messages.
153
153
 
154
154
  ## Task
@@ -304,27 +304,27 @@ Start with action verb (add, implement, resolve, update, simplify). Be specific
304
304
  - The output will be stored as \`.noto/commit-prompt.md\` and used by an AI to generate commits
305
305
 
306
306
  Generate the markdown guidelines now based on the commit history provided.
307
- `,Nt={wrapGenerate:async({doGenerate:o,params:t})=>{let a=Ve(JSON.stringify(t)),e=(await I.get()).commitGenerationCache;if(e&&a in e)return _e.parse(e[a]);let r=await o();return await I.update((s)=>({...s,commitGenerationCache:{[a]:_e.stringify(r)}})),r}},Ke=async(o,t,a,e=!1,r)=>{let s=await ue(r),{object:n}=await We({model:!e?At({model:s,middleware:Nt}):s,schema:ae.object({message:ae.string()}),messages:[{role:"system",content:Rt},{role:"user",content:W`
307
+ `,Ut={specificationVersion:"v3",wrapGenerate:async({doGenerate:o,params:t})=>{let a=Ve(JSON.stringify(t)),e=(await I.get()).commitGenerationCache;if(e&&a in e)return _e.parse(e[a]);let r=await o();return await I.update((s)=>({...s,commitGenerationCache:{[a]:_e.stringify(r)}})),r}},Ye=async(o,t,a,e=!1,r)=>{let s=await ue(r),{output:n}=await We({model:!e?Rt({model:s,middleware:Ut}):s,output:Ke.object({schema:ae.object({message:ae.string()})}),messages:[{role:"system",content:It},{role:"user",content:_`
308
308
  USER GUIDELINES:
309
- ${t??It}
309
+ ${t??$t}
310
310
  ${a?`
311
311
  USER CONTEXT:
312
312
  ${a}`:""}
313
313
 
314
314
  GIT DIFF:
315
315
  ${o}
316
- `}]});return n.message.trim()},Ye=async(o,t)=>{let a=await ue(t),{object:e}=await We({model:a,schema:ae.object({prompt:ae.string()}),messages:[{role:"system",content:$t},{role:"user",content:W`
316
+ `}]});return n.message.trim()},He=async(o,t)=>{let a=await ue(t),{output:e}=await We({model:a,output:Ke.object({schema:ae.object({prompt:ae.string()})}),messages:[{role:"system",content:Nt},{role:"user",content:_`
317
317
  COMMIT HISTORY:
318
318
  ${o.join(`
319
- `)}`}]});return e.prompt.trim()};var Ut=K`
319
+ `)}`}]});return e.prompt.trim()};var Tt=W`
320
320
  # Commit Message Guidelines
321
321
 
322
322
  # Add your custom guidelines here.
323
- # When no guidelines are present, noto will use conventional commits format by default.`,Xe=te.meta({description:"initialize noto in the repository"}).input(re.object({root:re.boolean().meta({description:"create the prompt file in the git root"}),generate:re.boolean().meta({description:"generate a prompt file based on existing commits"}),model:re.string().optional().meta({description:"specify the model to use"})})).mutation(async(o)=>{let{input:t}=o,a=await H(),e=a,r=process.cwd(),s=await Q(),n=null;if(s)if(!s.startsWith(r)){d.log.warn(K`${P.yellow("a prompt file already exists!")}
324
- ${P.gray(s)}`);let y=await d.confirm({message:"do you want to create in the current directory instead?",initialValue:!0});if(d.isCancel(y)||!y)return d.log.error("aborted"),await i(1);e=r}else return d.log.error(K`${P.red("a prompt file already exists.")}
325
- ${P.gray(s)}`),await i(1);if(a!==r&&!t.root){let y=await d.confirm({message:"do you want to create the prompt file in the git root?",initialValue:!0});if(d.isCancel(y))return d.log.error("aborted"),await i(1);if(!y)e=r}let h=await ye(20,!0),l=t.generate;if(l){if(!h||h.length<5)return d.log.error(K`${P.red("not enough commits to generate a prompt file.")}
326
- ${P.gray("at least 5 commits are required.")}`),await i(1)}else if(h&&h.length>=5){let y=await d.confirm({message:"do you want to generate a prompt file based on existing commits?",initialValue:!0});if(d.isCancel(y))return d.log.error("aborted"),await i(1);l=y}let O=d.spinner();if(h&&l)O.start("generating commit message guidelines"),n=await Ye(h,t.model),O.stop(P.green("generated commit message guidelines!"));else n=Ut;try{let y=`${e}/.noto`;await He.mkdir(y,{recursive:!0});let fe=`${y}/commit-prompt.md`;return await He.writeFile(fe,n,"utf-8"),d.log.success(K`${P.green("prompt file created!")}
327
- ${P.gray(fe)}`),await i(0)}catch{d.log.error(P.red("failed to create the prompt file!"))}});import{z as A}from"zod";import*as p from"@clack/prompts";import v from"picocolors";import Tt from"clipboardy";import{APICallError as Et,RetryError as St}from"ai";var Je=te.meta({description:"generate a commit message",default:!0,diffRequired:!0,promptRequired:!0}).input(A.object({message:A.string().or(A.boolean()).meta({description:"provide context for commit message",alias:"m"}),copy:A.boolean().meta({description:"copy the generated message to clipboard",alias:"c"}),preview:A.boolean().meta({description:"preview the generated message without committing",alias:"p"}),push:A.boolean().meta({description:"commit and push the changes"}),force:A.boolean().meta({description:"bypass cache and force regeneration of commit message",alias:"f"}),manual:A.boolean().meta({description:"custom commit message"}),model:A.string().optional().meta({description:"specify the model to use"})})).mutation(async(o)=>{let{input:t,ctx:a}=o,e=p.spinner();try{if(t.manual){let l=await p.text({message:"edit the generated commit message",placeholder:"chore: init repo"});if(p.isCancel(l))return p.log.error(v.red("nothing changed!")),await i(1);if(p.log.step(v.green(l)),await m.update((y)=>({...y,lastGeneratedMessage:l})),await L(l))p.log.step(v.dim("commit successful"));else p.log.error(v.red("failed to commit changes"));return await i(0)}let s=t.message;if(typeof s==="string")s=s.trim();else if(typeof s==="boolean"&&s===!0){let l=await p.text({message:"provide context for the commit message",placeholder:"describe the changes"});if(p.isCancel(l))return p.log.error(v.red("nothing changed!")),await i(1);s=l}e.start("generating commit message");let n=null;n=await Ke(a.git.diff,a.noto.prompt,typeof s==="string"?s:void 0,t.force,t.model),e.stop(v.white(n));let h=await p.text({message:"edit the generated commit message",initialValue:n,placeholder:n});if(p.isCancel(h))return p.log.error(v.red("nothing changed!")),await i(1);if(n=h,p.log.step(v.green(n)),await m.update((l)=>({...l,lastGeneratedMessage:n})),t.copy)Tt.writeSync(n),p.log.step(v.dim("copied commit message to clipboard"));if(!t.preview)if(await L(n))p.log.step(v.dim("commit successful"));else p.log.error(v.red("failed to commit changes"));if(t.push)if(await be())p.log.step(v.dim("push successful"));else p.log.error(v.red("failed to push changes"));return await i(0)}catch(r){let s;if(St.isInstance(r)&&Et.isInstance(r.lastError))s=Ot(r.lastError.responseBody);let n=s?`
328
- ${s}`:"";e.stop(v.red(`failed to generate commit message${n}`),1),await i(1)}});function Ot(o){if(typeof o!=="string")return;try{let t=JSON.parse(o);return t?.error?.message??t?.message}catch{return}}import{spawn as Gt}from"node:child_process";import{z as ge}from"zod";import*as $ from"@clack/prompts";import ie from"picocolors";import Qe from"semver";async function Dt(o){let t=await ke();if(!t.updateCommand){if(t.updateMessage)return $.log.warn(t.updateMessage),await i(0,!1);return $.log.error("unable to determine update command for your installation."),await i(1,!1)}let a=t.updateCommand.replace("@latest",`@${o}`),e=Gt(a,{stdio:"pipe",shell:!0}),r=$.spinner();r.start("upgrading noto");try{await new Promise((s,n)=>{e.on("close",(h)=>{if(h===0)s();else n()})}),r.stop(ie.green("noto has been updated successfully!"))}catch{return $.log.error(`automatic update failed. please try updating manually by running: ${t.updateCommand}`),await i(1,!1)}return await I.update((s)=>({...s,update:void 0})),await i(0,!1)}var Ze=x.meta({description:"upgrade noto"}).input(ge.object({stable:ge.boolean().optional().meta({description:"upgrade to the latest stable version"}),beta:ge.boolean().optional().meta({description:"upgrade to the latest beta version"})})).mutation(async(o)=>{let{input:t}=o;if(t.stable&&t.beta)return $.log.error("please choose either --stable or --beta option, not both."),await i(1,!1);let a=t.stable?"stable":t.beta?"beta":"auto",e=$.spinner();e.start("fetching latest version");let r=await Z(!0,!0,a);if(!r)return e.stop(`You're already on the latest version of noto (${ie.dim(`which is ${C}`)})`),await i(0,!1);e.stop(`noto ${ie.green(r.latest)} is out! You are on ${ie.dim(r.current)}.`);let n=Qe.prerelease(r.latest)!==null||a==="beta"?"beta":r.latest;return await Dt(n)});var et={checkout:Se,config:Be,prev:qe,init:Xe,noto:Je,upgrade:Ze};var tt=T.router(et);var ot=process.argv.slice(2),at="2.0.0-beta.1";if(ot.includes("--version")||ot.includes("-v"))console.log(at),process.exit(0);Ae();le(!0);Ft({name:"noto",router:tt,version:at}).run();
323
+ # When no guidelines are present, noto will use conventional commits format by default.`,Je=ee.meta({description:"initialize noto in the repository"}).input(re.object({root:re.boolean().meta({description:"create the prompt file in the git root"}),generate:re.boolean().meta({description:"generate a prompt file based on existing commits"}),model:re.string().optional().meta({description:"specify the model to use"})})).mutation(async(o)=>{let{input:t}=o,a=await Y(),e=a,r=process.cwd(),s=await J(),n=null;if(s)if(!s.startsWith(r)){d.log.warn(W`${P.yellow("a prompt file already exists!")}
324
+ ${P.gray(s)}`);let h=await d.confirm({message:"do you want to create in the current directory instead?",initialValue:!0});if(d.isCancel(h)||!h)return d.log.error("aborted"),await i(1);e=r}else return d.log.error(W`${P.red("a prompt file already exists.")}
325
+ ${P.gray(s)}`),await i(1);if(a!==r&&!t.root){let h=await d.confirm({message:"do you want to create the prompt file in the git root?",initialValue:!0});if(d.isCancel(h))return d.log.error("aborted"),await i(1);if(!h)e=r}let v=await ye(20,!0),l=t.generate;if(l){if(!v||v.length<5)return d.log.error(W`${P.red("not enough commits to generate a prompt file.")}
326
+ ${P.gray("at least 5 commits are required.")}`),await i(1)}else if(v&&v.length>=5){let h=await d.confirm({message:"do you want to generate a prompt file based on existing commits?",initialValue:!0});if(d.isCancel(h))return d.log.error("aborted"),await i(1);l=h}let O=d.spinner();if(v&&l)O.start("generating commit message guidelines"),n=await He(v,t.model),O.stop(P.green("generated commit message guidelines!"));else n=Tt;try{let h=`${e}/.noto`;await Xe.mkdir(h,{recursive:!0});let fe=`${h}/commit-prompt.md`;return await Xe.writeFile(fe,n,"utf-8"),d.log.success(W`${P.green("prompt file created!")}
327
+ ${P.gray(fe)}`),await i(0)}catch{d.log.error(P.red("failed to create the prompt file!"))}});import{z as A}from"zod";import*as p from"@clack/prompts";import b from"picocolors";import Et from"clipboardy";import{APICallError as St,RetryError as Ot}from"ai";var Qe=ee.meta({description:"generate a commit message",default:!0,diffRequired:!0,promptRequired:!0}).input(A.object({message:A.string().or(A.boolean()).meta({description:"provide context for commit message",alias:"m"}),copy:A.boolean().meta({description:"copy the generated message to clipboard",alias:"c"}),preview:A.boolean().meta({description:"preview the generated message without committing",alias:"p"}),push:A.boolean().meta({description:"commit and push the changes"}),force:A.boolean().meta({description:"bypass cache and force regeneration of commit message",alias:"f"}),manual:A.boolean().meta({description:"custom commit message"}),model:A.string().optional().meta({description:"specify the model to use"})})).mutation(async(o)=>{let{input:t,ctx:a}=o,e=p.spinner();try{if(t.manual){let l=await p.text({message:"edit the generated commit message",placeholder:"chore: init repo"});if(p.isCancel(l))return p.log.error(b.red("nothing changed!")),await i(1);if(p.log.step(b.green(l)),await m.update((h)=>({...h,lastGeneratedMessage:l})),await L(l))p.log.step(b.dim("commit successful"));else p.log.error(b.red("failed to commit changes"));return await i(0)}let s=t.message;if(typeof s==="string")s=s.trim();else if(typeof s==="boolean"&&s===!0){let l=await p.text({message:"provide context for the commit message",placeholder:"describe the changes"});if(p.isCancel(l))return p.log.error(b.red("nothing changed!")),await i(1);s=l}e.start("generating commit message");let n=null;n=await Ye(a.git.diff,a.noto.prompt,typeof s==="string"?s:void 0,t.force,t.model),e.stop(b.white(n));let v=await p.text({message:"edit the generated commit message",initialValue:n,placeholder:n});if(p.isCancel(v))return p.log.error(b.red("nothing changed!")),await i(1);if(n=v,p.log.step(b.green(n)),await m.update((l)=>({...l,lastGeneratedMessage:n})),t.copy)Et.writeSync(n),p.log.step(b.dim("copied commit message to clipboard"));if(!t.preview)if(await L(n))p.log.step(b.dim("commit successful"));else p.log.error(b.red("failed to commit changes"));if(t.push)if(await be())p.log.step(b.dim("push successful"));else p.log.error(b.red("failed to push changes"));return await i(0)}catch(r){let s;if(Ot.isInstance(r)&&St.isInstance(r.lastError))s=Gt(r.lastError.responseBody);let n=s?`
328
+ ${s}`:"";e.stop(b.red(`failed to generate commit message${n}`),1),await i(1)}});function Gt(o){if(typeof o!=="string")return;try{let t=JSON.parse(o);return t?.error?.message??t?.message}catch{return}}import{spawn as Dt}from"node:child_process";import{z as ge}from"zod";import*as $ from"@clack/prompts";import ie from"picocolors";import Ze from"semver";async function Ft(o){let t=await ke();if(!t.updateCommand){if(t.updateMessage)return $.log.warn(t.updateMessage),await i(0,!1);return $.log.error("unable to determine update command for your installation."),await i(1,!1)}let a=t.updateCommand.replace("@latest",`@${o}`),e=Dt(a,{stdio:"pipe",shell:!0}),r=$.spinner();r.start("upgrading noto");try{await new Promise((s,n)=>{e.on("close",(v)=>{if(v===0)s();else n()})}),r.stop(ie.green("noto has been updated successfully!"))}catch{return $.log.error(`automatic update failed. please try updating manually by running: ${t.updateCommand}`),await i(1,!1)}return await I.update((s)=>({...s,update:void 0})),await i(0,!1)}var et=x.meta({description:"upgrade noto"}).input(ge.object({stable:ge.boolean().optional().meta({description:"upgrade to the latest stable version"}),beta:ge.boolean().optional().meta({description:"upgrade to the latest beta version"})})).mutation(async(o)=>{let{input:t}=o;if(t.stable&&t.beta)return $.log.error("please choose either --stable or --beta option, not both."),await i(1,!1);let a=t.stable?"stable":t.beta?"beta":"auto",e=$.spinner();e.start("fetching latest version");let r=await Q(!0,!0,a);if(!r)return e.stop(`You're already on the latest version of noto (${ie.dim(`which is ${C}`)})`),await i(0,!1);e.stop(`noto ${ie.green(r.latest)} is out! You are on ${ie.dim(r.current)}.`);let n=Ze.prerelease(r.latest)!==null||a==="beta"?"beta":r.latest;return await Ft(n)});var tt={checkout:Se,config:Be,prev:qe,init:Je,noto:Qe,upgrade:et};var ot=T.router(tt);var at=process.argv.slice(2),rt="2.0.0-beta.3";if(at.includes("--version")||at.includes("-v"))console.log(rt),process.exit(0);Ae();le(!0);Lt({name:"noto",router:ot,version:rt}).run();
329
329
 
330
330
  // Made by a human on earth!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snelusha/noto",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.3",
4
4
  "description": "Generate clean commit messages in a snap! ✨",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,20 +43,20 @@
43
43
  "cli"
44
44
  ],
45
45
  "devDependencies": {
46
- "@biomejs/biome": "2.3.10",
47
- "@types/node": "^25.0.3",
46
+ "@biomejs/biome": "2.3.13",
47
+ "@types/node": "^25.1.0",
48
48
  "@types/semver": "^7.7.1",
49
- "bunup": "^0.16.11",
49
+ "bunup": "^0.16.22",
50
50
  "typescript": "^5.9.3",
51
- "vitest": "^4.0.16"
51
+ "vitest": "^4.0.18"
52
52
  },
53
53
  "dependencies": {
54
- "@ai-sdk/google": "^3.0.1",
55
- "@ai-sdk/provider": "^3.0.0",
56
- "@clack/prompts": "^0.11.0",
57
- "@trpc/server": "^11.8.1",
58
- "ai": "^6.0.3",
59
- "clipboardy": "^5.0.2",
54
+ "@ai-sdk/google": "^3.0.18",
55
+ "@ai-sdk/provider": "^3.0.6",
56
+ "@clack/prompts": "^1.0.0",
57
+ "@trpc/server": "^11.9.0",
58
+ "ai": "^6.0.64",
59
+ "clipboardy": "^5.1.0",
60
60
  "dedent": "^1.7.1",
61
61
  "latest-version": "^9.0.0",
62
62
  "picocolors": "^1.1.1",
@@ -64,7 +64,7 @@
64
64
  "simple-git": "^3.30.0",
65
65
  "superjson": "^2.2.6",
66
66
  "tinyexec": "^1.0.2",
67
- "trpc-cli": "^0.12.1",
68
- "zod": "^4.2.1"
67
+ "trpc-cli": "^0.12.2",
68
+ "zod": "^4.3.6"
69
69
  }
70
70
  }