@notis_ai/cli 0.2.2 → 0.2.3

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 (81) hide show
  1. package/README.md +19 -18
  2. package/config/notis_app_boundary_rules.json +50 -0
  3. package/dist/scaffolds/notis-database/app/globals.css +44 -0
  4. package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
  5. package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
  6. package/dist/scaffolds/notis-database/index.html +12 -0
  7. package/dist/scaffolds/notis-database/lib/types.ts +134 -0
  8. package/dist/scaffolds/notis-database/metadata/cover.png +0 -0
  9. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  10. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  11. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  12. package/dist/scaffolds/notis-database/notis.config.ts +27 -0
  13. package/dist/scaffolds/notis-database/package.json +31 -0
  14. package/dist/scaffolds/notis-database/src/dev-main.tsx +23 -0
  15. package/dist/scaffolds/notis-database/src/mock-runtime.ts +557 -0
  16. package/dist/scaffolds/notis-database/tailwind.config.ts +59 -0
  17. package/dist/scaffolds/{notes → notis-database}/tsconfig.json +2 -1
  18. package/dist/scaffolds/notis-database/vite.config.ts +22 -0
  19. package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
  20. package/dist/scaffolds/{notes → notis-notes}/app/page.tsx +783 -80
  21. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +28 -0
  22. package/dist/scaffolds/notis-notes/components/ui/button.tsx +53 -0
  23. package/dist/scaffolds/notis-notes/components/ui/card.tsx +56 -0
  24. package/dist/scaffolds/notis-notes/components.json +20 -0
  25. package/dist/scaffolds/notis-notes/lib/utils.ts +6 -0
  26. package/dist/scaffolds/notis-notes/metadata/cover.png +0 -0
  27. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  28. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  29. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  30. package/dist/scaffolds/{notes → notis-notes}/notis.config.ts +12 -7
  31. package/dist/scaffolds/{notes → notis-notes}/package.json +2 -2
  32. package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
  33. package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
  34. package/dist/scaffolds/notis-random/README.md +33 -0
  35. package/dist/scaffolds/notis-random/app/globals.css +11 -0
  36. package/dist/scaffolds/notis-random/app/history/page.tsx +66 -0
  37. package/dist/scaffolds/notis-random/app/layout.tsx +7 -0
  38. package/dist/scaffolds/notis-random/app/page.tsx +222 -0
  39. package/dist/scaffolds/notis-random/index.html +12 -0
  40. package/dist/scaffolds/notis-random/lib/notis-tools.ts +109 -0
  41. package/dist/scaffolds/notis-random/lib/rng.ts +42 -0
  42. package/dist/scaffolds/notis-random/lib/roll-record.ts +102 -0
  43. package/dist/scaffolds/notis-random/lib/utils.ts +25 -0
  44. package/dist/scaffolds/notis-random/metadata/cover.png +0 -0
  45. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  46. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  47. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  48. package/dist/scaffolds/notis-random/notis.config.ts +43 -0
  49. package/dist/scaffolds/notis-random/package.json +32 -0
  50. package/dist/scaffolds/notis-random/postcss.config.mjs +6 -0
  51. package/dist/scaffolds/notis-random/src/dev-main.tsx +70 -0
  52. package/dist/scaffolds/notis-random/src/mock-runtime.ts +128 -0
  53. package/dist/scaffolds/notis-random/tailwind.config.ts +43 -0
  54. package/dist/scaffolds/notis-random/tsconfig.json +23 -0
  55. package/dist/scaffolds/notis-random/vite.config.ts +11 -0
  56. package/dist/scaffolds.json +27 -4
  57. package/package.json +2 -1
  58. package/skills/notis-apps/SKILL.md +2 -2
  59. package/skills/notis-cli/SKILL.md +18 -16
  60. package/skills/notis-query/cli.md +9 -10
  61. package/src/cli.js +30 -2
  62. package/src/command-specs/apps.js +42 -15
  63. package/src/command-specs/helpers.js +73 -45
  64. package/src/command-specs/meta.js +7 -7
  65. package/src/command-specs/tools.js +196 -83
  66. package/src/runtime/app-boundary-validator.js +65 -14
  67. package/src/runtime/profiles.js +10 -4
  68. package/src/runtime/transport.js +122 -37
  69. package/template/app/page.tsx +1 -1
  70. package/template/notis.config.ts +1 -1
  71. package/template/packages/sdk/src/hooks/useTool.ts +1 -1
  72. /package/dist/scaffolds/{notes → notis-database}/app/layout.tsx +0 -0
  73. /package/dist/scaffolds/{notes → notis-database}/components/ui/badge.tsx +0 -0
  74. /package/dist/scaffolds/{notes → notis-database}/components/ui/button.tsx +0 -0
  75. /package/dist/scaffolds/{notes → notis-database}/components/ui/card.tsx +0 -0
  76. /package/dist/scaffolds/{notes → notis-database}/components.json +0 -0
  77. /package/dist/scaffolds/{notes → notis-database}/lib/utils.ts +0 -0
  78. /package/dist/scaffolds/{notes → notis-database}/postcss.config.mjs +0 -0
  79. /package/dist/scaffolds/{notes → notis-notes}/app/globals.css +0 -0
  80. /package/dist/scaffolds/{notes → notis-notes}/tailwind.config.ts +0 -0
  81. /package/dist/scaffolds/{notes → notis-notes}/vite.config.ts +0 -0
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
2
3
  import { CliError, EXIT_CODES } from './errors.js';
3
4
  import {
4
5
  DEFAULT_PROFILE,
@@ -8,31 +9,71 @@ import {
8
9
  saveConfig,
9
10
  } from './profiles.js';
10
11
 
11
- const META_TOOL_NAMES = new Set([
12
- 'notis_find_toolkits',
13
- 'notis_find_tools',
14
- 'notis_execute_tool',
15
- 'notis_find_additional_tools',
16
- ]);
17
-
18
- function canonicalizeToolName(toolName) {
19
- if (typeof toolName !== 'string' || !toolName.length) {
20
- return toolName;
21
- }
12
+ function escapeMultipartHeaderValue(value) {
13
+ return String(value ?? '')
14
+ .replace(/"/g, '%22')
15
+ .replace(/\r/g, '%0D')
16
+ .replace(/\n/g, '%0A');
17
+ }
22
18
 
23
- if (META_TOOL_NAMES.has(toolName) || toolName.startsWith('notis-default-')) {
24
- return toolName;
25
- }
19
+ function multipartTextPart(boundary, name, value) {
20
+ return [
21
+ `--${boundary}`,
22
+ `Content-Disposition: form-data; name="${escapeMultipartHeaderValue(name)}"`,
23
+ '',
24
+ value,
25
+ '',
26
+ ].join('\r\n');
27
+ }
26
28
 
27
- if (toolName.startsWith('notis_')) {
28
- return `notis-default-${toolName.slice('notis_'.length)}`;
29
- }
29
+ function multipartFileHeader(boundary, binding) {
30
+ const filename = escapeMultipartHeaderValue(binding.basename || binding.field_name);
31
+ return [
32
+ `--${boundary}`,
33
+ `Content-Disposition: form-data; name="${escapeMultipartHeaderValue(binding.field_name)}"; filename="${filename}"`,
34
+ `Content-Type: ${binding.contentType || 'application/octet-stream'}`,
35
+ '',
36
+ '',
37
+ ].join('\r\n');
38
+ }
30
39
 
31
- if (toolName.includes('_notis_')) {
32
- return `notis-default-${toolName.replace(/_notis_/g, '_')}`;
40
+ async function* multipartFileBody(boundary, payload, bindingMetadata, fileBindings) {
41
+ yield Buffer.from(multipartTextPart(boundary, 'payload', JSON.stringify(payload)));
42
+ yield Buffer.from(multipartTextPart(boundary, 'file_bindings', JSON.stringify(bindingMetadata)));
43
+ for (const binding of fileBindings) {
44
+ yield Buffer.from(multipartFileHeader(boundary, binding));
45
+ for await (const chunk of createReadStream(binding.localPath)) {
46
+ yield chunk;
47
+ }
48
+ yield Buffer.from('\r\n');
33
49
  }
50
+ yield Buffer.from(`--${boundary}--\r\n`);
51
+ }
34
52
 
35
- return toolName;
53
+ function createMultipartFileUpload(payload, bindingMetadata, fileBindings) {
54
+ const boundary = `----notis-cli-${randomUUID().replace(/-/g, '')}`;
55
+ const textParts = [
56
+ multipartTextPart(boundary, 'payload', JSON.stringify(payload)),
57
+ multipartTextPart(boundary, 'file_bindings', JSON.stringify(bindingMetadata)),
58
+ ];
59
+ const fileHeaderParts = fileBindings.map((binding) => multipartFileHeader(boundary, binding));
60
+ const closingPart = `--${boundary}--\r\n`;
61
+ const contentLength = [
62
+ ...textParts,
63
+ ...fileHeaderParts,
64
+ ...fileBindings.map(() => '\r\n'),
65
+ closingPart,
66
+ ].reduce((total, part) => total + Buffer.byteLength(part), 0)
67
+ + fileBindings.reduce((total, binding) => total + binding.size, 0);
68
+
69
+ return {
70
+ body: multipartFileBody(boundary, payload, bindingMetadata, fileBindings),
71
+ headers: {
72
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
73
+ 'Content-Length': String(contentLength),
74
+ },
75
+ duplex: 'half',
76
+ };
36
77
  }
37
78
 
38
79
  function normalizeBackendError(status, payload) {
@@ -197,6 +238,7 @@ export async function httpRequest({
197
238
  method = 'POST',
198
239
  path,
199
240
  body,
241
+ multipart = false,
200
242
  requireAuth = true,
201
243
  }) {
202
244
  await maybeRefreshDevPortalAuth(runtime);
@@ -211,21 +253,41 @@ export async function httpRequest({
211
253
  };
212
254
 
213
255
  const headers = {
214
- 'Content-Type': 'application/json',
215
256
  'X-Notis-CLI-Version': runtime.cliVersion,
216
257
  'X-Notis-Request-Id': requestId,
217
258
  };
259
+ if (!multipart) {
260
+ headers['Content-Type'] = 'application/json';
261
+ }
218
262
 
219
263
  if (requireAuth && runtime.jwt) {
220
264
  headers.Authorization = `Bearer ${runtime.jwt}`;
221
265
  }
222
266
 
267
+ const resolveBody = () => {
268
+ if (!multipart) {
269
+ return {
270
+ body: body ? JSON.stringify(body) : undefined,
271
+ headers: {},
272
+ };
273
+ }
274
+ if (typeof body === 'function') {
275
+ return body();
276
+ }
277
+ return {
278
+ body,
279
+ headers: {},
280
+ };
281
+ };
282
+
223
283
  try {
284
+ let requestBody = resolveBody();
224
285
  let response = await fetch(`${runtime.apiBase}${path}`, {
225
286
  method,
226
- headers,
227
- body: body ? JSON.stringify(body) : undefined,
287
+ headers: { ...headers, ...requestBody.headers },
288
+ body: requestBody.body,
228
289
  signal: controller.signal,
290
+ ...(requestBody.duplex ? { duplex: requestBody.duplex } : {}),
229
291
  });
230
292
 
231
293
  let payload = null;
@@ -243,11 +305,13 @@ export async function httpRequest({
243
305
  headers.Authorization = `Bearer ${runtime.jwt}`;
244
306
  }
245
307
  resetTimeout();
308
+ requestBody = resolveBody();
246
309
  response = await fetch(`${runtime.apiBase}${path}`, {
247
310
  method,
248
- headers,
249
- body: body ? JSON.stringify(body) : undefined,
311
+ headers: { ...headers, ...requestBody.headers },
312
+ body: requestBody.body,
250
313
  signal: controller.signal,
314
+ ...(requestBody.duplex ? { duplex: requestBody.duplex } : {}),
251
315
  });
252
316
  try {
253
317
  payload = await response.json();
@@ -302,22 +366,43 @@ export async function callTool({
302
366
  toolName,
303
367
  arguments_: argumentsPayload = {},
304
368
  idempotencyKey,
369
+ fileBindings = [],
305
370
  }) {
306
371
  const requestId = idempotencyKey || (runtime.mutating ? randomUUID() : null);
372
+ const payload = {
373
+ tool_name: toolName,
374
+ arguments: argumentsPayload,
375
+ idempotency_key: requestId,
376
+ cli_context: {
377
+ output_mode: runtime.outputMode,
378
+ profile: runtime.profileName,
379
+ cwd: process.cwd(),
380
+ agent_mode: runtime.agentMode,
381
+ cli_version: runtime.cliVersion,
382
+ },
383
+ };
384
+
385
+ if (Array.isArray(fileBindings) && fileBindings.length) {
386
+ const bindingMetadata = fileBindings.map((binding) => {
387
+ const {
388
+ contentType,
389
+ localPath,
390
+ ...metadata
391
+ } = binding;
392
+ return metadata;
393
+ });
394
+
395
+ return httpRequest({
396
+ runtime,
397
+ path: '/cli_tools',
398
+ body: () => createMultipartFileUpload(payload, bindingMetadata, fileBindings),
399
+ multipart: true,
400
+ });
401
+ }
402
+
307
403
  return httpRequest({
308
404
  runtime,
309
405
  path: '/cli_tools',
310
- body: {
311
- tool_name: canonicalizeToolName(toolName),
312
- arguments: argumentsPayload,
313
- idempotency_key: requestId,
314
- cli_context: {
315
- output_mode: runtime.outputMode,
316
- profile: runtime.profileName,
317
- cwd: process.cwd(),
318
- agent_mode: runtime.agentMode,
319
- cli_version: runtime.cliVersion,
320
- },
321
- },
406
+ body: payload,
322
407
  });
323
408
  }
@@ -25,7 +25,7 @@ type QueryItemsResult = {
25
25
 
26
26
  export default function HomePage() {
27
27
  const { app, ready } = useNotis();
28
- const queryItems = useTool<QueryItemsArgs, QueryItemsResult>('notis-default-query');
28
+ const queryItems = useTool<QueryItemsArgs, QueryItemsResult>('LOCAL_NOTIS_DATABASE_QUERY');
29
29
  const [documents, setDocuments] = useState<ItemDocument[]>([]);
30
30
 
31
31
  useEffect(() => {
@@ -32,6 +32,6 @@ export default defineNotisApp({
32
32
  ],
33
33
 
34
34
  tools: [
35
- 'notis-default-query',
35
+ 'LOCAL_NOTIS_DATABASE_QUERY',
36
36
  ],
37
37
  });
@@ -25,7 +25,7 @@ export interface UseToolResult<
25
25
  * Call a specific Notis tool by name.
26
26
  *
27
27
  * ```tsx
28
- * const { call, loading } = useTool('notis-default-web_search');
28
+ * const { call, loading } = useTool('LOCAL_NOTIS_WEB_SEARCH');
29
29
  * const result = await call({ query: 'latest news' });
30
30
  * ```
31
31
  */