@mohou/host 1.0.20

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 (108) hide show
  1. package/README.md +10 -0
  2. package/package.json +65 -0
  3. package/src/apps/monogram.ts +15 -0
  4. package/src/apps/registry.ts +238 -0
  5. package/src/apps/startup-seed.ts +95 -0
  6. package/src/bash/client.ts +21 -0
  7. package/src/bash/codes.ts +21 -0
  8. package/src/compile/allowlist.ts +89 -0
  9. package/src/compile/asset-path.ts +48 -0
  10. package/src/compile/autogen.ts +71 -0
  11. package/src/compile/build-vendor-cli.ts +8 -0
  12. package/src/compile/build-vendor.ts +160 -0
  13. package/src/compile/bundle-ui.ts +137 -0
  14. package/src/compile/codes.ts +22 -0
  15. package/src/compile/failure.ts +38 -0
  16. package/src/compile/imports.ts +43 -0
  17. package/src/compile/leave.ts +47 -0
  18. package/src/compile/load-backend.ts +95 -0
  19. package/src/compile/review.ts +342 -0
  20. package/src/compile/runner.ts +205 -0
  21. package/src/compile/sheet.ts +174 -0
  22. package/src/compile/wrapper.ts +297 -0
  23. package/src/credentials/codes.ts +26 -0
  24. package/src/credentials/create.ts +69 -0
  25. package/src/credentials/file.ts +113 -0
  26. package/src/credentials/provider.ts +66 -0
  27. package/src/events/app-events.ts +214 -0
  28. package/src/events/error-ring.ts +109 -0
  29. package/src/events/host-events.ts +39 -0
  30. package/src/events/view-bridge.ts +402 -0
  31. package/src/events/view-eval.ts +213 -0
  32. package/src/files/codes.ts +28 -0
  33. package/src/files/skip.ts +23 -0
  34. package/src/files/tools.ts +215 -0
  35. package/src/history/codes.ts +22 -0
  36. package/src/history/store.ts +287 -0
  37. package/src/host/activity.ts +110 -0
  38. package/src/host/author-mcp.ts +248 -0
  39. package/src/host/author-skill.ts +196 -0
  40. package/src/host/codes.ts +21 -0
  41. package/src/host/config.ts +239 -0
  42. package/src/host/heat.ts +35 -0
  43. package/src/host/layout.ts +81 -0
  44. package/src/host/log.ts +142 -0
  45. package/src/host/mcp-editor.ts +205 -0
  46. package/src/host/mcp-import.ts +141 -0
  47. package/src/host/mcp.ts +109 -0
  48. package/src/host/port.ts +83 -0
  49. package/src/host/session.ts +358 -0
  50. package/src/host/workbench.ts +38 -0
  51. package/src/http/app.ts +46 -0
  52. package/src/http/asset.ts +49 -0
  53. package/src/http/author.ts +157 -0
  54. package/src/http/bind.ts +217 -0
  55. package/src/http/client.ts +175 -0
  56. package/src/http/codes.ts +29 -0
  57. package/src/http/env.ts +3 -0
  58. package/src/http/guard.ts +20 -0
  59. package/src/http/iframe.ts +212 -0
  60. package/src/http/layout.ts +63 -0
  61. package/src/http/owner.ts +380 -0
  62. package/src/http/ports.ts +114 -0
  63. package/src/http/reply.ts +53 -0
  64. package/src/http/route-codes.ts +19 -0
  65. package/src/http/updates.ts +258 -0
  66. package/src/index.ts +201 -0
  67. package/src/install/codes.ts +21 -0
  68. package/src/install/install.ts +230 -0
  69. package/src/install/layout.ts +16 -0
  70. package/src/kernel/bind-brain.ts +117 -0
  71. package/src/kernel/call.ts +134 -0
  72. package/src/kernel/codes.ts +21 -0
  73. package/src/kernel/index.ts +13 -0
  74. package/src/kernel/model-policy.ts +83 -0
  75. package/src/kernel/model-stream.ts +86 -0
  76. package/src/kernel/present.ts +4 -0
  77. package/src/kernel/working-directory.ts +35 -0
  78. package/src/metrics/codes.ts +22 -0
  79. package/src/metrics/read.ts +48 -0
  80. package/src/owner/read.ts +85 -0
  81. package/src/pwsh/client.ts +47 -0
  82. package/src/pwsh/codes.ts +21 -0
  83. package/src/shell/command.ts +172 -0
  84. package/src/storage/codes.ts +33 -0
  85. package/src/storage/layout.ts +44 -0
  86. package/src/storage/open.ts +461 -0
  87. package/src/storage/schema.ts +176 -0
  88. package/src/storage/sql.ts +40 -0
  89. package/src/theme/codes.ts +20 -0
  90. package/src/theme/layout.ts +42 -0
  91. package/src/theme/paint.ts +108 -0
  92. package/src/theme/parse.ts +58 -0
  93. package/src/theme/pin.ts +197 -0
  94. package/src/theme/tokens.ts +30 -0
  95. package/src/tools/author.ts +970 -0
  96. package/src/tools/codes.ts +21 -0
  97. package/src/tools/diagnostics.ts +53 -0
  98. package/src/tools/http.ts +48 -0
  99. package/src/tools/mcp-list.ts +90 -0
  100. package/src/tools/schemas.ts +185 -0
  101. package/themes/theme-default.css +4 -0
  102. package/themes/theme-forest.css +4 -0
  103. package/themes/theme-hokage.css +4 -0
  104. package/themes/theme-matcha.css +4 -0
  105. package/themes/theme-slate.css +4 -0
  106. package/themes/theme-tokyo.css +4 -0
  107. package/themes/theme-yellow.css +4 -0
  108. package/themes/theme-zoro.css +4 -0
@@ -0,0 +1,21 @@
1
+ /** Codes the authoring implementation emits. Tool operations keep their own codes. */
2
+
3
+ export const authorCodes = ['unknown-tool', 'tool-args', 'call-batch', 'authoring-token', 'authoring-loopback', 'cancelled'] as const
4
+
5
+ /** A failure a projection matches. The message is for a person. */
6
+ export type AuthorCode = (typeof authorCodes)[number]
7
+
8
+ /** Failure before a tool runs, or a projection refusal. */
9
+ export class AuthorError extends Error {
10
+ readonly code: AuthorCode
11
+
12
+ /**
13
+ * @param code - one of {@link authorCodes}
14
+ * @param message - human text; not the match key
15
+ */
16
+ constructor(code: AuthorCode, message: string) {
17
+ super(message)
18
+ this.name = 'AuthorError'
19
+ this.code = code
20
+ }
21
+ }
@@ -0,0 +1,53 @@
1
+ /** Body cap for an iframe diagnostic post. Host policy, not a locked number. */
2
+ export const DEFAULT_DIAGNOSTIC_MAX_BYTES = 64_000
3
+
4
+ /** Diagnostic path segments. Spell them here, not at each caller. */
5
+ export const diagnosticLayout = {
6
+ root: '/api/app',
7
+ errors: 'errors',
8
+ alive: 'alive',
9
+ absent: 'absent',
10
+ viewEval: 'view/eval',
11
+ } as const
12
+
13
+ export function diagnosticUrl(appId: string, kind: 'errors' | 'alive' | 'absent' | 'viewEval'): string {
14
+ return `${diagnosticLayout.root}/${encodeURIComponent(appId)}/${diagnosticLayout[kind]}`
15
+ }
16
+
17
+ export interface DiagnosticPorts {
18
+ recordError(appId: string, raw: unknown): void
19
+ markAlive(appId: string): void
20
+ /** The panel has no frame for this app. */
21
+ markAbsent(appId: string): void
22
+ answerView(requestId: string, appId: string, raw: unknown): boolean
23
+ maxBodyBytes?: number
24
+ }
25
+
26
+ /** Admit one diagnostic body. A bad body is dropped. The route still answers 204. */
27
+ export function acceptDiagnostic(ports: DiagnosticPorts, appId: string, kind: string, body: string): void {
28
+ let parsed: unknown
29
+ try {
30
+ parsed = JSON.parse(body) as unknown
31
+ } catch {
32
+ return
33
+ }
34
+ if (kind === diagnosticLayout.alive) {
35
+ ports.markAlive(appId)
36
+ return
37
+ }
38
+ if (kind === diagnosticLayout.absent) {
39
+ ports.markAbsent(appId)
40
+ return
41
+ }
42
+ if (kind === diagnosticLayout.errors) {
43
+ ports.recordError(appId, parsed)
44
+ return
45
+ }
46
+ if (kind === diagnosticLayout.viewEval && isRecord(parsed) && typeof parsed.requestId === 'string') {
47
+ ports.answerView(parsed.requestId, appId, parsed)
48
+ }
49
+ }
50
+
51
+ function isRecord(value: unknown): value is Record<string, unknown> {
52
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
53
+ }
@@ -0,0 +1,48 @@
1
+ import { createAdaptorServer } from '@hono/node-server'
2
+
3
+ import { closeMcpSessions } from '../http/author.ts'
4
+ import { createLoopbackApp } from '../http/app.ts'
5
+ import { authoringTokenMatches, isLoopbackAddress } from '../http/guard.ts'
6
+ import type { LoopbackPorts } from '../http/ports.ts'
7
+ import { type createAuthorTools } from './author.ts'
8
+ import type { DiagnosticPorts } from './diagnostics.ts'
9
+
10
+ type AuthorTools = ReturnType<typeof createAuthorTools>
11
+
12
+ export { authoringTokenMatches, isLoopbackAddress }
13
+
14
+ /**
15
+ * Loopback listener. Author, owner, and iframe routes are one Hono app.
16
+ * @param options - the tool implementation, the token, and owner ports when the session has them
17
+ */
18
+ export function startAuthorHttp(options: {
19
+ readonly author: AuthorTools
20
+ readonly token: string
21
+ readonly port?: number
22
+ readonly diagnostics?: DiagnosticPorts
23
+ readonly loopback?: LoopbackPorts
24
+ }): Promise<{ port: number; close: () => Promise<void> }> {
25
+ const app = createLoopbackApp(options)
26
+ const server = createAdaptorServer({ fetch: app.fetch })
27
+ return new Promise((resolve, reject) => {
28
+ server.once('error', reject)
29
+ server.listen(options.port ?? 0, '127.0.0.1', () => {
30
+ const address = server.address()
31
+ const port = typeof address === 'object' && address !== null ? address.port : 0
32
+ resolve({
33
+ port,
34
+ close: async () => {
35
+ await closeMcpSessions()
36
+ await new Promise<void>((done, fail) => {
37
+ server.close((error) => {
38
+ if (error) fail(error)
39
+ else done()
40
+ })
41
+ // Open panel sockets, including the event stream, would keep close pending.
42
+ if ('closeAllConnections' in server) server.closeAllConnections()
43
+ })
44
+ },
45
+ })
46
+ })
47
+ })
48
+ }
@@ -0,0 +1,90 @@
1
+ import { McpClient, McpError, type McpCode } from '@mohou/mcp-client'
2
+
3
+ import type { McpLoadFailure } from '../host/mcp.ts'
4
+ import { AuthorError } from './codes.ts'
5
+
6
+ export interface AuthorMcpTool {
7
+ name: string
8
+ description?: string
9
+ inputSchema: Record<string, unknown>
10
+ }
11
+
12
+ export interface AuthorMcpServerNames {
13
+ id: string
14
+ tools?: readonly string[]
15
+ error?: { code: McpCode; message?: string }
16
+ }
17
+
18
+ export interface AuthorMcpServerTools {
19
+ id: string
20
+ tools?: AuthorMcpTool[]
21
+ error?: { code: McpCode; message?: string }
22
+ }
23
+
24
+ /**
25
+ * Authoring index of external MCP servers. Names only. No descriptions, no schemas, no env.
26
+ * One server failure does not drop the others, and a server boot left out is listed with the
27
+ * reference that kept it out, so the author learns why it is missing.
28
+ * @param client - host-held MCP client
29
+ * @param failures - servers boot left out
30
+ */
31
+ export async function listMcpForAuthor(
32
+ client: McpClient,
33
+ failures: readonly McpLoadFailure[] = [],
34
+ ): Promise<{ servers: AuthorMcpServerNames[] }> {
35
+ const servers: AuthorMcpServerNames[] = failures.map(failure => ({
36
+ id: failure.id,
37
+ error: { code: failure.code as McpCode, message: failure.message },
38
+ }))
39
+ for (const id of client.serverIds()) {
40
+ try {
41
+ const tools = await client.listTools(id)
42
+ servers.push({ id, tools: tools.map(tool => tool.name) })
43
+ } catch (error) {
44
+ servers.push({ id, error: { code: listFailure(error) } })
45
+ }
46
+ }
47
+ return { servers }
48
+ }
49
+
50
+ /**
51
+ * Tool detail for one connected MCP server. Optional `toolName` returns that one tool only.
52
+ * @param client - host-held MCP client
53
+ * @param serverId - key in the resolved config
54
+ * @param toolName - when set, only that tool; unknown name is `tool-args`
55
+ * @param failures - servers boot left out; asking about one answers with its reference failure
56
+ */
57
+ export async function toolsMcpForAuthor(
58
+ client: McpClient,
59
+ serverId: string,
60
+ toolName?: string,
61
+ failures: readonly McpLoadFailure[] = [],
62
+ ): Promise<AuthorMcpServerTools> {
63
+ const leftOut = failures.find(failure => failure.id === serverId)
64
+ if (leftOut !== undefined) {
65
+ return { id: serverId, error: { code: leftOut.code as McpCode, message: leftOut.message } }
66
+ }
67
+ try {
68
+ const listed = await client.listTools(serverId)
69
+ const tools = listed.map(tool => ({
70
+ name: tool.name,
71
+ ...tool.description === undefined ? {} : { description: tool.description },
72
+ inputSchema: tool.inputSchema,
73
+ }))
74
+ if (toolName === undefined) return { id: serverId, tools }
75
+ const match = tools.find(tool => tool.name === toolName)
76
+ if (match === undefined) {
77
+ throw new AuthorError('tool-args', `unknown tool: ${toolName} on ${serverId}`)
78
+ }
79
+ return { id: serverId, tools: [match] }
80
+ } catch (error) {
81
+ if (error instanceof AuthorError) throw error
82
+ return { id: serverId, error: { code: listFailure(error) } }
83
+ }
84
+ }
85
+
86
+ function listFailure(error: unknown): 'mcp-not-connected' | 'mcp-start-failed' {
87
+ return error instanceof McpError && (error.code === 'mcp-not-connected' || error.code === 'mcp-start-failed')
88
+ ? error.code
89
+ : 'mcp-start-failed'
90
+ }
@@ -0,0 +1,185 @@
1
+ import { authorMcpToolNames, authorToolNames, reloadMessage, type AuthorToolName } from './author.ts'
2
+
3
+ interface ToolInput {
4
+ readonly description: string
5
+ readonly inputSchema: {
6
+ readonly type: 'object'
7
+ readonly properties: Record<string, unknown>
8
+ readonly required?: readonly string[]
9
+ }
10
+ }
11
+
12
+ const appId = { type: 'string' }
13
+ const path = { type: 'string' }
14
+ const commit = { type: 'boolean' }
15
+
16
+ /** One projection of the author catalog. Callers do not invent a second argument list. */
17
+ export const authorToolInputs: Record<AuthorToolName, ToolInput> = {
18
+ mini_app_list: {
19
+ description: 'List mini-apps on this host.',
20
+ inputSchema: { type: 'object', properties: {} },
21
+ },
22
+ mini_app_get: {
23
+ description: 'Read one mini-app name, version, and directory.',
24
+ inputSchema: { type: 'object', properties: { appId }, required: ['appId'] },
25
+ },
26
+ mini_app_list_files: {
27
+ description: 'List files in a mini-app.',
28
+ inputSchema: { type: 'object', properties: { appId }, required: ['appId'] },
29
+ },
30
+ mini_app_read: {
31
+ description: 'Read a mini-app file. Optional line range.',
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: { appId, path, start: { type: 'number' }, end: { type: 'number' }, numbered: { type: 'boolean' } },
35
+ required: ['appId', 'path'],
36
+ },
37
+ },
38
+ mini_app_edit: {
39
+ description: 'Replace unique text in a mini-app file.',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ appId,
44
+ path,
45
+ edits: { type: 'array', items: { type: 'object', properties: { oldText: { type: 'string' }, newText: { type: 'string' } }, required: ['oldText', 'newText'] } },
46
+ commit,
47
+ },
48
+ required: ['appId', 'path', 'edits'],
49
+ },
50
+ },
51
+ mini_app_write: {
52
+ description: 'Create or overwrite a mini-app file.',
53
+ inputSchema: { type: 'object', properties: { appId, path, content: { type: 'string' }, commit }, required: ['appId', 'path', 'content'] },
54
+ },
55
+ mini_app_delete: {
56
+ description: 'Delete a mini-app file.',
57
+ inputSchema: { type: 'object', properties: { appId, path, commit }, required: ['appId', 'path'] },
58
+ },
59
+ mini_app_register: {
60
+ description: 'Create a mini-app directory from manifest fields. Write ui.tsx and main.api.ts with file tools.',
61
+ inputSchema: {
62
+ type: 'object',
63
+ properties: {
64
+ appId,
65
+ name: { type: 'string' },
66
+ description: { type: 'string' },
67
+ version: { type: 'string' },
68
+ acronym: { type: 'string' },
69
+ tags: { type: 'array', items: { type: 'string' } },
70
+ kind: { type: 'string' },
71
+ },
72
+ required: ['appId', 'name', 'description', 'version'],
73
+ },
74
+ },
75
+ mini_app_reload: {
76
+ description: reloadMessage,
77
+ inputSchema: { type: 'object', properties: { appId, cleanCaches: { type: 'boolean' } }, required: ['appId'] },
78
+ },
79
+ mini_app_call: {
80
+ description: 'Call a mini-app API method, or a batch.',
81
+ inputSchema: {
82
+ type: 'object',
83
+ properties: {
84
+ appId,
85
+ method: { type: 'string' },
86
+ args: { type: 'object' },
87
+ calls: { type: 'array', items: { type: 'object' } },
88
+ },
89
+ required: ['appId'],
90
+ },
91
+ },
92
+ mini_app_mcp_list: {
93
+ description: 'List connected MCP servers and their tool names. No descriptions or schemas.',
94
+ inputSchema: { type: 'object', properties: {} },
95
+ },
96
+ mini_app_mcp_tools: {
97
+ description: 'Read tool descriptions and input schemas for one MCP server. Optional toolName narrows to one tool.',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: { serverId: { type: 'string' }, toolName: { type: 'string' } },
101
+ required: ['serverId'],
102
+ },
103
+ },
104
+ mini_app_mcp_add: {
105
+ description: 'Add or replace one MCP server in the host config, and check it by opening it once. A value may be written as ${env:NAME} or ${credential:NAME}; the host resolves it and the secret never reaches the file. The row is live after the next host start.',
106
+ inputSchema: {
107
+ type: 'object',
108
+ properties: {
109
+ id: { type: 'string' },
110
+ description: { type: 'string' },
111
+ command: { type: 'string' },
112
+ args: { type: 'array', items: { type: 'string' } },
113
+ env: { type: 'object' },
114
+ url: { type: 'string' },
115
+ transport: { type: 'string', enum: ['sse', 'streamable-http'] },
116
+ headers: { type: 'object' },
117
+ enabled: { type: 'boolean' },
118
+ check: { type: 'boolean' },
119
+ force: { type: 'boolean' },
120
+ },
121
+ required: ['id'],
122
+ },
123
+ },
124
+ mini_app_mcp_remove: {
125
+ description: 'Remove one MCP server from the host config by id. The removal is live after the next host start.',
126
+ inputSchema: {
127
+ type: 'object',
128
+ properties: { id: { type: 'string' } },
129
+ required: ['id'],
130
+ },
131
+ },
132
+ mini_app_credential_list: {
133
+ description: 'List credential names. Values are not returned.',
134
+ inputSchema: { type: 'object', properties: {} },
135
+ },
136
+ mini_app_history_commit: {
137
+ description: 'Commit the mini-app tree.',
138
+ inputSchema: { type: 'object', properties: { appId, message: { type: 'string' } }, required: ['appId', 'message'] },
139
+ },
140
+ mini_app_history_list: {
141
+ description: 'List commits.',
142
+ inputSchema: { type: 'object', properties: { appId, limit: { type: 'number' } }, required: ['appId'] },
143
+ },
144
+ mini_app_history_reset: {
145
+ description: 'Reset the tree to a commit.',
146
+ inputSchema: { type: 'object', properties: { appId, commitId: { type: 'string' } }, required: ['appId', 'commitId'] },
147
+ },
148
+ mini_app_open: {
149
+ description: 'Show a mini-app in the panel.',
150
+ inputSchema: { type: 'object', properties: { appId, title: { type: 'string' } }, required: ['appId'] },
151
+ },
152
+ mini_app_errors: {
153
+ description: 'Read recent runner errors.',
154
+ inputSchema: { type: 'object', properties: { appId, since: { type: 'number' }, clear: { type: 'boolean' } }, required: ['appId'] },
155
+ },
156
+ mini_app_install: {
157
+ description: 'Add or remove libraries in a mini-app.',
158
+ inputSchema: {
159
+ type: 'object',
160
+ properties: {
161
+ appId,
162
+ packages: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, version: { type: 'string' } }, required: ['name'] } },
163
+ remove: { type: 'array', items: { type: 'string' } },
164
+ commit,
165
+ },
166
+ required: ['appId'],
167
+ },
168
+ },
169
+ mini_app_view_eval: {
170
+ description: 'Run JavaScript in an open mini-app view.',
171
+ inputSchema: {
172
+ type: 'object',
173
+ properties: { appId, code: { type: 'string' }, maxBytes: { type: 'number' }, timeoutMs: { type: 'number' } },
174
+ required: ['appId'],
175
+ },
176
+ },
177
+ }
178
+
179
+ export function authorToolList(): Array<{ name: AuthorToolName; description: string; inputSchema: ToolInput['inputSchema'] }> {
180
+ return authorToolNames.map(name => ({ name, ...authorToolInputs[name] }))
181
+ }
182
+
183
+ export function authorMcpToolList(): Array<{ name: string; description: string; inputSchema: ToolInput['inputSchema'] }> {
184
+ return authorMcpToolNames.map(name => ({ name, ...authorToolInputs[name] }))
185
+ }
@@ -0,0 +1,4 @@
1
+ /* name: Default */
2
+ /* name-zh-CN: 默认 */
3
+ :root[data-mode="light"] { --background: #f7f7f8; --foreground: #111111; --card: #ffffff; --card-foreground: #111111; --border: #e5e7eb; --muted: #f3f4f6; --muted-foreground: #6b7280; --primary: #2563eb; --primary-foreground: #ffffff; --secondary: #f3f4f6; --secondary-foreground: #111111; --accent: #eff6ff; --accent-foreground: #1e3a8a; --destructive: #dc2626; --destructive-foreground: #ffffff; --ring: #2563eb; --input: #e5e7eb; --radius: 10px; --shadow: rgba(15, 23, 42, 0.08); }
4
+ :root[data-mode="dark"] { --background: #0b0b0c; --foreground: #f4f4f5; --card: #171717; --card-foreground: #f4f4f5; --border: #2a2a2c; --muted: #27272a; --muted-foreground: #a1a1aa; --primary: #3b82f6; --primary-foreground: #ffffff; --secondary: #27272a; --secondary-foreground: #f4f4f5; --accent: #172554; --accent-foreground: #bfdbfe; --destructive: #ef4444; --destructive-foreground: #ffffff; --ring: #3b82f6; --input: #2a2a2c; --radius: 10px; --shadow: rgba(0, 0, 0, 0.45); }
@@ -0,0 +1,4 @@
1
+ /* name: Tundra */
2
+ /* name-zh-CN: 苔原 */
3
+ :root[data-mode="light"] { --background: #fdf6e3; --foreground: #5c6a72; --card: #fefaf0; --card-foreground: #5c6a72; --border: #e3ddc9; --muted: #f2ecdb; --muted-foreground: #859289; --primary: #93b259; --primary-foreground: #fdf6e3; --secondary: #eae4d2; --secondary-foreground: #5c6a72; --accent: #e0e6c8; --accent-foreground: #4d6a3d; --destructive: #e67e80; --destructive-foreground: #fdf6e3; --ring: #93b259; --input: #f7f1e0; --radius: 12px; --shadow: rgba(70, 90, 60, 0.12); }
4
+ :root[data-mode="dark"] { --background: #2d353b; --foreground: #d3c6aa; --card: #333c43; --card-foreground: #d3c6aa; --border: #414c52; --muted: #3d484d; --muted-foreground: #859289; --primary: #a7c080; --primary-foreground: #2d353b; --secondary: #3d484d; --secondary-foreground: #d3c6aa; --accent: #475258; --accent-foreground: #d3c6aa; --destructive: #e67e80; --destructive-foreground: #2d353b; --ring: #a7c080; --input: #272e33; --radius: 12px; --shadow: rgba(0, 0, 0, 0.4); }
@@ -0,0 +1,4 @@
1
+ /* name: Hokage Dawn */
2
+ /* name-zh-CN: 火影黎明 */
3
+ :root[data-mode="light"] { --background: #faf3ea; --foreground: #4a3a2c; --card: #ffffff; --card-foreground: #4a3a2c; --border: #eadcc8; --muted: #f3e9db; --muted-foreground: #a0886a; --primary: #c2660f; --primary-foreground: #ffffff; --secondary: #efe2d0; --secondary-foreground: #4a3a2c; --accent: #fbe6c8; --accent-foreground: #7c4a10; --destructive: #dc2626; --destructive-foreground: #ffffff; --ring: #c2660f; --input: #f8f0e4; --radius: 12px; --shadow: rgba(150, 90, 30, 0.12); }
4
+ :root[data-mode="dark"] { --background: #241a17; --foreground: #f0e3d8; --card: #2e211c; --card-foreground: #f0e3d8; --border: #45372e; --muted: #33251f; --muted-foreground: #b59f8c; --primary: #f59e0b; --primary-foreground: #241a17; --secondary: #33251f; --secondary-foreground: #f0e3d8; --accent: #4a3024; --accent-foreground: #fcd9b6; --destructive: #ef4444; --destructive-foreground: #241a17; --ring: #f59e0b; --input: #201612; --radius: 12px; --shadow: rgba(0, 0, 0, 0.5); }
@@ -0,0 +1,4 @@
1
+ /* name: Strawberry Matcha */
2
+ /* name-zh-CN: 草莓抹茶 */
3
+ :root[data-mode="light"] { --background: #faf5f3; --foreground: #4c4147; --card: #ffffff; --card-foreground: #4c4147; --border: #eadcd8; --muted: #f3eae6; --muted-foreground: #98857f; --primary: #e08a9c; --primary-foreground: #ffffff; --secondary: #f0e4df; --secondary-foreground: #4c4147; --accent: #e8f0da; --accent-foreground: #5a7a3f; --destructive: #d95f7f; --destructive-foreground: #ffffff; --ring: #e08a9c; --input: #f8f1ee; --radius: 14px; --shadow: rgba(180, 90, 110, 0.12); }
4
+ :root[data-mode="dark"] { --background: #28242b; --foreground: #e9d8d2; --card: #302b33; --card-foreground: #e9d8d2; --border: #443a44; --muted: #38313a; --muted-foreground: #b3a09f; --primary: #ee9aa6; --primary-foreground: #28242b; --secondary: #38313a; --secondary-foreground: #e9d8d2; --accent: #3f4a38; --accent-foreground: #c8e3b2; --destructive: #e06c8a; --destructive-foreground: #28242b; --ring: #ee9aa6; --input: #231f26; --radius: 14px; --shadow: rgba(0, 0, 0, 0.45); }
@@ -0,0 +1,4 @@
1
+ /* name: Graphite */
2
+ /* name-zh-CN: 石墨 */
3
+ :root[data-mode="light"] { --background: #ececee; --foreground: #09090b; --card: #ffffff; --card-foreground: #09090b; --border: #c8c8ce; --muted: #e0e0e5; --muted-foreground: #52525b; --primary: #27272a; --primary-foreground: #fafafa; --secondary: #e0e0e5; --secondary-foreground: #09090b; --accent: #d4d4d8; --accent-foreground: #18181b; --destructive: #dc2626; --destructive-foreground: #ffffff; --ring: #27272a; --input: #c8c8ce; --radius: 8px; --shadow: rgba(24, 24, 27, 0.12); }
4
+ :root[data-mode="dark"] { --background: #050506; --foreground: #fafafa; --card: #141416; --card-foreground: #fafafa; --border: #3f3f46; --muted: #1f1f23; --muted-foreground: #a1a1aa; --primary: #e4e4e7; --primary-foreground: #09090b; --secondary: #1f1f23; --secondary-foreground: #fafafa; --accent: #27272a; --accent-foreground: #f4f4f5; --destructive: #f87171; --destructive-foreground: #09090b; --ring: #e4e4e7; --input: #3f3f46; --radius: 8px; --shadow: rgba(0, 0, 0, 0.6); }
@@ -0,0 +1,4 @@
1
+ /* name: Tokyo Night */
2
+ /* name-zh-CN: 东京夜 */
3
+ :root[data-mode="light"] { --background: #d5d6db; --foreground: #343b59; --card: #f2f3f8; --card-foreground: #343b59; --border: #c9ccd9; --muted: #e9eaf2; --muted-foreground: #7c82a4; --primary: #2e7de9; --primary-foreground: #ffffff; --secondary: #e3e5ef; --secondary-foreground: #343b59; --accent: #dfe6f6; --accent-foreground: #274a94; --destructive: #db4b4b; --destructive-foreground: #ffffff; --ring: #2e7de9; --input: #e8eaf2; --radius: 10px; --shadow: rgba(30, 40, 90, 0.12); }
4
+ :root[data-mode="dark"] { --background: #1a1b26; --foreground: #a9b1d6; --card: #16161e; --card-foreground: #a9b1d6; --border: #2b2e3d; --muted: #1f2230; --muted-foreground: #787c99; --primary: #7aa2f7; --primary-foreground: #1a1b26; --secondary: #202330; --secondary-foreground: #a9b1d6; --accent: #33467c; --accent-foreground: #c0caf5; --destructive: #f7768e; --destructive-foreground: #1a1b26; --ring: #7aa2f7; --input: #14141b; --radius: 10px; --shadow: rgba(0, 0, 0, 0.5); }
@@ -0,0 +1,4 @@
1
+ /* name: Pill Yellow */
2
+ /* name-zh-CN: 药丸黄 */
3
+ :root[data-mode="light"] { --background: #faf6ef; --foreground: #3d3326; --card: #ffffff; --card-foreground: #3d3326; --border: #e8dfcd; --muted: #f2ecdf; --muted-foreground: #9a8a6f; --primary: #a37b16; --primary-foreground: #ffffff; --secondary: #ede5d3; --secondary-foreground: #3d3326; --accent: #f7edcb; --accent-foreground: #6b4e0a; --destructive: #dc2626; --destructive-foreground: #ffffff; --ring: #a37b16; --input: #f7f1e4; --radius: 12px; --shadow: rgba(140, 110, 40, 0.12); }
4
+ :root[data-mode="dark"] { --background: #1c1917; --foreground: #f5f0e6; --card: #242019; --card-foreground: #f5f0e6; --border: #3a332a; --muted: #2a2520; --muted-foreground: #b3a68f; --primary: #facc15; --primary-foreground: #1c1917; --secondary: #2a2520; --secondary-foreground: #f5f0e6; --accent: #3f3518; --accent-foreground: #fde68a; --destructive: #f87171; --destructive-foreground: #1c1917; --ring: #facc15; --input: #211d18; --radius: 12px; --shadow: rgba(0, 0, 0, 0.5); }
@@ -0,0 +1,4 @@
1
+ /* name: Three Swords */
2
+ /* name-zh-CN: 三刀流 */
3
+ :root[data-mode="light"] { --background: #f2f6f0; --foreground: #24302a; --card: #ffffff; --card-foreground: #24302a; --border: #d8e4d6; --muted: #e9efe6; --muted-foreground: #7d907e; --primary: #15803d; --primary-foreground: #ffffff; --secondary: #e3ecdf; --secondary-foreground: #24302a; --accent: #dcf2e2; --accent-foreground: #166534; --destructive: #dc2626; --destructive-foreground: #ffffff; --ring: #15803d; --input: #f4f8f2; --radius: 12px; --shadow: rgba(30, 80, 50, 0.1); }
4
+ :root[data-mode="dark"] { --background: #101512; --foreground: #d8e0d4; --card: #18211b; --card-foreground: #d8e0d4; --border: #2c3a30; --muted: #1d2720; --muted-foreground: #93a393; --primary: #4ade80; --primary-foreground: #101512; --secondary: #1d2720; --secondary-foreground: #d8e0d4; --accent: #14532d; --accent-foreground: #bbf7d0; --destructive: #f87171; --destructive-foreground: #101512; --ring: #4ade80; --input: #141b16; --radius: 12px; --shadow: rgba(0, 0, 0, 0.5); }