@ysk-kit/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/dist/add-capability.d.ts +4 -0
  3. package/dist/add-capability.d.ts.map +1 -0
  4. package/dist/add-capability.js +104 -0
  5. package/dist/add-capability.js.map +1 -0
  6. package/dist/add-module.d.ts +5 -0
  7. package/dist/add-module.d.ts.map +1 -0
  8. package/dist/add-module.js +210 -0
  9. package/dist/add-module.js.map +1 -0
  10. package/dist/agent-stubs.d.ts +3 -0
  11. package/dist/agent-stubs.d.ts.map +1 -0
  12. package/dist/agent-stubs.js +25 -0
  13. package/dist/agent-stubs.js.map +1 -0
  14. package/dist/apply-capability.d.ts +3 -0
  15. package/dist/apply-capability.d.ts.map +1 -0
  16. package/dist/apply-capability.js +189 -0
  17. package/dist/apply-capability.js.map +1 -0
  18. package/dist/capability-catalog.d.ts +25 -0
  19. package/dist/capability-catalog.d.ts.map +1 -0
  20. package/dist/capability-catalog.js +123 -0
  21. package/dist/capability-catalog.js.map +1 -0
  22. package/dist/capability-patches.d.ts +40 -0
  23. package/dist/capability-patches.d.ts.map +1 -0
  24. package/dist/capability-patches.js +616 -0
  25. package/dist/capability-patches.js.map +1 -0
  26. package/dist/check-agent.d.ts +9 -0
  27. package/dist/check-agent.d.ts.map +1 -0
  28. package/dist/check-agent.js +85 -0
  29. package/dist/check-agent.js.map +1 -0
  30. package/dist/generate-openapi.d.ts +2 -0
  31. package/dist/generate-openapi.d.ts.map +1 -0
  32. package/dist/generate-openapi.js +52 -0
  33. package/dist/generate-openapi.js.map +1 -0
  34. package/dist/help.d.ts +2 -0
  35. package/dist/help.d.ts.map +1 -0
  36. package/dist/help.js +54 -0
  37. package/dist/help.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +59 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/merge-prisma.d.ts +15 -0
  43. package/dist/merge-prisma.d.ts.map +1 -0
  44. package/dist/merge-prisma.js +90 -0
  45. package/dist/merge-prisma.js.map +1 -0
  46. package/dist/patch-text.d.ts +17 -0
  47. package/dist/patch-text.d.ts.map +1 -0
  48. package/dist/patch-text.js +72 -0
  49. package/dist/patch-text.js.map +1 -0
  50. package/dist/upgrade.d.ts +16 -0
  51. package/dist/upgrade.d.ts.map +1 -0
  52. package/dist/upgrade.js +110 -0
  53. package/dist/upgrade.js.map +1 -0
  54. package/package.json +41 -0
  55. package/templates/agent/skills/add-capability/SKILL.md +8 -0
  56. package/templates/agent/skills/add-module/SKILL.md +8 -0
  57. package/templates/agent/skills/envelope-api/SKILL.md +8 -0
  58. package/templates/agent/skills/fix-layers/SKILL.md +8 -0
  59. package/templates/agent/skills/new-product/SKILL.md +8 -0
  60. package/templates/agent/skills/verify-change/SKILL.md +8 -0
  61. package/templates/agent/ysk-kit.mdc +6 -0
  62. package/templates/capabilities/billing/apps/api/src/modules/billing/application/billing-service.ts +140 -0
  63. package/templates/capabilities/billing/apps/api/src/modules/billing/domain/billing-port.ts +23 -0
  64. package/templates/capabilities/billing/apps/api/src/modules/billing/domain/subscription-repository.ts +22 -0
  65. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/billing-router.ts +85 -0
  66. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/billing.app.test.ts +273 -0
  67. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/log-billing.ts +31 -0
  68. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/memory-subscription-repository.ts +27 -0
  69. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/prisma-subscription-repository.ts +52 -0
  70. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/stripe-billing.test.ts +127 -0
  71. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/stripe-billing.ts +160 -0
  72. package/templates/capabilities/billing/apps/web/src/features/orgs/billing-page.tsx +183 -0
  73. package/templates/capabilities/billing/packages/contracts/src/api/billing.ts +85 -0
  74. package/templates/capabilities/billing/packages/contracts/src/dto/billing.ts +59 -0
  75. package/templates/capabilities/billing/packages/sdk/src/resources/billing.ts +38 -0
  76. package/templates/capabilities/billing/packages/web-sdk/src/billing-hooks.ts +56 -0
  77. package/templates/capabilities/llm/apps/api/src/modules/llm/application/llm-service.ts +48 -0
  78. package/templates/capabilities/llm/apps/api/src/modules/llm/domain/usage-repository.ts +10 -0
  79. package/templates/capabilities/llm/apps/api/src/modules/llm/infra/llm-router.ts +88 -0
  80. package/templates/capabilities/llm/apps/api/src/modules/llm/infra/llm.app.test.ts +89 -0
  81. package/templates/capabilities/llm/apps/api/src/modules/llm/infra/memory-usage-repository.ts +13 -0
  82. package/templates/capabilities/llm/apps/api/src/modules/llm/infra/prisma-usage-repository.ts +17 -0
  83. package/templates/capabilities/llm/apps/web/src/features/llm/llm-page.tsx +53 -0
  84. package/templates/capabilities/llm/packages/contracts/src/api/llm.ts +26 -0
  85. package/templates/capabilities/llm/packages/contracts/src/dto/llm.ts +33 -0
  86. package/templates/capabilities/llm/packages/sdk/src/resources/llm.ts +68 -0
  87. package/templates/capabilities/llm/packages/web-sdk/src/llm-hooks.ts +12 -0
  88. package/templates/capabilities/push/apps/api/src/modules/devices/application/device-service.ts +42 -0
  89. package/templates/capabilities/push/apps/api/src/modules/devices/domain/device-repository.ts +22 -0
  90. package/templates/capabilities/push/apps/api/src/modules/devices/infra/device-router.ts +42 -0
  91. package/templates/capabilities/push/apps/api/src/modules/devices/infra/device.app.test.ts +34 -0
  92. package/templates/capabilities/push/apps/api/src/modules/devices/infra/memory-device-repository.ts +33 -0
  93. package/templates/capabilities/push/apps/api/src/modules/devices/infra/prisma-device-repository.ts +42 -0
  94. package/templates/capabilities/push/apps/mobile/src/adapters/push.ts +21 -0
  95. package/templates/capabilities/push/packages/contracts/src/api/devices.ts +33 -0
  96. package/templates/capabilities/push/packages/contracts/src/dto/device.ts +18 -0
  97. package/templates/capabilities/push/packages/sdk/src/resources/devices.ts +10 -0
  98. package/templates/capabilities/team/apps/api/src/modules/organizations/application/organization-service.ts +272 -0
  99. package/templates/capabilities/team/apps/api/src/modules/organizations/domain/organization-repository.ts +58 -0
  100. package/templates/capabilities/team/apps/api/src/modules/organizations/infra/memory-organization-repository.ts +141 -0
  101. package/templates/capabilities/team/apps/api/src/modules/organizations/infra/organization-router.ts +130 -0
  102. package/templates/capabilities/team/apps/api/src/modules/organizations/infra/organization.app.test.ts +213 -0
  103. package/templates/capabilities/team/apps/api/src/modules/organizations/infra/prisma-organization-repository.ts +142 -0
  104. package/templates/capabilities/team/apps/mobile/src/screens/invite-screen.tsx +40 -0
  105. package/templates/capabilities/team/apps/mobile/src/screens/org-detail-screen.tsx +154 -0
  106. package/templates/capabilities/team/apps/mobile/src/screens/orgs-screen.tsx +53 -0
  107. package/templates/capabilities/team/apps/web/src/features/orgs/invite-page.tsx +77 -0
  108. package/templates/capabilities/team/apps/web/src/features/orgs/org-detail-page.tsx +138 -0
  109. package/templates/capabilities/team/apps/web/src/features/orgs/orgs-page.tsx +73 -0
  110. package/templates/capabilities/team/packages/contracts/src/api/organizations.ts +154 -0
  111. package/templates/capabilities/team/packages/contracts/src/dto/organization.ts +58 -0
  112. package/templates/capabilities/team/packages/sdk/src/resources/organizations.ts +53 -0
  113. package/templates/capabilities/team/packages/web-sdk/src/organizations-hooks.ts +80 -0
  114. package/templates/module/api.ts.tmpl +32 -0
  115. package/templates/module/application-service.ts.tmpl +9 -0
  116. package/templates/module/domain-repository.ts.tmpl +15 -0
  117. package/templates/module/dto.ts.tmpl +21 -0
  118. package/templates/module/memory-repository.ts.tmpl +36 -0
  119. package/templates/module/model.prisma.tmpl +8 -0
  120. package/templates/module/prisma-repository.ts.tmpl +38 -0
  121. package/templates/module/router.ts.tmpl +32 -0
  122. package/templates/module/sdk-resource.ts.tmpl +17 -0
  123. package/templates/module/service.test.ts.tmpl +33 -0
  124. package/templates/module/web-hooks.ts.tmpl +24 -0
  125. package/templates/module/web-page.tsx.tmpl +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YSK Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ export declare const CAPABILITIES: readonly ["auth", "rbac", "audit-log", "storage", "i18n", "jobs", "mail", "notifications", "llm", "websocket", "push", "mobile", "team", "apikey", "crypto", "billing"];
2
+ export type Capability = (typeof CAPABILITIES)[number];
3
+ export declare const addCapability: (name: string, root?: string) => string[];
4
+ //# sourceMappingURL=add-capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-capability.d.ts","sourceRoot":"","sources":["../src/add-capability.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,yKAiBf,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AA4EvD,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,EAAE,aAAU,KAAG,MAAM,EAW9D,CAAC"}
@@ -0,0 +1,104 @@
1
+ import { applyCapability } from './apply-capability';
2
+ export const CAPABILITIES = [
3
+ 'auth',
4
+ 'rbac',
5
+ 'audit-log',
6
+ 'storage',
7
+ 'i18n',
8
+ 'jobs',
9
+ 'mail',
10
+ 'notifications',
11
+ 'llm',
12
+ 'websocket',
13
+ 'push',
14
+ 'mobile',
15
+ 'team',
16
+ 'apikey',
17
+ 'crypto',
18
+ 'billing',
19
+ ];
20
+ const ALIASES = { org: 'team' };
21
+ const HINTS = {
22
+ auth: [
23
+ 'saas flavor already includes register/login/otp/refresh/logout/me',
24
+ 'env: JWT_SECRET, JWT_ACCESS_TTL, OTP_TTL_SECONDS; TWILIO_* required in production',
25
+ 'run pnpm db:migrate after pulling Session / OtpChallenge schema',
26
+ ],
27
+ rbac: [
28
+ 'permissions live in @ysk-kit/contracts ROLE_PERMISSIONS',
29
+ 'requirePermission() is in @ysk-kit/api-express',
30
+ ],
31
+ 'audit-log': [
32
+ 'GET /v1/audit-logs requires audit.read',
33
+ 'IAuditLogger.record is called from use-cases',
34
+ ],
35
+ storage: [
36
+ 'POST /v1/files/presign requires file.upload',
37
+ 'local adapter is default; set S3_* for an S3-compatible bucket later',
38
+ ],
39
+ i18n: ['@ysk-kit/i18n t(dict, key, locale) default zh-HK'],
40
+ jobs: [
41
+ 'REDIS_URL + pnpm worker (memory queue in tests / when Redis unset)',
42
+ 'Bull Board at /admin/queues when Redis is on (ADMIN JWT, Express and Fastify)',
43
+ ],
44
+ mail: [
45
+ 'SMTP_URL optional; log adapter is default',
46
+ 'templates: auth.welcome, auth.reset, org.invite, auth.admin-otp',
47
+ ],
48
+ notifications: ['GET /v1/notifications and POST /v1/notifications/:id/read'],
49
+ llm: [
50
+ 'POST /v1/llm/complete and POST /v1/llm/stream (SSE)',
51
+ 'merges LlmUsage; wires createLlmService + registerLlmRoutes when composition is not already hand-wired',
52
+ 'env: LLM_BASE_URL (default https://api.x.ai/v1), LLM_API_KEY or XAI_API_KEY, LLM_MODEL=grok-4.7',
53
+ ],
54
+ websocket: [
55
+ 'Socket.IO on the API HTTP server, auth.token handshake',
56
+ 'wires createRealtimeFromEnv when composition is not already hand-wired; attachSocketIoRealtime stays in main.ts',
57
+ 'event notification.created',
58
+ 'live inbox: RUN_WORKERS=1 in-process, or REDIS_URL + standalone/PM2 worker (ysk-socket.io key)',
59
+ ],
60
+ push: [
61
+ 'PUT/GET/DELETE /v1/me/devices; worker enqueues push.send after notification.create',
62
+ 'merges Device; wires createDeviceService + registerDeviceRoutes when composition is not already hand-wired',
63
+ 'env: EXPO_ACCESS_TOKEN optional; FCM_PROJECT_ID, FCM_CLIENT_EMAIL, FCM_PRIVATE_KEY for native tokens',
64
+ 'Expo Go uses ExponentPushToken[...]; native FCM uses HTTP v1',
65
+ ],
66
+ mobile: [
67
+ 'apps/mobile is the Expo template (login, home, inbox, organisations, invite, DevicePort + FilePickerPort)',
68
+ 'eas.json preview/production; app.config.ts slug ysk-kit',
69
+ 'run pnpm --filter @ysk-kit/mobile start; Expo 57 / RN 0.86',
70
+ ],
71
+ team: [
72
+ 'Organization + Membership + email invite; platform User.role stays global',
73
+ 'org roles: OWNER | ADMIN | MEMBER; last OWNER cannot leave',
74
+ 'invite links use WEB_PUBLIC_URL/invite?token=',
75
+ 'when apps/mobile exists, restores organisation list, detail, and accept-invite screens',
76
+ ],
77
+ apikey: [
78
+ 'POST/GET/DELETE /v1/me/api-keys; Bearer ysk_live_… for machine clients',
79
+ 'permissions are a subset of the owner role; API keys cannot mint more keys',
80
+ ],
81
+ crypto: [
82
+ 'AES-256-GCM ICryptoPort; env CRYPTO_MASTER_KEY (64 hex chars)',
83
+ 'dev uses a built-in key when NODE_ENV is not production',
84
+ ],
85
+ billing: [
86
+ 'requires ysk add team first (Organization + Membership)',
87
+ 'GET /v1/billing/plans (public); org OWNER/ADMIN + billing.checkout for the rest',
88
+ 'log IBillingPort by default; STRIPE_SECRET_KEY + STRIPE_PRICE_PRO enable Checkout',
89
+ 'STRIPE_WEBHOOK_SECRET for POST /v1/billing/webhook (raw body)',
90
+ ],
91
+ };
92
+ export const addCapability = (name, root = '.') => {
93
+ const resolved = ALIASES[name] ?? name;
94
+ if (!CAPABILITIES.includes(resolved)) {
95
+ throw new Error(`unknown capability '${name}'. Try: ${CAPABILITIES.join(', ')}`);
96
+ }
97
+ const cap = resolved;
98
+ return [
99
+ ...applyCapability(root, cap),
100
+ `ysk add ${cap}: already part of the saas flavor`,
101
+ ...HINTS[cap],
102
+ ];
103
+ };
104
+ //# sourceMappingURL=add-capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-capability.js","sourceRoot":"","sources":["../src/add-capability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,MAAM;IACN,MAAM;IACN,WAAW;IACX,SAAS;IACT,MAAM;IACN,MAAM;IACN,MAAM;IACN,eAAe;IACf,KAAK;IACL,WAAW;IACX,MAAM;IACN,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,SAAS;CACD,CAAC;AAGX,MAAM,OAAO,GAA+B,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AAE5D,MAAM,KAAK,GAAiC;IAC1C,IAAI,EAAE;QACJ,mEAAmE;QACnE,mFAAmF;QACnF,iEAAiE;KAClE;IACD,IAAI,EAAE;QACJ,yDAAyD;QACzD,gDAAgD;KACjD;IACD,WAAW,EAAE;QACX,wCAAwC;QACxC,8CAA8C;KAC/C;IACD,OAAO,EAAE;QACP,6CAA6C;QAC7C,sEAAsE;KACvE;IACD,IAAI,EAAE,CAAC,kDAAkD,CAAC;IAC1D,IAAI,EAAE;QACJ,oEAAoE;QACpE,+EAA+E;KAChF;IACD,IAAI,EAAE;QACJ,2CAA2C;QAC3C,iEAAiE;KAClE;IACD,aAAa,EAAE,CAAC,2DAA2D,CAAC;IAC5E,GAAG,EAAE;QACH,qDAAqD;QACrD,wGAAwG;QACxG,iGAAiG;KAClG;IACD,SAAS,EAAE;QACT,wDAAwD;QACxD,iHAAiH;QACjH,4BAA4B;QAC5B,gGAAgG;KACjG;IACD,IAAI,EAAE;QACJ,oFAAoF;QACpF,4GAA4G;QAC5G,sGAAsG;QACtG,8DAA8D;KAC/D;IACD,MAAM,EAAE;QACN,2GAA2G;QAC3G,yDAAyD;QACzD,4DAA4D;KAC7D;IACD,IAAI,EAAE;QACJ,2EAA2E;QAC3E,4DAA4D;QAC5D,+CAA+C;QAC/C,wFAAwF;KACzF;IACD,MAAM,EAAE;QACN,wEAAwE;QACxE,4EAA4E;KAC7E;IACD,MAAM,EAAE;QACN,+DAA+D;QAC/D,yDAAyD;KAC1D;IACD,OAAO,EAAE;QACP,yDAAyD;QACzD,iFAAiF;QACjF,mFAAmF;QACnF,+DAA+D;KAChE;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,IAAI,GAAG,GAAG,EAAY,EAAE;IAClE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IACvC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAsB,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,WAAW,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,GAAG,GAAG,QAAsB,CAAC;IACnC,OAAO;QACL,GAAG,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC;QAC7B,WAAW,GAAG,mCAAmC;QACjD,GAAG,KAAK,CAAC,GAAG,CAAC;KACd,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const addModule: (root: string, name: string, opts: {
2
+ prisma: boolean;
3
+ web: boolean;
4
+ }) => string[];
5
+ //# sourceMappingURL=add-module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-module.d.ts","sourceRoot":"","sources":["../src/add-module.ts"],"names":[],"mappings":"AA2DA,eAAO,MAAM,SAAS,GACpB,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,MAAM;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,KACtC,MAAM,EAwWR,CAAC"}
@@ -0,0 +1,210 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { mergePrisma } from './merge-prisma';
5
+ import { ensureMarkerBlock, expressRouteInsertBefore, insertAfterLastImport, insertBeforeMatch, } from './patch-text';
6
+ const templateDir = join(dirname(fileURLToPath(import.meta.url)), '../templates/module');
7
+ const pascalCase = (name) => name
8
+ .split('-')
9
+ .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
10
+ .join('');
11
+ const camelCase = (name) => {
12
+ const pascal = pascalCase(name);
13
+ return `${pascal.charAt(0).toLowerCase()}${pascal.slice(1)}`;
14
+ };
15
+ const render = (src, vars) => src.replace(/\{\{(\w+)\}\}/g, (_, key) => {
16
+ const value = vars[key];
17
+ if (value === undefined)
18
+ throw new Error(`missing template var ${key}`);
19
+ return value;
20
+ });
21
+ const loadTemplate = (file, vars) => render(readFileSync(join(templateDir, file), 'utf8'), vars);
22
+ const writeNew = (path, content, logs) => {
23
+ if (existsSync(path)) {
24
+ logs.push(`exists ${path}`);
25
+ return;
26
+ }
27
+ mkdirSync(dirname(path), { recursive: true });
28
+ writeFileSync(path, content.endsWith('\n') ? content : `${content}\n`);
29
+ logs.push(`created ${path}`);
30
+ };
31
+ const patchFile = (path, transform, logs, label) => {
32
+ if (!existsSync(path))
33
+ return;
34
+ const before = readFileSync(path, 'utf8');
35
+ const after = transform(before);
36
+ if (after !== before) {
37
+ writeFileSync(path, after);
38
+ logs.push(label);
39
+ }
40
+ };
41
+ export const addModule = (root, name, opts) => {
42
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
43
+ throw new Error('module name must be kebab/lowercase (e.g. booking)');
44
+ }
45
+ const pascal = pascalCase(name);
46
+ const camel = camelCase(name);
47
+ const vars = { kebab: name, pascal, camel };
48
+ const logs = [];
49
+ writeNew(join(root, 'packages/contracts/src/dto', `${name}.ts`), loadTemplate('dto.ts.tmpl', vars), logs);
50
+ writeNew(join(root, 'packages/contracts/src/api', `${name}.ts`), loadTemplate('api.ts.tmpl', vars), logs);
51
+ writeNew(join(root, `apps/api/src/modules/${name}/domain/${name}-repository.ts`), loadTemplate('domain-repository.ts.tmpl', vars), logs);
52
+ writeNew(join(root, `apps/api/src/modules/${name}/application/${name}-service.ts`), loadTemplate('application-service.ts.tmpl', vars), logs);
53
+ writeNew(join(root, `apps/api/src/modules/${name}/infra/memory-${name}-repository.ts`), loadTemplate('memory-repository.ts.tmpl', vars), logs);
54
+ writeNew(join(root, `apps/api/src/modules/${name}/infra/prisma-${name}-repository.ts`), loadTemplate('prisma-repository.ts.tmpl', vars), logs);
55
+ writeNew(join(root, `apps/api/src/modules/${name}/infra/${name}-router.ts`), loadTemplate('router.ts.tmpl', vars), logs);
56
+ writeNew(join(root, `apps/api/src/modules/${name}/infra/${name}.test.ts`), loadTemplate('service.test.ts.tmpl', vars), logs);
57
+ writeNew(join(root, `packages/sdk/src/resources/${name}.ts`), loadTemplate('sdk-resource.ts.tmpl', vars), logs);
58
+ writeNew(join(root, `packages/web-sdk/src/${name}-hooks.ts`), loadTemplate('web-hooks.ts.tmpl', vars), logs);
59
+ if (opts.web) {
60
+ writeNew(join(root, `apps/web/src/features/${name}/${name}-page.tsx`), loadTemplate('web-page.tsx.tmpl', vars), logs);
61
+ }
62
+ if (opts.prisma) {
63
+ const fragment = loadTemplate('model.prisma.tmpl', vars);
64
+ writeNew(join(root, `modules/${name}/prisma/${name}.prisma`), fragment, logs);
65
+ const schemaPath = join(root, 'apps/api/prisma/schema.prisma');
66
+ if (existsSync(schemaPath)) {
67
+ const merged = mergePrisma(readFileSync(schemaPath, 'utf8'), fragment);
68
+ if (merged.actions.some((action) => action.startsWith('added '))) {
69
+ writeFileSync(schemaPath, merged.schema);
70
+ logs.push(...merged.actions);
71
+ }
72
+ else {
73
+ logs.push(...merged.actions);
74
+ }
75
+ }
76
+ }
77
+ patchFile(join(root, 'packages/contracts/src/dto/index.ts'), (src) => {
78
+ const line = `export * from './${name}';`;
79
+ if (src.includes(line))
80
+ return src;
81
+ return `${src.trimEnd()}\n${line}\n`;
82
+ }, logs, 'updated packages/contracts/src/dto/index.ts');
83
+ const barrel = join(root, 'packages/contracts/src/api/index.ts');
84
+ if (existsSync(barrel)) {
85
+ let barrelSrc = readFileSync(barrel, 'utf8');
86
+ const exportLine = `export * from './${name}';`;
87
+ if (!barrelSrc.includes(exportLine)) {
88
+ barrelSrc = `${barrelSrc.trimEnd()}\n${exportLine}\n`;
89
+ logs.push('updated packages/contracts/src/api/index.ts export');
90
+ }
91
+ if (barrelSrc.includes('export const appContract = c.router({')) {
92
+ const importLine = `import { ${camel}Contract } from './${name}';`;
93
+ if (!barrelSrc.includes(importLine)) {
94
+ barrelSrc = insertAfterLastImport(barrelSrc, importLine);
95
+ }
96
+ if (!barrelSrc.includes(`${camel}: ${camel}Contract`)) {
97
+ barrelSrc = insertBeforeMatch(barrelSrc, '\n});', `\n ${camel}: ${camel}Contract,`);
98
+ logs.push('wired contract onto appContract');
99
+ }
100
+ }
101
+ else {
102
+ logs.push(`wire manually: add ${camel}Contract to appContract`);
103
+ }
104
+ writeFileSync(barrel, barrelSrc);
105
+ }
106
+ patchFile(join(root, 'apps/api/src/app.ts'), (src) => {
107
+ let next = insertAfterLastImport(src, `import { register${pascal}Routes } from './modules/${name}/infra/${name}-router';`);
108
+ next = insertAfterLastImport(next, `import type { ${pascal}Service } from './modules/${name}/application/${name}-service';`);
109
+ if (next.includes('userService: UserService;') &&
110
+ !next.includes(`${camel}Service: ${pascal}Service;`)) {
111
+ next = next.replace('userService: UserService;', `userService: UserService;\n ${camel}Service: ${pascal}Service;`);
112
+ }
113
+ next = ensureMarkerBlock(next, `module:${name}`, ` register${pascal}Routes(app, input.${camel}Service);`, expressRouteInsertBefore(next));
114
+ return next;
115
+ }, logs, 'patched apps/api/src/app.ts');
116
+ patchFile(join(root, 'apps/api/src/app-fastify.ts'), (src) => {
117
+ let next = insertAfterLastImport(src, `import { ${camel}Handlers } from './modules/${name}/infra/${name}-router';`);
118
+ next = ensureMarkerBlock(next, `module:${name}:fastify`, ` mountFastify(app, appContract.${camel}, ${camel}Handlers(input.${camel}Service));`, ' if (input.bullmqQueues) await mountBullBoardFastify');
119
+ if (!next.includes(`module:${name}:fastify`) &&
120
+ next.includes('mountFastify(app, appContract.billing')) {
121
+ next = insertAfterLastImport(next, `import { ${camel}Handlers } from './modules/${name}/infra/${name}-router';`);
122
+ next = next.replace('mountFastify(app, appContract.billing, billingHandlers(input.billingService));', `mountFastify(app, appContract.billing, billingHandlers(input.billingService));\n mountFastify(app, appContract.${camel}, ${camel}Handlers(input.${camel}Service));`);
123
+ }
124
+ return next;
125
+ }, logs, 'patched apps/api/src/app-fastify.ts');
126
+ patchFile(join(root, 'apps/api/src/composition.ts'), (src) => {
127
+ let next = insertAfterLastImport(src, `import { create${pascal}Service } from './modules/${name}/application/${name}-service';`);
128
+ next = insertAfterLastImport(next, `import { createPrisma${pascal}Repository } from './modules/${name}/infra/prisma-${name}-repository';`);
129
+ if (next.includes('userService: UserService;') &&
130
+ !next.includes(`${camel}Service: ${pascal}Service;`)) {
131
+ next = insertAfterLastImport(next, `import type { ${pascal}Service } from './modules/${name}/application/${name}-service';`);
132
+ next = next.replace('userService: UserService;', `userService: UserService;\n ${camel}Service: ${pascal}Service;`);
133
+ }
134
+ next = ensureMarkerBlock(next, `module:${name}`, ` const ${camel}Service = create${pascal}Service(createPrisma${pascal}Repository(prisma));`, ' return {');
135
+ if (next.includes('pingReady: async () => {') &&
136
+ !next.includes(` ${camel}Service,\n pingReady: async () => {`)) {
137
+ next = next.replace('pingReady: async () => {', `${camel}Service,\n pingReady: async () => {`);
138
+ }
139
+ return next;
140
+ }, logs, 'patched apps/api/src/composition.ts');
141
+ patchFile(join(root, 'apps/api/src/main.ts'), (src) => {
142
+ if (src.includes(`${camel}Service: composition.${camel}Service`))
143
+ return src;
144
+ if (src.includes('userService: composition.userService,')) {
145
+ return src.replace('userService: composition.userService,', `userService: composition.userService,\n ${camel}Service: composition.${camel}Service,`);
146
+ }
147
+ return src;
148
+ }, logs, 'patched apps/api/src/main.ts');
149
+ const patchMemoryHarness = (rel) => {
150
+ patchFile(join(root, rel), (src) => {
151
+ let next = insertAfterLastImport(src, `import { create${pascal}Service } from './modules/${name}/application/${name}-service';`);
152
+ next = insertAfterLastImport(next, `import { createMemory${pascal}Repository } from './modules/${name}/infra/memory-${name}-repository';`);
153
+ if (next.includes('userService: createUserService(users, audit, queue, sessions),') &&
154
+ !next.includes(`${camel}Service: create${pascal}Service`)) {
155
+ next = next.replace('userService: createUserService(users, audit, queue, sessions),', `userService: createUserService(users, audit, queue, sessions),\n ${camel}Service: create${pascal}Service(createMemory${pascal}Repository()),`);
156
+ }
157
+ return next;
158
+ }, logs, `patched ${rel}`);
159
+ };
160
+ patchMemoryHarness('apps/api/src/create-memory-input.ts');
161
+ patchMemoryHarness('apps/api/src/app.test.ts');
162
+ patchFile(join(root, 'packages/sdk/src/index.ts'), (src) => {
163
+ let next = insertAfterLastImport(src, `import { ${camel}Resource } from './resources/${name}';`);
164
+ if (!next.includes(`${camel}: ${camel}Resource(http)`)) {
165
+ if (next.includes('users: usersResource(http),')) {
166
+ next = next.replace('users: usersResource(http),', `users: usersResource(http),\n ${camel}: ${camel}Resource(http),`);
167
+ }
168
+ else {
169
+ next = insertBeforeMatch(next, 'connectRealtime:', ` ${camel}: ${camel}Resource(http),\n `);
170
+ }
171
+ }
172
+ return next;
173
+ }, logs, 'patched packages/sdk/src/index.ts');
174
+ patchFile(join(root, 'packages/web-sdk/src/index.ts'), (src) => {
175
+ const line = `export { create${pascal}Hooks } from './${name}-hooks';`;
176
+ if (src.includes(line))
177
+ return src;
178
+ return `${src.trimEnd()}\n${line}\n`;
179
+ }, logs, 'patched packages/web-sdk/src/index.ts');
180
+ if (opts.web) {
181
+ patchFile(join(root, 'apps/web/src/router.tsx'), (src) => {
182
+ let next = insertAfterLastImport(src, `import { ${pascal}Page } from './features/${name}/${name}-page';`);
183
+ if (!next.includes(`const ${camel}Route = createRoute`)) {
184
+ next = insertBeforeMatch(next, 'const routeTree = ', `const ${camel}Route = createRoute({\n getParentRoute: () => rootRoute,\n path: '/${name}',\n component: ${pascal}Page,\n});\n\n`);
185
+ }
186
+ if (!next.includes(`${camel}Route,`) && next.includes('export const router')) {
187
+ next = insertBeforeMatch(next, '\n]);\n\nexport const router', `\n ${camel}Route,`);
188
+ }
189
+ if (next.includes('<span className="ml-auto" />') && !next.includes(`to="/${name}"`)) {
190
+ next = next.replace('<span className="ml-auto" />', `<Link to="/${name}" className="text-zinc-600 hover:text-zinc-900">\n ${pascal}\n </Link>\n <span className="ml-auto" />`);
191
+ }
192
+ if (next.includes('<Link to="/notifications"') &&
193
+ !next.includes(`to="/${name}"`) &&
194
+ next.includes('className={navClass}')) {
195
+ next = next.replace(` <Link to="/notifications" className={navClass}>
196
+ Inbox
197
+ </Link>`, ` <Link to="/notifications" className={navClass}>
198
+ Inbox
199
+ </Link>
200
+ <Link to="/${name}" className={navClass}>
201
+ ${pascal}
202
+ </Link>`);
203
+ }
204
+ return next;
205
+ }, logs, 'patched apps/web/src/router.tsx');
206
+ }
207
+ logs.push('next: run pnpm db:migrate if prisma models changed, then pnpm gen:openapi');
208
+ return logs;
209
+ };
210
+ //# sourceMappingURL=add-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-module.js","sourceRoot":"","sources":["../src/add-module.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const agentTemplatesDir: (kitRoot: string) => string;
2
+ export declare const writeAgentStubs: (dest: string, kitRoot: string) => void;
3
+ //# sourceMappingURL=agent-stubs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-stubs.d.ts","sourceRoot":"","sources":["../src/agent-stubs.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,KAAG,MACF,CAAC;AAEnD,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,IAmB/D,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const agentTemplatesDir = (kitRoot) => join(kitRoot, 'tooling/ysk-cli/templates/agent');
4
+ export const writeAgentStubs = (dest, kitRoot) => {
5
+ const templates = agentTemplatesDir(kitRoot);
6
+ const rule = join(templates, 'ysk-kit.mdc');
7
+ const skillsDir = join(templates, 'skills');
8
+ if (!existsSync(rule) || !existsSync(skillsDir)) {
9
+ throw new Error('agent templates missing');
10
+ }
11
+ mkdirSync(join(dest, '.cursor/rules'), { recursive: true });
12
+ cpSync(rule, join(dest, '.cursor/rules/ysk-kit.mdc'));
13
+ for (const name of readdirSync(skillsDir)) {
14
+ const skillDir = join(skillsDir, name);
15
+ const src = join(skillDir, 'SKILL.md');
16
+ if (!statSync(skillDir).isDirectory() || !existsSync(src))
17
+ continue;
18
+ for (const tree of ['.grok/skills', '.cursor/skills']) {
19
+ const dir = join(dest, tree, name);
20
+ mkdirSync(dir, { recursive: true });
21
+ cpSync(src, join(dir, 'SKILL.md'));
22
+ }
23
+ }
24
+ };
25
+ //# sourceMappingURL=agent-stubs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-stubs.js","sourceRoot":"","sources":["../src/agent-stubs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAU,EAAE,CAC3D,IAAI,CAAC,OAAO,EAAE,iCAAiC,CAAC,CAAC;AAEnD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAE,OAAe,EAAQ,EAAE;IACrE,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,2BAA2B,CAAC,CAAC,CAAC;IACtD,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACvC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACpE,KAAK,MAAM,IAAI,IAAI,CAAC,cAAc,EAAE,gBAAgB,CAAU,EAAE,CAAC;YAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YACnC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Capability } from './add-capability';
2
+ export declare const applyCapability: (root: string, name: Capability) => string[];
3
+ //# sourceMappingURL=apply-capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-capability.d.ts","sourceRoot":"","sources":["../src/apply-capability.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAmEnD,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,EAAE,MAAM,UAAU,KAAG,MAAM,EAsItE,CAAC"}
@@ -0,0 +1,189 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { CATALOG } from './capability-catalog';
5
+ import { mergePrisma } from './merge-prisma';
6
+ import { ensureEnvKey, ensureJsonDep } from './patch-text';
7
+ const readIf = (path) => existsSync(path) ? readFileSync(path, 'utf8') : null;
8
+ const fileContains = (root, rel, token) => {
9
+ const src = readIf(join(root, rel));
10
+ return Boolean(src?.includes(token));
11
+ };
12
+ const templatesRoot = join(dirname(fileURLToPath(import.meta.url)), '../templates/capabilities');
13
+ const copyCapabilityTree = (root, name, logs) => {
14
+ const from = join(templatesRoot, name);
15
+ if (!existsSync(from))
16
+ return false;
17
+ const hasWeb = existsSync(join(root, 'apps/web'));
18
+ const hasMobile = existsSync(join(root, 'apps/mobile'));
19
+ let copied = false;
20
+ const walk = (dir, rel) => {
21
+ for (const entry of readdirSync(dir)) {
22
+ const source = join(dir, entry);
23
+ const destRel = rel ? `${rel}/${entry}` : entry;
24
+ if (statSync(source).isDirectory()) {
25
+ walk(source, destRel);
26
+ continue;
27
+ }
28
+ if (destRel.startsWith('apps/web/') && !hasWeb)
29
+ continue;
30
+ if (destRel.startsWith('apps/mobile/') && !hasMobile)
31
+ continue;
32
+ const dest = join(root, destRel);
33
+ if (existsSync(dest)) {
34
+ const current = readFileSync(dest, 'utf8');
35
+ if (!current.includes('ysk add push restores client.devices')) {
36
+ logs.push(`exists ${destRel}`);
37
+ continue;
38
+ }
39
+ }
40
+ mkdirSync(dirname(dest), { recursive: true });
41
+ cpSync(source, dest);
42
+ copied = true;
43
+ logs.push(`copied ${destRel}`);
44
+ }
45
+ };
46
+ walk(from, '');
47
+ return copied;
48
+ };
49
+ const patchRel = (root, rel, transform, logs, label) => {
50
+ if (!transform)
51
+ return false;
52
+ const path = join(root, rel);
53
+ if (!existsSync(path))
54
+ return false;
55
+ const before = readFileSync(path, 'utf8');
56
+ const after = transform(before);
57
+ if (after === before)
58
+ return false;
59
+ writeFileSync(path, after);
60
+ logs.push(label);
61
+ return true;
62
+ };
63
+ export const applyCapability = (root, name) => {
64
+ const recipe = CATALOG[name];
65
+ const logs = [];
66
+ let changed = false;
67
+ if (name === 'billing') {
68
+ const schema = readIf(join(root, 'apps/api/prisma/schema.prisma'));
69
+ if (schema && !/model\s+Organization\s*\{/.test(schema)) {
70
+ throw new Error('ysk add billing requires team. Run: pnpm ysk add team');
71
+ }
72
+ }
73
+ const schemaPath = join(root, 'apps/api/prisma/schema.prisma');
74
+ if (recipe.prisma && existsSync(schemaPath)) {
75
+ const fragmentPath = join(root, recipe.prisma);
76
+ if (existsSync(fragmentPath)) {
77
+ const merged = mergePrisma(readFileSync(schemaPath, 'utf8'), readFileSync(fragmentPath, 'utf8'), {
78
+ ...(recipe.userFields ? { userFields: recipe.userFields } : {}),
79
+ ...(recipe.organizationFields ? { organizationFields: recipe.organizationFields } : {}),
80
+ });
81
+ const added = merged.actions.some((action) => action.startsWith('added '));
82
+ if (added) {
83
+ writeFileSync(schemaPath, merged.schema);
84
+ changed = true;
85
+ }
86
+ logs.push(...merged.actions);
87
+ }
88
+ }
89
+ else if ((recipe.userFields || recipe.organizationFields) && existsSync(schemaPath)) {
90
+ const merged = mergePrisma(readFileSync(schemaPath, 'utf8'), '', {
91
+ ...(recipe.userFields ? { userFields: recipe.userFields } : {}),
92
+ ...(recipe.organizationFields ? { organizationFields: recipe.organizationFields } : {}),
93
+ });
94
+ const added = merged.actions.some((action) => action.startsWith('added '));
95
+ if (added) {
96
+ writeFileSync(schemaPath, merged.schema);
97
+ changed = true;
98
+ }
99
+ logs.push(...merged.actions);
100
+ }
101
+ const envPath = join(root, '.env.example');
102
+ if (recipe.env && existsSync(envPath)) {
103
+ let env = readFileSync(envPath, 'utf8');
104
+ for (const key of recipe.env) {
105
+ const result = ensureEnvKey(env, key);
106
+ env = result.source;
107
+ if (result.added) {
108
+ changed = true;
109
+ logs.push(`appended ${key} to .env.example`);
110
+ }
111
+ }
112
+ writeFileSync(envPath, env);
113
+ }
114
+ const pkgPath = join(root, 'apps/api/package.json');
115
+ if (recipe.apiDeps && existsSync(pkgPath)) {
116
+ let json = readFileSync(pkgPath, 'utf8');
117
+ let pkgChanged = false;
118
+ for (const dep of recipe.apiDeps) {
119
+ const result = ensureJsonDep(json, dep, 'workspace:*');
120
+ json = result.source;
121
+ if (result.added) {
122
+ changed = true;
123
+ pkgChanged = true;
124
+ logs.push(`added ${dep} to apps/api`);
125
+ }
126
+ }
127
+ if (pkgChanged)
128
+ writeFileSync(pkgPath, json);
129
+ }
130
+ const skipSource = Boolean(recipe.skipSourceIf &&
131
+ (fileContains(root, 'apps/api/src/app.ts', recipe.skipSourceIf) ||
132
+ fileContains(root, 'apps/api/src/composition.ts', recipe.skipSourceIf)));
133
+ if (skipSource) {
134
+ logs.push('composition already hand-wired in saas — copy from ysk-kit');
135
+ }
136
+ else {
137
+ if (recipe.copySource && copyCapabilityTree(root, name, logs)) {
138
+ changed = true;
139
+ }
140
+ const patches = [
141
+ ['apps/api/src/app.ts', recipe.patchApp, 'patched apps/api/src/app.ts'],
142
+ ['apps/api/src/app-fastify.ts', recipe.patchFastify, 'patched apps/api/src/app-fastify.ts'],
143
+ [
144
+ 'apps/api/src/composition.ts',
145
+ recipe.patchComposition,
146
+ 'patched apps/api/src/composition.ts',
147
+ ],
148
+ ['apps/api/src/main.ts', recipe.patchMain, 'patched apps/api/src/main.ts'],
149
+ [
150
+ 'apps/api/src/create-memory-input.ts',
151
+ recipe.patchMemoryInput,
152
+ 'patched apps/api/src/create-memory-input.ts',
153
+ ],
154
+ ['apps/api/src/worker.ts', recipe.patchWorker, 'patched apps/api/src/worker.ts'],
155
+ ['packages/sdk/src/index.ts', recipe.patchSdk, 'patched packages/sdk/src/index.ts'],
156
+ [
157
+ 'packages/web-sdk/src/index.ts',
158
+ recipe.patchWebSdk,
159
+ 'patched packages/web-sdk/src/index.ts',
160
+ ],
161
+ ['apps/web/src/router.tsx', recipe.patchWebRouter, 'patched apps/web/src/router.tsx'],
162
+ [
163
+ 'packages/contracts/src/api/index.ts',
164
+ recipe.patchContracts,
165
+ 'patched packages/contracts/src/api/index.ts',
166
+ ],
167
+ ['apps/mobile/src/app.tsx', recipe.patchMobileApp, 'patched apps/mobile/src/app.tsx'],
168
+ [
169
+ 'apps/mobile/src/screens/home-screen.tsx',
170
+ recipe.patchMobileHome,
171
+ 'patched apps/mobile/src/screens/home-screen.tsx',
172
+ ],
173
+ [
174
+ 'apps/mobile/src/screens/login-screen.tsx',
175
+ recipe.patchMobileLogin,
176
+ 'patched apps/mobile/src/screens/login-screen.tsx',
177
+ ],
178
+ ];
179
+ for (const [rel, transform, label] of patches) {
180
+ if (patchRel(root, rel, transform, logs, label))
181
+ changed = true;
182
+ }
183
+ }
184
+ logs.unshift(changed ? `ysk add ${name}: applied` : `ysk add ${name}: already applied`);
185
+ logs.push('run pnpm db:migrate if prisma models changed');
186
+ logs.push('run pnpm gen:openapi after contracts change');
187
+ return logs;
188
+ };
189
+ //# sourceMappingURL=apply-capability.js.map