@draekien/create-d9-app 0.0.0-stage → 0.0.1

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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -2
  3. package/dist/index.js +53 -0
  4. package/index.js +11 -0
  5. package/package.json +36 -3
  6. package/templates/base/.claude/settings.json +36 -0
  7. package/templates/base/.codex/hooks.json +30 -0
  8. package/templates/base/.intent/hooks/intent-claude-gate.mjs +278 -0
  9. package/templates/base/.intent/hooks/intent-codex-gate.mjs +278 -0
  10. package/templates/base/AGENTS.md +28 -0
  11. package/templates/base/CLAUDE.md +1 -0
  12. package/templates/base/_gitignore +7 -0
  13. package/templates/base/apps/web/package.json +38 -0
  14. package/templates/base/apps/web/src/examples/form.test.tsx +10 -0
  15. package/templates/base/apps/web/src/examples/form.tsx +35 -0
  16. package/templates/base/apps/web/src/examples/greeting.ts +11 -0
  17. package/templates/base/apps/web/src/examples/hotkeys.test.tsx +7 -0
  18. package/templates/base/apps/web/src/examples/hotkeys.tsx +10 -0
  19. package/templates/base/apps/web/src/examples/query.test.tsx +18 -0
  20. package/templates/base/apps/web/src/examples/query.tsx +8 -0
  21. package/templates/base/apps/web/src/examples/table.test.tsx +7 -0
  22. package/templates/base/apps/web/src/examples/table.tsx +47 -0
  23. package/templates/base/apps/web/src/home.test.tsx +18 -0
  24. package/templates/base/apps/web/src/home.tsx +21 -0
  25. package/templates/base/apps/web/src/routeTree.gen.ts +68 -0
  26. package/templates/base/apps/web/src/router.tsx +20 -0
  27. package/templates/base/apps/web/src/routes/__root.tsx +26 -0
  28. package/templates/base/apps/web/src/routes/index.tsx +10 -0
  29. package/templates/base/apps/web/tsconfig.json +12 -0
  30. package/templates/base/apps/web/vite.config.ts +20 -0
  31. package/templates/base/package.json +28 -0
  32. package/templates/base/pnpm-workspace.yaml +7 -0
  33. package/templates/base/scripts/check-comments.ts +100 -0
  34. package/templates/base/tsconfig.json +13 -0
  35. package/templates/base/turbo.json +23 -0
  36. package/templates/tools/biome/.biome/effect-cleanup.grit +8 -0
  37. package/templates/tools/biome/.biome/handler-naming.grit +9 -0
  38. package/templates/tools/biome/.biome/no-default-props.grit +5 -0
  39. package/templates/tools/biome/.biome/no-derived-state-effect.grit +7 -0
  40. package/templates/tools/biome/.biome/no-double-assertion.grit +5 -0
  41. package/templates/tools/biome/.biome/no-fetch-in-effect.grit +6 -0
  42. package/templates/tools/biome/.biome/no-impure-render.grit +8 -0
  43. package/templates/tools/biome/.biome/no-inline-query-key.grit +7 -0
  44. package/templates/tools/biome/.biome/no-query-client-in-render.grit +7 -0
  45. package/templates/tools/biome/.biome/no-refetch-on-success.grit +7 -0
  46. package/templates/tools/biome/.biome/no-try-around-jsx.grit +9 -0
  47. package/templates/tools/biome/.biome/query-client-stale-time.grit +8 -0
  48. package/templates/tools/biome/biome.json +156 -0
  49. package/templates/tools/biome/package.json +8 -0
  50. package/templates/tools/lefthook/lefthook.yml +6 -0
  51. package/templates/tools/lefthook/package.json +5 -0
  52. package/templates/tools/shadcn/packages/ui/components.json +22 -0
  53. package/templates/tools/shadcn/packages/ui/package.json +33 -0
  54. package/templates/tools/shadcn/packages/ui/src/components/button.tsx +56 -0
  55. package/templates/tools/shadcn/packages/ui/src/lib/utils.ts +1 -0
  56. package/templates/tools/shadcn/packages/ui/src/styles/globals.css +87 -0
  57. package/templates/tools/shadcn/packages/ui/tsconfig.json +10 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Pei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,10 @@
1
- # Temporary Holding Version
1
+ # @draekien/create-d9-app
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Scaffolds a Turborepo monorepo.
4
+
5
+ ```sh
6
+ npm create @draekien/d9-app my-app
7
+ pnpm create @draekien/d9-app my-app
8
+ yarn create @draekien/d9-app my-app
9
+ bun create @draekien/d9-app my-app
10
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ import{builtinModules as e}from"node:module";import{spawnSync as t}from"node:child_process";import n from"node:fs";import r from"node:path";import{fileURLToPath as i}from"node:url";import{parseArgs as a,styleText as o}from"node:util";import s,{stdin as c,stdout as l}from"node:process";import u from"node:readline";var d=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);function f(e,n,r=!0){return t(`git`,n,{cwd:e,encoding:`utf8`,stdio:r?`pipe`:`inherit`})}function p(e){return f(e,[`rev-parse`,`--is-inside-work-tree`]).status===0}function m(e){let t=t=>f(e,[`config`,t]).stdout.trim()!==``,n=[];return t(`user.name`)||n.push(`-c`,`user.name=create-d9-app`),t(`user.email`)||n.push(`-c`,`user.email=create-d9-app@users.noreply.github.com`),n}function h(e){if(p(e))return{ok:!0,skipped:!0,output:``};let t=[[`init`],[`add`,`-A`],[...m(e),`commit`,`-m`,`chore: initial commit`]];for(let n of t){let t=f(e,n);if(t.status!==0)return{ok:!1,skipped:!1,output:`${t.stdout}${t.stderr}`}}return{ok:!0,skipped:!1,output:``}}function g({stdin:e,stdout:t,env:n,yes:r}){return!(!e.isTTY||!t.isTTY||n.CI||r)}function _({targetDir:e,cwd:t,install:n}){let i=r.relative(t,e),a=[];return i!==``&&a.push(`cd ${/\s/.test(i)?JSON.stringify(i):i}`),n||a.push(`pnpm install`),a.push(`pnpm dev`),`Next steps:\n${a.map(e=>` ${e}`).join(`
2
+ `)}\n`}const v={_gitignore:`.gitignore`};function y(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function b(e){let t=r.join(e,`tools`);return n.existsSync(t)?n.readdirSync(t,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name).sort():[]}function x(e,t){let n={...e};for(let[e,r]of Object.entries(t)){let t=n[e];n[e]=y(t)&&y(r)?x(t,r):r}return n}function S(e,t){n.mkdirSync(t,{recursive:!0});for(let i of n.readdirSync(e,{withFileTypes:!0})){let a=r.join(e,i.name),o=r.join(t,v[i.name]??i.name);if(i.isDirectory())S(a,o);else if(i.name===`package.json`&&n.existsSync(o)){let e=x(JSON.parse(n.readFileSync(o,`utf8`)),JSON.parse(n.readFileSync(a,`utf8`)));n.writeFileSync(o,`${JSON.stringify(e,null,2)}\n`)}else n.copyFileSync(a,o)}}function ee(e,t){for(let n of b(e))S(r.join(e,`tools`,n),t)}function te(e){return e.npm_config_user_agent?.startsWith(`pnpm/`)??!1}function ne(){return`create-d9-app must be run with pnpm. Run: pnpm create @draekien/d9-app <dir>`}const re=(()=>{let e=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g;return t=>{let n=0;for(e.lastIndex=0;e.test(t);)n+=1;return t.length-n}})(),ie=e=>e===12288||e>=65281&&e<=65376||e>=65504&&e<=65510,ae=e=>e===8987||e===9001||e>=12272&&e<=12287||e>=12289&&e<=12350||e>=12441&&e<=12543||e>=12549&&e<=12591||e>=12593&&e<=12686||e>=12688&&e<=12771||e>=12783&&e<=12830||e>=12832&&e<=12871||e>=12880&&e<=19903||e>=65040&&e<=65049||e>=65072&&e<=65106||e>=65108&&e<=65126||e>=65128&&e<=65131||e>=127488&&e<=127490||e>=127504&&e<=127547||e>=127552&&e<=127560||e>=131072&&e<=196605||e>=196608&&e<=262141,oe=/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y,se=/[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y,C=/(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/uy,ce=/\t{1,1000}/y,w=/[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F\u20E3?))*/uy,le=/(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y,ue=/\p{M}+/gu,de={limit:1/0,ellipsis:``},T=(e,t={},n={})=>{let r=t.limit??1/0,i=t.ellipsis??``,a=t?.ellipsisWidth??(i?T(i,de,n).width:0),o=n.controlWidth??0,s=n.tabWidth??8,c=n.emojiWidth??2,l=n.regularWidth??1,u=n.wideWidth??2,d=[[le,l],[oe,0],[se,o],[ce,s],[w,c],[C,u]],f=0,p=0,m=e.length,h=0,g=!1,_=m,v=Math.max(0,r-a),y=0,b=0,x=0,S=0;outer:for(;;){if(b>y||p>=m&&p>f){let t=e.slice(y,b)||e.slice(f,p);h=0;for(let e of t.replaceAll(ue,``)){let t=e.codePointAt(0)||0;if(S=ie(t)?2:ae(t)?u:l,x+S>v&&(_=Math.min(_,Math.max(y,f)+h)),x+S>r){g=!0;break outer}h+=e.length,x+=S}y=b=0}if(p>=m)break outer;for(let t=0,n=d.length;t<n;t++){let[n,i]=d[t];if(n.lastIndex=p,n.test(e)){if(h=n===C?re(e.slice(p,n.lastIndex)):n===w?1:n.lastIndex-p,S=h*i,x+S>v&&(_=Math.min(_,p+Math.floor((v-x)/i))),x+S>r){g=!0;break outer}x+=S,y=f,b=p,p=f=n.lastIndex;continue outer}}p+=1}return{width:g?v:x,index:g?_:m,truncated:g,ellipsed:g&&r>=a}},fe={limit:1/0,ellipsis:``,ellipsisWidth:0},E=(e,t={})=>T(e,fe,t).width,D=`]8;;`,O=RegExp(`(?:\\[(?<code>\\d+)m|\\${D}(?<uri>.*))`,`y`),pe=e=>{if(e>=30&&e<=37||e>=90&&e<=97)return 39;if(e>=40&&e<=47||e>=100&&e<=107)return 49;if(e===1||e===2)return 22;if(e===3)return 23;if(e===4)return 24;if(e===7)return 27;if(e===8)return 28;if(e===9)return 29;if(e===0)return 0},k=e=>`[${e}m`,A=e=>`${D}${e}`,j=(e,t,n)=>{let r=t[Symbol.iterator](),i=!1,a=!1,o=e.at(-1),s=o===void 0?0:E(o),c=r.next(),l=r.next(),u=0;for(;!c.done;){let o=c.value,d=E(o);s+d<=n?e[e.length-1]+=o:(e.push(o),s=0),(o===`\x1B`||o===`›`)&&(i=!0,a=t.startsWith(D,u+1)),i?a?o===`\x07`&&(i=!1,a=!1):o===`m`&&(i=!1):(s+=d,s===n&&!l.done&&(e.push(``),s=0)),c=l,l=r.next(),u+=o.length}o=e.at(-1),!s&&o!==void 0&&o.length&&e.length>1&&(e[e.length-2]+=e.pop())},me=e=>{let t=e.split(` `),n=t.length;for(;n&&!E(t[n-1]);)n--;return n===t.length?e:t.slice(0,n).join(` `)+t.slice(n).join(``)},he=(e,t,n={})=>{if(n.trim!==!1&&e.trim()===``)return``;let r=``,i,a,o=e.split(` `),s=[``],c=0;for(let e=0;e<o.length;e++){let r=o[e];if(n.trim!==!1){let e=s.at(-1)??``,t=e.trimStart();e.length!==t.length&&(s[s.length-1]=t,c=E(t))}e!==0&&(c>=t&&(n.wordWrap===!1||n.trim===!1)&&(s.push(``),c=0),(c||n.trim===!1)&&(s[s.length-1]+=` `,c++));let i=E(r);if(n.hard&&i>t){let e=t-c,n=1+Math.floor((i-e-1)/t);Math.floor((i-1)/t)<n&&s.push(``),j(s,r,t),c=E(s.at(-1)??``)}else{if(c+i>t&&c&&i){if(n.wordWrap===!1&&c<t){j(s,r,t),c=E(s.at(-1)??``);continue}s.push(``),c=0}c+i>t&&n.wordWrap===!1?(j(s,r,t),c=E(s.at(-1)??``)):(s[s.length-1]+=r,c+=i)}}n.trim!==!1&&(s=s.map(e=>me(e)));let l=s.join(`
3
+ `),u=!1;for(let e=0;e<l.length;e++){let t=l[e];if(r+=t,u)u=!1;else if(u=t>=`\ud800`&&t<=`\udbff`,u)continue;if(t===`\x1B`||t===`›`){O.lastIndex=e+1;let t=O.exec(l)?.groups;if(t?.code!==void 0){let e=Number.parseFloat(t.code);i=e===39?void 0:e}else t?.uri!==void 0&&(a=t.uri.length===0?void 0:t.uri)}if(l[e+1]===`
4
+ `){a&&(r+=A(``));let e=i?pe(i):void 0;i&&e&&(r+=k(e))}else t===`
5
+ `&&(i&&pe(i)&&(r+=k(i)),a&&(r+=A(a)))}return r},ge=/\r?\n/;function M(e,t,n){return String(e).normalize().split(ge).map(e=>he(e,t,n)).join(`
6
+ `)}var N=(/* @__PURE__ */ d(((e,t)=>{let n={to(e,t){return t?`[${t+1};${e+1}H`:`[${e+1}G`},move(e,t){let n=``;return e<0?n+=`[${-e}D`:e>0&&(n+=`[${e}C`),t<0?n+=`[${-t}A`:t>0&&(n+=`[${t}B`),n},up:(e=1)=>`[${e}A`,down:(e=1)=>`[${e}B`,forward:(e=1)=>`[${e}C`,backward:(e=1)=>`[${e}D`,nextLine:(e=1)=>`\x1B[E`.repeat(e),prevLine:(e=1)=>`\x1B[F`.repeat(e),left:`\x1B[G`,hide:`\x1B[?25l`,show:`\x1B[?25h`,save:`\x1B7`,restore:`\x1B8`};t.exports={cursor:n,scroll:{up:(e=1)=>`\x1B[S`.repeat(e),down:(e=1)=>`\x1B[T`.repeat(e)},erase:{screen:`\x1B[2J`,up:(e=1)=>`\x1B[1J`.repeat(e),down:(e=1)=>`\x1B[J`.repeat(e),line:`\x1B[2K`,lineEnd:`\x1B[K`,lineStart:`\x1B[1K`,lines(e){let t=``;for(let r=0;r<e;r++)t+=this.line+(r<e-1?n.up():``);return e&&(t+=n.left),t}},beep:`\x07`}})))();const P={actions:/* @__PURE__ */ new Set([`up`,`down`,`left`,`right`,`space`,`enter`,`cancel`]),aliases:/* @__PURE__ */ new Map([[`k`,`up`],[`j`,`down`],[`h`,`left`],[`l`,`right`],[``,`cancel`],[`escape`,`cancel`]]),messages:{cancel:`Canceled`,error:`Something went wrong`},withGuide:!0,accessible:void 0,date:{monthNames:[`January`,`February`,`March`,`April`,`May`,`June`,`July`,`August`,`September`,`October`,`November`,`December`],messages:{required:`Please enter a valid date`,invalidMonth:`There are only 12 months in a year`,invalidDay:(e,t)=>`There are only ${e} days in ${t}`,afterMin:e=>`Date must be on or after ${e.toISOString().slice(0,10)}`,beforeMax:e=>`Date must be on or before ${e.toISOString().slice(0,10)}`}}};function _e(e){if(e!==void 0)return e;if(P.accessible!==void 0)return P.accessible;let t=process.env.ACCESSIBLE;return t!==void 0&&t!==``&&t!==`0`&&t!==`false`}function F(e,t){if(typeof e==`string`)return P.aliases.get(e)===t;for(let n of e)if(n!==void 0&&F(n,t))return!0;return!1}function ve(e,t){if(e===t)return;let n=e.split(`
7
+ `),r=t.split(`
8
+ `),i=Math.max(n.length,r.length),a=[];for(let e=0;e<i;e++)n[e]!==r[e]&&a.push(e);return{lines:a,numLinesBefore:n.length,numLinesAfter:r.length,numLines:i}}globalThis.process.platform.startsWith(`win`);const I=Symbol(`clack:cancel`);function ye(e){return e===I}function L(e,t){let n=e;n.isTTY&&n.setRawMode(t)}const R=e=>`columns`in e&&typeof e.columns==`number`?e.columns:80,be=e=>`rows`in e&&typeof e.rows==`number`?e.rows:20;function xe(e,t,n,r=n,i=n,a){return M(t,R(e??l)-n.length,{hard:!0,trim:!1}).split(`
9
+ `).map((e,t,o)=>{let s=a?a(e,t):e;return t===0?`${r}${s}`:t===o.length-1?`${i}${s}`:`${n}${s}`}).join(`
10
+ `)}function Se(e,t){if(`~standard`in e){let n=e[`~standard`].validate(t);return n instanceof Promise?n.then(e=>e.issues?.at(0)?.message):n.issues?.at(0)?.message}return e(t)}var z=class{input;output;_abortSignal;rl;opts;_render;_track=!1;_prevFrame=``;_subscribers=/* @__PURE__ */ new Map;_cursor=0;state=`initial`;error=``;value;userInput=``;get accessible(){return _e(this.opts.accessible)}constructor(e,t=!0){let{input:n=c,output:r=l,render:i,signal:a,...o}=e;this.opts=o,this.onKeypress=this.onKeypress.bind(this),this.close=this.close.bind(this),this.render=this.render.bind(this),this._render=i.bind(this),this._track=t,this._abortSignal=a,this.input=n,this.output=r}unsubscribe(){this._subscribers.clear()}setSubscriber(e,t){let n=this._subscribers.get(e)??[];n.push(t),this._subscribers.set(e,n)}on(e,t){this.setSubscriber(e,{cb:t})}once(e,t){this.setSubscriber(e,{cb:t,once:!0})}emit(e,...t){let n=this._subscribers.get(e)??[],r=[];for(let e of n)e.cb(...t),e.once&&r.push(()=>n.splice(n.indexOf(e),1));for(let e of r)e()}prompt(){return new Promise(e=>{if(this._abortSignal){if(this._abortSignal.aborted)return this.state=`cancel`,this.close(),e(I);this._abortSignal.addEventListener(`abort`,()=>{this.state=`cancel`,this.close()},{once:!0})}this.rl=u.createInterface({input:this.input,tabSize:2,prompt:``,escapeCodeTimeout:50,terminal:!0}),this.rl.prompt(),this.opts.initialUserInput!==void 0&&this._setUserInput(this.opts.initialUserInput,!0),this.input.on(`keypress`,this.onKeypress),L(this.input,!0),this.output.on(`resize`,this.render),this.render(),this.once(`submit`,()=>{this.output.write(N.cursor.show),this.output.off(`resize`,this.render),L(this.input,!1),e(this.value)}),this.once(`cancel`,()=>{this.output.write(N.cursor.show),this.output.off(`resize`,this.render),L(this.input,!1),e(I)})})}_isActionKey(e,t){return e===` `}_shouldSubmit(e,t){return!0}_setValue(e){this.value=e,this.emit(`value`,this.value)}_setUserInput(e,t){this.userInput=e??``,this.emit(`userInput`,this.userInput),t&&this._track&&this.rl&&(this.rl.write(this.userInput),this._cursor=this.rl.cursor)}_clearUserInput(){this.rl?.write(null,{ctrl:!0,name:`u`}),this._setUserInput(``)}async onKeypress(e,t){if(this.state!==`validating`){if(this._track&&t.name!==`return`&&(t.name&&this._isActionKey(e,t)&&this.rl?.write(null,{ctrl:!0,name:`h`}),this._cursor=this.rl?.cursor??0,this._setUserInput(this.rl?.line)),this.state===`error`&&(this.state=`active`),t?.name&&(!this._track&&P.aliases.has(t.name)&&this.emit(`cursor`,P.aliases.get(t.name)),P.actions.has(t.name)&&this.emit(`cursor`,t.name)),e&&(e.toLowerCase()===`y`||e.toLowerCase()===`n`)&&this.emit(`confirm`,e.toLowerCase()===`y`),this.emit(`key`,e,t),t?.name===`return`&&this._shouldSubmit(e,t)){if(this.opts.validate){let e=Se(this.opts.validate,this.value),t;e instanceof Promise?(this.state=`validating`,this.render(),t=await e):t=e,t&&(this.error=t instanceof Error?t.message:t,this.state=`error`,this.rl?.write(this.userInput))}this.state!==`error`&&(this.state=`submit`)}F([e,t?.name,t?.sequence],`cancel`)&&(this.state=`cancel`),(this.state===`submit`||this.state===`cancel`)&&this.emit(`finalize`),this.render(),(this.state===`submit`||this.state===`cancel`)&&this.close()}}close(){this.input.unpipe(),this.input.removeListener(`keypress`,this.onKeypress),this.output.write(`
11
+ `),L(this.input,!1),this.rl?.close(),this.rl=void 0,this.emit(`${this.state}`,this.value),this.unsubscribe()}restoreCursor(){let e=M(this._prevFrame,process.stdout.columns,{hard:!0,trim:!1}).split(`
12
+ `).length-1;this.output.write(N.cursor.move(-999,e*-1))}render(){let e=M(this._render(this)??``,process.stdout.columns,{hard:!0,trim:!1});if(e!==this._prevFrame){if(this.state===`initial`)this.output.write(N.cursor.hide);else{let t=ve(this._prevFrame,e),n=be(this.output);if(this.restoreCursor(),t){let r=Math.max(0,t.numLinesAfter-n),i=Math.max(0,t.numLinesBefore-n),a=t.lines.find(e=>e>=r);if(a===void 0){this._prevFrame=e;return}if(t.lines.length===1){this.output.write(N.cursor.move(0,a-i)),this.output.write(N.erase.lines(1));let t=e.split(`
13
+ `);this.output.write(t[a]),this._prevFrame=e,this.output.write(N.cursor.move(0,t.length-a-1));return}if(t.lines.length>1){if(r<i)a=r;else{let e=a-i;e>0&&this.output.write(N.cursor.move(0,e))}this.output.write(N.erase.down());let t=e.split(`
14
+ `).slice(a);this.output.write(t.join(`
15
+ `)),this._prevFrame=e;return}}this.output.write(N.erase.down())}this.output.write(e),this.state===`initial`&&(this.state=`active`),this._prevFrame=e}}},Ce=class extends z{get cursor(){return+!this.value}get _value(){return this.cursor===0}constructor(e){super(e,!1),this.value=!!e.initialValue,this.on(`userInput`,()=>{this.value=this._value}),this.on(`confirm`,e=>{this.output.write(N.cursor.move(0,-1)),this.value=e,this.state=`submit`,this.close()}),this.on(`cursor`,()=>{this.value=!this.value})}},we=class extends z{get userInputWithCursor(){if(this.state===`submit`)return this.userInput;let e=this.userInput;if(this.cursor>=e.length)return`${this.userInput}\u2588`;let t=e.slice(0,this.cursor),n=e.slice(this.cursor,this.cursor+1),r=e.slice(this.cursor+1);return`${t}${o(`inverse`,n)}${r}`}get cursor(){return this._cursor}constructor(e){super({...e,initialUserInput:e.initialUserInput??e.initialValue}),this.on(`userInput`,e=>{this._setValue(e)}),this.on(`finalize`,()=>{this.value||=e.defaultValue,this.value===void 0&&(this.value=``)})}};function Te(){return s.platform===`win32`?!!s.env.CI||!!s.env.WT_SESSION||!!s.env.TERMINUS_SUBLIME||s.env.ConEmuTask===`{cmd::Cmder}`||s.env.TERM_PROGRAM===`Terminus-Sublime`||s.env.TERM_PROGRAM===`vscode`||s.env.TERM===`xterm-256color`||s.env.TERM===`alacritty`||s.env.TERMINAL_EMULATOR===`JetBrains-JediTerm`:s.env.TERM!==`linux`}const Ee=Te(),B=(e,t)=>Ee?e:t,V=B(`◆`,`*`),De=B(`■`,`x`),Oe=B(`▲`,`x`),H=B(`◇`,`o`),U=B(`│`,`|`),W=B(`└`,`—`),G=B(`●`,`>`),K=B(`○`,` `),q=B(`─`,`-`),ke=B(`╮`,`+`),Ae=B(`├`,`+`),je=B(`╯`,`+`),Me=B(`╰`,`+`),J=e=>{switch(e){case`initial`:case`active`:return o(`cyan`,V);case`cancel`:return o(`red`,De);case`error`:return o(`yellow`,Oe);case`submit`:return o(`green`,H);case`validating`:return o(`dim`,V)}},Y=e=>{let t=e.active??`Yes`,n=e.inactive??`No`;return new Ce({active:t,inactive:n,signal:e.signal,input:e.input,output:e.output,initialValue:e.initialValue??!0,render(){let r=e.withGuide??P.withGuide,i=`${J(this.state)} `,a=r?`${o(`gray`,U)} `:``,s=xe(e.output,e.message,a,i),c=`${r?`${o(`gray`,U)}
16
+ `:``}${s}
17
+ `,l=this.value?t:n;switch(this.state){case`submit`:return`${c}${r?`${o(`gray`,U)} `:``}${o(`dim`,l)}`;case`cancel`:return`${c}${r?`${o(`gray`,U)} `:``}${o([`strikethrough`,`dim`],l)}${r?`
18
+ ${o(`gray`,U)}`:``}`;default:{let i=r?`${o(`cyan`,U)} `:``,a=r?o(`cyan`,W):``;return`${c}${i}${this.value?`${o(`green`,G)} ${t}`:`${o(`dim`,K)} ${o(`dim`,t)}`}${e.vertical?r?`
19
+ ${o(`cyan`,U)} `:`
20
+ `:` ${o(`dim`,`/`)} `}${this.value?`${o(`dim`,K)} ${o(`dim`,n)}`:`${o(`green`,G)} ${n}`}
21
+ ${a}
22
+ `}}}}).prompt()};`${o(`dim`,`↑/↓`)}`,`${o(`dim`,`Space:`)}`,`${o(`dim`,`Enter:`)}`;const Ne=(e=``,t)=>{let n=t?.output??process.stdout,r=t?.withGuide??P.withGuide?`${o(`gray`,W)} `:``;n.write(`${r}${o(`red`,e)}
23
+
24
+ `)},Pe=e=>e,Fe=(e,t,n)=>{let r={hard:!0,trim:!1},i=M(e,t,r).split(`
25
+ `),a=i.reduce((e,t)=>Math.max(E(t),e),0);return M(e,t-(i.map(n).reduce((e,t)=>Math.max(E(t),e),0)-a),r)},Ie=(e=``,t=``,n)=>{let r=n?.output??s.stdout,i=n?.withGuide??P.withGuide,a=n?.format??Pe,c=[``,...Fe(e,R(r)-6,a).split(`
26
+ `).map(a),``],l=E(t),u=Math.max(c.reduce((e,t)=>{let n=E(t);return n>e?n:e},0),l)+2,d=c.map(e=>`${o(`gray`,U)} ${e}${` `.repeat(u-E(e))}${o(`gray`,U)}`).join(`
27
+ `),f=i?`${o(`gray`,U)}
28
+ `:``,p=i?Ae:Me;r.write(`${f}${o(`green`,H)} ${o(`reset`,t)} ${o(`gray`,q.repeat(Math.max(u-l-1,1))+ke)}
29
+ ${d}
30
+ ${o(`gray`,p+q.repeat(u+2)+je)}
31
+ `)};`${o(`dim`,`↑/↓`)}`,`${o(`dim`,`Enter:`)}`,`${o(`gray`,U)}`;const Le=e=>new we({validate:e.validate,placeholder:e.placeholder,defaultValue:e.defaultValue,initialValue:e.initialValue,output:e.output,signal:e.signal,input:e.input,render(){let t=e?.withGuide??P.withGuide,n=`${`${t?`${o(`gray`,U)}
32
+ `:``}${J(this.state)} `}${e.message}
33
+ `,r=e.placeholder&&e.placeholder.length>0?o(`inverse`,e.placeholder[0])+o(`dim`,e.placeholder.slice(1)):o([`inverse`,`hidden`],`_`),i=this.userInput?this.userInputWithCursor:r,a=this.value??``;switch(this.state){case`validating`:{let e=t?`${o(`cyan`,U)} `:``,r=t?o(`cyan`,W):``;return`${n}${e}${o(`dim`,i)}
34
+ ${r} ${o(`dim`,`Validating...`)}
35
+ `}case`error`:{let e=this.error?` ${o(`yellow`,this.error)}`:``,r=t?`${o(`yellow`,U)} `:``,a=t?o(`yellow`,W):``;return`${n.trim()}
36
+ ${r}${i}
37
+ ${a}${e}
38
+ `}case`submit`:{let e=a?`${t?` `:``}${o(`dim`,a)}`:``;return`${n}${t?o(`gray`,U):``}${e}`}case`cancel`:{let e=a?` ${o([`strikethrough`,`dim`],a)}`:``,r=t?o(`gray`,U):``;return`${n}${r}${e}${a.trim()?`
39
+ ${r}`:``}`}default:return`${n}${t?`${o(`cyan`,U)} `:``}${i}
40
+ ${t?o(`cyan`,W):``}
41
+ `}}}).prompt(),X=`my-app`;function Z(e){return{dir:e.dir??`my-app`,git:!e[`no-git`],install:!e[`no-install`]}}function Re(e){if(!(e.yes||e.dir))return`Missing required <dir>. Pass a project directory or --yes to use the default.`}function ze(e){let t=[`pnpm create @draekien/d9-app`,/\s/.test(e.dir)?JSON.stringify(e.dir):e.dir,`--yes`];return e.git||t.push(`--no-git`),e.install||t.push(`--no-install`),t.join(` `)}function Be(e,{output:t}){Ie(ze(e),`To create a project with the same options`,{output:t})}async function Ve(e,{input:t,output:n}){let r=Z(e);return{dir:e.dir===void 0?Q(await Le({message:`Where should the project be created?`,placeholder:X,defaultValue:X,input:t,output:n})):r.dir,git:e[`no-git`]?r.git:Q(await Y({message:`Initialise a git repository?`,initialValue:!0,input:t,output:n})),install:e[`no-install`]?r.install:Q(await Y({message:`Install dependencies?`,initialValue:!0,input:t,output:n}))}}function Q(e){return ye(e)&&(Ne(`Operation cancelled.`),process.exit(0)),e}const He=/* @__PURE__ */ new Set([`.git`,`.gitignore`,`.idea`,`.vscode`,`LICENSE`,`.DS_Store`]),Ue=/* @__PURE__ */ new Set([`node_modules`,`favicon.ico`]);function We(e){return r.basename(r.resolve(e))}function Ge(t){if(t.length===0)return`name length must be greater than zero`;if(t.length>214)return`name length must be at most 214 characters`;if(t!==t.trim())return`name cannot contain leading or trailing spaces`;if(t!==t.toLowerCase())return`name cannot contain uppercase letters`;if(t.startsWith(`.`))return`name cannot start with a period`;if(t.startsWith(`_`))return`name cannot start with an underscore`;if(encodeURIComponent(t)!==t||/[~)('!*]/.test(t))return`name can only contain URL-friendly characters`;if(Ue.has(t)||e.includes(t))return`name is reserved`}function Ke(e){return n.existsSync(e)?n.statSync(e).isDirectory()?n.readdirSync(e).filter(e=>!He.has(e)):[r.basename(e)]:[]}const qe=`Usage: create-d9-app <dir> [options]
42
+
43
+ Arguments:
44
+ <dir> Directory to create the project in; "." uses the current directory
45
+
46
+ Options:
47
+ --yes Accept every default and skip all prompts
48
+ --no-git Do not initialise a git repository or make the initial commit
49
+ --no-install Do not run pnpm install
50
+ --help Show this message
51
+ `,Je=i(new URL(`../templates`,import.meta.url)),Ye=r.join(Je,`base`);function Xe(e,t){n.mkdirSync(t,{recursive:!0});for(let i of n.readdirSync(e,{withFileTypes:!0})){let a=r.join(e,i.name),o=r.join(t,v[i.name]??i.name);i.isDirectory()?Xe(a,o):n.copyFileSync(a,o)}}function Ze(e,t){let i=r.join(e,`package.json`),a=JSON.parse(n.readFileSync(i,`utf8`));a.name=t,n.writeFileSync(i,`${JSON.stringify(a,null,2)}\n`)}function $(e){process.stderr.write(`${e}\n`),process.exit(1)}async function Qe(){let{values:e,positionals:n}=a({allowPositionals:!0,options:{yes:{type:`boolean`},"no-git":{type:`boolean`},"no-install":{type:`boolean`},help:{type:`boolean`}}});if(e.help){process.stdout.write(qe);return}te(process.env)||$(ne());let i={dir:n[0],yes:e.yes,"no-git":e[`no-git`],"no-install":e[`no-install`]},o=g({stdin:process.stdin,stdout:process.stdout,env:process.env,yes:!!e.yes}),s=o?void 0:Re(i);s&&(process.stderr.write(`${s}
52
+ ${qe}`),process.exit(1));let c=o?await Ve(i,{input:process.stdin,output:process.stdout}):Z(i),l=r.resolve(c.dir),u=We(l),d=Ge(u);d&&$(`Invalid package name "${u}" derived from the directory: ${d}.`);let f=Ke(l);if(f.length>0&&$(`${l} is not empty. Remove these entries or choose another directory:\n${f.map(e=>` ${e}`).join(`
53
+ `)}`),Xe(Ye,l),ee(Je,l),Ze(l,u),c.install){let e=t(`pnpm install`,{cwd:l,stdio:`inherit`,shell:!0});e.status!==0&&process.exit(e.status??1)}if(c.git){let e=h(l);e.ok||$(`git initialisation failed:\n${e.output}`)}process.stdout.write(`Created ${l}\n`),process.stdout.write(_({targetDir:l,cwd:process.cwd(),install:c.install})),o&&Be(c,{input:process.stdin,output:process.stdout})}await Qe();export{};
package/index.js ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ const major = Number.parseInt(process.versions.node.split(".")[0], 10)
3
+
4
+ if (major < 22) {
5
+ console.error(
6
+ `create-d9-app requires Node.js 22 or newer. You are running ${process.versions.node}.`,
7
+ )
8
+ process.exit(1)
9
+ }
10
+
11
+ await import("./dist/index.js")
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "@draekien/create-d9-app",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "Scaffold a Turborepo monorepo with the draekien default stack",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/draekien/create-d9-app.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "bin": {
13
+ "create-d9-app": "index.js"
14
+ },
15
+ "files": [
16
+ "index.js",
17
+ "dist",
18
+ "templates",
19
+ "!templates/biome.json"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "engines": {
25
+ "node": ">=22"
26
+ },
27
+ "devDependencies": {
28
+ "@clack/prompts": "1.8.1",
29
+ "@types/node": "22.20.5",
30
+ "rolldown": "1.2.13",
31
+ "typescript": "7.0.2",
32
+ "vitest": "5.0.3"
33
+ },
34
+ "scripts": {
35
+ "build": "rolldown -c rolldown.config.ts",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest run"
38
+ }
6
39
  }
@@ -0,0 +1,36 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume|clear|compact",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node",
10
+ "args": [
11
+ "${CLAUDE_PROJECT_DIR}/.intent/hooks/intent-claude-gate.mjs"
12
+ ],
13
+ "timeout": 10,
14
+ "statusMessage": "Loading Intent skill catalog"
15
+ }
16
+ ]
17
+ }
18
+ ],
19
+ "PreToolUse": [
20
+ {
21
+ "matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit",
22
+ "hooks": [
23
+ {
24
+ "type": "command",
25
+ "command": "node",
26
+ "args": [
27
+ "${CLAUDE_PROJECT_DIR}/.intent/hooks/intent-claude-gate.mjs"
28
+ ],
29
+ "timeout": 10,
30
+ "statusMessage": "Checking Intent guidance"
31
+ }
32
+ ]
33
+ }
34
+ ]
35
+ }
36
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume|clear|compact",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node \"$(git rev-parse --show-toplevel)/.intent/hooks/intent-codex-gate.mjs\"",
10
+ "timeout": 10,
11
+ "statusMessage": "Loading Intent skill catalog"
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "PreToolUse": [
17
+ {
18
+ "matcher": "Bash|apply_patch|Edit|Write",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "node \"$(git rev-parse --show-toplevel)/.intent/hooks/intent-codex-gate.mjs\"",
23
+ "timeout": 10,
24
+ "statusMessage": "Checking Intent guidance"
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }
30
+ }
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env node
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'
3
+ import { execFileSync } from 'node:child_process'
4
+ import { tmpdir } from 'node:os'
5
+ import { dirname, join, relative, sep } from 'node:path'
6
+ import { createHash } from 'node:crypto'
7
+ import { performance } from 'node:perf_hooks'
8
+
9
+ const AGENT = "claude"
10
+ const CATALOG_COMMAND = "pnpm exec intent list --json --no-notices"
11
+ const LOAD_COMMAND = "pnpm exec intent load <package>#<skill>"
12
+ const EDIT_TOOLS = new Set(["Edit","MultiEdit","NotebookEdit","Write"])
13
+ const GATE_DENY_REASON = "Blocked: check TanStack guidance before editing. If a listed skill matches, load it, then retry the edit."
14
+ const INTENT_INVOCATION_PATTERN = /(?:^|&&|\|\||;|\|)\s*((?:bunx\s+--no-install\s+--package\s+@tanstack\/intent\s+intent)|(?:npm\s+exec\s+--no\s+--\s+intent)|(?:yarn\s+exec\s+intent)|(?:bunx\s+@tanstack\/intent(?:@latest)?)|(?:pnpm\s+exec\s+intent)|(?:pnpm\s+dlx\s+@tanstack\/intent(?:@latest)?)|(?:npx\s+@tanstack\/intent(?:@latest)?)|(?:yarn\s+dlx\s+@tanstack\/intent(?:@latest)?)|(?:(?:[^\s|;&]*[\\/])?intent))\s+(list|load)(?:\s+([^\s|;&]+))?/i
15
+
16
+ try {
17
+ await main()
18
+ } catch {
19
+ }
20
+
21
+ process.exit(0)
22
+
23
+ async function main() {
24
+ const event = readEventFromStdin()
25
+
26
+ if (isSessionStartEvent(event)) {
27
+ const stateFile = stateFileForEvent(event)
28
+ const additionalContext = await createSessionCatalogContext(rootForEvent(event))
29
+ appendObservation(stateFile, { action: 'list', raw: CATALOG_COMMAND })
30
+ if (additionalContext) {
31
+ process.stdout.write(JSON.stringify(sessionStartOutput(additionalContext)))
32
+ }
33
+ return
34
+ }
35
+
36
+ const stateFile = stateFileForEvent(event)
37
+ const observation = observationFromEvent(event)
38
+
39
+ if (observation) {
40
+ appendObservation(stateFile, observation)
41
+ }
42
+
43
+ const toolName = event?.tool_name ?? event?.toolName
44
+ if (typeof toolName === 'string' && EDIT_TOOLS.has(toolName) && !hasIntentCheck(stateFile)) {
45
+ process.stdout.write(JSON.stringify(denyOutput()))
46
+ }
47
+ }
48
+
49
+ function readEventFromStdin() {
50
+ try {
51
+ return JSON.parse(readFileSync(0, 'utf8'))
52
+ } catch {
53
+ return {}
54
+ }
55
+ }
56
+
57
+ function isSessionStartEvent(event) {
58
+ return (event?.hook_event_name ?? event?.hookEventName) === 'SessionStart'
59
+ }
60
+
61
+ function rootForEvent(event) {
62
+ return typeof event?.cwd === 'string' && event.cwd ? event.cwd : process.cwd()
63
+ }
64
+
65
+ async function createSessionCatalogContext(root) {
66
+ try {
67
+ const start = performance.now()
68
+ const localCli = resolveLocalIntentCli(root)
69
+ const result = readIntentList(root, localCli)
70
+ const durationMs = performance.now() - start
71
+ console.error(
72
+ `[intent-${AGENT}-session-catalog] listIntentSkills found ${result.skills.length} skills from ${result.packages.length} packages in ${formatDuration(durationMs)} (packageJsonReadCount=${result.debug?.scan.packageJsonReadCount ?? 'unknown'})`,
73
+ )
74
+ return formatSessionCatalog(result, loadCommandForLocalCli(root, localCli))
75
+ } catch {
76
+ return ''
77
+ }
78
+ }
79
+
80
+ // The package-manager runner in CATALOG_COMMAND (npx, pnpm dlx, ...) resolves
81
+ // @tanstack/intent@latest against the registry on every run, which costs one
82
+ // to four seconds per session start. When the project has the package
83
+ // installed, run its CLI directly with this Node binary instead. Returns the
84
+ // CLI path and the node_modules directory it was found in, or null.
85
+ function resolveLocalIntentCli(root) {
86
+ let dir = root
87
+ let prev
88
+ while (dir !== prev) {
89
+ const nodeModulesDir = join(dir, 'node_modules')
90
+ const packageJsonPath = join(nodeModulesDir, '@tanstack', 'intent', 'package.json')
91
+ if (existsSync(packageJsonPath)) {
92
+ try {
93
+ const bin = JSON.parse(readFileSync(packageJsonPath, 'utf8')).bin
94
+ const relativeBin = typeof bin === 'string' ? bin : bin && bin.intent
95
+ if (typeof relativeBin === 'string') {
96
+ const cli = join(dirname(packageJsonPath), relativeBin)
97
+ if (existsSync(cli)) return { cli, nodeModulesDir }
98
+ }
99
+ } catch {
100
+ }
101
+ return null
102
+ }
103
+ prev = dir
104
+ dir = dirname(dir)
105
+ }
106
+ return null
107
+ }
108
+
109
+ // The load command shown to the agent. When the catalog came from a local
110
+ // install that also has a bin shim, suggest that shim (same installation, no
111
+ // registry lookup); otherwise suggest the package-manager runner.
112
+ function loadCommandForLocalCli(root, localCli) {
113
+ if (!localCli) return LOAD_COMMAND
114
+ const bin = join(localCli.nodeModulesDir, '.bin', 'intent')
115
+ if (!existsSync(bin)) return LOAD_COMMAND
116
+ return relative(root, bin).split(sep).join('/') + ' load <package>#<skill>'
117
+ }
118
+
119
+ function readIntentList(root, localCli) {
120
+ const options = {
121
+ cwd: root,
122
+ encoding: 'utf8',
123
+ env: { ...process.env, INTENT_AUDIENCE: 'agent' },
124
+ maxBuffer: 1024 * 1024,
125
+ stdio: ['ignore', 'pipe', 'pipe'],
126
+ timeout: 9000,
127
+ }
128
+ const output = localCli
129
+ ? execFileSync(process.execPath, [localCli.cli, 'list', '--json', '--no-notices'], options)
130
+ : execFileSync(CATALOG_COMMAND, { ...options, shell: true })
131
+ return JSON.parse(output)
132
+ }
133
+
134
+ function formatDuration(durationMs) {
135
+ return `${durationMs.toFixed(1)}ms`
136
+ }
137
+
138
+ function formatSessionCatalog(result, loadCommand) {
139
+ if (!Array.isArray(result.skills) || result.skills.length === 0) return ''
140
+
141
+ return [
142
+ 'TanStack Intent skills are available in this repository.',
143
+ '',
144
+ 'These are Intent skills, not native agent skills.',
145
+ 'Load a matching skill with: `' + loadCommand + '`.',
146
+ '',
147
+ 'Before substantial work, check whether one listed skill clearly matches the user task. If one clearly matches, load that full skill guidance with the Intent CLI before proceeding.',
148
+ '',
149
+ 'If no skill clearly matches, continue normally. Do not load a skill just to improve phrasing or gather nonessential context.',
150
+ '',
151
+ 'Available local Intent skills:',
152
+ formatSkillCatalog(result.skills),
153
+ formatWarnings(result),
154
+ ]
155
+ .filter(Boolean)
156
+ .join('\n')
157
+ }
158
+
159
+ function formatSkillCatalog(skills) {
160
+ return skills
161
+ .map((skill) => `- ${skill.use}: ${normalizeDescription(skill.description)}`)
162
+ .join('\n')
163
+ }
164
+
165
+ function normalizeDescription(description) {
166
+ return typeof description === 'string' ? description.replace(/\s+/g, ' ').trim() : ''
167
+ }
168
+
169
+ function formatWarnings(result) {
170
+ const warnings = [
171
+ ...(Array.isArray(result.warnings) ? result.warnings : []),
172
+ ...(Array.isArray(result.conflicts)
173
+ ? result.conflicts.map(
174
+ (conflict) =>
175
+ `Version conflict for ${conflict.packageName}; using ${conflict.chosen.version}`,
176
+ )
177
+ : []),
178
+ ]
179
+
180
+ if (warnings.length === 0) return ''
181
+ return `\nWarnings:\n${warnings.map((warning) => `- ${warning}`).join('\n')}`
182
+ }
183
+
184
+ function sessionStartOutput(additionalContext) {
185
+ if (AGENT === 'copilot') {
186
+ return { additionalContext }
187
+ }
188
+
189
+ return {
190
+ hookSpecificOutput: {
191
+ hookEventName: 'SessionStart',
192
+ additionalContext,
193
+ },
194
+ }
195
+ }
196
+
197
+ function stateFileForEvent(event) {
198
+ const sessionId = typeof event?.session_id === 'string' ? event.session_id : 'unknown'
199
+ const cwd = typeof event?.cwd === 'string' ? event.cwd : process.cwd()
200
+ const key = createHash('sha256').update(AGENT + '\0' + cwd + '\0' + sessionId).digest('hex')
201
+ return join(tmpdir(), 'tanstack-intent-hooks', key + '.jsonl')
202
+ }
203
+
204
+ function parseIntentInvocation(command) {
205
+ if (typeof command !== 'string') return undefined
206
+ const match = command.match(INTENT_INVOCATION_PATTERN)
207
+ if (!match?.[1] || !match[2]) return undefined
208
+ const action = match[2].toLowerCase()
209
+ if (action !== 'list' && action !== 'load') return undefined
210
+ const skillUse = action === 'load' ? match[3] : undefined
211
+ if (action === 'load' && !skillUse) return undefined
212
+ return action === 'load' ? { action, skillUse } : { action }
213
+ }
214
+
215
+ function observationFromEvent(event) {
216
+ if (!event || typeof event !== 'object') return undefined
217
+ const toolName = event.tool_name ?? event.toolName
218
+ const toolInput = event.tool_input ?? event.toolArgs
219
+ if (toolName !== 'Bash') return undefined
220
+ const command = typeof toolInput === 'string' ? safeCommandFromString(toolInput) : commandFromObject(toolInput)
221
+ const parsed = parseIntentInvocation(command)
222
+ if (!parsed || typeof command !== 'string') return undefined
223
+ return { action: parsed.action, skillUse: parsed.skillUse, raw: command }
224
+ }
225
+
226
+ function commandFromObject(value) {
227
+ return value && typeof value === 'object' ? value.command : undefined
228
+ }
229
+
230
+ function safeCommandFromString(value) {
231
+ try {
232
+ const command = commandFromObject(JSON.parse(value))
233
+ return typeof command === 'string' ? command : value
234
+ } catch {
235
+ return value
236
+ }
237
+ }
238
+
239
+ function appendObservation(stateFile, observation) {
240
+ try {
241
+ mkdirSync(dirname(stateFile), { recursive: true })
242
+ appendFileSync(stateFile, JSON.stringify({ ts: new Date().toISOString(), ...observation }) + '\n')
243
+ } catch {
244
+ }
245
+ }
246
+
247
+ function hasIntentCheck(stateFile) {
248
+ if (!existsSync(stateFile)) return false
249
+ try {
250
+ return readFileSync(stateFile, 'utf8')
251
+ .split('\n')
252
+ .filter(Boolean)
253
+ .some((line) => {
254
+ try {
255
+ const action = JSON.parse(line).action
256
+ return action === 'list' || action === 'load'
257
+ } catch {
258
+ return false
259
+ }
260
+ })
261
+ } catch {
262
+ return false
263
+ }
264
+ }
265
+
266
+ function denyOutput() {
267
+ if (AGENT === 'copilot') {
268
+ return { permissionDecision: 'deny', permissionDecisionReason: GATE_DENY_REASON }
269
+ }
270
+
271
+ return {
272
+ hookSpecificOutput: {
273
+ hookEventName: 'PreToolUse',
274
+ permissionDecision: 'deny',
275
+ permissionDecisionReason: GATE_DENY_REASON,
276
+ },
277
+ }
278
+ }