create-fullstack-scaffold 0.4.9 → 0.4.11

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 (139) hide show
  1. package/dist/cli/index.js +499 -100
  2. package/dist/cli/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/template/CLAUDE.md +18 -6
  5. package/template/drizzle/0004_add_merchants_products.sql +30 -0
  6. package/template/drizzle/meta/_journal.json +7 -0
  7. package/template/eslint-rules/module-boundary.js +6 -2
  8. package/template/eslint-rules/no-cross-module-service-import.js +105 -0
  9. package/template/eslint-rules/no-disable-type-safe-client.js +5 -0
  10. package/template/eslint.config.js +9 -0
  11. package/template/lint-scripts/config/project.config.ts +21 -0
  12. package/template/lint-scripts/validate-all.ts +48 -12
  13. package/template/lint-scripts/validators/index.ts +29 -0
  14. package/template/lint-scripts/validators/module-public-api.validator.ts +103 -0
  15. package/template/lint-scripts/validators/schema-uniqueness.validator.ts +147 -0
  16. package/template/modules.config.ts +2 -0
  17. package/template/package.json +2 -0
  18. package/template/playwright.config.ts +14 -0
  19. package/template/src/admin/App.tsx +40 -3
  20. package/template/src/admin/components/LanguageSwitcher.tsx +22 -0
  21. package/template/src/admin/components/NotificationDrawer.tsx +108 -38
  22. package/template/src/admin/components/StatsCard.tsx +3 -1
  23. package/template/src/admin/components/ThemeToggle.tsx +28 -0
  24. package/template/src/admin/hooks/useRoles.ts +2 -2
  25. package/template/src/admin/i18n/index.ts +18 -0
  26. package/template/src/admin/i18n/locales/en-US.json +381 -0
  27. package/template/src/admin/i18n/locales/zh-CN.json +381 -0
  28. package/template/src/admin/i18n/useLanguage.ts +21 -0
  29. package/template/src/admin/layouts/Header.tsx +37 -16
  30. package/template/src/admin/layouts/Layout.tsx +7 -3
  31. package/template/src/admin/layouts/Sidebar.tsx +70 -68
  32. package/template/src/admin/main.tsx +1 -0
  33. package/template/src/admin/pages/ContentPage.tsx +68 -56
  34. package/template/src/admin/pages/DashboardPage.tsx +82 -76
  35. package/template/src/admin/pages/DisputesPage.tsx +61 -53
  36. package/template/src/admin/pages/LoginPage.tsx +41 -33
  37. package/template/src/admin/pages/OrdersPage.tsx +69 -64
  38. package/template/src/admin/pages/PermissionsPage.tsx +10 -8
  39. package/template/src/admin/pages/PluginDashboardPage.tsx +3 -1
  40. package/template/src/admin/pages/PluginManagementPage.tsx +3 -1
  41. package/template/src/admin/pages/RegisterPage.tsx +18 -16
  42. package/template/src/admin/pages/RolesPage.tsx +51 -49
  43. package/template/src/admin/pages/SettingsPage.tsx +22 -22
  44. package/template/src/admin/pages/SystemLogsPage.tsx +32 -30
  45. package/template/src/admin/pages/TicketsPage.tsx +67 -59
  46. package/template/src/admin/pages/UsersPage.tsx +63 -53
  47. package/template/src/admin/pages/__tests__/RolesPage.test.tsx +2 -2
  48. package/template/src/admin/stores/themeStore.ts +32 -0
  49. package/template/src/cli/modules/admin/index.ts +200 -0
  50. package/template/src/cli/modules/captcha/index.ts +40 -0
  51. package/template/src/cli/modules/chat/index.ts +21 -0
  52. package/template/src/cli/modules/content/index.ts +144 -0
  53. package/template/src/cli/modules/dispute/index.ts +150 -0
  54. package/template/src/cli/modules/file/index.ts +55 -0
  55. package/template/src/cli/modules/index.ts +30 -5
  56. package/template/src/cli/modules/order/index.ts +170 -0
  57. package/template/src/cli/modules/permission/index.ts +195 -0
  58. package/template/src/cli/modules/tenant/index.ts +142 -0
  59. package/template/src/cli/modules/ticket/index.ts +167 -0
  60. package/template/src/cli/rpc/client.ts +2 -2
  61. package/template/src/client/index.css +73 -0
  62. package/template/src/merchant/App.tsx +2 -0
  63. package/template/src/merchant/components/MerchantGuard.tsx +2 -6
  64. package/template/src/merchant/components/__tests__/MerchantGuard.test.tsx +117 -0
  65. package/template/src/merchant/layouts/Header.tsx +2 -2
  66. package/template/src/merchant/pages/DashboardPage.tsx +2 -2
  67. package/template/src/merchant/pages/DisputesPage.tsx +14 -10
  68. package/template/src/merchant/pages/LoginPage.tsx +49 -0
  69. package/template/src/merchant/pages/OrdersPage.tsx +17 -10
  70. package/template/src/merchant/pages/ProductsPage.tsx +31 -8
  71. package/template/src/merchant/pages/SettingsPage.tsx +3 -7
  72. package/template/src/merchant/stores/__tests__/merchantStore.test.ts +317 -0
  73. package/template/src/merchant/stores/merchantStore.ts +24 -27
  74. package/template/src/server/db/schema/index.ts +2 -0
  75. package/template/src/server/db/schema/merchants.ts +26 -0
  76. package/template/src/server/db/schema/products.ts +25 -0
  77. package/template/src/server/db/test-setup.ts +237 -0
  78. package/template/src/server/middleware/__tests__/audit-log.test.ts +144 -0
  79. package/template/src/server/middleware/__tests__/cors.test.ts +84 -0
  80. package/template/src/server/middleware/__tests__/logger.test.ts +117 -0
  81. package/template/src/server/middleware/__tests__/rate-limit.test.ts +73 -0
  82. package/template/src/server/middleware/__tests__/realtime-env.test.ts +56 -0
  83. package/template/src/server/middleware/__tests__/tenant-isolation.test.ts +150 -0
  84. package/template/src/server/module-admin/__tests__/admin-routes.test.ts +1 -1
  85. package/template/src/server/module-admin/module.ts +4 -0
  86. package/template/src/server/module-admin/routes/admin-notification-routes.ts +3 -3
  87. package/template/src/server/module-admin/routes/user-management-routes.ts +2 -2
  88. package/template/src/server/module-auth/__tests__/auth-routes.test.ts +315 -0
  89. package/template/src/server/module-auth/__tests__/profile-routes.test.ts +83 -0
  90. package/template/src/server/module-auth/module.ts +2 -0
  91. package/template/src/server/module-content/module.ts +1 -0
  92. package/template/src/server/module-content/routes/content-routes.ts +2 -2
  93. package/template/src/server/module-dispute/module.ts +1 -0
  94. package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -2
  95. package/template/src/server/module-merchant/__tests__/merchant-routes.test.ts +218 -0
  96. package/template/src/server/module-merchant/__tests__/merchant-service.test.ts +183 -0
  97. package/template/src/server/module-merchant/index.ts +10 -0
  98. package/template/src/server/module-merchant/module.ts +33 -0
  99. package/template/src/server/module-merchant/routes/merchant-routes.ts +138 -0
  100. package/template/src/server/module-merchant/services/merchant-service.ts +292 -0
  101. package/template/src/server/module-notifications/index.ts +18 -0
  102. package/template/src/server/module-notifications/module.ts +2 -0
  103. package/template/src/server/module-order/module.ts +1 -0
  104. package/template/src/server/module-order/routes/order-routes.ts +2 -2
  105. package/template/src/server/module-permission/routes/role-routes.ts +3 -3
  106. package/template/src/server/module-plugin/__tests__/admin-category-service.test.ts +181 -0
  107. package/template/src/server/module-plugin/__tests__/admin-plugin-service.test.ts +277 -0
  108. package/template/src/server/module-plugin/__tests__/admin-stats-service.test.ts +170 -0
  109. package/template/src/server/module-plugin/__tests__/plugin-review-service.test.ts +260 -0
  110. package/template/src/server/module-plugin/__tests__/plugin-seed-service.test.ts +170 -0
  111. package/template/src/server/module-plugin/module.ts +3 -0
  112. package/template/src/server/module-tenant/__tests__/tenant-routes.test.ts +142 -0
  113. package/template/src/server/module-tenant/__tests__/tenant-service.test.ts +152 -0
  114. package/template/src/server/module-tenant/module.ts +1 -0
  115. package/template/src/server/module-tenant/services/tenant-service.ts +1 -37
  116. package/template/src/server/module-ticket/module.ts +1 -0
  117. package/template/src/server/module-ticket/routes/ticket-routes.ts +2 -2
  118. package/template/src/server/module-todos/module.ts +3 -0
  119. package/template/src/server/route-registry.ts +4 -0
  120. package/template/src/server/utils/__tests__/date.test.ts +130 -0
  121. package/template/src/server/utils/__tests__/env.test.ts +25 -0
  122. package/template/src/server/utils/__tests__/generate.test.ts +82 -0
  123. package/template/src/server/utils/__tests__/id-helpers.test.ts +29 -0
  124. package/template/src/server/utils/__tests__/json.test.ts +49 -0
  125. package/template/src/server/utils/__tests__/permission-utils.test.ts +26 -0
  126. package/template/src/server/utils/__tests__/uuid.test.ts +25 -0
  127. package/template/src/shared/core/module-manifest.ts +15 -0
  128. package/template/src/shared/modules/admin/schemas.ts +1 -1
  129. package/template/src/shared/modules/content/schemas.ts +2 -2
  130. package/template/src/shared/modules/dispute/schemas.ts +2 -2
  131. package/template/src/shared/modules/index.ts +181 -1
  132. package/template/src/shared/modules/merchant/index.ts +12 -0
  133. package/template/src/shared/modules/merchant/schemas.ts +63 -0
  134. package/template/src/shared/modules/order/schemas.ts +2 -2
  135. package/template/src/shared/modules/permission/index.ts +1 -1
  136. package/template/src/shared/modules/permission/schemas.ts +1 -1
  137. package/template/src/shared/modules/role/schemas.ts +2 -2
  138. package/template/src/shared/modules/ticket/schemas.ts +2 -2
  139. package/template/src/shared/schemas/index.ts +74 -2
@@ -0,0 +1,170 @@
1
+ import type { SiteInstance } from '@dyyz1993/xcli-core'
2
+ import { ok, fail } from '@dyyz1993/xcli-core'
3
+ import { z } from 'zod'
4
+ import { getClient } from '@cli/utils/api'
5
+
6
+ export function registerOrderCommands(site: SiteInstance) {
7
+ site.command('list', {
8
+ description: 'List all orders',
9
+ parameters: z.object({
10
+ limit: z.coerce.number().default(20).describe('Limit results'),
11
+ offset: z.coerce.number().default(0).describe('Offset'),
12
+ status: z.string().optional().describe('Filter by status'),
13
+ 'customer-name': z.string().optional().describe('Filter by customer name'),
14
+ }),
15
+ handler: async (params: unknown) => {
16
+ const p = params as { limit: number; offset: number; status?: string; 'customer-name'?: string }
17
+ try {
18
+ const client = getClient()
19
+ const query: Record<string, string> = {
20
+ limit: String(p.limit),
21
+ offset: String(p.offset),
22
+ }
23
+ if (p.status) query.status = p.status
24
+ if (p['customer-name']) query.customerName = p['customer-name']
25
+ const res = await client.api.orders.$get({ query })
26
+ const data = await res.json()
27
+ return ok(data)
28
+ } catch (err) {
29
+ return fail(err instanceof Error ? err.message : 'Failed to list orders')
30
+ }
31
+ },
32
+ })
33
+
34
+ site.command('get', {
35
+ description: 'Get order by ID',
36
+ parameters: z.object({
37
+ id: z.string().describe('Order ID'),
38
+ }),
39
+ handler: async (params: unknown) => {
40
+ const p = params as { id: string }
41
+ try {
42
+ const client = getClient()
43
+ const res = await client.api.orders[':id'].$get({ param: { id: p.id } })
44
+ const data = await res.json()
45
+ return ok(data)
46
+ } catch (err) {
47
+ return fail(err instanceof Error ? err.message : 'Failed to get order')
48
+ }
49
+ },
50
+ })
51
+
52
+ site.command('create', {
53
+ description: 'Create a new order',
54
+ parameters: z.object({
55
+ 'customer-name': z.string().min(1).describe('Customer name'),
56
+ 'customer-email': z.string().email().describe('Customer email'),
57
+ 'product-name': z.string().min(1).describe('Product name'),
58
+ amount: z.coerce.number().positive().describe('Order amount'),
59
+ }),
60
+ handler: async (params: unknown) => {
61
+ const p = params as { 'customer-name': string; 'customer-email': string; 'product-name': string; amount: number }
62
+ try {
63
+ const client = getClient()
64
+ const res = await client.api.orders.$post({
65
+ json: {
66
+ customerName: p['customer-name'],
67
+ customerEmail: p['customer-email'],
68
+ productName: p['product-name'],
69
+ amount: p.amount,
70
+ },
71
+ })
72
+ const data = await res.json()
73
+ return ok(data, ['Order created'])
74
+ } catch (err) {
75
+ return fail(err instanceof Error ? err.message : 'Failed to create order')
76
+ }
77
+ },
78
+ })
79
+
80
+ site.command('update', {
81
+ description: 'Update order status',
82
+ parameters: z.object({
83
+ id: z.string().describe('Order ID'),
84
+ status: z.enum(['pending', 'processing', 'completed', 'cancelled', 'disputed']).optional().describe('New status'),
85
+ }),
86
+ handler: async (params: unknown) => {
87
+ const p = params as { id: string; status?: string }
88
+ const { id, ...rest } = p
89
+ const body: Record<string, string | null> = {}
90
+ if (rest.status) body.status = rest.status
91
+ try {
92
+ const client = getClient()
93
+ const res = await client.api.orders[':id'].$put({ param: { id }, json: body })
94
+ const data = await res.json()
95
+ return ok(data, ['Order updated'])
96
+ } catch (err) {
97
+ return fail(err instanceof Error ? err.message : 'Failed to update order')
98
+ }
99
+ },
100
+ })
101
+
102
+ site.command('delete', {
103
+ description: 'Delete an order',
104
+ parameters: z.object({
105
+ id: z.string().describe('Order ID'),
106
+ }),
107
+ handler: async (params: unknown) => {
108
+ const p = params as { id: string }
109
+ try {
110
+ const client = getClient()
111
+ const res = await client.api.orders[':id'].$delete({ param: { id: p.id } })
112
+ const data = await res.json()
113
+ return ok(data, ['Order deleted'])
114
+ } catch (err) {
115
+ return fail(err instanceof Error ? err.message : 'Failed to delete order')
116
+ }
117
+ },
118
+ })
119
+
120
+ site.command('process', {
121
+ description: 'Process an order',
122
+ parameters: z.object({
123
+ id: z.string().describe('Order ID'),
124
+ }),
125
+ handler: async (params: unknown) => {
126
+ const p = params as { id: string }
127
+ try {
128
+ const client = getClient()
129
+ const res = await client.api.orders[':id'].process.$put({ param: { id: p.id } })
130
+ const data = await res.json()
131
+ return ok(data, ['Order processed'])
132
+ } catch (err) {
133
+ return fail(err instanceof Error ? err.message : 'Failed to process order')
134
+ }
135
+ },
136
+ })
137
+
138
+ site.command('cancel', {
139
+ description: 'Cancel an order',
140
+ parameters: z.object({
141
+ id: z.string().describe('Order ID'),
142
+ }),
143
+ handler: async (params: unknown) => {
144
+ const p = params as { id: string }
145
+ try {
146
+ const client = getClient()
147
+ const res = await client.api.orders[':id'].cancel.$put({ param: { id: p.id } })
148
+ const data = await res.json()
149
+ return ok(data, ['Order cancelled'])
150
+ } catch (err) {
151
+ return fail(err instanceof Error ? err.message : 'Failed to cancel order')
152
+ }
153
+ },
154
+ })
155
+
156
+ site.command('cart', {
157
+ description: 'Get current cart',
158
+ parameters: z.object({}),
159
+ handler: async () => {
160
+ try {
161
+ const client = getClient()
162
+ const res = await client.api.cart.$get()
163
+ const data = await res.json()
164
+ return ok(data)
165
+ } catch (err) {
166
+ return fail(err instanceof Error ? err.message : 'Failed to get cart')
167
+ }
168
+ },
169
+ })
170
+ }
@@ -0,0 +1,195 @@
1
+ import type { SiteInstance } from '@dyyz1993/xcli-core'
2
+ import { ok, fail } from '@dyyz1993/xcli-core'
3
+ import { z } from 'zod'
4
+ import { getClient } from '@cli/utils/api'
5
+
6
+ export function registerPermissionCommands(site: SiteInstance) {
7
+ site.command('roles', {
8
+ description: 'List all roles',
9
+ parameters: z.object({}),
10
+ handler: async () => {
11
+ try {
12
+ const client = getClient()
13
+ const res = await client.api.permissions.roles.$get()
14
+ const data = await res.json()
15
+ return ok(data)
16
+ } catch (err) {
17
+ return fail(err instanceof Error ? err.message : 'Failed to list roles')
18
+ }
19
+ },
20
+ })
21
+
22
+ site.command('get-role', {
23
+ description: 'Get role details by ID',
24
+ parameters: z.object({
25
+ id: z.string().describe('Role ID'),
26
+ }),
27
+ handler: async (params: unknown) => {
28
+ const p = params as { id: string }
29
+ try {
30
+ const client = getClient()
31
+ const res = await client.api.roles[':id'].$get({ param: { id: p.id } })
32
+ const data = await res.json()
33
+ return ok(data)
34
+ } catch (err) {
35
+ return fail(err instanceof Error ? err.message : 'Failed to get role')
36
+ }
37
+ },
38
+ })
39
+
40
+ site.command('create-role', {
41
+ description: 'Create a new role',
42
+ parameters: z.object({
43
+ name: z.string().min(1).describe('Role name'),
44
+ code: z.string().min(1).describe('Role code'),
45
+ label: z.string().min(1).describe('Role display label'),
46
+ description: z.string().optional().describe('Role description'),
47
+ }),
48
+ handler: async (params: unknown) => {
49
+ const p = params as { name: string; code: string; label: string; description?: string }
50
+ try {
51
+ const client = getClient()
52
+ const res = await client.api.roles.$post({ json: p })
53
+ const data = await res.json()
54
+ return ok(data, ['Role created'])
55
+ } catch (err) {
56
+ return fail(err instanceof Error ? err.message : 'Failed to create role')
57
+ }
58
+ },
59
+ })
60
+
61
+ site.command('update-role', {
62
+ description: 'Update a role',
63
+ parameters: z.object({
64
+ id: z.string().describe('Role ID'),
65
+ name: z.string().optional().describe('New name'),
66
+ description: z.string().optional().describe('New description'),
67
+ }),
68
+ handler: async (params: unknown) => {
69
+ const p = params as { id: string; name?: string; description?: string }
70
+ const { id, ...body } = p
71
+ try {
72
+ const client = getClient()
73
+ const res = await client.api.roles[':id'].$put({ param: { id }, json: body })
74
+ const data = await res.json()
75
+ return ok(data, ['Role updated'])
76
+ } catch (err) {
77
+ return fail(err instanceof Error ? err.message : 'Failed to update role')
78
+ }
79
+ },
80
+ })
81
+
82
+ site.command('delete-role', {
83
+ description: 'Delete a role',
84
+ parameters: z.object({
85
+ id: z.string().describe('Role ID'),
86
+ }),
87
+ handler: async (params: unknown) => {
88
+ const p = params as { id: string }
89
+ try {
90
+ const client = getClient()
91
+ const res = await client.api.roles[':id'].$delete({ param: { id: p.id } })
92
+ const data = await res.json()
93
+ return ok(data, ['Role deleted'])
94
+ } catch (err) {
95
+ return fail(err instanceof Error ? err.message : 'Failed to delete role')
96
+ }
97
+ },
98
+ })
99
+
100
+ site.command('permissions', {
101
+ description: 'List all permissions',
102
+ parameters: z.object({}),
103
+ handler: async () => {
104
+ try {
105
+ const client = getClient()
106
+ const res = await client.api.permissions.$get()
107
+ const data = await res.json()
108
+ return ok(data)
109
+ } catch (err) {
110
+ return fail(err instanceof Error ? err.message : 'Failed to list permissions')
111
+ }
112
+ },
113
+ })
114
+
115
+ site.command('categories', {
116
+ description: 'List permission categories',
117
+ parameters: z.object({}),
118
+ handler: async () => {
119
+ try {
120
+ const client = getClient()
121
+ const res = await client.api.permissions.categories.$get()
122
+ const data = await res.json()
123
+ return ok(data)
124
+ } catch (err) {
125
+ return fail(err instanceof Error ? err.message : 'Failed to list categories')
126
+ }
127
+ },
128
+ })
129
+
130
+ site.command('menu-config', {
131
+ description: 'Get menu configuration',
132
+ parameters: z.object({}),
133
+ handler: async () => {
134
+ try {
135
+ const client = getClient()
136
+ const res = await client.api.permissions['menu-config'].$get()
137
+ const data = await res.json()
138
+ return ok(data)
139
+ } catch (err) {
140
+ return fail(err instanceof Error ? err.message : 'Failed to get menu config')
141
+ }
142
+ },
143
+ })
144
+
145
+ site.command('audit-logs', {
146
+ description: 'List audit logs',
147
+ parameters: z.object({
148
+ limit: z.coerce.number().default(50).describe('Limit results'),
149
+ offset: z.coerce.number().default(0).describe('Offset'),
150
+ 'user-id': z.string().optional().describe('Filter by user ID'),
151
+ action: z.string().optional().describe('Filter by action'),
152
+ 'resource-type': z.string().optional().describe('Filter by resource type'),
153
+ }),
154
+ handler: async (params: unknown) => {
155
+ const p = params as { limit: number; offset: number; 'user-id'?: string; action?: string; 'resource-type'?: string }
156
+ try {
157
+ const client = getClient()
158
+ const query: Record<string, string> = {
159
+ limit: String(p.limit),
160
+ offset: String(p.offset),
161
+ }
162
+ if (p['user-id']) query.userId = p['user-id']
163
+ if (p.action) query.action = p.action
164
+ if (p['resource-type']) query.resourceType = p['resource-type']
165
+ const res = await client.api['audit-logs'].$get({ query })
166
+ const data = await res.json()
167
+ return ok(data)
168
+ } catch (err) {
169
+ return fail(err instanceof Error ? err.message : 'Failed to list audit logs')
170
+ }
171
+ },
172
+ })
173
+
174
+ site.command('update-role-permissions', {
175
+ description: 'Update permissions for a role',
176
+ parameters: z.object({
177
+ id: z.string().describe('Role ID'),
178
+ 'permission-ids': z.string().describe('Comma-separated permission IDs'),
179
+ }),
180
+ handler: async (params: unknown) => {
181
+ const p = params as { id: string; 'permission-ids': string }
182
+ try {
183
+ const client = getClient()
184
+ const res = await client.api.roles[':id'].permissions.$put({
185
+ param: { id: p.id },
186
+ json: { permissionIds: p['permission-ids'].split(',') },
187
+ })
188
+ const data = await res.json()
189
+ return ok(data, ['Role permissions updated'])
190
+ } catch (err) {
191
+ return fail(err instanceof Error ? err.message : 'Failed to update role permissions')
192
+ }
193
+ },
194
+ })
195
+ }
@@ -0,0 +1,142 @@
1
+ import type { SiteInstance } from '@dyyz1993/xcli-core'
2
+ import { ok, fail } from '@dyyz1993/xcli-core'
3
+ import { z } from 'zod'
4
+ import { getClient } from '@cli/utils/api'
5
+
6
+ export function registerTenantCommands(site: SiteInstance) {
7
+ site.command('list', {
8
+ description: 'List all tenants',
9
+ parameters: z.object({
10
+ page: z.coerce.number().default(1).describe('Page number'),
11
+ 'page-size': z.coerce.number().default(20).describe('Page size'),
12
+ status: z.string().optional().describe('Filter by status'),
13
+ plan: z.string().optional().describe('Filter by plan'),
14
+ }),
15
+ handler: async (params: unknown) => {
16
+ const p = params as { page: number; 'page-size': number; status?: string; plan?: string }
17
+ try {
18
+ const client = getClient()
19
+ const query: Record<string, string> = {
20
+ page: String(p.page),
21
+ pageSize: String(p['page-size']),
22
+ }
23
+ if (p.status) query.status = p.status
24
+ if (p.plan) query.plan = p.plan
25
+ const res = await client.api.tenants.$get({ query })
26
+ const data = await res.json()
27
+ return ok(data)
28
+ } catch (err) {
29
+ return fail(err instanceof Error ? err.message : 'Failed to list tenants')
30
+ }
31
+ },
32
+ })
33
+
34
+ site.command('get', {
35
+ description: 'Get tenant by ID',
36
+ parameters: z.object({
37
+ id: z.string().describe('Tenant ID'),
38
+ }),
39
+ handler: async (params: unknown) => {
40
+ const p = params as { id: string }
41
+ try {
42
+ const client = getClient()
43
+ const res = await client.api.tenants[':id'].$get({ param: { id: p.id } })
44
+ const data = await res.json()
45
+ return ok(data)
46
+ } catch (err) {
47
+ return fail(err instanceof Error ? err.message : 'Failed to get tenant')
48
+ }
49
+ },
50
+ })
51
+
52
+ site.command('get-by-slug', {
53
+ description: 'Get tenant by slug',
54
+ parameters: z.object({
55
+ slug: z.string().describe('Tenant slug'),
56
+ }),
57
+ handler: async (params: unknown) => {
58
+ const p = params as { slug: string }
59
+ try {
60
+ const client = getClient()
61
+ const res = await client.api.tenants.slug[':slug'].$get({ param: { slug: p.slug } })
62
+ const data = await res.json()
63
+ return ok(data)
64
+ } catch (err) {
65
+ return fail(err instanceof Error ? err.message : 'Failed to get tenant by slug')
66
+ }
67
+ },
68
+ })
69
+
70
+ site.command('create', {
71
+ description: 'Create a new tenant',
72
+ parameters: z.object({
73
+ name: z.string().min(1).describe('Tenant name'),
74
+ slug: z.string().min(1).describe('Tenant slug (lowercase, hyphens)'),
75
+ plan: z.enum(['free', 'starter', 'pro', 'enterprise']).default('free').describe('Plan'),
76
+ 'max-users': z.coerce.number().int().min(1).max(1000).default(5).describe('Max users'),
77
+ }),
78
+ handler: async (params: unknown) => {
79
+ const p = params as { name: string; slug: string; plan: string; 'max-users': number }
80
+ try {
81
+ const client = getClient()
82
+ const res = await client.api.tenants.$post({
83
+ json: {
84
+ name: p.name,
85
+ slug: p.slug,
86
+ plan: p.plan as 'free' | 'starter' | 'pro' | 'enterprise',
87
+ maxUsers: p['max-users'],
88
+ settings: null,
89
+ },
90
+ })
91
+ const data = await res.json()
92
+ return ok(data, ['Tenant created'])
93
+ } catch (err) {
94
+ return fail(err instanceof Error ? err.message : 'Failed to create tenant')
95
+ }
96
+ },
97
+ })
98
+
99
+ site.command('update', {
100
+ description: 'Update a tenant',
101
+ parameters: z.object({
102
+ id: z.string().describe('Tenant ID'),
103
+ name: z.string().optional().describe('New name'),
104
+ plan: z.enum(['free', 'starter', 'pro', 'enterprise']).optional().describe('New plan'),
105
+ status: z.enum(['active', 'suspended', 'trial']).optional().describe('New status'),
106
+ }),
107
+ handler: async (params: unknown) => {
108
+ const p = params as { id: string; name?: string; plan?: string; status?: string }
109
+ const { id, ...rest } = p
110
+ const body: Record<string, string | null> = {}
111
+ if (rest.name) body.name = rest.name
112
+ if (rest.plan) body.plan = rest.plan
113
+ if (rest.status) body.status = rest.status
114
+ try {
115
+ const client = getClient()
116
+ const res = await client.api.tenants[':id'].$put({ param: { id }, json: body })
117
+ const data = await res.json()
118
+ return ok(data, ['Tenant updated'])
119
+ } catch (err) {
120
+ return fail(err instanceof Error ? err.message : 'Failed to update tenant')
121
+ }
122
+ },
123
+ })
124
+
125
+ site.command('delete', {
126
+ description: 'Delete a tenant',
127
+ parameters: z.object({
128
+ id: z.string().describe('Tenant ID'),
129
+ }),
130
+ handler: async (params: unknown) => {
131
+ const p = params as { id: string }
132
+ try {
133
+ const client = getClient()
134
+ const res = await client.api.tenants[':id'].$delete({ param: { id: p.id } })
135
+ const data = await res.json()
136
+ return ok(data, ['Tenant deleted'])
137
+ } catch (err) {
138
+ return fail(err instanceof Error ? err.message : 'Failed to delete tenant')
139
+ }
140
+ },
141
+ })
142
+ }
@@ -0,0 +1,167 @@
1
+ import type { SiteInstance } from '@dyyz1993/xcli-core'
2
+ import { ok, fail } from '@dyyz1993/xcli-core'
3
+ import { z } from 'zod'
4
+ import { getClient } from '@cli/utils/api'
5
+
6
+ export function registerTicketCommands(site: SiteInstance) {
7
+ site.command('list', {
8
+ description: 'List all tickets',
9
+ parameters: z.object({
10
+ limit: z.coerce.number().default(20).describe('Limit results'),
11
+ offset: z.coerce.number().default(0).describe('Offset'),
12
+ }),
13
+ handler: async (params: unknown) => {
14
+ const p = params as { limit: number; offset: number }
15
+ try {
16
+ const client = getClient()
17
+ const res = await client.api.tickets.$get({
18
+ query: { limit: String(p.limit), offset: String(p.offset) },
19
+ })
20
+ const data = await res.json()
21
+ return ok(data)
22
+ } catch (err) {
23
+ return fail(err instanceof Error ? err.message : 'Failed to list tickets')
24
+ }
25
+ },
26
+ })
27
+
28
+ site.command('get', {
29
+ description: 'Get ticket by ID',
30
+ parameters: z.object({
31
+ id: z.string().describe('Ticket ID'),
32
+ }),
33
+ handler: async (params: unknown) => {
34
+ const p = params as { id: string }
35
+ try {
36
+ const client = getClient()
37
+ const res = await client.api.tickets[':id'].$get({ param: { id: p.id } })
38
+ const data = await res.json()
39
+ return ok(data)
40
+ } catch (err) {
41
+ return fail(err instanceof Error ? err.message : 'Failed to get ticket')
42
+ }
43
+ },
44
+ })
45
+
46
+ site.command('create', {
47
+ description: 'Create a new ticket',
48
+ parameters: z.object({
49
+ 'customer-name': z.string().min(1).describe('Customer name'),
50
+ 'customer-email': z.string().email().describe('Customer email'),
51
+ subject: z.string().min(1).describe('Ticket subject'),
52
+ description: z.string().min(1).describe('Ticket description'),
53
+ category: z.enum(['technical', 'billing', 'feature_request', 'bug_report', 'general']).default('general').describe('Category'),
54
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium').describe('Priority'),
55
+ }),
56
+ handler: async (params: unknown) => {
57
+ const p = params as {
58
+ 'customer-name': string
59
+ 'customer-email': string
60
+ subject: string
61
+ description: string
62
+ category: string
63
+ priority: string
64
+ }
65
+ try {
66
+ const client = getClient()
67
+ const res = await client.api.tickets.$post({
68
+ json: {
69
+ customerName: p['customer-name'],
70
+ customerEmail: p['customer-email'],
71
+ subject: p.subject,
72
+ description: p.description,
73
+ category: p.category as 'technical' | 'billing' | 'feature_request' | 'bug_report' | 'general',
74
+ priority: p.priority as 'low' | 'medium' | 'high' | 'urgent',
75
+ },
76
+ })
77
+ const data = await res.json()
78
+ return ok(data, ['Ticket created'])
79
+ } catch (err) {
80
+ return fail(err instanceof Error ? err.message : 'Failed to create ticket')
81
+ }
82
+ },
83
+ })
84
+
85
+ site.command('update', {
86
+ description: 'Update ticket status',
87
+ parameters: z.object({
88
+ id: z.string().describe('Ticket ID'),
89
+ status: z.enum(['open', 'closed', 'in_progress', 'waiting_customer', 'resolved']).optional().describe('New status'),
90
+ 'assigned-to': z.string().optional().describe('Assign to user'),
91
+ }),
92
+ handler: async (params: unknown) => {
93
+ const p = params as { id: string; status?: string; 'assigned-to'?: string }
94
+ const { id, ...rest } = p
95
+ const body: Record<string, string> = {}
96
+ if (rest.status) body.status = rest.status
97
+ if (rest['assigned-to']) body.assignedTo = rest['assigned-to']
98
+ try {
99
+ const client = getClient()
100
+ const res = await client.api.tickets[':id'].$put({ param: { id }, json: body })
101
+ const data = await res.json()
102
+ return ok(data, ['Ticket updated'])
103
+ } catch (err) {
104
+ return fail(err instanceof Error ? err.message : 'Failed to update ticket')
105
+ }
106
+ },
107
+ })
108
+
109
+ site.command('delete', {
110
+ description: 'Delete a ticket',
111
+ parameters: z.object({
112
+ id: z.string().describe('Ticket ID'),
113
+ }),
114
+ handler: async (params: unknown) => {
115
+ const p = params as { id: string }
116
+ try {
117
+ const client = getClient()
118
+ const res = await client.api.tickets[':id'].$delete({ param: { id: p.id } })
119
+ const data = await res.json()
120
+ return ok(data, ['Ticket deleted'])
121
+ } catch (err) {
122
+ return fail(err instanceof Error ? err.message : 'Failed to delete ticket')
123
+ }
124
+ },
125
+ })
126
+
127
+ site.command('reply', {
128
+ description: 'Reply to a ticket',
129
+ parameters: z.object({
130
+ id: z.string().describe('Ticket ID'),
131
+ content: z.string().min(1).describe('Reply content'),
132
+ author: z.string().min(1).describe('Author name'),
133
+ }),
134
+ handler: async (params: unknown) => {
135
+ const p = params as { id: string; content: string; author: string }
136
+ try {
137
+ const client = getClient()
138
+ const res = await client.api.tickets[':id'].reply.$post({
139
+ param: { id: p.id },
140
+ json: { content: p.content, author: p.author },
141
+ })
142
+ const data = await res.json()
143
+ return ok(data, ['Reply sent'])
144
+ } catch (err) {
145
+ return fail(err instanceof Error ? err.message : 'Failed to reply ticket')
146
+ }
147
+ },
148
+ })
149
+
150
+ site.command('close', {
151
+ description: 'Close a ticket',
152
+ parameters: z.object({
153
+ id: z.string().describe('Ticket ID'),
154
+ }),
155
+ handler: async (params: unknown) => {
156
+ const p = params as { id: string }
157
+ try {
158
+ const client = getClient()
159
+ const res = await client.api.tickets[':id'].close.$put({ param: { id: p.id } })
160
+ const data = await res.json()
161
+ return ok(data, ['Ticket closed'])
162
+ } catch (err) {
163
+ return fail(err instanceof Error ? err.message : 'Failed to close ticket')
164
+ }
165
+ },
166
+ })
167
+ }
@@ -1,5 +1,5 @@
1
1
  import { hc } from 'hono/client'
2
- import type { ClientApiType, AppType } from '@server/index'
2
+ import type { AppType } from '@server/index'
3
3
  import { WSClientImpl } from '@shared/core/ws-client'
4
4
  import { SSEClientImpl } from '@shared/core/sse-client'
5
5
 
@@ -16,7 +16,7 @@ export interface CliFetchExtendOptions {
16
16
  }
17
17
 
18
18
  export function createRPCClient(baseUrl: string) {
19
- return hc<ClientApiType>(baseUrl, {
19
+ return hc<AppType>(baseUrl, {
20
20
  webSocket: url => new WSClientImpl(url) as unknown as WebSocket,
21
21
  sse: url => new SSEClientImpl(url),
22
22
  })