joopjs 2.0.4 → 2.0.6

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.
@@ -0,0 +1,220 @@
1
+ # AGENTS.md — JoopJS SDK
2
+
3
+ This file instructs AI coding agents (Codex, OpenAI Agents, and similar) on how to generate code using the **joopjs** SDK.
4
+
5
+ ---
6
+
7
+ ## Package
8
+
9
+ ```bash
10
+ npm install joopjs
11
+ ```
12
+
13
+ **joopjs** is a framework-agnostic TypeScript SDK for enterprise financial applications. Works with React, Angular, Vue, Node.js, and plain TypeScript.
14
+
15
+ ---
16
+
17
+ ## Import Rules
18
+
19
+ ```ts
20
+ // Main entry — everything
21
+ import { JoopAuthService, JoopDigitalWalletService } from 'joopjs';
22
+
23
+ // Sub-path imports (tree-shakeable, preferred)
24
+ import { JoopGcmService } from 'joopjs/encryption';
25
+ import { JoopAuthService } from 'joopjs/auth';
26
+ import { JoopCacheService } from 'joopjs/cache';
27
+ ```
28
+
29
+ Available sub-paths: `auth`, `encryption`, `banking`, `security`, `api`, `core`, `session`, `device`, `observability`, `theme`, `i18n`, `ui`, `forms`, `router`, `state`, `workers`, `workflow`, `sync`, `platform`, `cache`, `network`, `analytics`, `validation`, `utilities`, `pwa`, `native-bridge`, `deeplink`, `react`, `angular`, `vue`, `india`.
30
+
31
+ **Never** import from internal paths (`joopjs/src/...`, `joopjs/dist/...`).
32
+
33
+ ---
34
+
35
+ ## Service Instantiation
36
+
37
+ All services are plain classes — no DI framework, no `@Injectable`, no container:
38
+
39
+ ```ts
40
+ const wallet = new JoopDigitalWalletService();
41
+ const auth = new JoopAuthService();
42
+ const gcm = new JoopGcmService();
43
+ const loans = new JoopLoanServicingService();
44
+ ```
45
+
46
+ Create once per module and reuse as singletons.
47
+
48
+ ---
49
+
50
+ ## Reactive Observables — Critical Rules
51
+
52
+ joopjs uses its own `JoopSubject` — **not RxJS**:
53
+
54
+ ```ts
55
+ // Subscribe — always returns an unsubscribe function
56
+ const unsub = service.event$().subscribe(value => {
57
+ console.log(value);
58
+ });
59
+
60
+ // Emit
61
+ subject.next(newValue); // CORRECT
62
+ subject.emit(newValue); // WRONG — throws, does not exist
63
+
64
+ // Current value (BehaviorSubject only)
65
+ const val = behaviorSubject.getValue();
66
+
67
+ // Clean up
68
+ unsub(); // call the returned function
69
+ // NOT .unsubscribe() // wrong — does not exist
70
+ // NOT subject.pipe(map(...)) // wrong — not RxJS
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Data Type Rules — Always Follow These
76
+
77
+ | Concept | Correct type | Never use |
78
+ |---------|-------------|-----------|
79
+ | Money amount | `number` | `BigDecimal`, `Money`, `string` |
80
+ | Currency | `string` ISO-4217 | enum, number |
81
+ | Timestamp | `number` Unix epoch ms — `Date.now()` | `new Date()`, `string` |
82
+ | ID | `string` | `number` |
83
+
84
+ ---
85
+
86
+ ## Error Handling
87
+
88
+ ```ts
89
+ try {
90
+ const result = await service.doSomething(config);
91
+ } catch (err) {
92
+ if (err instanceof Error) {
93
+ console.error(err.message);
94
+ }
95
+ }
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Common Code Patterns
101
+
102
+ ### Digital Wallet
103
+ ```ts
104
+ import { JoopDigitalWalletService } from 'joopjs';
105
+
106
+ const wallet = new JoopDigitalWalletService();
107
+ const w = wallet.createWallet('user-001', { currency: 'USD', label: 'Primary' });
108
+ wallet.topUp(w.id, 500);
109
+ wallet.pay(w.id, 50, 'merchant-id', 'Coffee');
110
+ wallet.transfer(w.id, toWalletId, 200);
111
+ const bal = wallet.getBalance(w.id);
112
+ wallet.balance$().subscribe(({ walletId, balance }) => { /* update UI */ });
113
+ ```
114
+
115
+ ### Auth + JWT
116
+ ```ts
117
+ import { JoopAuthService } from 'joopjs/auth';
118
+
119
+ const auth = new JoopAuthService();
120
+ const session = await auth.login(email, password);
121
+ auth.session$().subscribe(session => { /* null = logged out */ });
122
+ await auth.logout(session.sessionId);
123
+ ```
124
+
125
+ ### AES-GCM Encryption
126
+ ```ts
127
+ import { JoopGcmService } from 'joopjs/encryption';
128
+
129
+ const gcm = new JoopGcmService();
130
+ const key = await gcm.generateKey();
131
+ const { ciphertext, iv } = await gcm.encrypt('sensitive', key);
132
+ const plain = await gcm.decrypt(ciphertext, key, iv);
133
+ ```
134
+
135
+ ### Loan Servicing
136
+ ```ts
137
+ import { JoopLoanServicingService } from 'joopjs';
138
+
139
+ const loans = new JoopLoanServicingService();
140
+ const loan = loans.createLoan({
141
+ borrowerName: 'Alice', borrowerId: 'u-001',
142
+ principalAmount: 10000, annualInterestRatePercent: 8,
143
+ tenureMonths: 12, currency: 'USD'
144
+ });
145
+ loans.recordPayment(loan.id, loan.emiAmount);
146
+ const schedule = loans.getSchedule(loan.id);
147
+ ```
148
+
149
+ ### Sanctions Screening
150
+ ```ts
151
+ import { JoopSanctionsScreeningService } from 'joopjs';
152
+
153
+ const sanctions = new JoopSanctionsScreeningService();
154
+ sanctions.loadList('ofac', entities);
155
+ const result = sanctions.screen({ name: customerName, country: customerCountry });
156
+ if (result.status !== 'clear') { /* block or escalate */ }
157
+ ```
158
+
159
+ ### AML
160
+ ```ts
161
+ import { JoopAmlService } from 'joopjs';
162
+
163
+ const aml = new JoopAmlService();
164
+ aml.addRule({ id: 'r1', name: 'Large cash', type: 'cash', threshold: 10000, currency: 'USD', action: 'flag', enabled: true });
165
+ const alert = aml.checkTransaction({ id: 'tx1', amount: 15000, currency: 'USD', type: 'cash', userId: 'u-001', timestamp: Date.now() });
166
+ // null = passes; JoopAmlAlert = flagged
167
+ ```
168
+
169
+ ### Mutual Fund SIP
170
+ ```ts
171
+ import { JoopMutualFundService } from 'joopjs';
172
+
173
+ const mf = new JoopMutualFundService();
174
+ mf.registerFund({ id: 'f1', name: 'Growth Fund', category: 'equity', currentNav: 45.5, currency: 'USD' });
175
+ mf.invest('f1', 'investor-001', 1000);
176
+ const sip = mf.createSip('f1', 'investor-001', 500, 'monthly', Date.now());
177
+ mf.executeSip(sip.id);
178
+ ```
179
+
180
+ ### Framework Integration — React
181
+ ```tsx
182
+ import React, { useEffect } from 'react';
183
+ import { JoopDigitalWalletService } from 'joopjs';
184
+
185
+ const wallet = new JoopDigitalWalletService();
186
+
187
+ function WalletBalance() {
188
+ const [balance, setBalance] = React.useState(0);
189
+
190
+ useEffect(() => {
191
+ const unsub = wallet.balance$().subscribe(({ balance }) => setBalance(balance));
192
+ return unsub; // cleanup on unmount
193
+ }, []);
194
+
195
+ return <div>{balance}</div>;
196
+ }
197
+ ```
198
+
199
+ ---
200
+
201
+ ## TypeScript Types (prefix `Joop*`)
202
+
203
+ All types are exported from `'joopjs'`:
204
+
205
+ ```ts
206
+ // Auth
207
+ JoopAuthUser, JoopAuthSession, JoopAuthToken
208
+
209
+ // Banking
210
+ JoopWallet, JoopWalletTransaction, JoopWalletTxnType
211
+ JoopLoanAccount, JoopInstallment, JoopLoanAccountStatus
212
+ JoopFxForward, JoopForwardType, JoopForwardStatus
213
+ JoopLedgerAccount, JoopJournalEntry, JoopTrialBalance
214
+
215
+ // Finance
216
+ JoopMutualFund, JoopFundHolding, JoopSip, JoopSipStatus
217
+
218
+ // Security
219
+ JoopSanctionsEntity, JoopScreeningResult, JoopAmlAlert
220
+ ```
@@ -0,0 +1,169 @@
1
+ # GEMINI.md — JoopJS SDK
2
+
3
+ This file instructs Gemini CLI on how to generate code using the **joopjs** SDK.
4
+
5
+ ---
6
+
7
+ ## Package
8
+
9
+ ```bash
10
+ npm install joopjs
11
+ ```
12
+
13
+ **joopjs** is a framework-agnostic TypeScript SDK for enterprise financial applications. Works with React, Angular, Vue, Node.js, and plain TypeScript.
14
+
15
+ ---
16
+
17
+ ## Import Rules
18
+
19
+ ```ts
20
+ // Main entry — everything
21
+ import { JoopAuthService, JoopDigitalWalletService } from 'joopjs';
22
+
23
+ // Sub-path imports (tree-shakeable, preferred)
24
+ import { JoopGcmService } from 'joopjs/encryption';
25
+ import { JoopAuthService } from 'joopjs/auth';
26
+ import { JoopCacheService } from 'joopjs/cache';
27
+ ```
28
+
29
+ Available sub-paths: `auth`, `encryption`, `banking`, `security`, `api`, `core`, `session`, `device`, `observability`, `theme`, `i18n`, `ui`, `forms`, `router`, `state`, `workers`, `workflow`, `sync`, `platform`, `cache`, `network`, `analytics`, `validation`, `utilities`, `pwa`, `native-bridge`, `deeplink`, `react`, `angular`, `vue`, `india`.
30
+
31
+ Never import from internal paths (`joopjs/src/...`, `joopjs/dist/...`).
32
+
33
+ ---
34
+
35
+ ## Service Instantiation
36
+
37
+ All services are plain classes — no DI framework required:
38
+
39
+ ```ts
40
+ const wallet = new JoopDigitalWalletService();
41
+ const auth = new JoopAuthService();
42
+ const gcm = new JoopGcmService();
43
+ const loans = new JoopLoanServicingService();
44
+ ```
45
+
46
+ Create once per module and reuse as singletons.
47
+
48
+ ---
49
+
50
+ ## Reactive Observables
51
+
52
+ joopjs uses its own `JoopSubject` — **not RxJS**:
53
+
54
+ ```ts
55
+ // Subscribe — returns an unsubscribe function
56
+ const unsub = service.event$().subscribe(value => {
57
+ console.log(value);
58
+ });
59
+
60
+ // Emit (only from inside a service or your own subject)
61
+ subject.next(newValue); // CORRECT
62
+ subject.emit(newValue); // WRONG — .emit() does not exist
63
+
64
+ // Read current value (BehaviorSubject only)
65
+ const current = behaviorSubject.getValue();
66
+
67
+ // Unsubscribe
68
+ unsub(); // call the returned function
69
+ // NOT .unsubscribe() // wrong — does not exist
70
+ ```
71
+
72
+ Do not use RxJS operators (`.pipe()`, `.map()`, etc.) — these are not RxJS Observables.
73
+
74
+ ---
75
+
76
+ ## Data Types
77
+
78
+ | Concept | Type | Example |
79
+ |---------|------|---------|
80
+ | Money amount | `number` | `5000.50` |
81
+ | Currency | `string` ISO-4217 | `'USD'`, `'AED'`, `'INR'` |
82
+ | Timestamp | `number` epoch ms | `Date.now()` |
83
+ | ID | `string` | `'u-001'` |
84
+
85
+ Never use a Money class, BigDecimal, or `Date` objects.
86
+
87
+ ---
88
+
89
+ ## Error Handling
90
+
91
+ All services throw standard `Error` on invalid input:
92
+
93
+ ```ts
94
+ try {
95
+ const result = await service.doSomething(config);
96
+ } catch (err) {
97
+ if (err instanceof Error) {
98
+ console.error(err.message);
99
+ }
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Key Services Quick Reference
106
+
107
+ ### Banking
108
+ ```ts
109
+ import { JoopDigitalWalletService, JoopLoanServicingService } from 'joopjs';
110
+
111
+ const wallet = new JoopDigitalWalletService();
112
+ const w = wallet.createWallet('user-001', { currency: 'USD' });
113
+ wallet.topUp(w.id, 500);
114
+ wallet.pay(w.id, 50, 'merchant-id', 'Coffee');
115
+ wallet.balance$().subscribe(({ walletId, balance }) => { /* update UI */ });
116
+
117
+ const loans = new JoopLoanServicingService();
118
+ const loan = loans.createLoan({ borrowerName: 'Alice', borrowerId: 'u-001', principalAmount: 10000, annualInterestRatePercent: 8, tenureMonths: 12, currency: 'USD' });
119
+ loans.recordPayment(loan.id, loan.emiAmount);
120
+ ```
121
+
122
+ ### Auth
123
+ ```ts
124
+ import { JoopAuthService } from 'joopjs/auth';
125
+
126
+ const auth = new JoopAuthService();
127
+ const session = await auth.login(email, password);
128
+ auth.session$().subscribe(session => { /* null = logged out */ });
129
+ await auth.logout(session.sessionId);
130
+ ```
131
+
132
+ ### Encryption
133
+ ```ts
134
+ import { JoopGcmService } from 'joopjs/encryption';
135
+
136
+ const gcm = new JoopGcmService();
137
+ const key = await gcm.generateKey();
138
+ const { ciphertext, iv } = await gcm.encrypt('sensitive data', key);
139
+ const plain = await gcm.decrypt(ciphertext, key, iv);
140
+ ```
141
+
142
+ ### Security
143
+ ```ts
144
+ import { JoopAmlService, JoopSanctionsScreeningService } from 'joopjs';
145
+
146
+ const aml = new JoopAmlService();
147
+ aml.addRule({ id: 'r1', name: 'Large cash', type: 'cash', threshold: 10000, currency: 'USD', action: 'flag', enabled: true });
148
+ const alert = aml.checkTransaction({ id: 'tx1', amount: 15000, currency: 'USD', type: 'cash', userId: 'u-001', timestamp: Date.now() });
149
+
150
+ const sanctions = new JoopSanctionsScreeningService();
151
+ sanctions.loadList('ofac', entities);
152
+ const result = sanctions.screen({ name: 'John Doe', country: 'US' });
153
+ // result.status: 'clear' | 'hit' | 'review'
154
+ ```
155
+
156
+ ### Framework Integrations
157
+ ```tsx
158
+ // React
159
+ import { createJoopReact } from 'joopjs/react';
160
+ const { JoopProvider, useJoopAuth, useJoopTheme } = createJoopReact(React);
161
+
162
+ // Vue
163
+ import { createJoopVue } from 'joopjs/vue';
164
+ const { provideJoop, useJoopAuth } = createJoopVue(Vue);
165
+
166
+ // Angular
167
+ import { provideJoop } from 'joopjs/angular';
168
+ bootstrapApplication(AppComponent, { providers: [provideJoop(joop)] });
169
+ ```
package/package.json CHANGED
@@ -1,35 +1,31 @@
1
1
  {
2
2
  "name": "joopjs",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "Framework-agnostic JavaScript SDK for enterprise web applications. Works with React, Angular, Vue, and any JavaScript project.",
5
- "author": {
6
- "name": "Kundan Singh"
7
- },
8
- "license": "MIT",
9
5
  "keywords": [
10
6
  "sdk",
11
- "banking",
12
- "fintech",
13
- "finance",
14
7
  "encryption",
8
+ "session",
9
+ "storage",
15
10
  "auth",
16
- "aml",
17
- "sanctions",
18
- "wallet",
19
- "loan",
20
- "typescript",
21
- "react",
22
- "angular",
23
- "vue"
11
+ "http",
12
+ "logger",
13
+ "aes",
14
+ "x25519",
15
+ "typescript"
24
16
  ],
17
+ "license": "MIT",
18
+ "homepage": "https://kundan-leo.github.io/JoopJs/demo/",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/kundan-leo/JoopJs.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/kundan-leo/JoopJs/issues"
25
+ },
25
26
  "main": "dist/index.js",
26
27
  "module": "dist/index.mjs",
27
28
  "types": "dist/index.d.ts",
28
- "files": [
29
- "dist",
30
- "README.md",
31
- "CHANGELOG.md"
32
- ],
33
29
  "exports": {
34
30
  ".": {
35
31
  "import": "./dist/index.mjs",
@@ -86,6 +82,11 @@
86
82
  "require": "./dist/device/index.js",
87
83
  "types": "./dist/device/index.d.ts"
88
84
  },
85
+ "./dev": {
86
+ "import": "./dist/dev/index.mjs",
87
+ "require": "./dist/dev/index.js",
88
+ "types": "./dist/dev/index.d.ts"
89
+ },
89
90
  "./theme": {
90
91
  "import": "./dist/theme/index.mjs",
91
92
  "require": "./dist/theme/index.js",
@@ -202,6 +203,43 @@
202
203
  "types": "./dist/india/index.d.ts"
203
204
  }
204
205
  },
206
+ "files": [
207
+ "dist",
208
+ "CHANGELOG.md",
209
+ "ai-rules",
210
+ "scripts/setup-ai.mjs",
211
+ ".claude/skills/setup.md",
212
+ ".claude/skills/banking.md",
213
+ ".claude/skills/finance.md",
214
+ ".claude/skills/security.md",
215
+ ".claude/skills/auth.md",
216
+ ".claude/skills/encryption.md",
217
+ ".claude/skills/observables.md",
218
+ ".cursor/rules/joopjs.mdc",
219
+ ".windsurf/rules/joopjs.md",
220
+ ".github/copilot-instructions.md"
221
+ ],
222
+ "bin": {
223
+ "joopjs": "scripts/setup-ai.mjs"
224
+ },
225
+ "scripts": {
226
+ "build": "tsup",
227
+ "build:watch": "tsup --watch",
228
+ "typecheck": "tsc --noEmit",
229
+ "clean": "rimraf dist dist-playground",
230
+ "test": "vitest run",
231
+ "test:ui": "vitest --ui --open",
232
+ "test:watch": "vitest",
233
+ "test:coverage": "vitest run --coverage",
234
+ "playground": "vite playground/ --config playground/vite.config.ts",
235
+ "playground:build": "vite build playground/ --config playground/vite.config.ts",
236
+ "preflight": "node scripts/preflight.mjs",
237
+ "release:dry": "node scripts/release.mjs --dry-run",
238
+ "release:patch": "node scripts/release.mjs patch",
239
+ "release:minor": "node scripts/release.mjs minor",
240
+ "release:major": "node scripts/release.mjs major",
241
+ "release": "node scripts/release.mjs"
242
+ },
205
243
  "dependencies": {
206
244
  "@noble/ciphers": "^2.2.0",
207
245
  "@noble/curves": "^2.2.0",
@@ -214,14 +252,34 @@
214
252
  "vue": ">=3.0.0"
215
253
  },
216
254
  "peerDependenciesMeta": {
217
- "react": { "optional": true },
218
- "@angular/core": { "optional": true },
219
- "rxjs": { "optional": true },
220
- "vue": { "optional": true }
255
+ "react": {
256
+ "optional": true
257
+ },
258
+ "@angular/core": {
259
+ "optional": true
260
+ },
261
+ "rxjs": {
262
+ "optional": true
263
+ },
264
+ "vue": {
265
+ "optional": true
266
+ }
267
+ },
268
+ "devDependencies": {
269
+ "@vitest/coverage-v8": "^2.1.0",
270
+ "@vitest/ui": "^2.1.0",
271
+ "fake-indexeddb": "^6.2.5",
272
+ "jsdom": "^29.1.1",
273
+ "rimraf": "^5.0.0",
274
+ "tsup": "^8.3.0",
275
+ "typescript": "^5.3.3",
276
+ "vite": "^5.4.0",
277
+ "vitest": "^2.1.0"
221
278
  },
222
279
  "engines": {
223
280
  "node": ">=18.0.0"
224
281
  },
225
- "sideEffects": false
282
+ "author": {
283
+ "name": "Kundan Singh"
284
+ }
226
285
  }
227
-
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * joopjs setup-ai
4
+ *
5
+ * Copies AI assistant rules for joopjs into your project so that
6
+ * Claude Code, Cursor, Windsurf, GitHub Copilot, Gemini CLI, and Codex
7
+ * all understand how to use the joopjs SDK correctly.
8
+ *
9
+ * Usage:
10
+ * npx joopjs setup-ai # copy rules (skip existing files)
11
+ * npx joopjs setup-ai --force # overwrite existing files
12
+ * npx joopjs # show help
13
+ */
14
+
15
+ import { copyFileSync, mkdirSync, existsSync } from 'fs';
16
+ import { resolve, dirname, join, basename } from 'path';
17
+ import { fileURLToPath } from 'url';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const PACKAGE_DIR = resolve(__dirname, '..');
21
+ const PROJECT_DIR = process.cwd();
22
+ const FORCE = process.argv.includes('--force');
23
+ const CMD = process.argv[2];
24
+
25
+ // ── helpers ──────────────────────────────────────────────────────────────────
26
+ const ok = msg => console.log(` ✓ ${msg}`);
27
+ const skip = msg => console.log(` · skipped ${msg} (use --force to overwrite)`);
28
+ const info = msg => console.log(` ${msg}`);
29
+ const hr = () => console.log(`\n${'━'.repeat(52)}`);
30
+
31
+ function ensureDir(dir) {
32
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
33
+ }
34
+
35
+ function copyFile(src, dest) {
36
+ const label = dest.replace(PROJECT_DIR + '/', '').replace(PROJECT_DIR + '\\', '');
37
+ if (!existsSync(src)) return;
38
+ if (existsSync(dest) && !FORCE) { skip(label); return; }
39
+ ensureDir(dirname(dest));
40
+ copyFileSync(src, dest);
41
+ ok(label);
42
+ }
43
+
44
+ // ── help ─────────────────────────────────────────────────────────────────────
45
+ function showHelp() {
46
+ console.log(`
47
+ joopjs — AI rules setup for joopjs
48
+
49
+ Usage:
50
+ npx joopjs setup-ai Copy AI rules to your project
51
+ npx joopjs setup-ai --force Overwrite existing rule files
52
+
53
+ What gets copied:
54
+ .claude/skills/joopjs-*.md Claude Code skills (7 skills)
55
+ .cursor/rules/joopjs.mdc Cursor rule
56
+ .windsurf/rules/joopjs.md Windsurf Cascade rule
57
+ .github/copilot-instructions.md GitHub Copilot instructions
58
+ GEMINI.md Gemini CLI instructions
59
+ AGENTS.md Codex / OpenAI Agents instructions
60
+ `);
61
+ }
62
+
63
+ // ── main ─────────────────────────────────────────────────────────────────────
64
+ if (CMD !== 'setup-ai') {
65
+ showHelp();
66
+ process.exit(0);
67
+ }
68
+
69
+ hr();
70
+ console.log(' joopjs — AI Rules Setup');
71
+ if (FORCE) console.log(' MODE: --force (existing files will be overwritten)');
72
+ hr();
73
+ console.log('');
74
+
75
+ // ── 1. Claude Code skills ─────────────────────────────────────────────────────
76
+ console.log(' Claude Code skills:');
77
+ const CONSUMER_SKILLS = ['setup', 'banking', 'finance', 'security', 'auth', 'encryption', 'observables'];
78
+ for (const skill of CONSUMER_SKILLS) {
79
+ copyFile(
80
+ join(PACKAGE_DIR, '.claude', 'skills', `${skill}.md`),
81
+ join(PROJECT_DIR, '.claude', 'skills', `joopjs-${skill}.md`)
82
+ );
83
+ }
84
+
85
+ // ── 2. Cursor ─────────────────────────────────────────────────────────────────
86
+ console.log('\n Cursor:');
87
+ copyFile(
88
+ join(PACKAGE_DIR, '.cursor', 'rules', 'joopjs.mdc'),
89
+ join(PROJECT_DIR, '.cursor', 'rules', 'joopjs.mdc')
90
+ );
91
+
92
+ // ── 3. Windsurf ───────────────────────────────────────────────────────────────
93
+ console.log('\n Windsurf Cascade:');
94
+ copyFile(
95
+ join(PACKAGE_DIR, '.windsurf', 'rules', 'joopjs.md'),
96
+ join(PROJECT_DIR, '.windsurf', 'rules', 'joopjs.md')
97
+ );
98
+
99
+ // ── 4. GitHub Copilot ─────────────────────────────────────────────────────────
100
+ console.log('\n GitHub Copilot:');
101
+ copyFile(
102
+ join(PACKAGE_DIR, '.github', 'copilot-instructions.md'),
103
+ join(PROJECT_DIR, '.github', 'copilot-instructions.md')
104
+ );
105
+
106
+ // ── 5. Gemini CLI ─────────────────────────────────────────────────────────────
107
+ console.log('\n Gemini CLI:');
108
+ copyFile(
109
+ join(PACKAGE_DIR, 'ai-rules', 'GEMINI.md'),
110
+ join(PROJECT_DIR, 'GEMINI.md')
111
+ );
112
+
113
+ // ── 6. Codex / OpenAI Agents ─────────────────────────────────────────────────
114
+ console.log('\n Codex / OpenAI Agents:');
115
+ copyFile(
116
+ join(PACKAGE_DIR, 'ai-rules', 'AGENTS.md'),
117
+ join(PROJECT_DIR, 'AGENTS.md')
118
+ );
119
+
120
+ // ── summary ───────────────────────────────────────────────────────────────────
121
+ hr();
122
+ console.log(' Done! Your AI tools now understand joopjs.\n');
123
+ console.log(' Next steps:');
124
+ console.log(' 1. Claude Code — add skills to your CLAUDE.md:');
125
+ console.log('');
126
+ for (const skill of CONSUMER_SKILLS) {
127
+ console.log(` | /joopjs-${skill.padEnd(11)} | JoopJS ${skill} guide |`);
128
+ }
129
+ console.log('');
130
+ console.log(' 2. Cursor / Windsurf — rules are auto-detected from their folders.');
131
+ console.log(' 3. Copilot / Gemini / Codex — files are at project root, ready to use.');
132
+ hr();
133
+ console.log('');