@jchat/create-addon 1.0.0-alpha.10 → 1.0.0-alpha.12

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @jchat/create-addon
2
2
 
3
+ ## 1.0.0-alpha.12
4
+
5
+ ### Patch Changes
6
+
7
+ - 3d4d0ad: - Add interactive terminal UI powered by `@clack/prompts` with automatic TTY detection and positional argument support
8
+ - Add full-stack addon template scaffolding with Elysia API router, Kysely database schema, SQL migrations, and custom permission definitions
9
+ - Add multi-mountpoint support with dynamic UI rendering and directional RTL/LTR styles
10
+ - Expand locale scaffolding to all 20 host languages with 100% key parity and automated synchronization testing
11
+ - Strongly type `MountPoint` and `MOUNT_POINTS` contracts across generator and CLI interfaces
12
+ - Bundle contracts inline in `@jchat/create-addon` for standalone, zero-dependency CLI execution
13
+ - Export `./contracts` subpath from `@jchat/addon-sdk` for lightweight, framework-agnostic contract imports
14
+ - 7b86222: Add official customer-facing Agent Skills for JChat addon development and creation. Bundles `@jchat/addon-sdk` skills (`jchat-addon-development` and `create-jchat-addon`) for AI coding assistants and automatically scaffolds in-workspace skills when creating new addon projects.
15
+
16
+ ## 1.0.0-alpha.11
17
+
3
18
  ## 1.0.0-alpha.10
4
19
 
5
20
  ## 1.0.0-alpha.9
package/README.md CHANGED
@@ -1,39 +1,211 @@
1
1
  # @jchat/create-addon
2
2
 
3
- A CLI tool for generating a self-contained JChat addon development workspace.
3
+ Official CLI scaffolding tool for generating self-contained [JChat](https://jchatlive.com) addon development workspaces and interactive sandbox environments.
4
4
 
5
- ## Usage
5
+ ---
6
6
 
7
- Generate a new addon workspace:
7
+ ## Quick Start
8
+
9
+ ### 1. Interactive Scaffolding (Recommended)
10
+
11
+ Run without flags in a terminal for an interactive, guided setup powered by `@clack/prompts`:
12
+
13
+ ```bash
14
+ bunx @jchat/create-addon
15
+ ```
16
+
17
+ The interactive wizard will guide you through:
18
+
19
+ 1. **Addon ID**: URL-friendly slug (e.g., `poll-manager`).
20
+ 2. **Display Name**: Human-readable name (e.g., `Poll Manager`).
21
+ 3. **Template**: Full-Stack (UI + Backend API + DB) or Frontend Only.
22
+ 4. **Mount Points**: Where the addon attaches in the host UI.
23
+ 5. **Target Environment**: Standalone DevKit (Docker Sandbox) or Monorepo Workspace.
24
+
25
+ ---
26
+
27
+ ### 2. Non-Interactive / Scripted
28
+
29
+ Scaffold immediately by providing flags:
30
+
31
+ ```bash
32
+ # Standalone DevKit with full-stack template
33
+ bunx @jchat/create-addon poll-manager --template=fullstack --name="Poll Manager"
34
+
35
+ # Inside the JChat monorepo (outputs to addons/poll-manager)
36
+ bunx @jchat/create-addon poll-manager --workspace --template=fullstack
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Development Workflows
42
+
43
+ ### Standalone DevKit (Independent Repository)
44
+
45
+ When building an addon outside the JChat core monorepo, `@jchat/create-addon` scaffolds a complete DevKit featuring an isolated containerized sandbox (PostgreSQL 18, Redis 8, HTTP Mock server, and JChat sandbox runtime):
8
46
 
9
47
  ```bash
10
- bunx @jchat/create-addon <addon-id>
48
+ cd poll-manager
49
+
50
+ # 1. Initialize environment config
51
+ cp .env.example .env
52
+
53
+ # 2. Install dependencies
54
+ bun install
55
+
56
+ # 3. Start the local sandbox container environment
57
+ bun sandbox
11
58
  ```
12
59
 
13
- ### Options
60
+ #### Standalone DevKit Automation Scripts
61
+
62
+ | Command | Description |
63
+ | :-------------------- | :-------------------------------------------------------------------------- |
64
+ | `bun sandbox` | Boots Docker/Podman sandbox with live reload, database, and HTTP mock proxy |
65
+ | `bun sandbox:test` | Executes integration and unit tests within the sandbox container |
66
+ | `bun sandbox:package` | Builds and packages the addon into a redistributable `.zip` archive |
67
+ | `bun sandbox:reset` | Resets database fixtures and clears sandbox upload storage |
68
+ | `bun sandbox:cleanup` | Stops and removes all sandbox containers and networks |
69
+
70
+ ---
14
71
 
15
- - `<addon-id>`: (Required) The unique ID for your addon, typically hyphenated (e.g., `weather-addon`).
16
- - `--target=<directory>`: (Optional) The directory to generate the workspace in. Defaults to `../<addon-id>` relative to the current working directory.
17
- - `--image=<image>`: (Optional) A specific Docker / Podman container image to use for the sandbox environment. Defaults to `ghcr.io/mjjabarullah/jchat-sandbox`.
72
+ ### Monorepo Workspace (`--workspace`)
18
73
 
19
- ### Example
74
+ When developing locally inside the JChat monorepo:
20
75
 
21
76
  ```bash
22
- bunx @jchat/create-addon my-addon
23
- cd my-addon
77
+ # Scaffold into addons/<addon-id>
78
+ bunx @jchat/create-addon my-addon --workspace
79
+
80
+ # Start live development with hot reload
81
+ cd addons/my-addon
82
+ bun dev
83
+ ```
84
+
85
+ #### Addon Workspace Scripts
86
+
87
+ | Command | Description |
88
+ | :-------------- | :------------------------------------------------------------------ |
89
+ | `bun dev` | Starts `jchat-addon dev` with fast HMR |
90
+ | `bun build` | Runs typechecking (`tsc --noEmit`) and compiles `jchat-addon build` |
91
+ | `bun test` | Runs Bun unit tests and verifies 20-language translation parity |
92
+ | `bun typecheck` | Validates TypeScript contracts without emitting output |
93
+
94
+ ---
95
+
96
+ ## Workspace Structure
97
+
98
+ ### Full-Stack Addon Template (`--template=fullstack`)
99
+
100
+ ```text
101
+ my-addon/
102
+ ├── addon/
103
+ │ ├── migrations/
104
+ │ │ └── 001_initial.sql # Kysely/SQL database schema migrations
105
+ │ ├── src/
106
+ │ │ ├── locales/ # All 20 host languages
107
+ │ │ │ ├── en.json
108
+ │ │ │ ├── ... (20 languages)
109
+ │ │ │ └── index.ts
110
+ │ │ ├── locales.test.ts # Key-parity tests across all 20 languages
111
+ │ │ ├── api.ts # Backend ElysiaJS routes & business logic
112
+ │ │ ├── db.ts # Kysely database helper & typed tables
113
+ │ │ ├── manifest.ts # Addon permissions, metadata & mount points
114
+ │ │ ├── tailwind.css # Scoped Tailwind CSS v4 styling
115
+ │ │ └── ui.tsx # Frontend React UI component
116
+ │ ├── package.json
117
+ │ └── tsconfig.json
118
+ ├── .agents/
119
+ │ └── skills/
120
+ │ └── jchat-addon-development/ # AI pair-programming skill for the addon
121
+ ├── .env.example # Sandbox configuration & ports
122
+ ├── docker-compose.yml # Containerized PostgreSQL 18, Redis 8 & sandbox
123
+ ├── package.json # DevKit root lifecycle scripts
124
+ ├── README.md # Project documentation
125
+ └── scripts/
126
+ └── devkit.ts # Standalone sandbox orchestrator
24
127
  ```
25
128
 
26
- ## Requirements
129
+ ### Frontend-Only Addon Template (`--template=ui`)
130
+
131
+ Lightweight UI-only extension without server routes or database tables:
132
+
133
+ ```text
134
+ my-addon/
135
+ ├── addon/
136
+ │ ├── src/
137
+ │ │ ├── locales/ # 20 host language translations
138
+ │ │ ├── locales.test.ts # Key parity test
139
+ │ │ ├── manifest.ts # Client mount points & metadata
140
+ │ │ ├── tailwind.css # Scoped Tailwind CSS
141
+ │ │ └── ui.tsx # React UI component
142
+ │ ├── package.json
143
+ │ └── tsconfig.json
144
+ ├── docker-compose.yml
145
+ └── package.json
146
+ ```
147
+
148
+ ---
149
+
150
+ ## CLI Flags & Options
151
+
152
+ | Option | Description | Default |
153
+ | :----------------------- | :----------------------------------------------------------------------------------------- | :----------------------------------- |
154
+ | `<addon-id>` | Addon identifier (lowercase letters, numbers, hyphens). Prompted interactively if omitted. | — |
155
+ | `--target=<dir>` | Output directory for the scaffolded project. | `./<addon-id>` |
156
+ | `--template=<type>` | Addon template type: `fullstack` (UI + API + DB) or `ui` (Frontend only). | `fullstack` |
157
+ | `--mount-points=<p1,p2>` | Comma-separated list of UI mount points (see below). | `chat:toolbar,chat:plus-menu` |
158
+ | `--name=<displayName>` | Human-readable display name for the addon (e.g. `--name="Poll Manager"`). | Auto-derived from `<addon-id>` |
159
+ | `--workspace` | Target monorepo workspace mode (scaffolds to `addons/<addon-id>`). | `false` |
160
+ | `--image=<image>` | Custom container image for the JChat sandbox runtime. | `ghcr.io/mjjabarullah/jchat-sandbox` |
161
+ | `--help`, `-h` | Display CLI usage and options. | — |
162
+
163
+ ---
164
+
165
+ ## Supported Mount Points
166
+
167
+ Addons mount into specific extension slots across the host application:
168
+
169
+ | Mount Point | Description |
170
+ | :------------------------- | :---------------------------------------------------------- |
171
+ | `chat:toolbar` | Header toolbar button in the active room |
172
+ | `chat:plus-menu` | Action item inside the message composer plus (`+`) menu |
173
+ | `chat:sidebar` | Right-hand widget panel in chat |
174
+ | `addon:settings` | Addon configuration panel in site administration dashboard |
175
+ | `owner:settings` | Addon configuration panel in owner administration dashboard |
176
+ | `emoji-picker:gif-tab` | Custom tab inside the chat emoji / GIF picker panel |
177
+ | `emoji-picker:sticker-tab` | Custom tab inside the chat sticker picker panel |
178
+
179
+ ---
180
+
181
+ ## Addon SDK Subpath Architecture
182
+
183
+ Generated addons import exclusively from `@jchat/addon-sdk` subpaths:
184
+
185
+ - **`@jchat/addon-sdk`**: Manifest definitions, backend Elysia server utilities, permissions, contracts, and lifecycle types.
186
+ - **`@jchat/addon-sdk/ui`**: Base UI component library, design tokens, and unified `toast` notifications (`import { Button, toast } from "@jchat/addon-sdk/ui"`).
187
+ - **`@jchat/addon-sdk/icon`**: Centralized icon pack with direct TypeScript definitions (`import Icons from "@jchat/addon-sdk/icon"`).
188
+ - **`@jchat/addon-sdk/state`**: Shared atomic state powered by Jotai (`import { atom, useAtom } from "@jchat/addon-sdk/state"`).
189
+
190
+ ---
191
+
192
+ ## 20-Language Localization Out of the Box
193
+
194
+ All scaffolded addons automatically include string definitions for all 20 host languages supported by JChat:
195
+
196
+ `ar`, `bg`, `de`, `el`, `en`, `es`, `fr`, `he`, `hi`, `hr`, `kn`, `ml`, `nl`, `pt`, `ro`, `ru`, `ta`, `te`, `tr`, `ur`
197
+
198
+ A built-in test (`src/locales.test.ts`) verifies 100% key parity across all languages on every `bun test`.
199
+
200
+ ---
201
+
202
+ ## Prerequisites
27
203
 
28
- - Bun 1.3+
29
- - Docker or Podman
204
+ - **Bun**: 1.4.0+
205
+ - **Docker** or **Podman**: Required for running the local standalone sandbox environment (`bun sandbox`).
30
206
 
31
- ## Licensing
207
+ ---
32
208
 
33
- The creator package and JChat-authored template material are licensed under Apache License 2.0.
34
- You retain ownership of the original code and content you add to a generated project and may choose
35
- the license for that original work. Any JChat-authored template portions and third-party components
36
- remain subject to their respective licenses and notices.
209
+ ## License
37
210
 
38
- The generated developer sandbox does not grant a production JChat license. Production use remains
39
- subject to the applicable JChat and addon marketplace terms.
211
+ Proprietary — All rights reserved. See the [LICENSE](./LICENSE) file for terms and conditions.
package/dist/cli.d.mts CHANGED
@@ -1,11 +1,17 @@
1
+ import { n as MountPoint } from "./contracts.mjs";
2
+ import { t as AddonTemplateType } from "./generator.mjs";
1
3
  //#region src/cli.d.ts
2
- interface CreatorArguments {
4
+ export interface CreatorArguments {
3
5
  addonId: string;
4
6
  targetDir: string;
5
7
  sandboxImage?: string;
6
8
  workspace?: boolean;
9
+ template?: AddonTemplateType;
10
+ mountPoints?: MountPoint[];
11
+ displayName?: string;
7
12
  }
8
- declare function parseCreatorArguments(arguments_: readonly string[]): CreatorArguments;
9
- declare function runCreator(arguments_: readonly string[]): Promise<string>;
10
- //#endregion
11
- export { CreatorArguments, parseCreatorArguments, runCreator };
13
+ export declare function parseCreatorArguments(arguments_: readonly string[]): CreatorArguments;
14
+ export declare function isInsideJChatMonorepo(): boolean;
15
+ export declare function promptInteractiveOptions(initialAddonId?: string): Promise<CreatorArguments>;
16
+ export declare function runCreator(arguments_: readonly string[]): Promise<string>;
17
+ //#endregion
package/dist/cli.mjs CHANGED
@@ -1,2 +1,72 @@
1
1
  #!/usr/bin/env bun
2
- import{i as e,r as t}from"./generator.mjs";import*as n from"node:path";function r(e){let t=e.find(e=>!e.startsWith(`--`));if(t===void 0||t.startsWith(`-`))throw Error(`Usage: bunx @jchat/create-addon <addon-id> [--target=<directory>] [--image=<image>] [--workspace]`);let r=e.find(e=>e.startsWith(`--target=`)),i=e.find(e=>e.startsWith(`--image=`)),a=e.includes(`--workspace`),o=a?n.join(`addons`,t):t;return{addonId:t,targetDir:r?.slice(9)||o,sandboxImage:i?.slice(8)||void 0,workspace:a}}async function i(i){let a=await Bun.file(new URL(`../package.json`,import.meta.url)).json(),o=r(i);return o.workspace?(await t({addonId:o.addonId,targetDir:o.targetDir,isMonorepoWorkspace:!0,jchatVersion:a.version,addonSdkVersion:a.version}),n.resolve(o.targetDir)):(await e({...o,jchatVersion:a.version,addonSdkVersion:a.version,sandboxImage:o.sandboxImage??`${a.jchat?.sandboxImage??`ghcr.io/mjjabarullah/jchat-sandbox`}:${a.version.replaceAll(`+`,`-`)}`}),n.resolve(o.targetDir))}if(import.meta.main)try{let e=await i(process.argv.slice(2));console.log(`Created addon at ${e}`)}catch(e){console.error(e),process.exit(1)}export{r as parseCreatorArguments,i as runCreator};
2
+ import{t as e}from"./contracts.d.mts";import{a as t,i as n,o as r}from"./generator.mjs";import*as i from"node:fs";import*as a from"node:path";import{styleText as o}from"node:util";import s,{stdin as c,stdout as l}from"node:process";import*as u from"node:readline";import d from"node:readline";import{ReadStream as f}from"node:tty";var p=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);const m=(()=>{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}})(),h=e=>e===12288||e>=65281&&e<=65376||e>=65504&&e<=65510,g=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,_=/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y,v=/[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y,y=/(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/uy,b=/\t{1,1000}/y,x=/[\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,S=/(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y,ee=/\p{M}+/gu,te={limit:1/0,ellipsis:``},C=(e,t={},n={})=>{let r=t.limit??1/0,i=t.ellipsis??``,a=t?.ellipsisWidth??(i?C(i,te,n).width:0),o=n.controlWidth??0,s=n.tabWidth??8,c=n.emojiWidth??2,l=n.regularWidth??1,u=n.wideWidth??2,d=[[S,l],[_,0],[v,o],[b,s],[x,c],[y,u]],f=0,p=0,w=e.length,T=0,E=!1,D=w,O=Math.max(0,r-a),k=0,A=0,j=0,M=0;outer:for(;;){if(A>k||p>=w&&p>f){let t=e.slice(k,A)||e.slice(f,p);T=0;for(let e of t.replaceAll(ee,``)){let t=e.codePointAt(0)||0;if(M=h(t)?2:g(t)?u:l,j+M>O&&(D=Math.min(D,Math.max(k,f)+T)),j+M>r){E=!0;break outer}T+=e.length,j+=M}k=A=0}if(p>=w)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(T=n===y?m(e.slice(p,n.lastIndex)):n===x?1:n.lastIndex-p,M=T*i,j+M>O&&(D=Math.min(D,p+Math.floor((O-j)/i))),j+M>r){E=!0;break outer}j+=M,k=f,A=p,p=f=n.lastIndex;continue outer}}p+=1}return{width:E?O:j,index:E?D:w,truncated:E,ellipsed:E&&r>=a}},w={limit:1/0,ellipsis:``,ellipsisWidth:0},T=(e,t={})=>C(e,w,t).width,E=`]8;;`,D=RegExp(`(?:\\[(?<code>\\d+)m|\\${E}(?<uri>.*))`,`y`),O=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=>`${E}${e}`,j=(e,t,n)=>{let r=t[Symbol.iterator](),i=!1,a=!1,o=e.at(-1),s=o===void 0?0:T(o),c=r.next(),l=r.next(),u=0;for(;!c.done;){let o=c.value,d=T(o);s+d<=n?e[e.length-1]+=o:(e.push(o),s=0),(o===`\x1B`||o===`›`)&&(i=!0,a=t.startsWith(E,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())},M=e=>{let t=e.split(` `),n=t.length;for(;n&&!T(t[n-1]);)n--;return n===t.length?e:t.slice(0,n).join(` `)+t.slice(n).join(``)},ne=(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=T(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=T(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=T(s.at(-1)??``);continue}if(c+i>t&&c&&i){if(n.wordWrap===!1&&c<t){j(s,r,t),c=T(s.at(-1)??``);continue}s.push(``),c=0}if(c+i>t&&n.wordWrap===!1){j(s,r,t),c=T(s.at(-1)??``);continue}s[s.length-1]+=r,c+=i}n.trim!==!1&&(s=s.map(e=>M(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===`›`){D.lastIndex=e+1;let t=D.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?O(i):void 0;i&&e&&(r+=k(e))}else t===`
5
+ `&&(i&&O(i)&&(r+=k(i)),a&&(r+=A(a)))}return r},re=/\r?\n/;function N(e,t,n){return String(e).normalize().split(re).map(e=>ne(e,t,n)).join(`
6
+ `)}var P=p(((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`}}))();function F(e,t,n){if(!n.some(e=>!e.disabled))return e;let r=e+t,i=Math.max(n.length-1,0),a=r<0?i:r>i?0:r;return n[a]?.disabled?F(a,t<0?-1:1,n):a}const I={actions:new Set([`up`,`down`,`left`,`right`,`space`,`enter`,`cancel`]),aliases: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 ie(e){if(e!==void 0)return e;if(I.accessible!==void 0)return I.accessible;let t=process.env.ACCESSIBLE;return t!==void 0&&t!==``&&t!==`0`&&t!==`false`}function L(e,t){if(typeof e==`string`)return I.aliases.get(e)===t;for(let n of e)if(n!==void 0&&L(n,t))return!0;return!1}function ae(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}}const oe=globalThis.process.platform.startsWith(`win`),R=Symbol(`clack:cancel`);function z(e){return e===R}function B(e,t){let n=e;n.isTTY&&n.setRawMode(t)}function se({input:e=c,output:t=l,overwrite:n=!0,hideCursor:r=!0}={}){let i=u.createInterface({input:e,output:t,prompt:``,tabSize:1});u.emitKeypressEvents(e,i),e instanceof f&&e.isTTY&&e.setRawMode(!0);let a=(i,{name:o,sequence:s})=>{if(L([String(i),o,s],`cancel`)){r&&t.write(P.cursor.show),process.exit(0);return}if(!n)return;let c=o===`return`?0:-1,l=o===`return`?-1:0;u.moveCursor(t,c,l,()=>{u.clearLine(t,1,()=>{e.once(`keypress`,a)})})};return r&&t.write(P.cursor.hide),e.once(`keypress`,a),()=>{e.off(`keypress`,a),r&&t.write(P.cursor.show),e instanceof f&&e.isTTY&&!oe&&e.setRawMode(!1),i.terminal=!1,i.close()}}const V=e=>`columns`in e&&typeof e.columns==`number`?e.columns:80,ce=e=>`rows`in e&&typeof e.rows==`number`?e.rows:20;function H(e,t,n,r=n,i=n,a){return N(t,V(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 le(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 U=class{input;output;_abortSignal;rl;opts;_render;_track=!1;_prevFrame=``;_subscribers=new Map;_cursor=0;state=`initial`;error=``;value;userInput=``;get accessible(){return ie(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(R);this._abortSignal.addEventListener(`abort`,()=>{this.state=`cancel`,this.close()},{once:!0})}this.rl=d.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),B(this.input,!0),this.output.on(`resize`,this.render),this.render(),this.once(`submit`,()=>{this.output.write(P.cursor.show),this.output.off(`resize`,this.render),B(this.input,!1),e(this.value)}),this.once(`cancel`,()=>{this.output.write(P.cursor.show),this.output.off(`resize`,this.render),B(this.input,!1),e(R)})})}_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&&I.aliases.has(t.name)&&this.emit(`cursor`,I.aliases.get(t.name)),I.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=le(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`)}L([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
+ `),B(this.input,!1),this.rl?.close(),this.rl=void 0,this.emit(`${this.state}`,this.value),this.unsubscribe()}restoreCursor(){let e=N(this._prevFrame,process.stdout.columns,{hard:!0,trim:!1}).split(`
12
+ `).length-1;this.output.write(P.cursor.move(-999,e*-1))}render(){let e=N(this._render(this)??``,process.stdout.columns,{hard:!0,trim:!1});if(e!==this._prevFrame){if(this.state===`initial`)this.output.write(P.cursor.hide);else{let t=ae(this._prevFrame,e),n=ce(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(P.cursor.move(0,a-i)),this.output.write(P.erase.lines(1));let t=e.split(`
13
+ `);this.output.write(t[a]),this._prevFrame=e,this.output.write(P.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(P.cursor.move(0,e))}this.output.write(P.erase.down());let t=e.split(`
14
+ `).slice(a);this.output.write(t.join(`
15
+ `)),this._prevFrame=e;return}}this.output.write(P.erase.down())}this.output.write(e),this.state===`initial`&&(this.state=`active`),this._prevFrame=e}}},ue=class extends U{options;cursor=0;get _value(){return this.options[this.cursor]?.value}get _enabledOptions(){return this.options.filter(e=>e.disabled!==!0)}toggleAll(){let e=this._enabledOptions,t=this.value!==void 0&&this.value.length===e.length;this.value=t?[]:e.map(e=>e.value)}toggleInvert(){let e=this.value;if(!e)return;let t=this._enabledOptions.filter(t=>!e.includes(t.value));this.value=t.map(e=>e.value)}toggleValue(){this.value===void 0&&(this.value=[]);let e=this.value.includes(this._value);this.value=e?this.value.filter(e=>e!==this._value):[...this.value,this._value]}constructor(e){super(e,!1),this.options=e.options,this.value=[...e.initialValues??[]];let t=Math.max(this.options.findIndex(({value:t})=>t===e.cursorAt),0);this.cursor=this.options[t]?.disabled?F(t,1,this.options):t,this.on(`key`,(e,t)=>{t.name===`a`&&this.toggleAll(),t.name===`i`&&this.toggleInvert()}),this.on(`cursor`,e=>{switch(e){case`left`:case`up`:this.cursor=F(this.cursor,-1,this.options);break;case`down`:case`right`:this.cursor=F(this.cursor,1,this.options);break;case`space`:this.toggleValue()}})}};let de=class extends U{options;cursor=0;get _selectedValue(){return this.options[this.cursor]}changeValue(){let e=this._selectedValue;this.value=e===void 0?void 0:e.value}constructor(e){super(e,!1),this.options=e.options;let t=this.options.findIndex(({value:t})=>t===e.initialValue),n=t===-1?0:t;this.cursor=this.options[n]?.disabled?F(n,1,this.options):n,this.changeValue(),this.on(`cursor`,e=>{switch(e){case`left`:case`up`:this.cursor=F(this.cursor,-1,this.options);break;case`down`:case`right`:this.cursor=F(this.cursor,1,this.options)}this.changeValue()})}};var fe=class extends U{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 pe(){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 W=pe(),me=()=>process.env.CI===`true`,G=(e,t)=>W?e:t,he=G(`◆`,`*`),ge=G(`■`,`x`),_e=G(`▲`,`x`),ve=G(`◇`,`o`),ye=G(`┌`,`T`),K=G(`│`,`|`),q=G(`└`,`—`),be=G(`●`,`>`),xe=G(`○`,` `),Se=G(`◻`,`[•]`),Ce=G(`◼`,`[+]`),we=G(`◻`,`[ ]`),J=e=>{switch(e){case`initial`:case`active`:return o(`cyan`,he);case`cancel`:return o(`red`,ge);case`error`:return o(`yellow`,_e);case`submit`:return o(`green`,ve);case`validating`:return o(`dim`,he)}},Te=e=>{switch(e){case`initial`:case`active`:return o(`cyan`,K);case`cancel`:return o(`red`,K);case`error`:return o(`yellow`,K);case`submit`:return o(`green`,K)}};function Ee(e,t){let n=[`${t?`${o(`cyan`,K)} `:``}${e.join(` • `)}`];return t&&n.push(o(`cyan`,q)),n}const De=(e,t,n,r,i,a=!1)=>{let o=t,s=0;if(a)for(let t=r-1;t>=n;t--){let n=e[t];if(n&&(o-=n.length),s++,o<=i)break}else for(let t=n;t<r;t++){let n=e[t];if(n&&(o-=n.length),s++,o<=i)break}return{lineCount:o,removals:s}},Y=({cursor:e,options:t,style:n,output:r=process.stdout,maxItems:i=1/0,columnPadding:a=0,rowPadding:s=4})=>{let c=V(r)-a,l=ce(r),u=o(`dim`,`...`),d=Math.max(l-s,0),f=Math.max(Math.min(i,d),5),p=0;e>=f-3&&(p=Math.max(Math.min(e-f+3,t.length-f),0));let m=f<t.length&&p>0,h=f<t.length&&p+f<t.length,g=Math.min(p+f,t.length),_=[],v=0;m&&v++,h&&v++;let y=p+ +!!m,b=g-+!!h;for(let r=y;r<b;r++){let i=t[r],a=N(i?n(i,r===e):``,c,{hard:!0,trim:!1}).split(`
16
+ `);_.push(a),v+=a.length}if(v>d){let t=0,n=0,r=v,i=e-y,a=d,o=()=>De(_,r,0,i,a),s=()=>De(_,r,i+1,_.length,a,!0);m?({lineCount:r,removals:t}=o(),r>a&&(h||--a,{lineCount:r,removals:n}=s())):(h||--a,{lineCount:r,removals:n}=s(),r>a&&(--a,{lineCount:r,removals:t}=o())),t>0&&(m=!0,_.splice(0,t)),n>0&&(h=!0,_.splice(_.length-n,n))}let x=[];m&&x.push(u);for(let e of _)for(let t of e)x.push(t);return h&&x.push(u),x},Oe=[`${o(`dim`,`↑/↓`)} to navigate`,`${o(`dim`,`Space:`)} select`,`${o(`dim`,`Enter:`)} confirm`],X=(e,t)=>e.split(`
17
+ `).map(e=>t(e)).join(`
18
+ `),ke=e=>{let t=(e,t)=>{let n=e.label??String(e.value);return t===`disabled`?`${o(`gray`,we)} ${X(n,e=>o([`strikethrough`,`gray`],e))}${e.hint?` ${o(`dim`,`(${e.hint??`disabled`})`)}`:``}`:t===`active`?`${o(`cyan`,Se)} ${n}${e.hint?` ${o(`dim`,`(${e.hint})`)}`:``}`:t===`selected`?`${o(`green`,Ce)} ${X(n,e=>o(`dim`,e))}${e.hint?` ${o(`dim`,`(${e.hint})`)}`:``}`:t===`cancelled`?`${X(n,e=>o([`strikethrough`,`dim`],e))}`:t===`active-selected`?`${o(`green`,Ce)} ${n}${e.hint?` ${o(`dim`,`(${e.hint})`)}`:``}`:t===`submitted`?`${X(n,e=>o(`dim`,e))}`:`${o(`dim`,we)} ${X(n,e=>o(`dim`,e))}`},n=e.required??!0,r=e.showInstructions??!0;return new ue({options:e.options,signal:e.signal,input:e.input,output:e.output,initialValues:e.initialValues,required:n,cursorAt:e.cursorAt,validate(e){if(n&&(e===void 0||e.length===0))return`Please select at least one option.
19
+ ${o(`reset`,o(`dim`,`Press ${o([`gray`,`bgWhite`,`inverse`],` space `)} to select, ${o(`gray`,o(`bgWhite`,o(`inverse`,` enter `)))} to submit`))}`},render(){let n=e.withGuide??I.withGuide,i=H(e.output,e.message,n?`${Te(this.state)} `:``,`${J(this.state)} `),a=`${n?`${o(`gray`,K)}
20
+ `:``}${i}
21
+ `,s=this.value??[],c=(e,n)=>{if(e.disabled)return t(e,`disabled`);let r=s.includes(e.value);return n&&r?t(e,`active-selected`):r?t(e,`selected`):t(e,n?`active`:`inactive`)};switch(this.state){case`submit`:{let r=this.options.filter(({value:e})=>s.includes(e)).map(e=>t(e,`submitted`)).join(o(`dim`,`, `))||o(`dim`,`none`);return`${a}${H(e.output,r,n?`${o(`gray`,K)} `:``)}`}case`cancel`:{let r=this.options.filter(({value:e})=>s.includes(e)).map(e=>t(e,`cancelled`)).join(o(`dim`,`, `));return r.trim()===``?`${a}${o(`gray`,K)}`:`${a}${H(e.output,r,n?`${o(`gray`,K)} `:``)}${n?`
22
+ ${o(`gray`,K)}`:``}`}case`error`:{let t=n?`${o(`yellow`,K)} `:``,r=this.error.split(`
23
+ `).map((e,t)=>t===0?`${n?`${o(`yellow`,q)} `:``}${o(`yellow`,e)}`:` ${e}`).join(`
24
+ `),i=a.split(`
25
+ `).length,s=r.split(`
26
+ `).length+1;return`${a}${t}${Y({output:e.output,options:this.options,cursor:this.cursor,maxItems:e.maxItems,columnPadding:t.length,rowPadding:i+s,style:c}).join(`
27
+ ${t}`)}
28
+ ${r}
29
+ `}default:{let t=n?`${o(`cyan`,K)} `:``,i=a.split(`
30
+ `).length,s=r?Ee(Oe,n):n?[o(`cyan`,q)]:[],l=s.join(`
31
+ `),u=s.length+1;return`${a}${t}${Y({output:e.output,options:this.options,cursor:this.cursor,maxItems:e.maxItems,columnPadding:t.length,rowPadding:i+u,style:c}).join(`
32
+ ${t}`)}
33
+ ${l}
34
+ `}}}}).prompt()},Z=(e=``,t)=>{let n=t?.output??process.stdout,r=t?.withGuide??I.withGuide?`${o(`gray`,q)} `:``;n.write(`${r}${o(`red`,e)}
35
+
36
+ `)},Ae=(e=``,t)=>{let n=t?.output??process.stdout,r=t?.withGuide??I.withGuide?`${o(`gray`,ye)} `:``;n.write(`${r}${e}
37
+ `)},je=(e=``,t)=>{let n=t?.output??process.stdout,r=t?.withGuide??I.withGuide?`${o(`gray`,K)}
38
+ ${o(`gray`,q)} `:``;n.write(`${r}${e}
39
+
40
+ `)},Me=e=>o(`magenta`,e),Ne=({indicator:e=`dots`,onCancel:t,output:n=process.stdout,cancelMessage:r,errorMessage:i,frames:a=W?[`◒`,`◐`,`◓`,`◑`]:[`•`,`o`,`O`,`0`],delay:s=W?80:120,signal:c,...l}={})=>{let u=me(),d,f,p=!1,m=!1,h=``,g,_=performance.now(),v=V(n),y=l?.styleFrame??Me,b=e=>{let n=e>1?i??I.messages.error:r??I.messages.cancel;m=e===1,p&&(O(n,e),m&&typeof t==`function`&&t())},x=()=>b(2),S=()=>b(1),ee=()=>{process.on(`uncaughtExceptionMonitor`,x),process.on(`unhandledRejection`,x),process.on(`SIGINT`,S),process.on(`SIGTERM`,S),process.on(`exit`,b),c&&c.addEventListener(`abort`,S)},te=()=>{process.removeListener(`uncaughtExceptionMonitor`,x),process.removeListener(`unhandledRejection`,x),process.removeListener(`SIGINT`,S),process.removeListener(`SIGTERM`,S),process.removeListener(`exit`,b),c&&c.removeEventListener(`abort`,S)},C=()=>{if(g===void 0)return;u&&n.write(`
41
+ `);let e=N(g,v,{hard:!0,trim:!1}).split(`
42
+ `);e.length>1&&n.write(P.cursor.up(e.length-1)),n.write(P.cursor.to(0)),n.write(P.erase.down())},w=e=>e.replace(/\.+$/,``),T=e=>{let t=(performance.now()-e)/1e3,n=Math.floor(t/60),r=Math.floor(t%60);return n>0?`[${n}m ${r}s]`:`[${r}s]`},E=l.withGuide??I.withGuide,D=(t=``)=>{p=!0,d=se({output:n}),h=w(t),_=performance.now(),E&&n.write(`${o(`gray`,K)}
43
+ `);let r=0,i=0;ee(),f=setInterval(()=>{if(u&&h===g)return;C(),g=h;let t=y(a[r]),o;if(u)o=`${t} ${h}...`;else if(e===`timer`)o=`${t} ${h} ${T(_)}`;else{let e=`.`.repeat(Math.floor(i)).slice(0,3);o=`${t} ${h}${e}`}let s=N(o,v,{hard:!0,trim:!1});n.write(s),r=r+1<a.length?r+1:0,i=i<4?i+.125:0},s)},O=(t=``,r=0,i=!1)=>{if(!p)return;p=!1,clearInterval(f),C();let a=r===0?o(`green`,ve):r===1?o(`red`,ge):o(`red`,_e);h=t??h,i||(e===`timer`?n.write(`${a} ${h} ${T(_)}
44
+ `):n.write(`${a} ${h}
45
+ `)),te(),d()};return{start:D,stop:(e=``)=>O(e,0),message:(e=``)=>{h=w(e??h)},cancel:(e=``)=>O(e,1),error:(e=``)=>O(e,2),clear:()=>O(``,0,!0),get isCancelled(){return m}}},Pe=[`${o(`dim`,`↑/↓`)} to navigate`,`${o(`dim`,`Enter:`)} confirm`],Q=(e,t)=>e.includes(`
46
+ `)?e.split(`
47
+ `).map(e=>t(e)).join(`
48
+ `):t(e),Fe=e=>{let t=(e,t)=>{if(e===void 0)return``;let n=e.label??String(e.value);switch(t){case`disabled`:return`${o(`gray`,xe)} ${Q(n,e=>o(`gray`,e))}${e.hint?` ${o(`dim`,`(${e.hint??`disabled`})`)}`:``}`;case`selected`:return`${Q(n,e=>o(`dim`,e))}`;case`active`:return`${o(`green`,be)} ${n}${e.hint?` ${o(`dim`,`(${e.hint})`)}`:``}`;case`cancelled`:return`${Q(n,e=>o([`strikethrough`,`dim`],e))}`;default:return`${o(`dim`,xe)} ${Q(n,e=>o(`dim`,e))}`}},n=e.showInstructions??!0;return new de({options:e.options,signal:e.signal,input:e.input,output:e.output,initialValue:e.initialValue,render(){let r=e.withGuide??I.withGuide,i=`${J(this.state)} `,a=`${Te(this.state)} `,s=H(e.output,e.message,a,i),c=`${r?`${o(`gray`,K)}
49
+ `:``}${s}
50
+ `;switch(this.state){case`submit`:{let n=r?`${o(`gray`,K)} `:``;return`${c}${H(e.output,t(this.options[this.cursor],`selected`),n)}`}case`cancel`:{let n=r?`${o(`gray`,K)} `:``;return`${c}${H(e.output,t(this.options[this.cursor],`cancelled`),n)}${r?`
51
+ ${o(`gray`,K)}`:``}`}default:{let i=r?`${o(`cyan`,K)} `:``,a=c.split(`
52
+ `).length,s=n?Ee(Pe,r):r?[o(`cyan`,q)]:[],l=s.join(`
53
+ `),u=s.length+1;return`${c}${i}${Y({output:e.output,cursor:this.cursor,options:this.options,maxItems:e.maxItems,columnPadding:i.length,rowPadding:a+u,style:(e,n)=>t(e,e.disabled?`disabled`:n?`active`:`inactive`)}).join(`
54
+ ${i}`)}
55
+ ${l}
56
+ `}}}}).prompt()};`${o(`gray`,K)}`;const $=e=>new fe({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??I.withGuide,n=`${`${t?`${o(`gray`,K)}
57
+ `:``}${J(this.state)} `}${e.message}
58
+ `,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`,K)} `:``,r=t?o(`cyan`,q):``;return`${n}${e}${o(`dim`,i)}
59
+ ${r} ${o(`dim`,`Validating...`)}
60
+ `}case`error`:{let e=this.error?` ${o(`yellow`,this.error)}`:``,r=t?`${o(`yellow`,K)} `:``,a=t?o(`yellow`,q):``;return`${n.trim()}
61
+ ${r}${i}
62
+ ${a}${e}
63
+ `}case`submit`:{let e=a?`${t?` `:``}${o(`dim`,a)}`:``;return`${n}${t?o(`gray`,K):``}${e}`}case`cancel`:{let e=a?` ${o([`strikethrough`,`dim`],a)}`:``,r=t?o(`gray`,K):``;return`${n}${r}${e}${a.trim()?`
64
+ ${r}`:``}`}default:return`${n}${t?`${o(`cyan`,K)} `:``}${i}
65
+ ${t?o(`cyan`,q):``}
66
+ `}}}).prompt();function Ie(e){let t=e.find(e=>!e.startsWith(`--`));if(t===void 0||t.startsWith(`-`))throw Error(`Usage: bunx @jchat/create-addon <addon-id> [--target=<directory>] [--image=<image>] [--workspace] [--template=ui|fullstack] [--mount-points=p1,p2] [--name=<displayName>]`);let n=e.find(e=>e.startsWith(`--target=`)),r=e.find(e=>e.startsWith(`--image=`)),i=e.find(e=>e.startsWith(`--template=`)),o=e.find(e=>e.startsWith(`--mount-points=`)||e.startsWith(`--mountPoints=`)),s=e.find(e=>e.startsWith(`--name=`)),c=e.includes(`--workspace`),l=c?a.join(`addons`,t):t,u=i?i.split(`=`)[1]:void 0,d=o?o.split(`=`)[1]?.split(`,`).filter(Boolean):void 0,f=s?s.slice(7):void 0;return{addonId:t,targetDir:n?.slice(9)||l,sandboxImage:r?.slice(8)||void 0,workspace:c,...u===void 0?{}:{template:u},...d===void 0?{}:{mountPoints:d},...f===void 0?{}:{displayName:f}}}function Le(){try{let e=process.cwd();for(let t=0;t<6;t++){let t=a.join(e,`package.json`);if(i.existsSync(t)){let e=i.readFileSync(t,`utf8`),n=JSON.parse(e);if(n.name===`jchat`&&Array.isArray(n.workspaces))return!0}let n=a.dirname(e);if(n===e)break;e=n}}catch{}return!1}async function Re(t){Ae(`@jchat/create-addon`);let r=t?.trim();if(!r||!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(r)){let e=await $({message:`What is your addon ID?`,placeholder:`my-custom-addon`,initialValue:t,validate(e){if(!e||e.trim().length===0)return`Addon ID is required`;if(!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(e.trim()))return`Addon ID must contain lowercase letters, numbers, and hyphens (e.g. my-custom-addon)`}});z(e)&&(Z(`Scaffolding cancelled.`),process.exit(0)),r=String(e).trim()}let i=await $({message:`What is the display name?`,initialValue:n(r),validate(e){if(!e||e.trim().length===0)return`Display name is required`}});z(i)&&(Z(`Scaffolding cancelled.`),process.exit(0));let o=await Fe({message:`Choose an addon template:`,options:[{value:`fullstack`,label:`Full-Stack (UI + Backend API + Database)`,hint:`Recommended - includes Elysia routes, Kysely DB, migrations, permissions`},{value:`ui`,label:`Frontend Only (UI Component + Locales)`,hint:`Lightweight client-only extension`}],initialValue:`fullstack`});z(o)&&(Z(`Scaffolding cancelled.`),process.exit(0));let s=await ke({message:`Where should the addon be mounted?`,options:[{value:e.CHAT_TOOLBAR,label:`Chat Toolbar`,hint:`Top header/toolbar in chat`},{value:e.CHAT_PLUS_MENU,label:`Plus Menu (+)`,hint:`Message composer action menu`},{value:e.CHAT_SIDEBAR,label:`Chat Sidebar`,hint:`Right-hand sidebar widget`},{value:e.ADDON_SETTINGS,label:`Addon Settings`,hint:`Site admin dashboard panel`},{value:e.OWNER_SETTINGS,label:`Owner Settings`,hint:`Owner dashboard panel`},{value:e.EMOJI_PICKER_GIF_TAB,label:`GIF Tab`,hint:`Emoji/GIF picker panel tab`},{value:e.EMOJI_PICKER_STICKER_TAB,label:`Sticker Tab`,hint:`Emoji/Sticker picker panel tab`}],initialValues:[e.CHAT_TOOLBAR,e.CHAT_PLUS_MENU],required:!0});z(s)&&(Z(`Scaffolding cancelled.`),process.exit(0));let c=Le(),l=await Fe({message:`Target environment:`,options:c?[{value:`workspace`,label:`JChat Monorepo Workspace (addons/<id>)`,hint:`Detected JChat root`},{value:`standalone`,label:`Standalone DevKit (Docker Sandbox)`,hint:`Independent repository`}]:[{value:`standalone`,label:`Standalone DevKit (Docker Sandbox)`,hint:`Independent repository`},{value:`workspace`,label:`Monorepo Workspace (addons/<id>)`,hint:`Inside existing monorepo`}],initialValue:c?`workspace`:`standalone`});z(l)&&(Z(`Scaffolding cancelled.`),process.exit(0));let u=l===`workspace`,d=u?a.join(`addons`,r):r,f=await $({message:`Target directory:`,initialValue:d,validate(e){if(!e||e.trim().length===0)return`Target directory is required`}});return z(f)&&(Z(`Scaffolding cancelled.`),process.exit(0)),{addonId:r,targetDir:String(f).trim(),displayName:String(i).trim(),template:o,mountPoints:s,workspace:u}}async function ze(e){let n=await Bun.file(new URL(`../package.json`,import.meta.url)).json();if(e.includes(`--help`)||e.includes(`-h`))return console.log(`Usage: bunx @jchat/create-addon [addon-id] [--target=<directory>] [--image=<image>] [--workspace] [--template=ui|fullstack] [--mount-points=p1,p2] [--name=<displayName>]`),``;let i=!e.some(e=>e.startsWith(`-`))&&!(!process.stdin.isTTY||process.env.CI),o=e.find(e=>!e.startsWith(`-`)),s=i?await Re(o):Ie(e),c=i?Ne():void 0;c?.start(`Scaffolding ${s.displayName??s.addonId}...`);let l=``;return s.workspace?(await t({addonId:s.addonId,targetDir:s.targetDir,isMonorepoWorkspace:!0,jchatVersion:n.version,addonSdkVersion:n.version,template:s.template,mountPoints:s.mountPoints,displayName:s.displayName}),l=a.resolve(s.targetDir),c?.stop(`Addon created successfully at ${l}`),i&&je(`Next steps:
67
+ cd ${s.targetDir}
68
+ bun run dev`)):(await r({...s,jchatVersion:n.version,addonSdkVersion:n.version,sandboxImage:s.sandboxImage??`${n.jchat?.sandboxImage??`ghcr.io/mjjabarullah/jchat-sandbox`}:${n.version.replaceAll(`+`,`-`)}`,template:s.template,mountPoints:s.mountPoints,displayName:s.displayName}),l=a.resolve(s.targetDir),c?.stop(`Standalone DevKit created successfully at ${l}`),i&&je(`Next steps:
69
+ cd ${s.targetDir}
70
+ cp .env.example .env
71
+ bun install
72
+ bun sandbox`)),i||console.log(`Created addon at ${l}`),l}if(import.meta.main)try{await ze(process.argv.slice(2))}catch(e){console.error(e),process.exit(1)}export{Le as isInsideJChatMonorepo,Ie as parseCreatorArguments,Re as promptInteractiveOptions,ze as runCreator};
@@ -0,0 +1,40 @@
1
+ //#region ../addon-sdk/dist/contracts.d.mts
2
+ /** Valid locations where an addon can be rendered within the Jchat layout. */
3
+ declare const MOUNT_POINTS: {
4
+ /** Top of the main chat column */
5
+ readonly CHAT_HEADER: "chat-header";
6
+ /** Below the main chat header, above messages */
7
+ readonly CHAT_TOOLBAR: "chat-toolbar";
8
+ /** The persistent right-side sidebar */
9
+ readonly CHAT_SIDEBAR: "chat-sidebar";
10
+ /** Top of the left sidebar (room list) */
11
+ readonly SIDEBAR_TOP: "sidebar-top";
12
+ /** Bottom of the left sidebar */
13
+ readonly SIDEBAR_BOTTOM: "sidebar-bottom";
14
+ /** Action list shown when clicking a user's avatar */
15
+ readonly USER_POPOVER_ACTIONS: "user-popover-actions";
16
+ /** Menu shown when right-clicking a message */
17
+ readonly MESSAGE_CONTEXT_MENU: "message-context-menu";
18
+ /** Floating overlay rendered on top of the message stream */
19
+ readonly MESSAGE_FEED_OVERLAY: "message-feed-overlay";
20
+ /** Main admin dashboard area */
21
+ readonly ADMIN_PANEL: "admin-panel";
22
+ /** User or site settings dashboard area */
23
+ readonly SETTINGS_PANEL: "settings-panel";
24
+ /** The 'plus' (+) menu in the message composer */
25
+ readonly CHAT_PLUS_MENU: "chat-plus-menu";
26
+ /** Custom tab in the emoji/gif selection panel */
27
+ readonly EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab";
28
+ /** Custom tab in the emoji/sticker selection panel */
29
+ readonly EMOJI_PICKER_STICKER_TAB: "emoji-picker-sticker-tab";
30
+ /** Full-screen or modal dialog specifically for TubeScan */
31
+ readonly TUBESCAN_DIALOG: "tubescan-dialog";
32
+ /** Dedicated settings panel rendered in the Owner Dashboard */
33
+ readonly OWNER_SETTINGS: "owner-settings";
34
+ /** Dedicated settings panel rendered in the Site Admin Dashboard */
35
+ readonly ADDON_SETTINGS: "addon-settings";
36
+ };
37
+ /** Union type of all valid addon mount points. */
38
+ type MountPoint = (typeof MOUNT_POINTS)[keyof typeof MOUNT_POINTS];
39
+ //#endregion
40
+ export { MountPoint as n, MOUNT_POINTS as t };
@@ -0,0 +1,39 @@
1
+ import { n as MountPoint } from "./contracts.mjs";
2
+ //#region src/generator.d.ts
3
+ type AddonTemplateType = "ui" | "fullstack";
4
+ interface StandaloneDevkitOptions {
5
+ addonId: string;
6
+ targetDir: string;
7
+ jchatVersion: string;
8
+ addonSdkVersion: string;
9
+ sandboxImage?: string;
10
+ template?: AddonTemplateType;
11
+ mountPoints?: MountPoint[];
12
+ displayName?: string;
13
+ }
14
+ interface AddonWorkspaceOptions {
15
+ addonId: string;
16
+ targetDir: string;
17
+ isMonorepoWorkspace?: boolean;
18
+ jchatVersion: string;
19
+ addonSdkVersion: string;
20
+ template?: AddonTemplateType;
21
+ mountPoints?: MountPoint[];
22
+ displayName?: string;
23
+ }
24
+ declare const HOST_LOCALES: readonly ["ar", "bg", "de", "el", "en", "es", "fr", "he", "hi", "hr", "kn", "ml", "nl", "pt", "ro", "ru", "ta", "te", "tr", "ur"];
25
+ declare function createAddonFiles(options: {
26
+ addonId: string;
27
+ isMonorepoWorkspace?: boolean;
28
+ jchatVersion: string;
29
+ addonSdkVersion: string;
30
+ template?: AddonTemplateType;
31
+ mountPoints?: MountPoint[];
32
+ displayName?: string;
33
+ }): Readonly<Record<string, string>>;
34
+ declare function writeAddonWorkspace(options: AddonWorkspaceOptions): Promise<void>;
35
+ declare function createStandaloneDevkitFiles(options: StandaloneDevkitOptions): Readonly<Record<string, string>>;
36
+ declare function writeStandaloneDevkit(options: StandaloneDevkitOptions): Promise<void>;
37
+ declare function toDisplayName(addonId: string): string;
38
+ //#endregion
39
+ export { createAddonFiles as a, writeAddonWorkspace as c, StandaloneDevkitOptions as i, writeStandaloneDevkit as l, AddonWorkspaceOptions as n, createStandaloneDevkitFiles as o, HOST_LOCALES as r, toDisplayName as s, AddonTemplateType as t };
@@ -1,6 +1,6 @@
1
- import*as e from"node:fs";import*as t from"node:path";function n(e){try{Bun.semver.order(e,e)}catch{throw Error(`Invalid semantic version: ${e}`)}let t=/^(?:v|=)?(\d+)\./.exec(e)?.[1],n=t?Number(t):NaN;if(!Number.isSafeInteger(n))throw Error(`Invalid semantic version: ${e}`);return`>=${e} <${n+1}.0.0`}const r=/^[a-z0-9]+(?:-[a-z0-9]+)*$/;function i(e){if(!r.test(e.addonId))throw Error(`Addon id must contain lowercase letters, numbers, and hyphens`);let t={id:e.addonId,name:P(e.addonId),version:`1.0.0`,description:`${P(e.addonId)} addon for JChat`,author:`Developer`,compatibility:{jchat:n(e.jchatVersion??`1.0.0`),addonSdk:n(e.addonSdkVersion??`1.0.0`)},locales:[`ar`,`de`,`en`,`es`,`fr`,`hi`,`kn`,`ml`,`ta`,`te`,`ur`],defaultLocale:`en`,frontend:{bundle:`./ui.js`,mountPoints:[`chat-toolbar`]},permissions:[]},i=e.isMonorepoWorkspace?d(e.addonId):m(e.addonId,e.addonSdkVersion??`1.0.0`),a=e.isMonorepoWorkspace?f():h();return Object.freeze({"package.json":`${JSON.stringify(i,null,2)}\n`,"tsconfig.json":`${JSON.stringify(a,null,2)}\n`,"src/manifest.ts":g(t),"src/tailwind.css":j(),"src/locales/ar.json":_(t.name),"src/locales/de.json":v(t.name),"src/locales/en.json":y(t.name),"src/locales/es.json":b(t.name),"src/locales/fr.json":x(t.name),"src/locales/hi.json":S(t.name),"src/locales/kn.json":C(t.name),"src/locales/ml.json":w(t.name),"src/locales/ta.json":T(t.name),"src/locales/te.json":E(t.name),"src/locales/ur.json":D(t.name),"src/locales/index.ts":O(),"src/locales.test.ts":k(),"src/ui.tsx":A(t.name)})}async function a(n){let r=t.resolve(n.targetDir);if(e.existsSync(r))throw Error(`Addon target already exists: ${r}`);let a=Object.entries(i(n));for(let[n]of a){let i=t.join(r,...n.split(`/`));e.mkdirSync(t.dirname(i),{recursive:!0})}await Promise.all(a.map(([e,n])=>{let i=t.join(r,...e.split(`/`));return Bun.write(i,n)}))}function o(e){if(!r.test(e.addonId))throw Error(`Addon id must contain lowercase letters, numbers, and hyphens`);let t=e.sandboxImage??`ghcr.io/mjjabarullah/jchat-sandbox:${e.jchatVersion.replace(/\+/g,`-`)}`,n=i({addonId:e.addonId,isMonorepoWorkspace:!1,jchatVersion:e.jchatVersion,addonSdkVersion:e.addonSdkVersion}),a={".env.example":c(e.addonId,t),".gitignore":[`node_modules/`,`addon/node_modules/`,`addon/build/`,`.env`,``].join(`
2
- `),"README.md":N(e.addonId),"docker-compose.yml":l(),"package.json":`${JSON.stringify(u(e.addonId),null,2)}\n`,"scripts/devkit.ts":M()};for(let[e,t]of Object.entries(n))a[`addon/${e}`]=t;return Object.freeze(a)}async function s(n){let r=t.resolve(n.targetDir);if(e.existsSync(r))throw Error(`Development kit target already exists: ${r}`);let i=Object.entries(o(n));for(let[n]of i){let i=t.join(r,...n.split(`/`));e.mkdirSync(t.dirname(i),{recursive:!0})}await Promise.all(i.map(([e,n])=>{let i=t.join(r,...e.split(`/`));return Bun.write(i,n)}))}function c(e,t){return[`JCHAT_SANDBOX_IMAGE=${t}`,`JCHAT_SANDBOX_ADDON_ID=${e}`,`JCHAT_SANDBOX_PORT=4317`,`JCHAT_SANDBOX_HTTP_MOCK_PORT=4318`,`JCHAT_SANDBOX_HTTP_MOCK_OFFLINE=false`,`JCHAT_SANDBOX_ADDON_HTTP_MOCK_URL=http://http-mock:4318`,``].join(`
3
- `)}function l(){return`services:
1
+ import{t as e}from"./contracts.d.mts";import*as t from"node:fs";import*as n from"node:path";function r(e){try{Bun.semver.order(e,e)}catch{throw Error(`Invalid semantic version: ${e}`)}let t=/^(?:v|=)?(\d+)\./.exec(e)?.[1],n=t?Number(t):NaN;if(!Number.isSafeInteger(n))throw Error(`Invalid semantic version: ${e}`);return`>=${e} <${n+1}.0.0`}const i=[`ar`,`bg`,`de`,`el`,`en`,`es`,`fr`,`he`,`hi`,`hr`,`kn`,`ml`,`nl`,`pt`,`ro`,`ru`,`ta`,`te`,`tr`,`ur`],a=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,o={ar:e=>({welcome:`مرحبًا بك في ${e}`,mounted_at:`تم التثبيت في {{mountPoint}}`,test_button:`اختبار الإضافة`,toast_message:`مرحبًا من ${e}`}),bg:e=>({welcome:`Добре дошли в ${e}`,mounted_at:`Монтирано на {{mountPoint}}`,test_button:`Тестване на добавката`,toast_message:`Здравейте от ${e}`}),de:e=>({welcome:`Willkommen bei ${e}`,mounted_at:`Eingebunden an {{mountPoint}}`,test_button:`Addon testen`,toast_message:`Hallo von ${e}`}),el:e=>({welcome:`Καλώς ήρθατε στο ${e}`,mounted_at:`Προσαρτήθηκε στο {{mountPoint}}`,test_button:`Δοκιμή πρόσθετου`,toast_message:`Γεια σας από το ${e}`}),en:e=>({welcome:`Welcome to ${e}`,mounted_at:`Mounted at {{mountPoint}}`,test_button:`Test addon`,toast_message:`Hello from ${e}`}),es:e=>({welcome:`Bienvenido a ${e}`,mounted_at:`Montado en {{mountPoint}}`,test_button:`Probar complemento`,toast_message:`Hola desde ${e}`}),fr:e=>({welcome:`Bienvenue sur ${e}`,mounted_at:`Monté sur {{mountPoint}}`,test_button:`Tester le plugin`,toast_message:`Bonjour de ${e}`}),he:e=>({welcome:`ברוכים הבאים אל ${e}`,mounted_at:`מותקן ב-{{mountPoint}}`,test_button:`בדיקת התוסף`,toast_message:`שלום מאת ${e}`}),hi:e=>({welcome:`${e} में आपका स्वागत है`,mounted_at:`{{mountPoint}} पर माउंट किया गया`,test_button:`ऐडऑन का परीक्षण करें`,toast_message:`${e} की ओर से नमस्कार`}),hr:e=>({welcome:`Dobrodošli u ${e}`,mounted_at:`Ugrađeno na {{mountPoint}}`,test_button:`Testiraj dodatak`,toast_message:`Pozdrav iz ${e}`}),kn:e=>({welcome:`${e} ಗೆ ಸುಸ್ವಾಗತ`,mounted_at:`{{mountPoint}} ನಲ್ಲಿ ಸ್ಥಾಪಿಸಲಾಗಿದೆ`,test_button:`ಆಡ್ಆನ್ ಪರೀಕ್ಷಿಸಿ`,toast_message:`${e} ನಿಂದ ನಮಸ್ಕಾರ`}),ml:e=>({welcome:`${e}-ലേക്ക് സ്വാഗതം`,mounted_at:`{{mountPoint}}-ൽ ഘടിപ്പിച്ചിരിക്കുന്നു`,test_button:`ആഡ്ഓൺ പരിശോധിക്കുക`,toast_message:`${e}-ൽ നിന്നുള്ള ആശംസകൾ`}),nl:e=>({welcome:`Welkom bij ${e}`,mounted_at:`Gemonteerd op {{mountPoint}}`,test_button:`Addon testen`,toast_message:`Hallo vanuit ${e}`}),pt:e=>({welcome:`Bem-vindo ao ${e}`,mounted_at:`Montado em {{mountPoint}}`,test_button:`Testar extensão`,toast_message:`Olá de ${e}`}),ro:e=>({welcome:`Bun venit la ${e}`,mounted_at:`Montat la {{mountPoint}}`,test_button:`Testează extensia`,toast_message:`Bună din ${e}`}),ru:e=>({welcome:`Добро пожаловать в ${e}`,mounted_at:`Подключено в {{mountPoint}}`,test_button:`Проверить дополнение`,toast_message:`Привет от ${e}`}),ta:e=>({welcome:`${e}-க்கு வரவேற்கிறோம்`,mounted_at:`{{mountPoint}}-ல் பொருத்தப்பட்டுள்ளது`,test_button:`செருகுநிரலை சோதிக்கவும்`,toast_message:`${e}-லிருந்து வணக்கம்`}),te:e=>({welcome:`${e}కి స్వాగతం`,mounted_at:`{{mountPoint}} వద్ద మౌంట్ చేయబడింది`,test_button:`యాడ్ఆన్‌ని పరీక్షించండి`,toast_message:`${e} నుండి నమస్కారం`}),tr:e=>({welcome:`${e}'e hoş geldiniz`,mounted_at:`{{mountPoint}} konumuna yerleştirildi`,test_button:`Eklentiyi test et`,toast_message:`${e}'den merhaba`}),ur:e=>({welcome:`${e} میں خوش آمدید`,mounted_at:`{{mountPoint}} پر نصب ہے`,test_button:`ایڈ آن کا تجربہ کریں`,toast_message:`${e} کی طرف سے ہیلو`})};function s(t){if(!a.test(t.addonId))throw Error(`Addon id must contain lowercase letters, numbers, and hyphens`);let n=(t.template??`ui`)===`fullstack`,s=t.displayName?.trim()||A(t.addonId),c=t.mountPoints&&t.mountPoints.length>0?t.mountPoints:[e.CHAT_TOOLBAR,e.CHAT_PLUS_MENU],l={id:t.addonId,name:s,version:`1.0.0`,description:`${s} addon for JChat`,author:`Developer`,compatibility:{jchat:r(t.jchatVersion),addonSdk:r(t.addonSdkVersion)},locales:[...i],defaultLocale:`en`,frontend:{bundle:`./ui.js`,mountPoints:c},...n?{server:{bundle:`./api.ts`,migrations:[`migrations/001_initial.sql`]},permissions:[`addon.${t.addonId}.use`,`addon.${t.addonId}.manage`],permissionDefinitions:[{name:`addon.${t.addonId}.use`,label:`Use ${s}`,description:`Access and use ${s} in chat`},{name:`addon.${t.addonId}.manage`,label:`Manage ${s}`,description:`Configure and moderate ${s} settings`}]}:{permissions:[]}},u=t.isMonorepoWorkspace?m(t.addonId,s):_(t.addonId,t.addonSdkVersion),d=t.isMonorepoWorkspace?h():v(),f={"package.json":`${JSON.stringify(u,null,2)}\n`,"tsconfig.json":`${JSON.stringify(d,null,2)}\n`,"src/manifest.ts":y(l),"src/tailwind.css":S(),"src/locales/index.ts":b(),"src/locales.test.ts":x(),"src/ui.tsx":E(s,c,n),".agents/skills/jchat-addon-development/SKILL.md":O()};for(let e of i){let t=o[e];f[`src/locales/${e}.json`]=`${JSON.stringify(t(s),null,2)}\n`}return n&&(f[`src/db.ts`]=C(),f[`src/api.ts`]=T(t.addonId),f[`migrations/001_initial.sql`]=w(t.addonId)),Object.freeze(f)}async function c(e){let r=n.resolve(e.targetDir);if(t.existsSync(r))throw Error(`Addon target already exists: ${r}`);let i=Object.entries(s(e));for(let[e]of i){let i=n.join(r,...e.split(`/`));t.mkdirSync(n.dirname(i),{recursive:!0})}await Promise.all(i.map(([e,t])=>{let i=n.join(r,...e.split(`/`));return Bun.write(i,t)}))}function l(e){if(!a.test(e.addonId))throw Error(`Addon id must contain lowercase letters, numbers, and hyphens`);let t=e.sandboxImage??`ghcr.io/mjjabarullah/jchat-sandbox:${e.jchatVersion.replace(/\+/g,`-`)}`,n=s({addonId:e.addonId,isMonorepoWorkspace:!1,jchatVersion:e.jchatVersion,addonSdkVersion:e.addonSdkVersion,template:e.template,mountPoints:e.mountPoints,displayName:e.displayName}),r={".env.example":d(e.addonId,t),".gitignore":[`node_modules/`,`addon/node_modules/`,`addon/build/`,`.env`,``].join(`
2
+ `),"README.md":k(e.addonId),"docker-compose.yml":f(),"package.json":`${JSON.stringify(p(e.addonId),null,2)}\n`,"scripts/devkit.ts":D(),".agents/skills/jchat-addon-development/SKILL.md":O()};for(let[e,t]of Object.entries(n))r[`addon/${e}`]=t;return Object.freeze(r)}async function u(e){let r=n.resolve(e.targetDir);if(t.existsSync(r))throw Error(`Development kit target already exists: ${r}`);let i=Object.entries(l(e));for(let[e]of i){let i=n.join(r,...e.split(`/`));t.mkdirSync(n.dirname(i),{recursive:!0})}await Promise.all(i.map(([e,t])=>{let i=n.join(r,...e.split(`/`));return Bun.write(i,t)}))}function d(e,t){return[`JCHAT_SANDBOX_IMAGE=${t}`,`JCHAT_SANDBOX_ADDON_ID=${e}`,`JCHAT_SANDBOX_PORT=4317`,`JCHAT_SANDBOX_HTTP_MOCK_PORT=4318`,`JCHAT_SANDBOX_HTTP_MOCK_OFFLINE=false`,`JCHAT_SANDBOX_ADDON_HTTP_MOCK_URL=http://http-mock:4318`,``].join(`
3
+ `)}function f(){return`services:
4
4
  sandbox:
5
5
  image: \${JCHAT_SANDBOX_IMAGE:?Copy .env.example to .env}
6
6
  restart: unless-stopped
@@ -73,46 +73,64 @@ volumes:
73
73
  sandbox_postgres:
74
74
  sandbox_redis:
75
75
  sandbox_uploads:
76
- `}function u(e){return{name:`${e}-development-kit`,private:!0,type:`module`,workspaces:[`addon`],scripts:{sandbox:`bun run ./scripts/devkit.ts run`,"sandbox:test":`bun run ./scripts/devkit.ts test`,"sandbox:reset":`bun run ./scripts/devkit.ts reset`,"sandbox:package":`bun run ./scripts/devkit.ts package`,"sandbox:cleanup":`bun run ./scripts/devkit.ts cleanup`},devDependencies:{"@types/bun":`^1.4.0`},engines:{bun:`>=1.4.0`}}}function d(e){return{name:`@jchat/${e}`,version:`1.0.0-alpha.0`,private:!0,description:`${P(e)} addon for JChat`,repository:{type:`git`,url:`git+https://github.com/mjjabarullah/jchat.git`,directory:`addons/${e}`},type:`module`,scripts:{dev:`jchat-addon dev`,build:`tsc --noEmit && jchat-addon build`,test:`bun test`,lint:`oxlint .`,format:`oxfmt . --write`,typecheck:`tsc --noEmit`},dependencies:{"lucide-react":`catalog:`,"usehooks-ts":`catalog:`},devDependencies:{"@tailwindcss/vite":`catalog:`,"@types/bun":`catalog:`,"@types/react":`catalog:`,"@types/react-dom":`catalog:`,elysia:`catalog:`,vite:`catalog:`},peerDependencies:{"@jchat/addon-sdk":`workspace:*`,"@tanstack/react-query":`catalog:`,react:`catalog:`,"react-dom":`catalog:`}}}function f(){return{extends:`@jchat/addon-sdk/tsconfig.json`,include:[`src/**/*`]}}function p(){try{let n=import.meta.dirname;for(let r=0;r<6;r++){let r=t.join(n,`package.json`);if(e.existsSync(r)){let t=e.readFileSync(r,`utf8`),n=JSON.parse(t);if(n.catalog&&typeof n.catalog==`object`)return n.catalog}let i=t.dirname(n);if(i===n)break;n=i}}catch{}return{}}function m(e,t){let n=p();return{name:`@jchat/${e}`,version:`1.0.0`,private:!0,type:`module`,scripts:{dev:`jchat-addon dev`,build:`tsc --noEmit && jchat-addon build`,test:`bun test`,typecheck:`tsc --noEmit`},dependencies:{"@jchat/addon-sdk":`^${t}`,"@tanstack/react-query":n[`@tanstack/react-query`]??`^5.101.4`,"lucide-react":n[`lucide-react`]??`1.31.0`,react:n.react??`^19.2.8`,"react-dom":n[`react-dom`]??`^19.2.8`,"usehooks-ts":n[`usehooks-ts`]??`^3.1.1`},devDependencies:{"@tailwindcss/vite":n[`@tailwindcss/vite`]??`^4.3.3`,"@types/bun":n[`@types/bun`]??`^1.4.0`,"@types/react":n[`@types/react`]??`^19.2.18`,"@types/react-dom":n[`@types/react-dom`]??`^19.2.4`,elysia:n.elysia??`^1.4.29`,tailwindcss:n.tailwindcss??`^4.3.3`,typescript:n.typescript??`^7.0.2`,vite:n.vite??`^8.2.1`}}}function h(){return{extends:`@jchat/addon-sdk/tsconfig.json`,include:[`src/**/*.ts`,`src/**/*.tsx`]}}function g(e){return`import type { AddonManifest } from "@jchat/addon-sdk"
76
+ `}function p(e){return{name:`${e}-development-kit`,private:!0,type:`module`,workspaces:[`addon`],scripts:{sandbox:`bun run ./scripts/devkit.ts run`,"sandbox:test":`bun run ./scripts/devkit.ts test`,"sandbox:reset":`bun run ./scripts/devkit.ts reset`,"sandbox:package":`bun run ./scripts/devkit.ts package`,"sandbox:cleanup":`bun run ./scripts/devkit.ts cleanup`},devDependencies:{"@types/bun":`^1.4.0`},engines:{bun:`>=1.4.0`}}}function m(e,t){return{name:`@jchat/${e}`,version:`1.0.0-alpha.0`,private:!0,description:`${t} addon for JChat`,repository:{type:`git`,url:`git+https://github.com/mjjabarullah/jchat.git`,directory:`addons/${e}`},type:`module`,scripts:{dev:`jchat-addon dev`,build:`tsc --noEmit && jchat-addon build`,test:`bun test`,lint:`oxlint .`,format:`oxfmt . --write`,typecheck:`tsc --noEmit`},dependencies:{},devDependencies:{"@tailwindcss/vite":`catalog:`,"@types/bun":`catalog:`,"@types/react":`catalog:`,"@types/react-dom":`catalog:`,elysia:`catalog:`,vite:`catalog:`},peerDependencies:{"@jchat/addon-sdk":`workspace:*`,"@tanstack/react-query":`catalog:`,react:`catalog:`,"react-dom":`catalog:`}}}function h(){return{extends:`@jchat/addon-sdk/tsconfig.json`,include:[`src/**/*`]}}function g(){try{let e=import.meta.dirname;for(let r=0;r<6;r++){let r=n.join(e,`package.json`);if(t.existsSync(r)){let e=t.readFileSync(r,`utf8`),n=JSON.parse(e);if(n.catalog&&typeof n.catalog==`object`)return n.catalog}let i=n.dirname(e);if(i===e)break;e=i}}catch{}return{}}function _(e,t){let n=g();return{name:`@jchat/${e}`,version:`1.0.0`,private:!0,type:`module`,scripts:{dev:`jchat-addon dev`,build:`tsc --noEmit && jchat-addon build`,test:`bun test`,typecheck:`tsc --noEmit`},dependencies:{},devDependencies:{"@tailwindcss/vite":n[`@tailwindcss/vite`]??`^4.3.3`,"@types/bun":n[`@types/bun`]??`^1.4.0`,"@types/react":n[`@types/react`]??`^19.2.18`,"@types/react-dom":n[`@types/react-dom`]??`^19.2.4`,elysia:n.elysia??`^1.4.29`,tailwindcss:n.tailwindcss??`^4.3.3`,vite:n.vite??`^8.2.1`},peerDependencies:{"@jchat/addon-sdk":`^${t}`,"@tanstack/react-query":n[`@tanstack/react-query`]??`^5.101.4`,react:n.react??`^19.2.8`,"react-dom":n[`react-dom`]??`^19.2.8`}}}function v(){return{extends:`@jchat/addon-sdk/tsconfig.json`,include:[`src/**/*.ts`,`src/**/*.tsx`]}}function y(e){return`import type { AddonManifest } from "@jchat/addon-sdk"
77
77
 
78
78
  const manifest: AddonManifest = ${JSON.stringify(e,null,2)}
79
79
 
80
80
  export default manifest
81
- `}function _(e){return`${JSON.stringify({welcome:`مرحبًا بك في ${e}`,mounted_at:`تم التثبيت في {{mountPoint}}`,test_button:`اختبار الإضافة`,toast_message:`مرحبًا من ${e}`},null,2)}\n`}function v(e){return`${JSON.stringify({welcome:`Willkommen bei ${e}`,mounted_at:`Eingebunden an {{mountPoint}}`,test_button:`Addon testen`,toast_message:`Hallo von ${e}`},null,2)}\n`}function y(e){return`${JSON.stringify({welcome:`Welcome to ${e}`,mounted_at:`Mounted at {{mountPoint}}`,test_button:`Test addon`,toast_message:`Hello from ${e}`},null,2)}\n`}function b(e){return`${JSON.stringify({welcome:`Bienvenido a ${e}`,mounted_at:`Montado en {{mountPoint}}`,test_button:`Probar complemento`,toast_message:`Hola desde ${e}`},null,2)}\n`}function x(e){return`${JSON.stringify({welcome:`Bienvenue sur ${e}`,mounted_at:`Monté sur {{mountPoint}}`,test_button:`Tester le plugin`,toast_message:`Bonjour de ${e}`},null,2)}\n`}function S(e){return`${JSON.stringify({welcome:`${e} में आपका स्वागत है`,mounted_at:`{{mountPoint}} पर माउंट किया गया`,test_button:`ऐडऑन का परीक्षण करें`,toast_message:`${e} की ओर से नमस्कार`},null,2)}\n`}function C(e){return`${JSON.stringify({welcome:`${e} ಗೆ ಸುಸ್ವಾಗತ`,mounted_at:`{{mountPoint}} ನಲ್ಲಿ ಸ್ಥಾಪಿಸಲಾಗಿದೆ`,test_button:`ಆಡ್ಆನ್ ಪರೀಕ್ಷಿಸಿ`,toast_message:`${e} ನಿಂದ ನಮಸ್ಕಾರ`},null,2)}\n`}function w(e){return`${JSON.stringify({welcome:`${e}-ലേക്ക് സ്വാഗതം`,mounted_at:`{{mountPoint}}-ൽ ഘടിപ്പിച്ചിരിക്കുന്നു`,test_button:`ആഡ്ഓൺ പരിശോധിക്കുക`,toast_message:`${e}-ൽ നിന്നുള്ള ആശംസകൾ`},null,2)}\n`}function T(e){return`${JSON.stringify({welcome:`${e}-க்கு வரவேற்கிறோம்`,mounted_at:`{{mountPoint}}-ல் பொருத்தப்பட்டுள்ளது`,test_button:`செருகுநிரலை சோதிக்கவும்`,toast_message:`${e}-லிருந்து வணக்கம்`},null,2)}\n`}function E(e){return`${JSON.stringify({welcome:`${e}కి స్వాగతం`,mounted_at:`{{mountPoint}} వద్ద మౌంట్ చేయబడింది`,test_button:`యాడ్ఆన్‌ని పరీక్షించండి`,toast_message:`${e} నుండి నమస్కారం`},null,2)}\n`}function D(e){return`${JSON.stringify({welcome:`${e} میں خوش آمدید`,mounted_at:`{{mountPoint}} پر نصب ہے`,test_button:`ایڈ آن کا تجربہ کریں`,toast_message:`${e} کی طرف سے ہیلو`},null,2)}\n`}function O(){return`import ar from "./ar.json"
81
+ `}function b(){return`import ar from "./ar.json"
82
+ import bg from "./bg.json"
82
83
  import de from "./de.json"
84
+ import el from "./el.json"
83
85
  import en from "./en.json"
84
86
  import es from "./es.json"
85
87
  import fr from "./fr.json"
88
+ import he from "./he.json"
86
89
  import hi from "./hi.json"
90
+ import hr from "./hr.json"
87
91
  import kn from "./kn.json"
88
92
  import ml from "./ml.json"
93
+ import nl from "./nl.json"
94
+ import pt from "./pt.json"
95
+ import ro from "./ro.json"
96
+ import ru from "./ru.json"
89
97
  import ta from "./ta.json"
90
98
  import te from "./te.json"
99
+ import tr from "./tr.json"
91
100
  import ur from "./ur.json"
92
101
 
93
102
  export type TranslationKey = keyof typeof en
94
103
 
95
104
  export const locales = {
96
105
  ar,
106
+ bg,
97
107
  de,
108
+ el,
98
109
  en,
99
110
  es,
100
111
  fr,
112
+ he,
101
113
  hi,
114
+ hr,
102
115
  kn,
103
116
  ml,
117
+ nl,
118
+ pt,
119
+ ro,
120
+ ru,
104
121
  ta,
105
122
  te,
123
+ tr,
106
124
  ur
107
125
  } as const
108
126
 
109
127
  export default locales
110
- `}function k(){return`import { describe, expect, test } from "bun:test"
111
- import { validateAddonLocales } from "@jchat/addon-sdk"
128
+ `}function x(){return`import { describe, expect, test } from "bun:test"
129
+ import { validateAddonLocales } from "@jchat/addon-sdk/i18n-validator"
112
130
  import * as path from "node:path"
113
131
 
114
132
  describe("addon locales", () => {
115
- test("maintains key parity across all 11 host languages", () => {
133
+ test("maintains key parity across all 20 host languages", () => {
116
134
  const localesDir = path.join(import.meta.dir, "locales")
117
135
  const result = validateAddonLocales(localesDir)
118
136
  if (!result.valid) {
@@ -121,16 +139,242 @@ describe("addon locales", () => {
121
139
  expect(result.valid).toBe(true)
122
140
  })
123
141
  })
124
- `}function A(e){return`import type { AddonProps } from "@jchat/addon-sdk"
125
- import { useAddonSDK, useIcons, useTranslation } from "@jchat/addon-sdk"
142
+ `}function S(){return`@import "@jchat/addon-sdk/ui/tailwind.css";`}function C(){return`import type { ColumnType } from "@jchat/addon-sdk"
143
+
144
+ export interface AddonItemTable {
145
+ id: string
146
+ siteId: string
147
+ roomId: string
148
+ userId: string
149
+ title: string
150
+ createdAt: ColumnType<Date, Date | string, Date | string>
151
+ }
152
+
153
+ export interface AddonDatabase {
154
+ addon_items: AddonItemTable
155
+ }
156
+ `}function w(e){let t=`${e.replace(/-/g,`_`)}_items`;return`CREATE TABLE IF NOT EXISTS "${t}" (
157
+ "id" VARCHAR(36) PRIMARY KEY,
158
+ "siteId" VARCHAR(36) NOT NULL,
159
+ "roomId" VARCHAR(36) NOT NULL,
160
+ "userId" VARCHAR(36) NOT NULL,
161
+ "title" VARCHAR(255) NOT NULL,
162
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
163
+ );
164
+
165
+ CREATE INDEX IF NOT EXISTS "${`idx_${t}_site_room`}"
166
+ ON "${t}" ("siteId", "roomId", "createdAt" DESC);
167
+ `}function T(e){let t=`${e.replace(/-/g,`_`)}_items`;return`import type { AddonApp, AddonServerContext } from "@jchat/addon-sdk"
168
+ import type { AddonDatabase } from "./db"
169
+
170
+ export default (
171
+ {
172
+ db,
173
+ t,
174
+ cache,
175
+ emit,
176
+ getUser,
177
+ hasPermission
178
+ }: AddonServerContext<AddonDatabase>,
179
+ app: AddonApp
180
+ ) => {
181
+ return app
182
+ .get(
183
+ "/items",
184
+ async ({ query, user }) => {
185
+ const siteId = user?.siteId ?? ""
186
+ const roomId = query.roomId ?? ""
187
+ const items = await (db as any)
188
+ .selectFrom("${t}")
189
+ .where("siteId", "=", siteId)
190
+ .where("roomId", "=", roomId)
191
+ .orderBy("createdAt", "desc")
192
+ .limit(20)
193
+ .selectAll()
194
+ .execute()
195
+
196
+ return { items }
197
+ },
198
+ {
199
+ query: t.Object({
200
+ roomId: t.Optional(t.String())
201
+ })
202
+ }
203
+ )
204
+ .post(
205
+ "/items",
206
+ async ({ body, user, set }) => {
207
+ if (!user) {
208
+ set.status = 401
209
+ return { error: "Unauthorized" }
210
+ }
211
+
212
+ const id = Bun.randomUUIDv7()
213
+ const siteId = user.siteId
214
+ const roomId = body.roomId
215
+ const title = body.title
216
+
217
+ await (db as any)
218
+ .insertInto("${t}")
219
+ .values({
220
+ id,
221
+ siteId,
222
+ roomId,
223
+ userId: user.id,
224
+ title,
225
+ createdAt: new Date()
226
+ })
227
+ .execute()
228
+
229
+ await emit("addon:${e}:item-created", { id, title, userId: user.id })
230
+ return { id, title }
231
+ },
232
+ {
233
+ body: t.Object({
234
+ roomId: t.String(),
235
+ title: t.String({ minLength: 1, maxLength: 255 })
236
+ })
237
+ }
238
+ )
239
+ }
240
+ `}function E(e,t,n){return n?`import type { AddonProps } from "@jchat/addon-sdk"
241
+ import { useAddonMutation, useAddonQuery, useAddonSDK, useTranslation } from "@jchat/addon-sdk"
242
+ import Icons from "@jchat/addon-sdk/icon"
243
+ import { useState } from "react"
244
+ import locales from "./locales"
245
+ import "./tailwind.css"
246
+
247
+ interface ItemRecord {
248
+ id: string
249
+ title: string
250
+ createdAt: string
251
+ }
252
+
253
+ export default function Addon({ mountPoint, roomId }: AddonProps) {
254
+ const sdk = useAddonSDK()
255
+ const { t, dir } = useTranslation(locales)
256
+ const [title, setTitle] = useState("")
257
+
258
+ const { data, refetch, isLoading } = useAddonQuery<{ items: ItemRecord[] }>({
259
+ path: \`/items?roomId=\${roomId ?? ""}\`
260
+ })
261
+
262
+ const { mutate: createItem, isPending } = useAddonMutation<
263
+ { id: string; title: string },
264
+ { roomId: string; title: string }
265
+ >({
266
+ path: "/items",
267
+ onSuccess: () => {
268
+ setTitle("")
269
+ refetch()
270
+ sdk.toast(t("toast_message", { name: "${e}" }))
271
+ }
272
+ })
273
+
274
+ if (mountPoint === "chat-sidebar") {
275
+ return (
276
+ <aside dir={dir} className="flex h-full flex-col border-s border-border bg-card p-4 text-card-foreground">
277
+ <header className="mb-4 flex items-center gap-2 border-b border-border pb-2">
278
+ <Icons.Plus aria-hidden="true" className="size-5 text-primary" />
279
+ <h2 className="font-semibold">${e}</h2>
280
+ </header>
281
+ <p className="mb-3 text-xs text-muted-foreground">{t("mounted_at", { mountPoint })}</p>
282
+ <form
283
+ className="mb-4 flex gap-2"
284
+ onSubmit={(e) => {
285
+ e.preventDefault()
286
+ if (!title.trim() || !roomId) return
287
+ createItem({ roomId, title: title.trim() })
288
+ }}
289
+ >
290
+ <input
291
+ type="text"
292
+ value={title}
293
+ onChange={(e) => setTitle(e.target.value)}
294
+ placeholder="Add new item..."
295
+ className="flex-1 rounded border border-input bg-background px-2 py-1 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
296
+ />
297
+ <button
298
+ type="submit"
299
+ disabled={isPending || !title.trim()}
300
+ className="rounded bg-primary px-3 py-1 text-sm text-primary-foreground disabled:opacity-50"
301
+ >
302
+ {isPending ? "..." : t("test_button")}
303
+ </button>
304
+ </form>
305
+ <div className="flex-1 space-y-1 overflow-y-auto">
306
+ {isLoading && <p className="text-xs text-muted-foreground">Loading...</p>}
307
+ {data?.items?.map((item) => (
308
+ <div key={item.id} className="rounded border border-border/50 bg-background/50 p-2 text-xs">
309
+ {item.title}
310
+ </div>
311
+ ))}
312
+ </div>
313
+ </aside>
314
+ )
315
+ }
316
+
317
+ return (
318
+ <div dir={dir} className="rounded border border-border bg-card p-2 text-card-foreground">
319
+ <div className="mb-2 flex items-center justify-between gap-2">
320
+ <div className="flex items-center gap-2">
321
+ <Icons.Plus aria-hidden="true" className="size-4 text-primary" />
322
+ <strong>${e}</strong>
323
+ </div>
324
+ <span className="text-xs text-muted-foreground">{t("mounted_at", { mountPoint })}</span>
325
+ </div>
326
+ <div className="flex gap-2">
327
+ <input
328
+ type="text"
329
+ value={title}
330
+ onChange={(e) => setTitle(e.target.value)}
331
+ placeholder="New item..."
332
+ className="flex-1 rounded border border-input bg-background px-2 py-1 text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
333
+ />
334
+ <button
335
+ type="button"
336
+ disabled={isPending || !title.trim()}
337
+ className="rounded bg-primary px-3 py-1 text-xs text-primary-foreground disabled:opacity-50"
338
+ onClick={() => {
339
+ if (!title.trim()) return
340
+ createItem({ roomId: roomId ?? "general", title: title.trim() })
341
+ }}
342
+ >
343
+ {isPending ? "..." : t("test_button")}
344
+ </button>
345
+ </div>
346
+ </div>
347
+ )
348
+ }
349
+ `:`import type { AddonProps } from "@jchat/addon-sdk"
350
+ import { useAddonSDK, useTranslation } from "@jchat/addon-sdk"
351
+ import Icons from "@jchat/addon-sdk/icon"
126
352
  import locales from "./locales"
127
353
  import "./tailwind.css"
128
354
 
129
355
  export default function Addon({ mountPoint }: AddonProps) {
130
356
  const sdk = useAddonSDK()
131
- const { Icons } = useIcons()
132
357
  const { t, dir } = useTranslation(locales)
133
358
 
359
+ if (mountPoint === "chat-sidebar") {
360
+ return (
361
+ <aside dir={dir} className="flex h-full flex-col border-s border-border bg-card p-4 text-card-foreground">
362
+ <header className="mb-4 flex items-center gap-2 border-b border-border pb-2">
363
+ <Icons.Plus aria-hidden="true" className="size-5 text-primary" />
364
+ <h2 className="font-semibold">${e}</h2>
365
+ </header>
366
+ <p className="mb-2 text-sm text-muted-foreground">{t("mounted_at", { mountPoint })}</p>
367
+ <button
368
+ type="button"
369
+ className="mt-4 rounded bg-primary px-3 py-2 text-primary-foreground"
370
+ onClick={() => sdk.toast(t("toast_message", { name: "${e}" }))}
371
+ >
372
+ {t("test_button")}
373
+ </button>
374
+ </aside>
375
+ )
376
+ }
377
+
134
378
  return (
135
379
  <div dir={dir} className="rounded border border-border bg-card p-2 text-card-foreground">
136
380
  <div className="mb-2 flex items-center gap-2">
@@ -148,7 +392,7 @@ export default function Addon({ mountPoint }: AddonProps) {
148
392
  </div>
149
393
  )
150
394
  }
151
- `}function j(){return`@import "@jchat/addon-sdk/ui/tailwind.css";`}function M(){return`import * as fs from "node:fs"
395
+ `}function D(){return`import * as fs from "node:fs"
152
396
  import * as path from "node:path"
153
397
 
154
398
  type Command = "run" | "test" | "reset" | "package" | "cleanup"
@@ -217,7 +461,246 @@ function readEnvironment(file: string): Record<string, string> {
217
461
  })
218
462
  )
219
463
  }
220
- `}function N(e){return`# ${P(e)} Development Kit
464
+ `}function O(){return`---
465
+ name: jchat-addon-development
466
+ description: Comprehensive guide for developing JChat addons using @jchat/addon-sdk, including manifests, UI widgets, direct icon imports, Elysia backend, isolated DB schemas, and i18n.
467
+ ---
468
+
469
+ # JChat Addon Development Skill
470
+
471
+ Use this skill when developing, updating, or debugging JChat addons using \`@jchat/addon-sdk\`.
472
+
473
+ ---
474
+
475
+ ## 1. Architectural Guardrails (Non-Negotiable)
476
+
477
+ 1. **Import Boundaries**:
478
+ - Addons MUST ONLY import \`@jchat/addon-sdk\`, \`@jchat/addon-sdk/ui\` (components & toast), \`@jchat/addon-sdk/state\` (atomic state), \`@jchat/addon-sdk/icon\`, and \`@jchat/addon-sdk/server\`.
479
+ - NEVER import private internal monorepo packages (\`@jchat/icon\`, \`@jchat/ui\`, \`@jchat/db\`, \`@jchat/shared\`, etc.).
480
+ - Host externals (\`react\`, \`react-dom\`, \`@tanstack/react-query\`) are provided at runtime by the JChat host.
481
+ 2. **Icons**:
482
+ - Use direct imports: \`import Icons from "@jchat/addon-sdk/icon"\`.
483
+ - Never call \`useIcons()\` or install \`lucide-react\` / \`react-icons\` directly in the addon.
484
+ 3. **Database Isolation**:
485
+ - All addon tables MUST reside in the isolated \`addons\` PostgreSQL schema (e.g. \`addons.GlimpseStories\`).
486
+ - Never write to or alter core tables (\`Users\`, \`Rooms\`, \`Messages\`, \`Sites\`).
487
+ 4. **Real-Time Event Namespacing**:
488
+ - All WebSocket/Redis events MUST follow the canonical format: \`addon:<addonId>:<eventName>\` (e.g. \`addon:pollwise:voted\`).
489
+ 5. **Localization (i18n)**:
490
+ - All addons must support all 20 host languages:
491
+ \`ar\`, \`bg\`, \`de\`, \`el\`, \`en\`, \`es\`, \`fr\`, \`he\`, \`hi\`, \`hr\`, \`kn\`, \`ml\`, \`nl\`, \`pt\`, \`ro\`, \`ru\`, \`ta\`, \`te\`, \`tr\`, \`ur\`.
492
+ - All locale dictionaries in \`src/locales/*.json\` must maintain 100% key parity.
493
+ 6. **Directional Styling (RTL/LTR)**:
494
+ - Use Tailwind CSS logical properties (\`ps-*\`, \`pe-*\`, \`ms-*\`, \`me-*\`, \`text-start\`, \`text-end\`, \`start-*\`, \`end-*\`, \`rounded-s-*\`, \`rounded-e-*\`).
495
+ - Never use physical \`pl-*\`, \`pr-*\`, \`ml-*\`, \`mr-*\`, \`text-left\`, or \`text-right\`.
496
+
497
+ ---
498
+
499
+ ## 2. Manifest Contract (\`src/manifest.ts\`)
500
+
501
+ Define addon metadata and capabilities with \`defineAddonManifest\`:
502
+
503
+ \`\`\`typescript
504
+ import { defineAddonManifest } from "@jchat/addon-sdk"
505
+
506
+ export default defineAddonManifest({
507
+ id: "pollwise",
508
+ name: "Pollwise Polls",
509
+ version: "1.0.0",
510
+ description: "Create and vote on live interactive polls in chat rooms.",
511
+ author: "JChat Ecosystem Team",
512
+ compatibility: {
513
+ jchat: ">=1.0.0-alpha.0 <2.0.0",
514
+ addonSdk: ">=1.0.0-alpha.0 <2.0.0"
515
+ },
516
+ locales: [
517
+ "ar", "bg", "de", "el", "en", "es", "fr", "he", "hi", "hr",
518
+ "kn", "ml", "nl", "pt", "ro", "ru", "ta", "te", "tr", "ur"
519
+ ],
520
+ defaultLocale: "en",
521
+ frontend: {
522
+ bundle: "./ui.js",
523
+ mountPoints: ["chat-toolbar", "room-settings"]
524
+ },
525
+ backend: {
526
+ bundle: "./server.js"
527
+ },
528
+ permissions: ["network", "storage"]
529
+ })
530
+ \`\`\`
531
+
532
+ ---
533
+
534
+ ## 3. Frontend UI Development (\`src/ui.tsx\`)
535
+
536
+ Addon UI components receive \`AddonProps\` and use hooks from \`@jchat/addon-sdk\`:
537
+
538
+ \`\`\`tsx
539
+ import {
540
+ type AddonProps,
541
+ useAddonSDK,
542
+ useAddonQuery,
543
+ useAddonMutation,
544
+ useAddonTranslation
545
+ } from "@jchat/addon-sdk"
546
+ import Icons from "@jchat/addon-sdk/icon"
547
+
548
+ export default function PollwiseAddon({ mountPoint, roomId }: AddonProps) {
549
+ const sdk = useAddonSDK()
550
+ const { t } = useAddonTranslation()
551
+
552
+ // 1. Data fetching via TanStack Query bridge
553
+ const { data: poll, isLoading } = useAddonQuery({
554
+ queryKey: ["poll", roomId],
555
+ queryFn: () => sdk.api.get<{ id: string; question: string }>(\`/rooms/\${roomId}/poll\`)
556
+ })
557
+
558
+ // 2. Mutations
559
+ const voteMutation = useAddonMutation({
560
+ mutationFn: (optionId: string) => sdk.api.post(\`/rooms/\${roomId}/vote\`, { optionId }),
561
+ onSuccess: () => {
562
+ sdk.ui.toast({
563
+ title: t("vote_success_title"),
564
+ description: t("vote_success_desc"),
565
+ variant: "success"
566
+ })
567
+ }
568
+ })
569
+
570
+ // 3. Real-time event listener
571
+ useEffect(() => {
572
+ const unsubscribe = sdk.events.on("addon:pollwise:updated", (updatedPoll) => {
573
+ // Handle incoming real-time payload
574
+ })
575
+ return () => unsubscribe()
576
+ }, [sdk])
577
+
578
+ if (isLoading) return <div>{t("loading")}</div>
579
+
580
+ return (
581
+ <div className="p-4 rounded-xl bg-card border border-border">
582
+ <div className="flex items-center gap-2 mb-3">
583
+ <Icons.BarChart3 className="size-5 text-primary" />
584
+ <h3 className="font-semibold text-foreground">{poll?.question}</h3>
585
+ </div>
586
+ <button
587
+ onClick={() => voteMutation.mutate("option-1")}
588
+ className="px-3 py-1.5 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
589
+ >
590
+ {t("vote_button")}
591
+ </button>
592
+ </div>
593
+ )
594
+ }
595
+ \`\`\`
596
+
597
+ ### Direct Icon Usage
598
+ Icons are rendered directly:
599
+ \`\`\`tsx
600
+ import Icons, { resolveIcon } from "@jchat/addon-sdk/icon"
601
+
602
+ // Direct JSX:
603
+ <Icons.Sparkles className="size-4 text-primary" />
604
+ <Icons.Send className="size-5" />
605
+
606
+ // Dynamic resolution:
607
+ const DynamicIcon = resolveIcon("Smile", Icons.HelpCircle)
608
+ <DynamicIcon className="size-4" />
609
+ \`\`\`
610
+
611
+ ---
612
+
613
+ ## 4. Backend Server Development (\`src/server.ts\`)
614
+
615
+ Fullstack addons expose ElysiaJS routes running within the JChat server runtime:
616
+
617
+ \`\`\`typescript
618
+ import { createAddonServer } from "@jchat/addon-sdk/server"
619
+ import { t } from "elysia"
620
+
621
+ export default createAddonServer("pollwise", ({ app, db, events }) => {
622
+ return app
623
+ .get("/rooms/:roomId/poll", async ({ params: { roomId } }) => {
624
+ // Query isolated addons schema using Kysely
625
+ const poll = await db
626
+ .selectFrom("addons.Polls")
627
+ .selectAll()
628
+ .where("roomId", "=", roomId)
629
+ .executeTakeFirst()
630
+
631
+ return poll ?? { message: "No active poll" }
632
+ })
633
+ .post(
634
+ "/rooms/:roomId/vote",
635
+ async ({ params: { roomId }, body, user }) => {
636
+ // Record vote in DB
637
+ await db
638
+ .insertInto("addons.PollVotes")
639
+ .values({
640
+ pollId: body.pollId,
641
+ userId: user.id,
642
+ optionId: body.optionId
643
+ })
644
+ .execute()
645
+
646
+ // Broadcast real-time event to room subscribers
647
+ await events.publish(\`addon:pollwise:updated\`, {
648
+ roomId,
649
+ pollId: body.pollId
650
+ })
651
+
652
+ return { success: true }
653
+ },
654
+ {
655
+ body: t.Object({
656
+ pollId: t.String(),
657
+ optionId: t.String()
658
+ })
659
+ }
660
+ )
661
+ })
662
+ \`\`\`
663
+
664
+ ---
665
+
666
+ ## 5. Addon Localization (\`src/locales/\`)
667
+
668
+ Every translatable string in the UI must use \`t("key")\`.
669
+ Translations reside in \`src/locales/<lang>.json\` for all 20 supported languages:
670
+
671
+ \`\`\`json
672
+ // src/locales/en.json
673
+ {
674
+ "welcome": "Welcome to Pollwise",
675
+ "vote_button": "Submit Vote",
676
+ "vote_success_title": "Vote Recorded",
677
+ "vote_success_desc": "Thank you for participating."
678
+ }
679
+ \`\`\`
680
+
681
+ Validate key parity across all 20 languages:
682
+ \`\`\`bash
683
+ bun check:translations
684
+ \`\`\`
685
+
686
+ ---
687
+
688
+ ## 6. Build and Verification Workflow
689
+
690
+ \`\`\`bash
691
+ # 1. Typecheck the addon
692
+ bun run typecheck
693
+
694
+ # 2. Bundle the addon artifacts (frontend bundle into ui.js, backend into server.js)
695
+ bun run build
696
+
697
+ # 3. In the standalone devkit: start the disposable sandbox environment
698
+ bun run devkit:start
699
+
700
+ # 4. View sandbox logs
701
+ bun run devkit:logs
702
+ \`\`\`
703
+ `}function k(e){return`# ${A(e)} Development Kit
221
704
 
222
705
  This workspace develops \`${e}\` against the license-free JChat developer sandbox without
223
706
  including JChat Core source.
@@ -243,4 +726,4 @@ Open \`http://127.0.0.1:4317\`. The HTTP fixture recorder is available at
243
726
 
244
727
  The referenced JChat packages and sandbox image must be published before this standalone kit can
245
728
  install or start outside the JChat repository.
246
- `}function P(e){return e.split(`-`).map(e=>`${e[0]?.toUpperCase()??``}${e.slice(1)}`).join(` `)}export{s as i,o as n,a as r,i as t};
729
+ `}function A(e){return e.split(`-`).map(e=>`${e[0]?.toUpperCase()??``}${e.slice(1)}`).join(` `)}export{c as a,A as i,s as n,u as o,l as r,i as t};
package/dist/index.d.mts CHANGED
@@ -1,26 +1,3 @@
1
- //#region src/generator.d.ts
2
- interface StandaloneDevkitOptions {
3
- addonId: string;
4
- targetDir: string;
5
- jchatVersion: string;
6
- addonSdkVersion: string;
7
- sandboxImage?: string;
8
- }
9
- interface AddonWorkspaceOptions {
10
- addonId: string;
11
- targetDir: string;
12
- isMonorepoWorkspace?: boolean;
13
- jchatVersion?: string;
14
- addonSdkVersion?: string;
15
- }
16
- declare function createAddonFiles(options: {
17
- addonId: string;
18
- isMonorepoWorkspace?: boolean;
19
- jchatVersion?: string;
20
- addonSdkVersion?: string;
21
- }): Readonly<Record<string, string>>;
22
- declare function writeAddonWorkspace(options: AddonWorkspaceOptions): Promise<void>;
23
- declare function createStandaloneDevkitFiles(options: StandaloneDevkitOptions): Readonly<Record<string, string>>;
24
- declare function writeStandaloneDevkit(options: StandaloneDevkitOptions): Promise<void>;
25
- //#endregion
26
- export { type AddonWorkspaceOptions, type StandaloneDevkitOptions, createAddonFiles, createStandaloneDevkitFiles, writeAddonWorkspace, writeStandaloneDevkit };
1
+ import { n as MountPoint, t as MOUNT_POINTS } from "./contracts.mjs";
2
+ import { a as createAddonFiles, c as writeAddonWorkspace, i as StandaloneDevkitOptions, l as writeStandaloneDevkit, n as AddonWorkspaceOptions, o as createStandaloneDevkitFiles, r as HOST_LOCALES, s as toDisplayName, t as AddonTemplateType } from "./generator.mjs";
3
+ export { type AddonTemplateType, type AddonWorkspaceOptions, HOST_LOCALES, MOUNT_POINTS, type MountPoint, type StandaloneDevkitOptions, createAddonFiles, createStandaloneDevkitFiles, toDisplayName, writeAddonWorkspace, writeStandaloneDevkit };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{i as e,n as t,r as n,t as r}from"./generator.mjs";export{r as createAddonFiles,t as createStandaloneDevkitFiles,n as writeAddonWorkspace,e as writeStandaloneDevkit};
1
+ import{t as e}from"./contracts.d.mts";import{a as t,i as n,n as r,o as i,r as a,t as o}from"./generator.mjs";export{o as HOST_LOCALES,e as MOUNT_POINTS,r as createAddonFiles,a as createStandaloneDevkitFiles,n as toDisplayName,t as writeAddonWorkspace,i as writeStandaloneDevkit};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jchat/create-addon",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.12",
4
4
  "private": false,
5
5
  "description": "Command line tool for jchat addon creation.",
6
6
  "keywords": [
@@ -27,15 +27,24 @@
27
27
  "CHANGELOG.md"
28
28
  ],
29
29
  "type": "module",
30
- "main": "./dist/index.mjs",
31
- "types": "./dist/index.d.mts",
30
+ "main": "./src/index.ts",
31
+ "types": "./src/index.ts",
32
32
  "exports": {
33
33
  ".": {
34
- "types": "./dist/index.d.mts",
35
- "import": "./dist/index.mjs"
34
+ "types": "./src/index.ts",
35
+ "import": "./src/index.ts",
36
+ "default": "./src/index.ts"
36
37
  }
37
38
  },
38
39
  "publishConfig": {
40
+ "main": "./dist/index.mjs",
41
+ "types": "./dist/index.d.mts",
42
+ "exports": {
43
+ ".": {
44
+ "types": "./dist/index.d.mts",
45
+ "import": "./dist/index.mjs"
46
+ }
47
+ },
39
48
  "access": "public"
40
49
  },
41
50
  "scripts": {
@@ -46,10 +55,13 @@
46
55
  "typecheck": "tsc --noEmit",
47
56
  "prepack": "bun run build"
48
57
  },
58
+ "dependencies": {
59
+ "@clack/prompts": "catalog:"
60
+ },
49
61
  "devDependencies": {
50
62
  "@jchat/addon-sdk": "workspace:*",
51
63
  "@types/bun": "catalog:",
52
- "tsdown": "^0.22.14"
64
+ "tsdown": "catalog:"
53
65
  },
54
66
  "engines": {
55
67
  "bun": ">=1.4.0"