@notis_ai/cli 0.2.0-beta.175.1 → 0.2.0-beta.178.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 (59) hide show
  1. package/README.md +25 -39
  2. package/dist/agent-hooks/notis-agent-hook.mjs +197 -155
  3. package/{template/packages → dist}/sdk/src/config.ts +4 -0
  4. package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +3 -1
  5. package/{template/packages → dist}/sdk/src/index.ts +1 -0
  6. package/{template/packages → dist}/sdk/src/runtime.ts +13 -0
  7. package/{template/packages → dist}/sdk/src/styles.css +72 -74
  8. package/package.json +1 -1
  9. package/skills/notis-apps/cli.md +23 -37
  10. package/src/command-specs/apps.js +18 -14
  11. package/src/command-specs/diagnostics.js +15 -14
  12. package/src/command-specs/reports.js +61 -50
  13. package/src/runtime/agent-browser.js +10 -5
  14. package/src/runtime/app-platform.js +27 -3
  15. package/src/runtime/app-test-server.js +2 -1
  16. package/template/.harness/index.html.tmpl +12 -4
  17. package/template/packages/sdk/tsconfig.json +0 -16
  18. /package/{template/packages → dist}/sdk/package.json +0 -0
  19. /package/{template/packages → dist}/sdk/src/agentContext.ts +0 -0
  20. /package/{template/packages → dist}/sdk/src/components/Dialog.tsx +0 -0
  21. /package/{template/packages → dist}/sdk/src/components/DocumentEditor.tsx +0 -0
  22. /package/{template/packages → dist}/sdk/src/components/Markdown.tsx +0 -0
  23. /package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +0 -0
  24. /package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +0 -0
  25. /package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
  26. /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  27. /package/{template/packages → dist}/sdk/src/components/NotisCommentBoundary.tsx +0 -0
  28. /package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +0 -0
  29. /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
  30. /package/{template/packages → dist}/sdk/src/components/Skeleton.tsx +0 -0
  31. /package/{template/packages → dist}/sdk/src/documents.ts +0 -0
  32. /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
  33. /package/{template/packages → dist}/sdk/src/hooks/useAgentContext.ts +0 -0
  34. /package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +0 -0
  35. /package/{template/packages → dist}/sdk/src/hooks/useCloudComputer.ts +0 -0
  36. /package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +0 -0
  37. /package/{template/packages → dist}/sdk/src/hooks/useDatabaseSchema.ts +0 -0
  38. /package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +0 -0
  39. /package/{template/packages → dist}/sdk/src/hooks/useDocument.ts +0 -0
  40. /package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +0 -0
  41. /package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +0 -0
  42. /package/{template/packages → dist}/sdk/src/hooks/useLongPressSelection.ts +0 -0
  43. /package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +0 -0
  44. /package/{template/packages → dist}/sdk/src/hooks/useNotisNavigation.ts +0 -0
  45. /package/{template/packages → dist}/sdk/src/hooks/useQuery.ts +0 -0
  46. /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
  47. /package/{template/packages → dist}/sdk/src/hooks/useToolQuery.ts +0 -0
  48. /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
  49. /package/{template/packages → dist}/sdk/src/hooks/useTopBarSearch.ts +0 -0
  50. /package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +0 -0
  51. /package/{template/packages → dist}/sdk/src/interactions/actions.ts +0 -0
  52. /package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +0 -0
  53. /package/{template/packages → dist}/sdk/src/interactions/visibility.ts +0 -0
  54. /package/{template/packages → dist}/sdk/src/interactions.ts +0 -0
  55. /package/{template/packages → dist}/sdk/src/provider.tsx +0 -0
  56. /package/{template/packages → dist}/sdk/src/queryCache.ts +0 -0
  57. /package/{template/packages → dist}/sdk/src/tailwind.ts +0 -0
  58. /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
  59. /package/{template/packages → dist}/sdk/src/vite.ts +0 -0
@@ -1,82 +1,93 @@
1
- import { readFileSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
- import { appsCommandSpecs } from './apps.js';
4
- import { buildArtifact, prepareAppRelease, resolveProjectDir } from '../runtime/app-platform.js';
1
+ import { readFileSync, writeFileSync, rmSync } from 'node:fs';
2
+ import { resolve, join } from 'node:path';
3
+ import { appsCommandSpecs, appsInitHandler, appsBuildHandler, appsVerifyHandler } from './apps.js';
4
+ import { buildArtifact, prepareAppRelease, resolveProjectDir, loadAppConfig } from '../runtime/app-platform.js';
5
5
  import { nextIdempotencyKey, runToolCommand } from './helpers.js';
6
6
  import { usageError, EXIT_CODES } from '../runtime/errors.js';
7
7
 
8
- const reuse = (command, name = command) => {
9
- const spec = appsCommandSpecs.find(item => item.command_path.join(' ') === `apps ${command}`);
10
- return {
11
- ...spec,
8
+ async function assertReportProject(ctx) {
9
+ const dir = resolveProjectDir(ctx.args.dir || '.');
10
+ if ((await loadAppConfig(dir)).kind !== 'report') throw usageError('Set kind: "report" in notis.config.ts. Reports have no app installation or owned resources.');
11
+ return dir;
12
+ }
13
+
14
+ async function initReport(ctx) {
15
+ const dir = resolveProjectDir(ctx.args.dir || `./${ctx.args.name.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`);
16
+ const result = await appsInitHandler({ ...ctx, args: { ...ctx.args, dir }, options: {}, output: { emitSuccess: value => value } });
17
+ writeFileSync(join(dir, 'notis.config.ts'), `import { defineNotisApp } from '@notis/sdk/config';
18
+ export default defineNotisApp({
19
+ kind: 'report', name: ${JSON.stringify(ctx.args.name)},
20
+ routes: [{ path: '/', slug: 'home', name: 'Report', default: true }],
21
+ tools: [],
22
+ });
23
+ `);
24
+ writeFileSync(join(dir, 'app/page.tsx'), `'use client';
25
+ import { useNotis, NotisCommentBoundary } from '@notis/sdk';
26
+ export default function Report() {
27
+ const { app } = useNotis();
28
+ return <NotisCommentBoundary><main className="notis-app-shell space-y-6">
29
+ <h1 className="text-2xl font-semibold">{app?.name || 'Report'}</h1>
30
+ <p className="text-muted-foreground">Your report is ready to build.</p>
31
+ </main></NotisCommentBoundary>;
32
+ }
33
+ `);
34
+ rmSync(join(dir, 'CHANGELOG.md'), { force: true });
35
+ return ctx.output.emitSuccess({ ...result, command: 'reports init', hints: [
36
+ { command: `cd ${dir} && npm install`, reason: 'Install dependencies' },
37
+ { command: `notis reports build ${dir}`, reason: 'Build the standalone report' },
38
+ ] });
39
+ }
40
+
41
+ const localReportCommand = (command, name = command) => {
42
+ const app = appsCommandSpecs.find(item => item.command_path.join(' ') === `apps ${command}`);
43
+ return { ...app, command_path: ['reports', name],
44
+ summary: `${name[0].toUpperCase() + name.slice(1)} a standalone live SDK report.`,
45
+ when_to_use: 'Author a private standalone document using the shared view runtime; no app or database is required.',
46
+ args_schema: { ...app.args_schema, options: [...(app.args_schema.options || []).filter(option => !['--listing', '--from <slug>'].includes(option.flags)), ...(command === 'verify' ? [{ flags: '--document-id <id>', description: 'Saved report for live verification.' }, { flags: '--expected-revision <revision>', description: 'Saved report revision for live verification.' }] : [])] },
47
+ examples: [`notis reports ${name} ${command === 'init' ? '"Weekly report" ./weekly-report' : './weekly-report'}`],
12
48
  handler: async ctx => {
13
- const output = new Proxy(ctx.output, {
14
- get(target, key) {
15
- if (key === 'emitSuccess') return result => {
16
- const value = { ...result, warnings: (result.warnings || []).filter(warning => !warning.startsWith('Store readiness:')) };
17
- if (value.data?.listing) { value.data = { ...value.data }; delete value.data.listing; }
18
- return target.emitSuccess(value);
19
- };
20
- const value = Reflect.get(target, key);
21
- return typeof value === 'function' ? value.bind(target) : value;
22
- },
23
- });
24
- return spec.handler({ ...ctx, options: { ...ctx.options, listing: false, ...(name === 'preview' ? { keepOpen: true } : {}) }, output });
25
- },
26
- command_path: ['reports', name],
27
- summary: `${name[0].toUpperCase() + name.slice(1)} a record-owned SDK report locally.${name === 'preview' ? ' Keeps the preview server and browser session open.' : ''}`,
28
- args_schema: {
29
- ...spec.args_schema,
30
- options: (spec.args_schema?.options || []).map(option => option.flags === '--listing'
31
- ? { ...option, description: 'Ignored for reports; saving a report does not publish a Store listing.' }
32
- : option),
49
+ if (command === 'init') return initReport(ctx);
50
+ await assertReportProject(ctx);
51
+ return (command === 'build' ? appsBuildHandler : appsVerifyHandler)({ ...ctx, options: { ...ctx.options, ...(name === 'preview' ? { keepOpen: true } : {}) } });
33
52
  },
34
- examples: (spec.examples || []).filter(example => !example.includes('--listing')).map(example => example.replace(`apps ${command}`, `reports ${name}`)),
35
- when_to_use: 'Author an independent report without deploying its owning app.',
36
53
  };
37
54
  };
38
55
  export const reportsCommandSpecs = [
39
- reuse('init'), reuse('build'), reuse('verify'), reuse('verify', 'preview'),
56
+ localReportCommand('init'), localReportCommand('build'), localReportCommand('verify'), localReportCommand('verify', 'preview'),
40
57
  {
41
58
  command_path: ['reports', 'save'],
42
- summary: 'Build, verify and save a report into an app-owned database record.',
43
- when_to_use: 'Persist an independently authored report, not an app release.',
59
+ summary: 'Build, verify and save a standalone live report document.',
60
+ when_to_use: 'Persist an independently owned report, not an app release.',
44
61
  args_schema: {
45
62
  arguments: [{ token: '[dir]', key: 'dir', description: 'Report source directory.' }],
46
63
  options: [
47
- { flags: '--database-id <id>', description: 'Required. Owning app database.' },
48
- { flags: '--document-id <id>', description: 'Existing record to update or attach to.' },
49
- { flags: '--attach', description: 'Attach to an existing non-view record.' },
50
- { flags: '--expected-revision <revision>', description: 'Fresh view revision (0 for a record without a view).' },
51
- { flags: '--title <title>', description: 'Required, including updates. Record title.' },
64
+ { flags: '--document-id <id>', description: 'Existing report document to update.' },
65
+ { flags: '--expected-revision <revision>', description: 'Current saved report revision; required for updates.' },
66
+ { flags: '--title <title>', description: 'Required, including updates. Document title.' },
52
67
  { flags: '--context-file <file>', description: 'Required. UTF-8 readable report content and structure.' },
53
- { flags: '--properties-file <file>', description: 'JSON database property values keyed by name.' },
54
68
  ],
55
69
  },
56
- examples: ['notis reports save ./weekly-report --database-id <id> --title \"Weekly review\" --context-file ./context.md'], mutates: true, idempotent: true,
70
+ examples: ['notis reports save ./weekly-report --title \"Weekly review\" --context-file ./context.md'], mutates: true, idempotent: true,
57
71
  backend_call: { type: 'tool', name: 'LOCAL_NOTIS_SAVE_REPORT' },
58
72
  async handler(ctx) {
59
- const dir = resolveProjectDir(ctx.args.dir || '.');
60
- if (!ctx.options.databaseId || !ctx.options.title || !ctx.options.contextFile) throw usageError('--database-id, --title and --context-file are required.');
61
- if (ctx.options.documentId && (!/^\d+$/.test(String(ctx.options.expectedRevision ?? '')) || !Number.isSafeInteger(Number(ctx.options.expectedRevision)))) throw usageError('--expected-revision is required for update/attach.');
62
- if (ctx.options.attach && !ctx.options.documentId) throw usageError('--attach requires --document-id.');
73
+ const dir = await assertReportProject(ctx);
74
+ if (!ctx.options.title || !ctx.options.contextFile) throw usageError('--title and --context-file are required.');
75
+ if (ctx.options.documentId && (!/^\d+$/.test(String(ctx.options.expectedRevision ?? '')) || !Number.isSafeInteger(Number(ctx.options.expectedRevision)))) throw usageError('--expected-revision is required for update.');
63
76
  const context = readFileSync(resolve(ctx.options.contextFile), 'utf8');
64
- const properties = ctx.options.propertiesFile ? JSON.parse(readFileSync(resolve(ctx.options.propertiesFile), 'utf8')) : {};
65
77
  await buildArtifact(dir, { stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit' });
66
78
  const release = prepareAppRelease(dir);
67
79
  try {
68
80
  if (release.manifest.routes?.length !== 1) throw usageError('Reports require exactly one SDK route.');
69
81
  let verification;
70
- const verify = appsCommandSpecs.find(item => item.command_path.join(' ') === 'apps verify');
71
- const exit = await verify.handler({ ...ctx, args: { dir: release.projectDir }, options: { skipBuild: true, mode: 'stub' }, output: { ...ctx.output, isMachineMode: () => true, emitSuccess: value => { verification = value; } } });
82
+ const exit = await appsVerifyHandler({ ...ctx, args: { dir: release.projectDir }, options: { skipBuild: true, mode: 'stub' }, output: { ...ctx.output, isMachineMode: () => true, emitSuccess: value => { verification = value; } } });
72
83
  if (exit !== EXIT_CODES.ok || verification?.data?.status !== 'passed') throw usageError('Report verification failed; nothing saved.');
73
84
  const files = { ...release.files, ...Object.fromEntries(Object.entries(release.sourceFiles).map(([path, data]) => [`source/${path}`, data])) };
74
85
  const result = await runToolCommand({ runtime: { ...ctx.runtime, timeoutMs: Math.max(ctx.runtime.timeoutMs || 0, 90000) }, toolName: 'LOCAL_NOTIS_SAVE_REPORT', mutating: true,
75
86
  idempotencyKey: nextIdempotencyKey(ctx.globalOptions), arguments_: {
76
- operation: ctx.options.attach ? 'attach' : ctx.options.documentId ? 'update' : 'create',
77
- database_id: ctx.options.databaseId, title: ctx.options.title, properties,
87
+ operation: ctx.options.documentId ? 'update' : 'create',
88
+ title: ctx.options.title,
78
89
  ...(ctx.options.documentId ? { document_id: ctx.options.documentId, expected_revision: Number(ctx.options.expectedRevision) } : {}),
79
- report: { schema: 'notis-report/v2', context, artifact: { manifest: release.manifest, files, encoding: 'base64' } },
90
+ report: { schema: 'notis-report/v3', context, artifact: { manifest: release.manifest, files, encoding: 'base64' } },
80
91
  } });
81
92
  if (!result?.payload?.document?.id || !result.payload.document.view_revision) throw usageError('Save returned no record identity. Read back before retrying; the outcome may be unknown.');
82
93
  return ctx.output.emitSuccess({ command: 'reports save', data: result.payload });
@@ -228,6 +228,7 @@ export async function runHarnessRoute({
228
228
  timeoutMs = 10_000,
229
229
  snapshotPath = null,
230
230
  designViewports = DEFAULT_DESIGN_VIEWPORTS,
231
+ waitForRuntime = false,
231
232
  }) {
232
233
  const opened = await runAgentBrowser(['--session', sessionName, 'open', url], {
233
234
  timeoutMs: Math.min(Math.max(timeoutMs, 5000), 30_000),
@@ -244,17 +245,21 @@ export async function runHarnessRoute({
244
245
  }
245
246
 
246
247
  const deadline = Date.now() + timeoutMs;
248
+ let readySince = null;
247
249
  while (Date.now() < deadline) {
248
250
  const lastRead = await readHarness(sessionName, 5000);
249
251
  if (!lastRead.ok) {
252
+ readySince = null;
250
253
  await delay(250);
251
254
  continue;
252
255
  }
253
256
  const harness = lastRead.harness;
254
- if (harness?.mounted || (Array.isArray(harness?.errors) && harness.errors.length > 0)) {
255
- await delay(250);
256
- break;
257
- }
257
+ if (Array.isArray(harness?.errors) && harness.errors.length > 0) break;
258
+ const pending = (harness?.runtimeCalls || []).some(call => call?.ok == null);
259
+ if (harness?.mounted && (!waitForRuntime || !pending)) {
260
+ readySince ??= Date.now();
261
+ if (Date.now() - readySince >= 250) break;
262
+ } else readySince = null;
258
263
  await delay(250);
259
264
  }
260
265
 
@@ -283,8 +288,8 @@ export async function runHarnessRoute({
283
288
  }
284
289
  }
285
290
 
286
- const timedOut = !harness.mounted && Date.now() >= deadline;
287
291
  const runtimeCalls = Array.isArray(harness.runtimeCalls) ? harness.runtimeCalls : [];
292
+ const timedOut = (!harness.mounted || (waitForRuntime && runtimeCalls.some(call => call?.ok == null))) && Date.now() >= deadline;
288
293
 
289
294
  // Design assertions only mean something on a mounted route. Loading
290
295
  // placeholders are excused while a runtime call is still in flight.
@@ -27,7 +27,7 @@ const BUNDLE_DIR = join(OUTPUT_DIR, 'bundle');
27
27
  const MANIFEST_FILE = join(OUTPUT_DIR, 'manifest.json');
28
28
  const METADATA_DIR = 'metadata';
29
29
  const CLI_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
30
- const TEMPLATE_SDK_DIR = join(CLI_ROOT, 'template', 'packages', 'sdk');
30
+ const TEMPLATE_SDK_DIR = join(CLI_ROOT, 'dist', 'sdk');
31
31
  export const NOTIS_APP_CATEGORIES = [
32
32
  'Productivity',
33
33
  'Sales & Marketing',
@@ -286,13 +286,14 @@ export function detectProjectWarnings(projectDir, appConfig = null) {
286
286
  appConfig?.capabilities?.workspaceDatabases === 'read';
287
287
  if (
288
288
  appConfig
289
+ && appConfig.kind !== 'report'
289
290
  && !readsWorkspaceDatabases
290
291
  && (!Array.isArray(appConfig.databases) || appConfig.databases.length === 0)
291
292
  ) {
292
293
  warnings.push('No database references declared in notis.config.ts.');
293
294
  }
294
295
 
295
- if (appConfig) {
296
+ if (appConfig && appConfig.kind !== 'report') {
296
297
  try {
297
298
  const listing = inspectListingReadiness(projectDir, appConfig);
298
299
  warnings.push(...listing.errors, ...listing.warnings);
@@ -813,6 +814,29 @@ export function generateManifest(appConfig, projectDir) {
813
814
  return entry;
814
815
  });
815
816
 
817
+ if (appConfig.kind === 'report') {
818
+ if (routes.length !== 1 || routes[0].collection || routes[0].resourceDeepLinks) {
819
+ throw usageError('Reports require one standalone route, without app collections or resource deep links.');
820
+ }
821
+ if (['databases', 'skills', 'automations', 'onboarding', 'tagline', 'categories', 'author', 'screenshots'].some(key => {
822
+ const value = appConfig[key];
823
+ return Array.isArray(value) ? value.length > 0 : Boolean(value);
824
+ })) throw usageError('Reports cannot own databases, skills, automations or Store metadata.');
825
+ if (Object.values(appConfig.capabilities || {}).some(value => value !== 'read')) {
826
+ throw usageError('Reports support read capabilities only; shell grants require an installed app.');
827
+ }
828
+ const databaseAccess = appConfig.databaseAccess || [];
829
+ if (!Array.isArray(databaseAccess) || databaseAccess.some(entry =>
830
+ !entry || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(entry.id || '') ||
831
+ !['read', 'write'].includes(entry.access)
832
+ )) throw usageError('databaseAccess entries require an existing database UUID and read/write access.');
833
+ return { kind: 'report', version: 1, spec_version: 4,
834
+ routes: routes.map(route => ({ ...route, default: true, tool_access: { base: { tools: appConfig.tools || [] } } })),
835
+ bundle: { js: 'bundle/app.js', css: 'bundle/app.css' },
836
+ tools: appConfig.tools || [], tool_bindings: normalizeAppToolBindings(appConfig.toolBindings),
837
+ capabilities: normalizeAppCapabilities(appConfig.capabilities), database_access: databaseAccess };
838
+ }
839
+
816
840
  // Database entries may be a bare slug (structure only) or an object opting
817
841
  // into shipping rows to installers. Normalize to the manifest's snake_case.
818
842
  const databases = (appConfig.databases || []).map((entry) => {
@@ -1847,7 +1871,7 @@ function listFilesRecursive(dir, base = dir, results = []) {
1847
1871
  /**
1848
1872
  * Keep an app's embedded `packages/sdk` copy identical to the SDK this CLI
1849
1873
  * ships. The copy is a mirror by contract (the parity test in the CLI keeps
1850
- * the template in step with packages/sdk), so an app never edits it; before
1874
+ * the bundled artifact generated from packages/sdk), so an app never edits it; before
1851
1875
  * this sync existed every app silently ran whatever SDK snapshot it was
1852
1876
  * scaffolded with and never received hook or style updates.
1853
1877
  *
@@ -80,7 +80,7 @@ function resolveHarnessReactVersion(projectDir) {
80
80
  const candidates = [
81
81
  join(projectDir, 'node_modules', '@notis', 'sdk', 'package.json'),
82
82
  join(REPO_ROOT, 'packages', 'sdk', 'package.json'),
83
- join(CLI_ROOT, 'template', 'packages', 'sdk', 'package.json'),
83
+ join(CLI_ROOT, 'dist', 'sdk', 'package.json'),
84
84
  ];
85
85
  for (const candidate of candidates) {
86
86
  const pkg = readJsonFile(candidate);
@@ -164,6 +164,7 @@ function buildHarnessDescriptor({ state, manifest, appConfig, route, scenario =
164
164
  );
165
165
 
166
166
  return {
167
+ ...(state.resource ? { resource: state.resource } : {}),
167
168
  app: {
168
169
  id: state.appId || 'harness-app',
169
170
  slug: state.slug,
@@ -207,8 +207,8 @@
207
207
  }
208
208
 
209
209
  async function runtimeQuery(body) {
210
- if (!apiBase || !jwt || !descriptor.app || !descriptor.app.id) {
211
- throw new Error('live harness mode requires apiBase, jwt, and app id');
210
+ if (!apiBase || !jwt || !(descriptor.resource?.kind === 'report' ? descriptor.resource.id : descriptor.app?.id)) {
211
+ throw new Error('live harness mode requires apiBase, jwt, and runtime resource identity');
212
212
  }
213
213
  const response = await fetch(`${String(apiBase).replace(/\/$/, '')}/portal_views/runtime_query`, {
214
214
  method: 'POST',
@@ -217,7 +217,9 @@
217
217
  Authorization: `Bearer ${jwt}`,
218
218
  },
219
219
  body: JSON.stringify({
220
- app_id: descriptor.app.id,
220
+ ...(descriptor.resource?.kind === 'report'
221
+ ? { document_id: descriptor.resource.id, revision: descriptor.resource.revision }
222
+ : { app_id: descriptor.app.id }),
221
223
  route_slug: descriptor.route && descriptor.route.slug,
222
224
  ...body,
223
225
  }),
@@ -230,7 +232,10 @@
230
232
  if (!payload) {
231
233
  throw new Error(snippet ? `Runtime response was not JSON: ${snippet}` : 'Runtime response was empty');
232
234
  }
233
- if (payload.status === 'error' || payload.error) {
235
+ if (payload.status === 'error' || payload.error || (descriptor.resource?.kind === 'report' && (
236
+ ['error', 'failed', 'failure'].includes(String(payload.status || '').trim().toLowerCase())
237
+ || payload.ok === false || payload.success === false || payload.successful === false
238
+ ))) {
234
239
  throw new Error(payload.message || payload.error || 'Runtime request failed');
235
240
  }
236
241
  return payload;
@@ -238,6 +243,7 @@
238
243
 
239
244
  function stubRuntime() {
240
245
  return {
246
+ resource: descriptor.resource,
241
247
  app: descriptor.app,
242
248
  route: descriptor.route,
243
249
  context: descriptor.context || {},
@@ -287,6 +293,7 @@
287
293
  request: async (path, options) => tracked(
288
294
  record('request', { path, options }),
289
295
  async () => {
296
+ if (descriptor.resource?.kind === 'report') throw new Error('Reports access data through declared tools.');
290
297
  if (fixtures && fixtures.requests && Object.prototype.hasOwnProperty.call(fixtures.requests, path)) {
291
298
  return structuredClone(fixtures.requests[path]);
292
299
  }
@@ -349,6 +356,7 @@
349
356
  request: async (path, options) => tracked(
350
357
  record('request', { path, options }),
351
358
  async () => {
359
+ if (descriptor.resource?.kind === 'report') throw new Error('Reports access data through declared tools.');
352
360
  const response = await fetch(`${String(apiBase).replace(/\/$/, '')}${path}`, {
353
361
  method: (options && options.method) || 'GET',
354
362
  headers: {
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "types": ["node", "react", "react-dom"],
4
- "target": "ES2022",
5
- "module": "ESNext",
6
- "moduleResolution": "bundler",
7
- "jsx": "react-jsx",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "declaration": true,
12
- "outDir": "dist",
13
- "rootDir": "src"
14
- },
15
- "include": ["src"]
16
- }
File without changes
File without changes
File without changes
File without changes