@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.
- package/README.md +548 -98
- package/config/portal-route-roots.json +47 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +32901 -5934
- package/dist/base-skills/notis-apps/SKILL.md +76 -51
- package/dist/base-skills/notis-apps/references/architecture.md +116 -164
- package/dist/base-skills/notis-apps/references/design.md +26 -107
- package/dist/base-skills/notis-apps/references/memory.md +58 -0
- package/dist/base-skills/notis-apps/references/reading.md +52 -74
- package/dist/base-skills/notis-apps/references/release.md +105 -96
- package/dist/base-skills/notis-apps/references/sdk.md +115 -60
- package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
- package/dist/base-skills/notis-apps/references/views.md +145 -0
- package/dist/base-skills/notis-cli/SKILL.md +29 -12
- package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
- package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +6 -5
- package/dist/base-skills/notis-cli/references/tool-examples.md +4 -4
- package/dist/base-skills/notis-query/SKILL.md +34 -60
- package/dist/base-skills/notis-query/references/database-discovery.md +60 -59
- package/dist/base-skills/notis-query/references/documents.md +53 -39
- package/dist/base-skills/notis-query/references/query.md +57 -524
- package/dist/sdk/src/bundleModule.ts +79 -0
- package/dist/sdk/src/components/DocumentEditor.tsx +6 -3
- package/dist/sdk/src/components/DocumentPage.tsx +144 -0
- package/dist/sdk/src/components/RecordComponents.tsx +33 -0
- package/dist/sdk/src/components/RenderChartData.tsx +19 -0
- package/dist/sdk/src/config.ts +4 -26
- package/dist/sdk/src/documents.ts +2 -0
- package/dist/sdk/src/hooks/useAction.ts +28 -0
- package/dist/sdk/src/hooks/useBackend.ts +3 -2
- package/dist/sdk/src/hooks/useCloudComputer.ts +35 -4
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/dist/sdk/src/hooks/useCollectionInteractions.ts +4 -2
- package/dist/sdk/src/hooks/useHandover.ts +7 -7
- package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
- package/dist/sdk/src/hooks/useNativeDocuments.ts +83 -0
- package/dist/sdk/src/hooks/useNotis.ts +3 -0
- package/dist/sdk/src/hooks/useNotisNavigation.ts +26 -11
- package/dist/sdk/src/hooks/useQuery.ts +7 -4
- package/dist/sdk/src/hooks/useViewParams.ts +121 -0
- package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
- package/dist/sdk/src/index.ts +36 -0
- package/dist/sdk/src/interactions/shortcuts.tsx +42 -5
- package/dist/sdk/src/nativeData.ts +39 -0
- package/dist/sdk/src/presentation.tsx +82 -0
- package/dist/sdk/src/provider.tsx +9 -4
- package/dist/sdk/src/queryCache.ts +5 -3
- package/dist/sdk/src/queryErrors.ts +51 -0
- package/dist/sdk/src/runtime.ts +163 -13
- package/dist/sdk/src/space.ts +425 -0
- package/dist/sdk/src/styles.css +96 -72
- package/dist/sdk/src/vite.ts +11 -1
- package/dist/skill-sync/index.js +571 -80
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +858 -233
- package/dist/space-harness/host.js +66 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +1028 -0
- package/dist/view-renderer/dist/theme.css +73 -0
- package/dist/view-renderer/src/dom.js +180 -0
- package/dist/view-renderer/src/errors.js +8 -0
- package/dist/view-renderer/src/host.tsx +175 -0
- package/dist/view-renderer/src/index.js +4 -0
- package/dist/view-renderer/src/pool.js +76 -0
- package/dist/view-renderer/src/remote-broker.js +59 -0
- package/dist/view-renderer/src/renderer.js +201 -0
- package/package.json +16 -5
- package/skills/notis-apps/cli.md +323 -163
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +3 -3
- package/skills/notis-onboarding/BRIEF.md +7 -3
- package/src/command-specs/apps.js +2 -1
- package/src/command-specs/auth.js +2 -2
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/handover.js +331 -79
- package/src/command-specs/helpers.js +2 -0
- package/src/command-specs/index.js +11 -5
- package/src/command-specs/profile.js +42 -1
- package/src/command-specs/skills.js +242 -1
- package/src/command-specs/spaces.js +645 -0
- package/src/command-specs/tools.js +0 -6
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +92 -29
- package/src/runtime/app-boundary-validator.js +7 -4
- package/src/runtime/app-data-literals.js +82 -0
- package/src/runtime/app-platform.js +321 -253
- package/src/runtime/computer-identity.d.ts +30 -0
- package/src/runtime/computer-identity.js +151 -0
- package/src/runtime/config-lock.js +135 -0
- package/src/runtime/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/delegated-context.js +5 -21
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/oauth.js +18 -1
- package/src/runtime/profiles.js +5 -107
- package/src/runtime/retired-sdk-files.js +20 -0
- package/src/runtime/screenshot-framing.js +85 -0
- package/src/runtime/skill-sync/cloud-client.ts +26 -4
- package/src/runtime/skill-sync/index.ts +230 -35
- package/src/runtime/skill-sync/local-scanner.ts +188 -29
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +70 -0
- package/src/runtime/skill-sync/sync-plan.ts +16 -9
- package/src/runtime/skill-sync/types.ts +32 -6
- package/src/runtime/source-root.js +32 -0
- package/src/runtime/space-collection-scaffold.js +66 -0
- package/src/runtime/space-database-structures.js +229 -0
- package/src/runtime/space-fixture-broker.js +50 -0
- package/src/runtime/space-lock.js +337 -0
- package/src/runtime/space-platform.js +105 -0
- package/src/runtime/space-source.js +281 -0
- package/src/runtime/space-test-server.js +327 -0
- package/src/runtime/space-verification.js +89 -0
- package/src/runtime/space-verify-browser-worker.js +51 -0
- package/src/runtime/space-view-manifest.js +254 -0
- package/src/runtime/store-screenshot.js +10 -7
- package/src/runtime/sync-skills.d.ts +3 -1
- package/src/runtime/sync-skills.js +10 -2
- package/src/runtime/transport.js +8 -6
- package/src/runtime/view-render.js +49 -0
- package/src/skill-sync-worker.js +14 -2
- package/src/space-harness-host.ts +103 -0
- package/template/.harness/index.html.tmpl +7 -10
- package/template/app/globals.css +1 -0
- 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
|
-
#
|
|
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
|
-
|
|
138
|
-
const setStatus = (msg,
|
|
139
|
-
|
|
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
|
|
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)}`,
|
|
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)}`,
|
|
431
|
+
setStatus(`boot failed: ${error && error.message ? error.message : String(error)}`, true);
|
|
435
432
|
}
|
|
436
433
|
</script>
|
|
437
434
|
</body>
|
package/template/app/globals.css
CHANGED
|
@@ -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
|
-
];
|