@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.
- package/README.md +25 -39
- package/dist/agent-hooks/notis-agent-hook.mjs +197 -155
- package/{template/packages → dist}/sdk/src/config.ts +4 -0
- package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +3 -1
- package/{template/packages → dist}/sdk/src/index.ts +1 -0
- package/{template/packages → dist}/sdk/src/runtime.ts +13 -0
- package/{template/packages → dist}/sdk/src/styles.css +72 -74
- package/package.json +1 -1
- package/skills/notis-apps/cli.md +23 -37
- package/src/command-specs/apps.js +18 -14
- package/src/command-specs/diagnostics.js +15 -14
- package/src/command-specs/reports.js +61 -50
- package/src/runtime/agent-browser.js +10 -5
- package/src/runtime/app-platform.js +27 -3
- package/src/runtime/app-test-server.js +2 -1
- package/template/.harness/index.html.tmpl +12 -4
- package/template/packages/sdk/tsconfig.json +0 -16
- /package/{template/packages → dist}/sdk/package.json +0 -0
- /package/{template/packages → dist}/sdk/src/agentContext.ts +0 -0
- /package/{template/packages → dist}/sdk/src/components/Dialog.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/DocumentEditor.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/Markdown.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/NotisCommentBoundary.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/Skeleton.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/documents.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useAgentContext.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useCloudComputer.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useDatabaseSchema.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useDocument.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useLongPressSelection.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useNotisNavigation.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useQuery.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useToolQuery.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTopBarSearch.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +0 -0
- /package/{template/packages → dist}/sdk/src/interactions/actions.ts +0 -0
- /package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/interactions/visibility.ts +0 -0
- /package/{template/packages → dist}/sdk/src/interactions.ts +0 -0
- /package/{template/packages → dist}/sdk/src/provider.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/queryCache.ts +0 -0
- /package/{template/packages → dist}/sdk/src/tailwind.ts +0 -0
- /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
- /package/{template/packages → dist}/sdk/src/vite.ts +0 -0
|
@@ -196,6 +196,10 @@ export interface NotisAppToolBinding {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
export interface NotisAppConfig {
|
|
199
|
+
/** Standalone reports share the view engine without app installation. */
|
|
200
|
+
kind?: 'app' | 'report';
|
|
201
|
+
/** Existing data sources, never owned or materialized by a report. */
|
|
202
|
+
databaseAccess?: Array<{ id: string; access: 'read' | 'write' }>;
|
|
199
203
|
/** URL-safe app slug. Existing apps may still use a display name here. */
|
|
200
204
|
name: string;
|
|
201
205
|
/** Human display title, Raycast-style. Falls back to `name`. */
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useNotisRuntime } from '../provider';
|
|
4
|
-
import type { AppDescriptor, CollectionItemDetail, DatabaseDescriptor, RouteDescriptor } from '../runtime';
|
|
4
|
+
import type { RuntimeResource, AppDescriptor, CollectionItemDetail, DatabaseDescriptor, RouteDescriptor } from '../runtime';
|
|
5
5
|
|
|
6
6
|
interface NotisContext {
|
|
7
|
+
resource: RuntimeResource | null;
|
|
7
8
|
/** App metadata (id, name, icon, description). Null before runtime loads. */
|
|
8
9
|
app: AppDescriptor | null;
|
|
9
10
|
/** Current route descriptor. Null before runtime loads. */
|
|
@@ -27,6 +28,7 @@ export function useNotis(): NotisContext {
|
|
|
27
28
|
const runtime = useNotisRuntime();
|
|
28
29
|
|
|
29
30
|
return {
|
|
31
|
+
resource: runtime?.resource ?? null,
|
|
30
32
|
app: runtime?.app ?? null,
|
|
31
33
|
route: runtime?.route ?? null,
|
|
32
34
|
databases: runtime?.databases ?? [],
|
|
@@ -364,7 +364,20 @@ export interface CloudComputerFacts {
|
|
|
364
364
|
cli_auth: { gh: CloudComputerCliAuthFacts };
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
export interface RuntimeResource {
|
|
368
|
+
kind: 'app' | 'report';
|
|
369
|
+
/** Host-provided adoption eligibility, never report-authored authority. */
|
|
370
|
+
analytics_eligible?: boolean;
|
|
371
|
+
id: string;
|
|
372
|
+
revision: number;
|
|
373
|
+
name?: string;
|
|
374
|
+
icon?: string | null;
|
|
375
|
+
description?: string | null;
|
|
376
|
+
}
|
|
377
|
+
|
|
367
378
|
export interface NotisRuntime {
|
|
379
|
+
/** Authenticated runtime identity; app below is presentation metadata only. */
|
|
380
|
+
resource?: RuntimeResource;
|
|
368
381
|
/** Optional host-scoped in-memory read cache. Older hosts remain supported. */
|
|
369
382
|
queryClient?: NotisQueryClient;
|
|
370
383
|
app: AppDescriptor;
|
|
@@ -30,93 +30,91 @@ dialog.notis-dialog::backdrop { background: rgb(0 0 0 / 0.45); }
|
|
|
30
30
|
.notis-dialog-description { margin: 0.5rem 0 0; color: hsl(var(--muted-foreground)); font-size: 0.875rem; line-height: 1.5; }
|
|
31
31
|
|
|
32
32
|
/* Framework-neutral styles: the app owns its Tailwind entry point. This file
|
|
33
|
-
* is also consumed in shadow roots and by apps on older Tailwind versions.
|
|
34
|
-
@layer
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
* is also consumed in shadow roots and by apps on older Tailwind versions.
|
|
34
|
+
* Keep rules outside Tailwind-owned @layer blocks: Vite processes this import
|
|
35
|
+
* separately from the app stylesheet, so Tailwind 3 has no matching directives. */
|
|
36
|
+
* {
|
|
37
|
+
border-color: hsl(var(--border));
|
|
38
|
+
}
|
|
39
|
+
[data-notis-app-root] {
|
|
40
|
+
color: hsl(var(--foreground));
|
|
41
|
+
display: block;
|
|
42
|
+
min-height: 100%;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
45
|
+
.notis-app-shell {
|
|
46
|
+
margin-inline: auto;
|
|
47
|
+
width: 100%;
|
|
48
|
+
max-width: var(--portal-page-max-width);
|
|
49
|
+
padding: 1.5rem 1rem;
|
|
50
|
+
}
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
/* Flat panel: no border or shadow around items. */
|
|
53
|
+
.notis-app-surface {
|
|
54
|
+
border-radius: 1rem;
|
|
55
|
+
background-color: hsl(var(--muted));
|
|
56
|
+
color: hsl(var(--card-foreground));
|
|
57
|
+
}
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
.list-row {
|
|
60
|
+
border-radius: 0.75rem;
|
|
61
|
+
background-color: hsl(var(--muted));
|
|
62
|
+
padding: 0.75rem 1rem;
|
|
63
|
+
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
|
64
|
+
transition-duration: 150ms;
|
|
65
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
66
|
+
}
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
.list-row-selected {
|
|
69
|
+
background-color: hsl(var(--primary) / 0.06);
|
|
70
|
+
}
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
/* Split pages are full-bleed; only the list pane carries a hairline. */
|
|
73
|
+
.notis-app-split {
|
|
74
|
+
display: flex;
|
|
75
|
+
height: 100%;
|
|
76
|
+
min-height: 0;
|
|
77
|
+
width: 100%;
|
|
78
|
+
max-width: none;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
}
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
.notis-app-pane-list {
|
|
83
|
+
width: 100%;
|
|
84
|
+
flex-shrink: 0;
|
|
85
|
+
background-color: hsl(var(--muted) / 0.4);
|
|
86
|
+
}
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
.notis-app-pane-detail {
|
|
89
|
+
min-width: 0;
|
|
90
|
+
flex: 1 1 0%;
|
|
91
|
+
background-color: hsl(var(--background));
|
|
92
|
+
}
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
.notis-app-section {
|
|
95
|
+
padding: 1.25rem;
|
|
96
|
+
}
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
@media (min-width: 768px) {
|
|
99
|
+
.notis-app-shell { padding-block: 2rem; }
|
|
100
|
+
.notis-app-section { padding: 1.5rem; }
|
|
101
|
+
}
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
103
|
+
@media (min-width: 1024px) {
|
|
104
|
+
.list-row { background-color: transparent; }
|
|
105
|
+
.list-row:hover { background-color: hsl(var(--muted) / 0.6); }
|
|
106
|
+
.list-row-selected,
|
|
107
|
+
.list-row-selected:hover { background-color: hsl(var(--primary) / 0.06); }
|
|
108
|
+
.notis-app-split { flex-direction: row; }
|
|
109
|
+
.notis-app-pane-list {
|
|
110
|
+
width: 20rem;
|
|
111
|
+
border-right-width: 1px;
|
|
112
|
+
border-right-color: hsl(var(--border));
|
|
115
113
|
}
|
|
114
|
+
}
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
116
|
+
@media (min-width: 1280px) {
|
|
117
|
+
.notis-app-pane-list { width: 24rem; }
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
/*
|
package/package.json
CHANGED
package/skills/notis-apps/cli.md
CHANGED
|
@@ -28,7 +28,7 @@ change the package release label, check resource compatibility and deploy as a n
|
|
|
28
28
|
|
|
29
29
|
## Reports
|
|
30
30
|
|
|
31
|
-
For a
|
|
31
|
+
For a standalone live report, use `reports init → build → verify/preview → save` instead of the app deployment workflow above. Author exactly one route with kind: report, declare its tools, and supply a readable context file. No installed app or database is required. Revisions preserve the record ID and require its freshly read revision. See the product `notis-reports` skill for source recovery, ownership and readback. These commands do not deploy an app or publish a Store listing.
|
|
32
32
|
|
|
33
33
|
## Commands
|
|
34
34
|
|
|
@@ -229,85 +229,71 @@ Examples:
|
|
|
229
229
|
|
|
230
230
|
### `npx --package @notis_ai/cli@latest -- notis reports init <name> [dir]`
|
|
231
231
|
|
|
232
|
-
Init a
|
|
232
|
+
Init a standalone live SDK report.
|
|
233
233
|
|
|
234
|
-
When to use: Author
|
|
235
|
-
|
|
236
|
-
Options:
|
|
237
|
-
- `--from <slug>` — Start from a published Store app listed by `notis apps scaffolds list`. Downloads its source from the public app registry.
|
|
234
|
+
When to use: Author a private standalone document using the shared view runtime; no app or database is required.
|
|
238
235
|
|
|
239
236
|
Examples:
|
|
240
|
-
- `npx --package @notis_ai/cli@latest -- notis
|
|
241
|
-
- `npx --package @notis_ai/cli@latest -- notis reports init "Mind the Flo"`
|
|
242
|
-
- `npx --package @notis_ai/cli@latest -- notis reports init "My CRM" --from databases`
|
|
243
|
-
- `npx --package @notis_ai/cli@latest -- notis reports init "My App" ~/code/my-app`
|
|
237
|
+
- `npx --package @notis_ai/cli@latest -- notis reports init "Weekly report" ./weekly-report`
|
|
244
238
|
|
|
245
239
|
### `npx --package @notis_ai/cli@latest -- notis reports build [dir]`
|
|
246
240
|
|
|
247
|
-
Build a
|
|
241
|
+
Build a standalone live SDK report.
|
|
248
242
|
|
|
249
|
-
When to use: Author
|
|
243
|
+
When to use: Author a private standalone document using the shared view runtime; no app or database is required.
|
|
250
244
|
|
|
251
245
|
Examples:
|
|
252
|
-
- `npx --package @notis_ai/cli@latest -- notis reports build`
|
|
253
|
-
- `npx --package @notis_ai/cli@latest -- notis reports build ./my-app`
|
|
246
|
+
- `npx --package @notis_ai/cli@latest -- notis reports build ./weekly-report`
|
|
254
247
|
|
|
255
248
|
### `npx --package @notis_ai/cli@latest -- notis reports verify [dir]`
|
|
256
249
|
|
|
257
|
-
Verify a
|
|
250
|
+
Verify a standalone live SDK report.
|
|
258
251
|
|
|
259
|
-
When to use: Author
|
|
252
|
+
When to use: Author a private standalone document using the shared view runtime; no app or database is required.
|
|
260
253
|
|
|
261
254
|
Options:
|
|
262
255
|
- `--routes <slugs>` — Comma-separated route slugs. Default: every route in manifest.
|
|
263
256
|
- `--port <n>` — Loopback port. Default: auto-pick.
|
|
264
257
|
- `--skip-build` — Skip notis apps build; reuse existing .notis/output/.
|
|
265
258
|
- `--mode <mode>` — stub | live. Default stub. Live posts to /portal_views/runtime_query with the CLI JWT and fails routes whose runtime calls all errored.
|
|
266
|
-
- `--listing` — Ignored for reports; saving a report does not publish a Store listing.
|
|
267
259
|
- `--no-browser` — Start the harness server and print URLs; do not drive agent-browser.
|
|
268
260
|
- `--keep-open` — Leave server + browser session running after report (for manual triage).
|
|
261
|
+
- `--document-id <id>` — Saved report for live verification.
|
|
262
|
+
- `--expected-revision <revision>` — Saved report revision for live verification.
|
|
269
263
|
|
|
270
264
|
Examples:
|
|
271
|
-
- `npx --package @notis_ai/cli@latest -- notis reports verify`
|
|
272
|
-
- `npx --package @notis_ai/cli@latest -- notis reports verify --routes notes`
|
|
273
|
-
- `npx --package @notis_ai/cli@latest -- notis reports verify --mode live`
|
|
274
|
-
- `npx --package @notis_ai/cli@latest -- notis reports verify --no-browser # start the harness, drive agent-browser yourself`
|
|
265
|
+
- `npx --package @notis_ai/cli@latest -- notis reports verify ./weekly-report`
|
|
275
266
|
|
|
276
267
|
### `npx --package @notis_ai/cli@latest -- notis reports preview [dir]`
|
|
277
268
|
|
|
278
|
-
Preview a
|
|
269
|
+
Preview a standalone live SDK report.
|
|
279
270
|
|
|
280
|
-
When to use: Author
|
|
271
|
+
When to use: Author a private standalone document using the shared view runtime; no app or database is required.
|
|
281
272
|
|
|
282
273
|
Options:
|
|
283
274
|
- `--routes <slugs>` — Comma-separated route slugs. Default: every route in manifest.
|
|
284
275
|
- `--port <n>` — Loopback port. Default: auto-pick.
|
|
285
276
|
- `--skip-build` — Skip notis apps build; reuse existing .notis/output/.
|
|
286
277
|
- `--mode <mode>` — stub | live. Default stub. Live posts to /portal_views/runtime_query with the CLI JWT and fails routes whose runtime calls all errored.
|
|
287
|
-
- `--listing` — Ignored for reports; saving a report does not publish a Store listing.
|
|
288
278
|
- `--no-browser` — Start the harness server and print URLs; do not drive agent-browser.
|
|
289
279
|
- `--keep-open` — Leave server + browser session running after report (for manual triage).
|
|
280
|
+
- `--document-id <id>` — Saved report for live verification.
|
|
281
|
+
- `--expected-revision <revision>` — Saved report revision for live verification.
|
|
290
282
|
|
|
291
283
|
Examples:
|
|
292
|
-
- `npx --package @notis_ai/cli@latest -- notis reports preview`
|
|
293
|
-
- `npx --package @notis_ai/cli@latest -- notis reports preview --routes notes`
|
|
294
|
-
- `npx --package @notis_ai/cli@latest -- notis reports preview --mode live`
|
|
295
|
-
- `npx --package @notis_ai/cli@latest -- notis reports preview --no-browser # start the harness, drive agent-browser yourself`
|
|
284
|
+
- `npx --package @notis_ai/cli@latest -- notis reports preview ./weekly-report`
|
|
296
285
|
|
|
297
286
|
### `npx --package @notis_ai/cli@latest -- notis reports save [dir]`
|
|
298
287
|
|
|
299
|
-
Build, verify and save a
|
|
288
|
+
Build, verify and save a standalone live report document.
|
|
300
289
|
|
|
301
|
-
When to use: Persist an independently
|
|
290
|
+
When to use: Persist an independently owned report, not an app release.
|
|
302
291
|
|
|
303
292
|
Options:
|
|
304
|
-
- `--
|
|
305
|
-
- `--
|
|
306
|
-
- `--
|
|
307
|
-
- `--expected-revision <revision>` — Fresh view revision (0 for a record without a view).
|
|
308
|
-
- `--title <title>` — Required, including updates. Record title.
|
|
293
|
+
- `--document-id <id>` — Existing report document to update.
|
|
294
|
+
- `--expected-revision <revision>` — Current saved report revision; required for updates.
|
|
295
|
+
- `--title <title>` — Required, including updates. Document title.
|
|
309
296
|
- `--context-file <file>` — Required. UTF-8 readable report content and structure.
|
|
310
|
-
- `--properties-file <file>` — JSON database property values keyed by name.
|
|
311
297
|
|
|
312
298
|
Examples:
|
|
313
|
-
- `npx --package @notis_ai/cli@latest -- notis reports save ./weekly-report --
|
|
299
|
+
- `npx --package @notis_ai/cli@latest -- notis reports save ./weekly-report --title "Weekly review" --context-file ./context.md`
|
|
@@ -276,6 +276,9 @@ function assertHarnessResult(result, route, databaseSlugs, mode = 'stub', capabi
|
|
|
276
276
|
});
|
|
277
277
|
}
|
|
278
278
|
if (mode === 'live') {
|
|
279
|
+
if (runtimeCalls.some(call => call?.ok == null)) {
|
|
280
|
+
assertions.push({ ok: false, code: 'runtime_calls_pending', message: 'Live verification ended before every started runtime call settled.' });
|
|
281
|
+
}
|
|
279
282
|
// In live mode an app that catches every failed call and renders its error
|
|
280
283
|
// state still mounts cleanly, so the render assertions above all pass. Only
|
|
281
284
|
// the recorded outcomes reveal that nothing real came back.
|
|
@@ -379,7 +382,7 @@ async function appsListHandler(ctx) {
|
|
|
379
382
|
});
|
|
380
383
|
}
|
|
381
384
|
|
|
382
|
-
async function appsInitHandler(ctx) {
|
|
385
|
+
export async function appsInitHandler(ctx) {
|
|
383
386
|
const projectDir = ctx.args.dir
|
|
384
387
|
? resolveProjectDir(ctx.args.dir)
|
|
385
388
|
: defaultAppProjectDir(slugify(ctx.args.name));
|
|
@@ -504,7 +507,7 @@ async function appsCreateHandler(ctx) {
|
|
|
504
507
|
});
|
|
505
508
|
}
|
|
506
509
|
|
|
507
|
-
async function appsBuildHandler(ctx) {
|
|
510
|
+
export async function appsBuildHandler(ctx) {
|
|
508
511
|
const projectDir = resolveProjectDir(ctx.args.dir || '.');
|
|
509
512
|
const problems = detectProjectProblems(projectDir);
|
|
510
513
|
if (problems.length) {
|
|
@@ -563,13 +566,15 @@ async function closeHarnessResources(sessionNames, testServer, rawOutputDir = nu
|
|
|
563
566
|
}
|
|
564
567
|
}
|
|
565
568
|
|
|
566
|
-
async function appsVerifyHandler(ctx) {
|
|
569
|
+
export async function appsVerifyHandler(ctx) {
|
|
567
570
|
const projectDir = resolveProjectDir(ctx.args.dir || '.');
|
|
568
571
|
const problems = detectProjectProblems(projectDir);
|
|
569
572
|
if (problems.length) {
|
|
570
573
|
throw usageError(`Project has problems:\n${problems.map((p) => ` - ${p}`).join('\n')}`);
|
|
571
574
|
}
|
|
572
575
|
|
|
576
|
+
const appConfig = await loadAppConfig(projectDir);
|
|
577
|
+
const isReport = appConfig.kind === 'report';
|
|
573
578
|
const mode = ctx.options.mode || 'stub';
|
|
574
579
|
if (!['stub', 'live'].includes(mode)) {
|
|
575
580
|
throw usageError('--mode must be either "stub" or "live".');
|
|
@@ -586,8 +591,10 @@ async function appsVerifyHandler(ctx) {
|
|
|
586
591
|
if (!ctx.runtime.jwt) {
|
|
587
592
|
throw usageError('Live verify mode requires CLI auth. Run notis login and retry.');
|
|
588
593
|
}
|
|
589
|
-
|
|
590
|
-
|
|
594
|
+
if (isReport) {
|
|
595
|
+
if (!ctx.options.documentId || !/^\d+$/.test(String(ctx.options.expectedRevision ?? ''))) throw usageError('Live report verification requires --document-id and --expected-revision of the saved report.');
|
|
596
|
+
} else linkedState = readLinkedState(projectDir, linkedStateProfileKey(ctx.runtime));
|
|
597
|
+
if (!isReport && !linkedState?.app_id) {
|
|
591
598
|
throw usageError('Live verify mode requires a linked app. Run `notis apps link <app-id> .` first.');
|
|
592
599
|
}
|
|
593
600
|
}
|
|
@@ -599,8 +606,7 @@ async function appsVerifyHandler(ctx) {
|
|
|
599
606
|
}
|
|
600
607
|
|
|
601
608
|
const manifest = readManifest(projectDir);
|
|
602
|
-
const
|
|
603
|
-
const listing = inspectListingReadiness(projectDir, appConfig);
|
|
609
|
+
const listing = isReport ? { ready: true, errors: [], warnings: [] } : inspectListingReadiness(projectDir, appConfig);
|
|
604
610
|
// Store readiness is a publish concern, not a render concern. Verify reports
|
|
605
611
|
// it so the gaps stay visible while the app is still being built; only
|
|
606
612
|
// --listing (and `apps publish`) turn it back into a hard gate.
|
|
@@ -609,7 +615,7 @@ async function appsVerifyHandler(ctx) {
|
|
|
609
615
|
}
|
|
610
616
|
const listingWarnings = [
|
|
611
617
|
...[...listing.errors, ...listing.warnings].map((message) => `Store readiness: ${message}`),
|
|
612
|
-
...findUnknownScreenshotScenarios(projectDir, resolveListingScreenshots(projectDir, appConfig)),
|
|
618
|
+
...(isReport ? [] : findUnknownScreenshotScenarios(projectDir, resolveListingScreenshots(projectDir, appConfig))),
|
|
613
619
|
];
|
|
614
620
|
const routes = routeSelection(manifest, parseRouteSlugs(ctx.options.routes));
|
|
615
621
|
const port = parsePort(ctx.options.port) || await getAvailablePort();
|
|
@@ -635,6 +641,7 @@ async function appsVerifyHandler(ctx) {
|
|
|
635
641
|
slug: appSlug,
|
|
636
642
|
projectDir,
|
|
637
643
|
appId: linkedState?.app_id || 'harness-app',
|
|
644
|
+
resource: isReport ? { kind: 'report', id: ctx.options.documentId || 'preview', revision: Number(ctx.options.expectedRevision || 0) } : undefined,
|
|
638
645
|
}],
|
|
639
646
|
port,
|
|
640
647
|
harness: {
|
|
@@ -708,7 +715,8 @@ async function appsVerifyHandler(ctx) {
|
|
|
708
715
|
const result = await runHarnessRoute({
|
|
709
716
|
url,
|
|
710
717
|
sessionName: browserSessionName,
|
|
711
|
-
timeoutMs: Number.parseInt(ctx.globalOptions.timeoutMs || '', 10) || 10_000,
|
|
718
|
+
timeoutMs: Number.parseInt(ctx.globalOptions.timeoutMs || '', 10) || (mode === 'live' ? 90_000 : 10_000),
|
|
719
|
+
waitForRuntime: mode === 'live',
|
|
712
720
|
snapshotPath,
|
|
713
721
|
});
|
|
714
722
|
const assertions = assertHarnessResult(
|
|
@@ -766,11 +774,7 @@ async function appsVerifyHandler(ctx) {
|
|
|
766
774
|
},
|
|
767
775
|
summary,
|
|
768
776
|
results,
|
|
769
|
-
listing: {
|
|
770
|
-
ready: listing.ready,
|
|
771
|
-
gated: ctx.options.listing === true,
|
|
772
|
-
problems: listing.errors,
|
|
773
|
-
},
|
|
777
|
+
...(!isReport ? { listing: { ready: listing.ready, gated: ctx.options.listing === true, problems: listing.errors } } : {}),
|
|
774
778
|
};
|
|
775
779
|
|
|
776
780
|
if (!keepOpen) await cleanup();
|
|
@@ -58,7 +58,8 @@ async function discoverSupabaseSqlTool(runtime) {
|
|
|
58
58
|
|
|
59
59
|
export function unwrapToolExecutionPayload(payload) {
|
|
60
60
|
return (
|
|
61
|
-
payload?.data?.results?.[0]?.response?.data
|
|
61
|
+
payload?.data?.data?.results?.[0]?.response?.data
|
|
62
|
+
?? payload?.data?.results?.[0]?.response?.data
|
|
62
63
|
?? payload?.results?.[0]?.response?.data
|
|
63
64
|
?? payload?.data?.result
|
|
64
65
|
?? payload?.result
|
|
@@ -74,30 +75,30 @@ function parseJsonCandidate(value) {
|
|
|
74
75
|
return JSON.parse(trimmed);
|
|
75
76
|
} catch {
|
|
76
77
|
const boundaryEnd = trimmed.lastIndexOf('</untrusted-data');
|
|
77
|
-
const
|
|
78
|
-
? trimmed.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
const boundaryName = boundaryEnd >= 0
|
|
79
|
+
? trimmed.slice(boundaryEnd + 2, trimmed.indexOf('>', boundaryEnd)) : null;
|
|
80
|
+
const opening = boundaryName ? `<${boundaryName}>` : null;
|
|
81
|
+
for (let boundaryStart = opening ? trimmed.indexOf(opening) : -1;
|
|
82
|
+
boundaryStart >= 0 && boundaryStart < boundaryEnd;
|
|
83
|
+
boundaryStart = trimmed.indexOf(opening, boundaryStart + opening.length)) {
|
|
84
|
+
const contentStart = boundaryStart + opening.length;
|
|
82
85
|
// Supabase MCP sometimes returns the prose envelope with literal "\\n"
|
|
83
86
|
// separators. Strip those only at the boundary: globally replacing them
|
|
84
87
|
// corrupts valid escaped newlines inside JSON string values.
|
|
85
88
|
const inner = trimmed
|
|
86
89
|
.slice(contentStart, boundaryEnd)
|
|
87
90
|
.trim()
|
|
88
|
-
.replace(/^(?:\\r?\\n)+/, '')
|
|
89
|
-
.replace(/(?:\\r?\\n)+$/, '')
|
|
91
|
+
.replace(/^(?:(?:\\r)?\\n)+/, '')
|
|
92
|
+
.replace(/(?:(?:\\r)?\\n)+$/, '')
|
|
90
93
|
.trim();
|
|
91
94
|
try {
|
|
92
95
|
return JSON.parse(inner);
|
|
93
|
-
} catch
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
throw usageError(
|
|
97
|
-
`Supabase diagnostic rows were not valid JSON: ${error instanceof Error ? error.message : String(error)}; prefix=${JSON.stringify(inner.slice(0, 100))}`,
|
|
98
|
-
);
|
|
96
|
+
} catch {
|
|
97
|
+
// The provider also mentions this marker in its introductory prose.
|
|
98
|
+
// Try its actual opening, never a nested tool result's different marker.
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
if (boundaryEnd >= 0) throw usageError('Supabase diagnostic rows were not valid JSON.');
|
|
101
102
|
const arrayMatch = trimmed.match(/(\[[\s\S]*\])/);
|
|
102
103
|
if (arrayMatch) {
|
|
103
104
|
try {
|