@notis_ai/cli 0.2.0 → 0.2.1

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 (58) hide show
  1. package/README.md +39 -10
  2. package/package.json +7 -1
  3. package/src/command-specs/apps.js +852 -47
  4. package/src/command-specs/helpers.js +28 -3
  5. package/src/command-specs/index.js +1 -1
  6. package/src/command-specs/tools.js +33 -6
  7. package/src/runtime/agent-browser.js +192 -0
  8. package/src/runtime/app-boundary-validator.js +132 -0
  9. package/src/runtime/app-dev-server.js +577 -0
  10. package/src/runtime/app-dev-sessions.js +87 -0
  11. package/src/runtime/app-platform.js +646 -71
  12. package/src/runtime/cli-mode.generated.js +4 -0
  13. package/src/runtime/cli-mode.js +29 -0
  14. package/src/runtime/output.js +2 -2
  15. package/src/runtime/ports.js +15 -0
  16. package/src/runtime/profiles.js +34 -3
  17. package/src/runtime/transport.js +129 -4
  18. package/template/.harness/index.html.tmpl +260 -0
  19. package/template/app/globals.css +3 -0
  20. package/template/app/layout.tsx +6 -0
  21. package/template/app/page.tsx +55 -0
  22. package/template/components/ui/badge.tsx +28 -0
  23. package/template/components/ui/button.tsx +53 -0
  24. package/template/components/ui/card.tsx +56 -0
  25. package/template/components.json +20 -0
  26. package/template/lib/utils.ts +6 -0
  27. package/template/notis.config.ts +33 -0
  28. package/template/package.json +31 -0
  29. package/template/packages/notis-sdk/package.json +26 -0
  30. package/template/packages/notis-sdk/src/components/MultiSelectActionBar.tsx +272 -0
  31. package/template/packages/notis-sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  32. package/template/packages/notis-sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  33. package/template/packages/notis-sdk/src/config.ts +71 -0
  34. package/template/packages/notis-sdk/src/helpers.ts +131 -0
  35. package/template/packages/notis-sdk/src/hooks/useAppState.ts +50 -0
  36. package/template/packages/notis-sdk/src/hooks/useBackend.ts +41 -0
  37. package/template/packages/notis-sdk/src/hooks/useCollectionItem.ts +58 -0
  38. package/template/packages/notis-sdk/src/hooks/useDatabase.ts +88 -0
  39. package/template/packages/notis-sdk/src/hooks/useDocument.ts +61 -0
  40. package/template/packages/notis-sdk/src/hooks/useMultiSelect.ts +502 -0
  41. package/template/packages/notis-sdk/src/hooks/useNotis.ts +33 -0
  42. package/template/packages/notis-sdk/src/hooks/useNotisNavigation.ts +49 -0
  43. package/template/packages/notis-sdk/src/hooks/useTool.ts +49 -0
  44. package/template/packages/notis-sdk/src/hooks/useTools.ts +56 -0
  45. package/template/packages/notis-sdk/src/hooks/useTopBarSearch.ts +73 -0
  46. package/template/packages/notis-sdk/src/hooks/useUpsertDocument.ts +58 -0
  47. package/template/packages/notis-sdk/src/index.ts +67 -0
  48. package/template/packages/notis-sdk/src/provider.tsx +43 -0
  49. package/template/packages/notis-sdk/src/runtime.ts +182 -0
  50. package/template/packages/notis-sdk/src/styles.css +38 -0
  51. package/template/packages/notis-sdk/src/ui.ts +15 -0
  52. package/template/packages/notis-sdk/src/vite.ts +56 -0
  53. package/template/packages/notis-sdk/tsconfig.json +15 -0
  54. package/template/postcss.config.mjs +8 -0
  55. package/template/tailwind.config.ts +58 -0
  56. package/template/tsconfig.json +22 -0
  57. package/template/vite.config.ts +10 -0
  58. package/src/runtime/app-preview-server.js +0 -272
@@ -1,272 +0,0 @@
1
- /**
2
- * Local preview server for built Notis app artifacts.
3
- *
4
- * Serves the static site from .notis/output/site/ with the Notis runtime
5
- * injected into HTML pages. The runtime provides mock implementations for
6
- * databases (seed data from manifest) and tool stubs.
7
- *
8
- * Usage: `notis apps preview [dir]` starts this server on localhost:8787.
9
- */
10
-
11
- import { createServer } from 'node:http';
12
- import { existsSync, readFileSync } from 'node:fs';
13
- import { extname, join, resolve } from 'node:path';
14
-
15
- import { readManifest } from './app-platform.js';
16
-
17
- // ---------------------------------------------------------------------------
18
- // Static file serving
19
- // ---------------------------------------------------------------------------
20
-
21
- function contentTypeFor(path) {
22
- const types = {
23
- '.html': 'text/html; charset=utf-8',
24
- '.css': 'text/css; charset=utf-8',
25
- '.js': 'text/javascript; charset=utf-8',
26
- '.json': 'application/json; charset=utf-8',
27
- '.svg': 'image/svg+xml',
28
- '.png': 'image/png',
29
- '.jpg': 'image/jpeg',
30
- '.jpeg': 'image/jpeg',
31
- '.ico': 'image/x-icon',
32
- '.woff': 'font/woff',
33
- '.woff2': 'font/woff2',
34
- };
35
- return types[extname(path)] || 'application/octet-stream';
36
- }
37
-
38
- function replacePlaceholders(content) {
39
- return content
40
- .replaceAll('/__NOTIS_APP_BASE__', '')
41
- .replaceAll('__NOTIS_APP_BASE__', '')
42
- .replaceAll('/__NOTIS_ASSET_BASE__', '')
43
- .replaceAll('__NOTIS_ASSET_BASE__', '');
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // Mock runtime generation
48
- // ---------------------------------------------------------------------------
49
-
50
- function buildSeedValue(property, index) {
51
- switch (property?.type) {
52
- case 'number':
53
- return (index + 1) * 180;
54
- case 'checkbox':
55
- return index % 2 === 0;
56
- case 'date':
57
- return `2026-04-0${Math.min(index + 2, 9)}`;
58
- case 'select':
59
- case 'status':
60
- return property.options?.[index % (property.options?.length || 1)]?.name || 'Active';
61
- case 'multi_select':
62
- return (property.options || []).slice(0, 2).map((o) => o.name);
63
- default:
64
- return `${property?.name || 'Value'} ${index + 1}`;
65
- }
66
- }
67
-
68
- function buildSeedDocuments(databases) {
69
- const state = {};
70
- for (const db of databases) {
71
- const titleProp = db.properties?.find((p) => p.type === 'title')?.name || 'title';
72
- state[db.slug] = Array.from({ length: 3 }, (_, i) => {
73
- const properties = {};
74
- for (const prop of db.properties || []) {
75
- properties[prop.name] = prop.name === titleProp
76
- ? `${db.title} ${i + 1}`
77
- : buildSeedValue(prop, i);
78
- }
79
- return {
80
- id: `${db.slug}-seed-${i + 1}`,
81
- databaseSlug: db.slug,
82
- title: properties[titleProp] || `${db.title} ${i + 1}`,
83
- properties,
84
- icon: db.icon || null,
85
- };
86
- });
87
- }
88
- return state;
89
- }
90
-
91
- function runtimeScript({ app, route, tools, databases }) {
92
- const seedState = buildSeedDocuments(databases);
93
- return `<script>
94
- (function() {
95
- var DB = ${JSON.stringify(databases)};
96
- var STATE = ${JSON.stringify(seedState)};
97
- var COLLECTION = ${JSON.stringify(route.collection || null)};
98
-
99
- function titleProp(slug) {
100
- var db = DB.find(function(d) { return d.slug === slug; });
101
- return db && db.properties ? (db.properties.find(function(p) { return p.type === 'title'; }) || {}).name || 'title' : 'title';
102
- }
103
-
104
- function dbSlug(v) { return v || (COLLECTION && COLLECTION.database) || null; }
105
-
106
- window.__NOTIS_RUNTIME__ = {
107
- app: ${JSON.stringify(app)},
108
- route: ${JSON.stringify(route)},
109
- databases: DB,
110
-
111
- listTools: function() {
112
- return Promise.resolve(${JSON.stringify(tools)});
113
- },
114
-
115
- callTool: function(name, args) {
116
- console.log('[notis-preview] Tool call:', name, args);
117
- return Promise.reject(new Error('Tool calls are not available in local preview. Deploy the app to use real tools.'));
118
- },
119
-
120
- queryDatabase: function(args) {
121
- var slug = dbSlug(args && args.databaseSlug);
122
- var docs = (STATE[slug] || []).slice(args && args.offset || 0);
123
- return Promise.resolve({ documents: docs });
124
- },
125
-
126
- getDocument: function(args) {
127
- var id = args && args.documentId;
128
- for (var slug in STATE) {
129
- var match = STATE[slug].find(function(d) { return d.id === id; });
130
- if (match) return Promise.resolve(match);
131
- }
132
- return Promise.reject(new Error('Document not found in preview.'));
133
- },
134
-
135
- upsertDocument: function(args) {
136
- var slug = dbSlug(args && args.databaseSlug);
137
- if (!slug) return Promise.reject(new Error('No database for upsert.'));
138
- var docs = STATE[slug] || [];
139
- var tp = titleProp(slug);
140
- var existing = docs.find(function(d) { return d.id === (args && args.documentId); });
141
- var props = Object.assign({}, existing && existing.properties, args && args.properties);
142
- if (args && args.title) props[tp] = args.title;
143
- var doc = {
144
- id: existing ? existing.id : slug + '-' + Date.now(),
145
- databaseSlug: slug,
146
- title: props[tp] || 'Untitled',
147
- properties: props,
148
- icon: null
149
- };
150
- if (existing) {
151
- var idx = docs.indexOf(existing);
152
- docs[idx] = doc;
153
- } else {
154
- docs.unshift(doc);
155
- }
156
- STATE[slug] = docs;
157
- return Promise.resolve({ status: 'success', document: doc });
158
- },
159
-
160
- listCollectionItems: function(args) {
161
- var slug = dbSlug(args && args.databaseSlug);
162
- if (!slug) return Promise.resolve({ items: [] });
163
- var tp = (args && args.titleProperty) || titleProp(slug);
164
- var docs = (STATE[slug] || []).slice(0, (args && args.pageSize) || 100);
165
- return Promise.resolve({
166
- items: docs.map(function(d) {
167
- return { id: d.id, title: (d.properties && d.properties[tp]) || d.title || 'Untitled', icon: d.icon };
168
- })
169
- });
170
- },
171
-
172
- request: function() {
173
- return Promise.reject(new Error('Backend requests are not available in local preview.'));
174
- }
175
- };
176
- })();
177
- </script>`;
178
- }
179
-
180
- function injectRuntime(html, injection) {
181
- if (html.includes('</head>')) {
182
- return html.replace('</head>', injection + '\n</head>');
183
- }
184
- return injection + '\n' + html;
185
- }
186
-
187
- // ---------------------------------------------------------------------------
188
- // Server
189
- // ---------------------------------------------------------------------------
190
-
191
- function normalizePathname(pathname) {
192
- if (!pathname || pathname === '/') return '/';
193
- return pathname.endsWith('/') ? pathname.slice(0, -1) || '/' : pathname;
194
- }
195
-
196
- function entryHtmlPath(routePath) {
197
- if (routePath === '/') return 'index.html';
198
- return `${routePath.replace(/^\/+/, '')}/index.html`;
199
- }
200
-
201
- export async function startPreviewServer({ projectDir, port }) {
202
- const manifest = readManifest(projectDir);
203
- const siteDir = resolve(projectDir, '.notis/output/site');
204
- const databases = (manifest.databases || []).filter((d) => d && typeof d.slug === 'string');
205
- const routesByPath = new Map((manifest.routes || []).map((r) => [normalizePathname(r.path), r]));
206
- const defaultRoute = (manifest.routes || []).find((r) => r.default) || manifest.routes?.[0];
207
-
208
- if (!defaultRoute) {
209
- throw new Error('Manifest contains no routes.');
210
- }
211
-
212
- const server = createServer((request, response) => {
213
- const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
214
- const pathname = normalizePathname(url.pathname);
215
-
216
- // Redirect / to default route if it's not /
217
- if (pathname === '/' && defaultRoute.path !== '/') {
218
- response.writeHead(302, { Location: defaultRoute.path });
219
- response.end();
220
- return;
221
- }
222
-
223
- // Serve route HTML with runtime injection
224
- const route = routesByPath.get(pathname);
225
- if (route) {
226
- const htmlPath = join(siteDir, entryHtmlPath(route.path));
227
- if (!existsSync(htmlPath)) {
228
- response.writeHead(404, { 'Content-Type': 'text/plain' });
229
- response.end(`Route HTML not found: ${htmlPath}`);
230
- return;
231
- }
232
-
233
- const raw = readFileSync(htmlPath, 'utf-8');
234
- const html = injectRuntime(
235
- replacePlaceholders(raw),
236
- runtimeScript({
237
- app: manifest.app,
238
- route,
239
- tools: (manifest.tools || []).map((name) => ({ name })),
240
- databases,
241
- }),
242
- );
243
- response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
244
- response.end(html);
245
- return;
246
- }
247
-
248
- // Serve static files
249
- const filePath = join(siteDir, pathname.replace(/^\/+/, ''));
250
- if (!existsSync(filePath)) {
251
- response.writeHead(404, { 'Content-Type': 'text/plain' });
252
- response.end('Not found');
253
- return;
254
- }
255
-
256
- const isHtml = extname(filePath) === '.html';
257
- const body = isHtml
258
- ? replacePlaceholders(readFileSync(filePath, 'utf-8'))
259
- : readFileSync(filePath);
260
-
261
- response.writeHead(200, { 'Content-Type': contentTypeFor(filePath), 'Cache-Control': 'no-store' });
262
- response.end(body);
263
- });
264
-
265
- await new Promise((resolveP, rejectP) => {
266
- server.on('error', rejectP);
267
- server.listen(port, '127.0.0.1', resolveP);
268
- });
269
-
270
- // Keep running until interrupted
271
- await new Promise(() => {});
272
- }