@leeoohoo/ui-apps-devkit 0.1.0 → 0.1.2

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 (62) hide show
  1. package/README.md +75 -60
  2. package/bin/chatos-uiapp.js +4 -4
  3. package/package.json +26 -20
  4. package/src/cli.js +53 -53
  5. package/src/commands/dev.js +14 -14
  6. package/src/commands/init.js +131 -129
  7. package/src/commands/install.js +47 -46
  8. package/src/commands/pack.js +72 -72
  9. package/src/commands/validate.js +138 -80
  10. package/src/lib/args.js +49 -49
  11. package/src/lib/config.js +29 -29
  12. package/src/lib/fs.js +78 -78
  13. package/src/lib/path-boundary.js +16 -16
  14. package/src/lib/plugin.js +45 -45
  15. package/src/lib/state-constants.js +2 -0
  16. package/src/lib/template.js +172 -168
  17. package/src/sandbox/server.js +1957 -692
  18. package/templates/basic/README.md +78 -54
  19. package/templates/basic/chatos.config.json +5 -5
  20. package/templates/basic/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +214 -181
  21. package/templates/basic/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +75 -74
  22. package/templates/basic/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  23. package/templates/basic/docs/CHATOS_UI_APPS_OVERVIEW.md +112 -107
  24. package/templates/basic/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +242 -227
  25. package/templates/basic/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  26. package/templates/basic/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  27. package/templates/basic/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  28. package/templates/basic/plugin/apps/app/compact.mjs +41 -0
  29. package/templates/basic/plugin/apps/app/index.mjs +287 -263
  30. package/templates/basic/plugin/apps/app/mcp-prompt.en.md +7 -7
  31. package/templates/basic/plugin/apps/app/mcp-prompt.zh.md +7 -7
  32. package/templates/basic/plugin/apps/app/mcp-server.mjs +15 -15
  33. package/templates/basic/plugin/backend/index.mjs +37 -37
  34. package/templates/basic/template.json +7 -7
  35. package/templates/notepad/README.md +55 -24
  36. package/templates/notepad/chatos.config.json +4 -4
  37. package/templates/notepad/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +214 -181
  38. package/templates/notepad/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +75 -74
  39. package/templates/notepad/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  40. package/templates/notepad/docs/CHATOS_UI_APPS_OVERVIEW.md +112 -107
  41. package/templates/notepad/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +242 -227
  42. package/templates/notepad/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  43. package/templates/notepad/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  44. package/templates/notepad/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  45. package/templates/notepad/plugin/apps/app/api.mjs +30 -30
  46. package/templates/notepad/plugin/apps/app/compact.mjs +41 -0
  47. package/templates/notepad/plugin/apps/app/dom.mjs +14 -14
  48. package/templates/notepad/plugin/apps/app/ds-tree.mjs +35 -35
  49. package/templates/notepad/plugin/apps/app/index.mjs +1056 -1056
  50. package/templates/notepad/plugin/apps/app/layers.mjs +338 -338
  51. package/templates/notepad/plugin/apps/app/markdown.mjs +120 -120
  52. package/templates/notepad/plugin/apps/app/mcp-prompt.en.md +22 -22
  53. package/templates/notepad/plugin/apps/app/mcp-prompt.zh.md +22 -22
  54. package/templates/notepad/plugin/apps/app/mcp-server.mjs +206 -199
  55. package/templates/notepad/plugin/apps/app/styles.mjs +355 -355
  56. package/templates/notepad/plugin/apps/app/tags.mjs +21 -21
  57. package/templates/notepad/plugin/apps/app/ui.mjs +280 -280
  58. package/templates/notepad/plugin/backend/index.mjs +99 -99
  59. package/templates/notepad/plugin/plugin.json +23 -23
  60. package/templates/notepad/plugin/shared/notepad-paths.mjs +59 -41
  61. package/templates/notepad/plugin/shared/notepad-store.mjs +765 -765
  62. package/templates/notepad/template.json +8 -8
@@ -1,200 +1,207 @@
1
+ /**
2
+ * MCP server entry.
3
+ *
4
+ * Note: ChatOS import excludes `node_modules/`, so if you import third-party
5
+ * deps (e.g. @modelcontextprotocol/sdk, zod), you must bundle this file into
6
+ * a single output (or vendor deps into the plugin directory).
7
+ */
1
8
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { z } from 'zod';
4
- import { createNotepadStore } from '../../shared/notepad-store.mjs';
5
- import { resolveUiAppDataDir } from '../../shared/notepad-paths.mjs';
6
-
7
- const PLUGIN_ID = '__PLUGIN_ID__';
8
- const SERVER_NAME = '__PLUGIN_ID__.__APP_ID__';
9
-
10
- const store = createNotepadStore({ dataDir: resolveUiAppDataDir({ pluginId: PLUGIN_ID }) });
11
-
12
- const server = new McpServer({
13
- name: SERVER_NAME,
14
- version: '0.1.0',
15
- });
16
-
17
- const toText = (payload) => ({
18
- content: [
19
- {
20
- type: 'text',
21
- text: JSON.stringify(payload, null, 2),
22
- },
23
- ],
24
- });
25
-
26
- server.registerTool(
27
- 'init',
28
- {
29
- title: 'Init Notepad Storage',
30
- description: 'Initialize the Notepad storage (data directory, index, notes root).',
31
- inputSchema: z.object({}).optional(),
32
- },
33
- async () => toText(await store.init())
34
- );
35
-
36
- server.registerTool(
37
- 'list_folders',
38
- {
39
- title: 'List Folders',
40
- description: 'List all folders (categories) under Notepad notes root.',
41
- inputSchema: z.object({}).optional(),
42
- },
43
- async () => toText(await store.listFolders())
44
- );
45
-
46
- server.registerTool(
47
- 'create_folder',
48
- {
49
- title: 'Create Folder',
50
- description: 'Create a folder (category). Supports nested paths like "work/ideas".',
51
- inputSchema: z.object({
52
- folder: z.string().min(1).describe('Folder path, relative to notes root (e.g., "work/ideas")'),
53
- }),
54
- },
55
- async ({ folder }) => toText(await store.createFolder({ folder }))
56
- );
57
-
58
- server.registerTool(
59
- 'rename_folder',
60
- {
61
- title: 'Rename Folder',
62
- description: 'Rename/move a folder (and updates indexed notes folder paths).',
63
- inputSchema: z.object({
64
- from: z.string().min(1).describe('Source folder path'),
65
- to: z.string().min(1).describe('Target folder path'),
66
- }),
67
- },
68
- async ({ from, to }) => toText(await store.renameFolder({ from, to }))
69
- );
70
-
71
- server.registerTool(
72
- 'delete_folder',
73
- {
74
- title: 'Delete Folder',
75
- description: 'Delete a folder. If recursive=true, deletes all notes under it and removes them from index.',
76
- inputSchema: z.object({
77
- folder: z.string().min(1).describe('Folder path to delete'),
78
- recursive: z.boolean().optional().describe('Delete folder recursively'),
79
- }),
80
- },
81
- async ({ folder, recursive } = {}) => toText(await store.deleteFolder({ folder, recursive: recursive === true }))
82
- );
83
-
84
- server.registerTool(
85
- 'list_notes',
86
- {
87
- title: 'List Notes',
88
- description: 'List notes with optional folder/tags/title filtering.',
89
- inputSchema: z.object({
90
- folder: z.string().optional().describe('Folder path; empty means all'),
91
- recursive: z.boolean().optional().describe('Include notes in subfolders (default true)'),
92
- tags: z.array(z.string()).optional().describe('Filter notes by tags'),
93
- match: z.enum(['all', 'any']).optional().describe('Tag match mode'),
94
- query: z.string().optional().describe('Substring filter for title/folder'),
95
- limit: z.number().int().min(1).max(500).optional().describe('Max notes to return'),
96
- }),
97
- },
98
- async ({ folder, recursive, tags, match, query, limit } = {}) =>
99
- toText(await store.listNotes({ folder, recursive, tags, match, query, limit }))
100
- );
101
-
102
- server.registerTool(
103
- 'create_note',
104
- {
105
- title: 'Create Note',
106
- description: 'Create a new markdown note under a folder with optional title/content/tags.',
107
- inputSchema: z.object({
108
- folder: z.string().optional().describe('Folder path to create note in'),
109
- title: z.string().optional().describe('Note title'),
110
- content: z.string().optional().describe('Markdown content (optional)'),
111
- tags: z.array(z.string()).optional().describe('Tags (optional)'),
112
- }),
113
- },
114
- async ({ folder, title, content, tags } = {}) => toText(await store.createNote({ folder, title, content, tags }))
115
- );
116
-
117
- server.registerTool(
118
- 'read_note',
119
- {
120
- title: 'Read Note',
121
- description: 'Read a note by id (returns metadata and markdown content).',
122
- inputSchema: z.object({
123
- id: z.string().min(1).describe('Note id'),
124
- }),
125
- },
126
- async ({ id }) => toText(await store.getNote({ id }))
127
- );
128
-
129
- server.registerTool(
130
- 'update_note',
131
- {
132
- title: 'Update Note',
133
- description: 'Update note metadata/content by id. You can also move it by changing folder.',
134
- inputSchema: z.object({
135
- id: z.string().min(1).describe('Note id'),
136
- title: z.string().optional().describe('New title'),
137
- content: z.string().optional().describe('New markdown content'),
138
- folder: z.string().optional().describe('New folder path'),
139
- tags: z.array(z.string()).optional().describe('Replace tags'),
140
- }),
141
- },
142
- async ({ id, title, content, folder, tags } = {}) => toText(await store.updateNote({ id, title, content, folder, tags }))
143
- );
144
-
145
- server.registerTool(
146
- 'delete_note',
147
- {
148
- title: 'Delete Note',
149
- description: 'Delete a note by id (removes file and index entry).',
150
- inputSchema: z.object({
151
- id: z.string().min(1).describe('Note id'),
152
- }),
153
- },
154
- async ({ id }) => toText(await store.deleteNote({ id }))
155
- );
156
-
157
- server.registerTool(
158
- 'list_tags',
159
- {
160
- title: 'List Tags',
161
- description: 'List all tags with usage counts.',
162
- inputSchema: z.object({}).optional(),
163
- },
164
- async () => toText(await store.listTags())
165
- );
166
-
167
- server.registerTool(
168
- 'search_notes',
169
- {
170
- title: 'Search Notes',
171
- description: 'Search notes by query, optionally filtered by folder/tags; can include content search.',
172
- inputSchema: z.object({
173
- query: z.string().min(1).describe('Search keyword'),
174
- folder: z.string().optional().describe('Folder path filter'),
175
- recursive: z.boolean().optional().describe('Include notes in subfolders (default true)'),
176
- tags: z.array(z.string()).optional().describe('Tag filter'),
177
- match: z.enum(['all', 'any']).optional().describe('Tag match mode'),
178
- includeContent: z.boolean().optional().describe('Search in content (default true)'),
179
- limit: z.number().int().min(1).max(200).optional().describe('Max results'),
180
- }),
181
- },
182
- async ({ query, folder, recursive, tags, match, includeContent, limit } = {}) =>
183
- toText(await store.searchNotes({ query, folder, recursive, tags, match, includeContent: includeContent !== false, limit }))
184
- );
185
-
186
- async function main() {
187
- const initRes = await store.init();
188
- if (!initRes?.ok) {
189
- // eslint-disable-next-line no-console
190
- console.error('Notepad MCP init failed:', initRes?.message || initRes);
191
- }
192
- const transport = new StdioServerTransport();
193
- await server.connect(transport);
194
- }
195
-
196
- main().catch((error) => {
197
- // eslint-disable-next-line no-console
198
- console.error('MCP server error:', error);
199
- process.exit(1);
200
- });
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { z } from 'zod';
11
+ import { createNotepadStore } from '../../shared/notepad-store.mjs';
12
+ import { resolveUiAppDataDir } from '../../shared/notepad-paths.mjs';
13
+
14
+ const PLUGIN_ID = '__PLUGIN_ID__';
15
+ const SERVER_NAME = '__PLUGIN_ID__.__APP_ID__';
16
+
17
+ const store = createNotepadStore({ dataDir: resolveUiAppDataDir({ pluginId: PLUGIN_ID }) });
18
+
19
+ const server = new McpServer({
20
+ name: SERVER_NAME,
21
+ version: '0.1.0',
22
+ });
23
+
24
+ const toText = (payload) => ({
25
+ content: [
26
+ {
27
+ type: 'text',
28
+ text: JSON.stringify(payload, null, 2),
29
+ },
30
+ ],
31
+ });
32
+
33
+ server.registerTool(
34
+ 'init',
35
+ {
36
+ title: 'Init Notepad Storage',
37
+ description: 'Initialize the Notepad storage (data directory, index, notes root).',
38
+ inputSchema: z.object({}).optional(),
39
+ },
40
+ async () => toText(await store.init())
41
+ );
42
+
43
+ server.registerTool(
44
+ 'list_folders',
45
+ {
46
+ title: 'List Folders',
47
+ description: 'List all folders (categories) under Notepad notes root.',
48
+ inputSchema: z.object({}).optional(),
49
+ },
50
+ async () => toText(await store.listFolders())
51
+ );
52
+
53
+ server.registerTool(
54
+ 'create_folder',
55
+ {
56
+ title: 'Create Folder',
57
+ description: 'Create a folder (category). Supports nested paths like "work/ideas".',
58
+ inputSchema: z.object({
59
+ folder: z.string().min(1).describe('Folder path, relative to notes root (e.g., "work/ideas")'),
60
+ }),
61
+ },
62
+ async ({ folder }) => toText(await store.createFolder({ folder }))
63
+ );
64
+
65
+ server.registerTool(
66
+ 'rename_folder',
67
+ {
68
+ title: 'Rename Folder',
69
+ description: 'Rename/move a folder (and updates indexed notes folder paths).',
70
+ inputSchema: z.object({
71
+ from: z.string().min(1).describe('Source folder path'),
72
+ to: z.string().min(1).describe('Target folder path'),
73
+ }),
74
+ },
75
+ async ({ from, to }) => toText(await store.renameFolder({ from, to }))
76
+ );
77
+
78
+ server.registerTool(
79
+ 'delete_folder',
80
+ {
81
+ title: 'Delete Folder',
82
+ description: 'Delete a folder. If recursive=true, deletes all notes under it and removes them from index.',
83
+ inputSchema: z.object({
84
+ folder: z.string().min(1).describe('Folder path to delete'),
85
+ recursive: z.boolean().optional().describe('Delete folder recursively'),
86
+ }),
87
+ },
88
+ async ({ folder, recursive } = {}) => toText(await store.deleteFolder({ folder, recursive: recursive === true }))
89
+ );
90
+
91
+ server.registerTool(
92
+ 'list_notes',
93
+ {
94
+ title: 'List Notes',
95
+ description: 'List notes with optional folder/tags/title filtering.',
96
+ inputSchema: z.object({
97
+ folder: z.string().optional().describe('Folder path; empty means all'),
98
+ recursive: z.boolean().optional().describe('Include notes in subfolders (default true)'),
99
+ tags: z.array(z.string()).optional().describe('Filter notes by tags'),
100
+ match: z.enum(['all', 'any']).optional().describe('Tag match mode'),
101
+ query: z.string().optional().describe('Substring filter for title/folder'),
102
+ limit: z.number().int().min(1).max(500).optional().describe('Max notes to return'),
103
+ }),
104
+ },
105
+ async ({ folder, recursive, tags, match, query, limit } = {}) =>
106
+ toText(await store.listNotes({ folder, recursive, tags, match, query, limit }))
107
+ );
108
+
109
+ server.registerTool(
110
+ 'create_note',
111
+ {
112
+ title: 'Create Note',
113
+ description: 'Create a new markdown note under a folder with optional title/content/tags.',
114
+ inputSchema: z.object({
115
+ folder: z.string().optional().describe('Folder path to create note in'),
116
+ title: z.string().optional().describe('Note title'),
117
+ content: z.string().optional().describe('Markdown content (optional)'),
118
+ tags: z.array(z.string()).optional().describe('Tags (optional)'),
119
+ }),
120
+ },
121
+ async ({ folder, title, content, tags } = {}) => toText(await store.createNote({ folder, title, content, tags }))
122
+ );
123
+
124
+ server.registerTool(
125
+ 'read_note',
126
+ {
127
+ title: 'Read Note',
128
+ description: 'Read a note by id (returns metadata and markdown content).',
129
+ inputSchema: z.object({
130
+ id: z.string().min(1).describe('Note id'),
131
+ }),
132
+ },
133
+ async ({ id }) => toText(await store.getNote({ id }))
134
+ );
135
+
136
+ server.registerTool(
137
+ 'update_note',
138
+ {
139
+ title: 'Update Note',
140
+ description: 'Update note metadata/content by id. You can also move it by changing folder.',
141
+ inputSchema: z.object({
142
+ id: z.string().min(1).describe('Note id'),
143
+ title: z.string().optional().describe('New title'),
144
+ content: z.string().optional().describe('New markdown content'),
145
+ folder: z.string().optional().describe('New folder path'),
146
+ tags: z.array(z.string()).optional().describe('Replace tags'),
147
+ }),
148
+ },
149
+ async ({ id, title, content, folder, tags } = {}) => toText(await store.updateNote({ id, title, content, folder, tags }))
150
+ );
151
+
152
+ server.registerTool(
153
+ 'delete_note',
154
+ {
155
+ title: 'Delete Note',
156
+ description: 'Delete a note by id (removes file and index entry).',
157
+ inputSchema: z.object({
158
+ id: z.string().min(1).describe('Note id'),
159
+ }),
160
+ },
161
+ async ({ id }) => toText(await store.deleteNote({ id }))
162
+ );
163
+
164
+ server.registerTool(
165
+ 'list_tags',
166
+ {
167
+ title: 'List Tags',
168
+ description: 'List all tags with usage counts.',
169
+ inputSchema: z.object({}).optional(),
170
+ },
171
+ async () => toText(await store.listTags())
172
+ );
173
+
174
+ server.registerTool(
175
+ 'search_notes',
176
+ {
177
+ title: 'Search Notes',
178
+ description: 'Search notes by query, optionally filtered by folder/tags; can include content search.',
179
+ inputSchema: z.object({
180
+ query: z.string().min(1).describe('Search keyword'),
181
+ folder: z.string().optional().describe('Folder path filter'),
182
+ recursive: z.boolean().optional().describe('Include notes in subfolders (default true)'),
183
+ tags: z.array(z.string()).optional().describe('Tag filter'),
184
+ match: z.enum(['all', 'any']).optional().describe('Tag match mode'),
185
+ includeContent: z.boolean().optional().describe('Search in content (default true)'),
186
+ limit: z.number().int().min(1).max(200).optional().describe('Max results'),
187
+ }),
188
+ },
189
+ async ({ query, folder, recursive, tags, match, includeContent, limit } = {}) =>
190
+ toText(await store.searchNotes({ query, folder, recursive, tags, match, includeContent: includeContent !== false, limit }))
191
+ );
192
+
193
+ async function main() {
194
+ const initRes = await store.init();
195
+ if (!initRes?.ok) {
196
+ // eslint-disable-next-line no-console
197
+ console.error('Notepad MCP init failed:', initRes?.message || initRes);
198
+ }
199
+ const transport = new StdioServerTransport();
200
+ await server.connect(transport);
201
+ }
202
+
203
+ main().catch((error) => {
204
+ // eslint-disable-next-line no-console
205
+ console.error('MCP server error:', error);
206
+ process.exit(1);
207
+ });