@notis_ai/cli 0.2.0-beta.178.1 → 0.2.0-beta.1792022492092.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 (124) hide show
  1. package/README.md +548 -98
  2. package/config/portal-route-roots.json +47 -0
  3. package/dist/agent-hooks/notis-agent-hook.mjs +32901 -5934
  4. package/dist/base-skills/notis-apps/SKILL.md +76 -51
  5. package/dist/base-skills/notis-apps/references/architecture.md +116 -164
  6. package/dist/base-skills/notis-apps/references/design.md +26 -107
  7. package/dist/base-skills/notis-apps/references/memory.md +58 -0
  8. package/dist/base-skills/notis-apps/references/reading.md +52 -74
  9. package/dist/base-skills/notis-apps/references/release.md +105 -96
  10. package/dist/base-skills/notis-apps/references/sdk.md +115 -60
  11. package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
  12. package/dist/base-skills/notis-apps/references/views.md +145 -0
  13. package/dist/base-skills/notis-cli/SKILL.md +29 -12
  14. package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
  15. package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
  16. package/dist/base-skills/notis-cli/references/native-databases.md +6 -5
  17. package/dist/base-skills/notis-cli/references/tool-examples.md +4 -4
  18. package/dist/base-skills/notis-query/SKILL.md +34 -60
  19. package/dist/base-skills/notis-query/references/database-discovery.md +60 -59
  20. package/dist/base-skills/notis-query/references/documents.md +53 -39
  21. package/dist/base-skills/notis-query/references/query.md +57 -524
  22. package/dist/sdk/src/bundleModule.ts +79 -0
  23. package/dist/sdk/src/components/DocumentEditor.tsx +6 -3
  24. package/dist/sdk/src/components/DocumentPage.tsx +144 -0
  25. package/dist/sdk/src/components/RecordComponents.tsx +33 -0
  26. package/dist/sdk/src/components/RenderChartData.tsx +19 -0
  27. package/dist/sdk/src/config.ts +4 -26
  28. package/dist/sdk/src/documents.ts +2 -0
  29. package/dist/sdk/src/hooks/useAction.ts +28 -0
  30. package/dist/sdk/src/hooks/useBackend.ts +3 -2
  31. package/dist/sdk/src/hooks/useCloudComputer.ts +35 -4
  32. package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
  33. package/dist/sdk/src/hooks/useCollectionInteractions.ts +4 -2
  34. package/dist/sdk/src/hooks/useHandover.ts +7 -7
  35. package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
  36. package/dist/sdk/src/hooks/useNativeDocuments.ts +83 -0
  37. package/dist/sdk/src/hooks/useNotis.ts +3 -0
  38. package/dist/sdk/src/hooks/useNotisNavigation.ts +26 -11
  39. package/dist/sdk/src/hooks/useQuery.ts +7 -4
  40. package/dist/sdk/src/hooks/useViewParams.ts +121 -0
  41. package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
  42. package/dist/sdk/src/index.ts +36 -0
  43. package/dist/sdk/src/interactions/shortcuts.tsx +42 -5
  44. package/dist/sdk/src/nativeData.ts +39 -0
  45. package/dist/sdk/src/presentation.tsx +82 -0
  46. package/dist/sdk/src/provider.tsx +9 -4
  47. package/dist/sdk/src/queryCache.ts +5 -3
  48. package/dist/sdk/src/queryErrors.ts +51 -0
  49. package/dist/sdk/src/runtime.ts +163 -13
  50. package/dist/sdk/src/space.ts +425 -0
  51. package/dist/sdk/src/styles.css +96 -72
  52. package/dist/sdk/src/vite.ts +11 -1
  53. package/dist/skill-sync/index.js +571 -80
  54. package/dist/skill-sync/index.js.map +4 -4
  55. package/dist/skill-sync-worker.mjs +858 -233
  56. package/dist/space-harness/host.js +66 -0
  57. package/dist/view-renderer/dist/host.css +1 -0
  58. package/dist/view-renderer/dist/host.js +1028 -0
  59. package/dist/view-renderer/dist/theme.css +73 -0
  60. package/dist/view-renderer/src/dom.js +180 -0
  61. package/dist/view-renderer/src/errors.js +8 -0
  62. package/dist/view-renderer/src/host.tsx +175 -0
  63. package/dist/view-renderer/src/index.js +4 -0
  64. package/dist/view-renderer/src/pool.js +76 -0
  65. package/dist/view-renderer/src/remote-broker.js +59 -0
  66. package/dist/view-renderer/src/renderer.js +201 -0
  67. package/package.json +16 -5
  68. package/skills/notis-apps/cli.md +323 -163
  69. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +3 -3
  70. package/skills/notis-onboarding/BRIEF.md +7 -3
  71. package/src/command-specs/apps.js +2 -1
  72. package/src/command-specs/auth.js +2 -2
  73. package/src/command-specs/files.js +29 -0
  74. package/src/command-specs/handover.js +331 -79
  75. package/src/command-specs/helpers.js +2 -0
  76. package/src/command-specs/index.js +11 -5
  77. package/src/command-specs/profile.js +42 -1
  78. package/src/command-specs/skills.js +242 -1
  79. package/src/command-specs/spaces.js +645 -0
  80. package/src/command-specs/tools.js +0 -6
  81. package/src/command-specs/views.js +69 -0
  82. package/src/runtime/agent-browser.js +92 -29
  83. package/src/runtime/app-boundary-validator.js +7 -4
  84. package/src/runtime/app-data-literals.js +82 -0
  85. package/src/runtime/app-platform.js +321 -253
  86. package/src/runtime/computer-identity.d.ts +30 -0
  87. package/src/runtime/computer-identity.js +151 -0
  88. package/src/runtime/config-lock.js +135 -0
  89. package/src/runtime/confined-space-build.js +74 -0
  90. package/src/runtime/confined-space-verify.js +49 -0
  91. package/src/runtime/delegated-context.js +5 -21
  92. package/src/runtime/native-record-files.js +47 -0
  93. package/src/runtime/oauth.js +18 -1
  94. package/src/runtime/profiles.js +5 -107
  95. package/src/runtime/retired-sdk-files.js +20 -0
  96. package/src/runtime/screenshot-framing.js +85 -0
  97. package/src/runtime/skill-sync/cloud-client.ts +26 -4
  98. package/src/runtime/skill-sync/index.ts +230 -35
  99. package/src/runtime/skill-sync/local-scanner.ts +188 -29
  100. package/src/runtime/skill-sync/native-identity.ts +102 -0
  101. package/src/runtime/skill-sync/native-plan.ts +70 -0
  102. package/src/runtime/skill-sync/sync-plan.ts +16 -9
  103. package/src/runtime/skill-sync/types.ts +32 -6
  104. package/src/runtime/source-root.js +32 -0
  105. package/src/runtime/space-collection-scaffold.js +66 -0
  106. package/src/runtime/space-database-structures.js +229 -0
  107. package/src/runtime/space-fixture-broker.js +50 -0
  108. package/src/runtime/space-lock.js +337 -0
  109. package/src/runtime/space-platform.js +105 -0
  110. package/src/runtime/space-source.js +281 -0
  111. package/src/runtime/space-test-server.js +327 -0
  112. package/src/runtime/space-verification.js +89 -0
  113. package/src/runtime/space-verify-browser-worker.js +51 -0
  114. package/src/runtime/space-view-manifest.js +254 -0
  115. package/src/runtime/store-screenshot.js +10 -7
  116. package/src/runtime/sync-skills.d.ts +3 -1
  117. package/src/runtime/sync-skills.js +10 -2
  118. package/src/runtime/transport.js +8 -6
  119. package/src/runtime/view-render.js +49 -0
  120. package/src/skill-sync-worker.js +14 -2
  121. package/src/space-harness-host.ts +103 -0
  122. package/template/.harness/index.html.tmpl +7 -10
  123. package/template/app/globals.css +1 -0
  124. package/src/command-specs/reports.js +0 -97
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+ import * as ReactDOM from 'react-dom';
3
+ import * as ReactDOMClient from 'react-dom/client';
4
+ import * as JSXRuntime from 'react/jsx-runtime';
5
+ import { importBundleSource, resolveBundleRouteExport } from '../../sdk/src/bundleModule';
6
+ import { SdkPresentation } from '../../sdk/src/presentation';
7
+ import { createPrefetchQueue, createQueryClient } from '../../sdk/src/queryCache';
8
+ import type { NotisRuntime } from '../../sdk/src/runtime';
9
+ import { SPACE_VIEWER_READ_OPERATIONS } from '../../sdk/src/space';
10
+ import type { SpaceActionOptions, SpaceDocumentBodyRequest, SpaceViewerReadOperation } from '../../sdk/src/space';
11
+
12
+ type Call = { op: string; actionId?: string; ok: boolean | null; error?: string };
13
+ const state = { mounted: false, renderStarted: false, errors: [] as Array<{ phase: string; message: string }>, runtimeCalls: [] as Call[] };
14
+ Object.assign(window, { __harness: state, React, ReactDOM, ReactDOMClient, __ReactJSXRuntime: JSXRuntime });
15
+ const diagnostic = (phase: string, reason: unknown) => state.errors.push({ phase,
16
+ message: reason instanceof Error ? reason.message : String(reason || 'Verification failed.') });
17
+ window.addEventListener('error', event => diagnostic('window_error', event.error || event.message));
18
+ window.addEventListener('unhandledrejection', event => diagnostic('unhandled_rejection', event.reason));
19
+ window.addEventListener('securitypolicyviolation', event => diagnostic('security_policy', `Blocked ${event.violatedDirective}`));
20
+
21
+ async function start() {
22
+ const response = await fetch('/snapshot', { credentials: 'omit' });
23
+ if (!response.ok) throw new Error('The frozen verification snapshot could not be loaded.');
24
+ const snapshot = await response.json();
25
+ const authority = structuredClone(snapshot.descriptor.space);
26
+ const runFixture = async (call: Call, input: unknown) => {
27
+ state.runtimeCalls.push(call);
28
+ try {
29
+ const result = await fetch('/fixture', { method: 'POST', credentials: 'omit',
30
+ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input), signal: AbortSignal.timeout(5_000) });
31
+ const payload = await result.json();
32
+ if (!result.ok) throw new Error(payload.message || 'No matching synthetic fixture.');
33
+ call.ok = true; return payload.result;
34
+ } catch (error) {
35
+ call.ok = false; call.error = error instanceof Error ? error.message : 'Verification request failed.';
36
+ throw error;
37
+ }
38
+ };
39
+ const protocolFailure = (message: string): Promise<never> => { diagnostic('protocol', message); return Promise.reject(new Error(message)); };
40
+ const deny = () => protocolFailure('Use a declared Space action.');
41
+ const queue = createPrefetchQueue(2);
42
+ const runtime: NotisRuntime = {
43
+ ...snapshot.descriptor, databases: [],
44
+ context: { ...snapshot.descriptor.context,
45
+ resourceId: snapshot.descriptor.context?.resource_id ?? snapshot.descriptor.context?.resourceId ?? null,
46
+ collectionItem: snapshot.descriptor.context?.collection_item ?? snapshot.descriptor.context?.collectionItem ?? null },
47
+ queryClient: createQueryClient({ maxEntries: 100, schedule: queue }),
48
+ listTools: deny, callTool: deny, request: deny,
49
+ callAction<T>(id: string, inputs: Record<string, unknown> = {}, options: SpaceActionOptions = {}): Promise<T> {
50
+ if (!Object.hasOwn(authority.actions, id)) return protocolFailure('Undeclared verification action.');
51
+ if ((options.readOnly || options.dedupe) && authority.actions[id].readOnly !== true) {
52
+ return protocolFailure('A write action cannot run as a cached read.');
53
+ }
54
+ return runFixture({ op: 'callAction', actionId: id, ok: null }, { action_id: id, inputs });
55
+ },
56
+ viewerRead: authority.viewerReads?.length ? <T,>(operation: SpaceViewerReadOperation, input: Record<string, unknown> = {}): Promise<T> => {
57
+ const family = Object.hasOwn(SPACE_VIEWER_READ_OPERATIONS, operation) ? SPACE_VIEWER_READ_OPERATIONS[operation] : null;
58
+ if (!family || !authority.viewerReads.includes(family)) return protocolFailure('Undeclared verification viewer read.');
59
+ return runFixture({ op: 'viewerRead', actionId: operation, ok: null }, { viewer_read: { operation, input } });
60
+ } : undefined,
61
+ shown: Object.values(authority.shows || {}).some(list => 'database' in list) ? <T,>(name: string, params: Record<string, unknown> = {}): Promise<T> => {
62
+ const list = authority.shows && Object.hasOwn(authority.shows, name) ? authority.shows[name] : null;
63
+ if (!list || !('database' in list)) return protocolFailure('Undeclared verification list.');
64
+ return runFixture({ op: 'shown', actionId: name, ok: null }, { shown: { name, params } });
65
+ } : undefined,
66
+ documentBody(request: SpaceDocumentBodyRequest) {
67
+ return runFixture({ op: 'documentBody', actionId: request?.readAction, ok: null }, { document_body: request });
68
+ },
69
+ navigate(payload) {
70
+ if (payload.kind !== 'space-reference' || typeof payload.alias !== 'string' || !authority.navigation?.includes(payload.alias)) {
71
+ return protocolFailure('Offline navigation requires a declared synthetic destination.');
72
+ }
73
+ // This observes a declaration only; no live navigation or access is claimed.
74
+ state.runtimeCalls.push({ op: 'navigate', actionId: payload.alias, ok: true });
75
+ },
76
+ publishActiveResource() {}, captureContextSelection() {},
77
+ addContext: async () => false, updateContext: async () => false, removeContext: async () => false,
78
+ registerTopBarSearch() {}, setTopBarSearchValue() {}, setTopBarSearchLoading() {},
79
+ };
80
+ const module = await importBundleSource(snapshot.source);
81
+ const view = resolveBundleRouteExport(module, ['SpaceView'], true);
82
+ if (!view) throw new Error('The SpaceView export is missing from this presentation.');
83
+ const host = document.createElement('div'); host.style.cssText = 'min-height:100vh;height:100vh';
84
+ const shadow = host.attachShadow({ mode: 'open' });
85
+ const css = document.createElement('style'); css.textContent = snapshot.css; shadow.appendChild(css);
86
+ const mount = document.createElement('div'); mount.setAttribute('data-notis-app-mount', 'true');
87
+ mount.style.cssText = 'min-height:0;height:100%'; shadow.appendChild(mount);
88
+ document.body.appendChild(host);
89
+ const controller = document.createElement('div'); document.body.appendChild(controller);
90
+ const mode = new URLSearchParams(location.search).get('theme') === 'dark' ? 'dark' : 'light';
91
+ document.documentElement.classList.toggle('dark', mode === 'dark');
92
+ state.renderStarted = true;
93
+ ReactDOMClient.createRoot(controller).render(React.createElement(SdkPresentation, {
94
+ mount, runtime, component: view.component, shell: module.__AppShell, theme: { mode, style: { colorScheme: mode } },
95
+ resetKey: snapshot.cacheKey,
96
+ onCommit: (hasContent: boolean) => { state.mounted = hasContent; },
97
+ onError: (error: Error) => { state.mounted = false; state.errors.push({ phase: 'render', message: error.message }); },
98
+ onProtocolError: (error: Error) => diagnostic('protocol', error),
99
+ fallback: (error: Error) => React.createElement('div', { role: 'alert' }, error.message),
100
+ }));
101
+ }
102
+
103
+ void start().catch(reason => state.errors.push({ phase: 'boot', message: reason instanceof Error ? reason.message : 'Verification did not start.' }));
@@ -93,12 +93,10 @@
93
93
  color: hsl(var(--foreground));
94
94
  -webkit-font-smoothing: antialiased;
95
95
  }
96
- #harness-status { position: fixed; top: 0; left: 0; right: 0; padding: 4px 8px; font-size: 11px; background: #111; color: #0f0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; z-index: 9999; }
97
- #root { padding-top: 24px; min-height: 100vh; }
96
+ #root { min-height: 100vh; }
98
97
  </style>
99
98
  </head>
100
99
  <body>
101
- <div id="harness-status">harness: booting</div>
102
100
  <div id="root" data-notis-app-root></div>
103
101
  <script type="module">
104
102
  const routeExport = {{ROUTE_EXPORT}};
@@ -134,10 +132,9 @@
134
132
  return Object.prototype.hasOwnProperty.call(tools, name) ? tools[name] : undefined;
135
133
  }
136
134
 
137
- const status = document.getElementById('harness-status');
138
- const setStatus = (msg, color = '#0f0') => {
139
- status.textContent = `harness: ${msg}`;
140
- status.style.color = color;
135
+ // Boot progress goes to the console so the rendered page stays identical to the app.
136
+ const setStatus = (msg, failed = false) => {
137
+ (failed ? console.error : console.info)(`[notis harness] ${msg}`);
141
138
  };
142
139
 
143
140
  window.__harness = {
@@ -416,13 +413,13 @@
416
413
  window.__harness.mounted = true;
417
414
  const errors = window.__harness.errors.length;
418
415
  const calls = window.__harness.runtimeCalls.length;
419
- setStatus(`mounted (errors=${errors}, runtimeCalls=${calls})`, errors ? '#f55' : '#0f0');
416
+ setStatus(`mounted (errors=${errors}, runtimeCalls=${calls})`, errors > 0);
420
417
  } catch (error) {
421
418
  window.__harness.errors.push({
422
419
  type: 'scenario',
423
420
  message: error && error.message ? error.message : String(error),
424
421
  });
425
- setStatus(`scenario failed: ${error && error.message ? error.message : String(error)}`, '#f55');
422
+ setStatus(`scenario failed: ${error && error.message ? error.message : String(error)}`, true);
426
423
  }
427
424
  }));
428
425
  } catch (error) {
@@ -431,7 +428,7 @@
431
428
  message: error && error.message ? error.message : String(error),
432
429
  stack: error && error.stack,
433
430
  });
434
- setStatus(`boot failed: ${error && error.message ? error.message : String(error)}`, '#f55');
431
+ setStatus(`boot failed: ${error && error.message ? error.message : String(error)}`, true);
435
432
  }
436
433
  </script>
437
434
  </body>
@@ -3,6 +3,7 @@
3
3
  @config "../tailwind.config.ts";
4
4
  @source "./";
5
5
  @source "../components";
6
+ @source "../spaces";
6
7
  @source "../lib";
7
8
  @source "../packages/sdk/src";
8
9
 
@@ -1,97 +0,0 @@
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
- import { nextIdempotencyKey, runToolCommand } from './helpers.js';
6
- import { usageError, EXIT_CODES } from '../runtime/errors.js';
7
-
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'}`],
48
- handler: async ctx => {
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 } : {}) } });
52
- },
53
- };
54
- };
55
- export const reportsCommandSpecs = [
56
- localReportCommand('init'), localReportCommand('build'), localReportCommand('verify'), localReportCommand('verify', 'preview'),
57
- {
58
- command_path: ['reports', 'save'],
59
- summary: 'Build, verify and save a standalone live report document.',
60
- when_to_use: 'Persist an independently owned report, not an app release.',
61
- args_schema: {
62
- arguments: [{ token: '[dir]', key: 'dir', description: 'Report source directory.' }],
63
- options: [
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.' },
67
- { flags: '--context-file <file>', description: 'Required. UTF-8 readable report content and structure.' },
68
- ],
69
- },
70
- examples: ['notis reports save ./weekly-report --title \"Weekly review\" --context-file ./context.md'], mutates: true, idempotent: true,
71
- backend_call: { type: 'tool', name: 'LOCAL_NOTIS_SAVE_REPORT' },
72
- async handler(ctx) {
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.');
76
- const context = readFileSync(resolve(ctx.options.contextFile), 'utf8');
77
- await buildArtifact(dir, { stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit' });
78
- const release = prepareAppRelease(dir);
79
- try {
80
- if (release.manifest.routes?.length !== 1) throw usageError('Reports require exactly one SDK route.');
81
- let verification;
82
- const exit = await appsVerifyHandler({ ...ctx, args: { dir: release.projectDir }, options: { skipBuild: true, mode: 'stub' }, output: { ...ctx.output, isMachineMode: () => true, emitSuccess: value => { verification = value; } } });
83
- if (exit !== EXIT_CODES.ok || verification?.data?.status !== 'passed') throw usageError('Report verification failed; nothing saved.');
84
- const files = { ...release.files, ...Object.fromEntries(Object.entries(release.sourceFiles).map(([path, data]) => [`source/${path}`, data])) };
85
- const result = await runToolCommand({ runtime: { ...ctx.runtime, timeoutMs: Math.max(ctx.runtime.timeoutMs || 0, 90000) }, toolName: 'LOCAL_NOTIS_SAVE_REPORT', mutating: true,
86
- idempotencyKey: nextIdempotencyKey(ctx.globalOptions), arguments_: {
87
- operation: ctx.options.documentId ? 'update' : 'create',
88
- title: ctx.options.title,
89
- ...(ctx.options.documentId ? { document_id: ctx.options.documentId, expected_revision: Number(ctx.options.expectedRevision) } : {}),
90
- report: { schema: 'notis-report/v3', context, artifact: { manifest: release.manifest, files, encoding: 'base64' } },
91
- } });
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.');
93
- return ctx.output.emitSuccess({ command: 'reports save', data: result.payload });
94
- } finally { release.close(); }
95
- },
96
- },
97
- ];