@notis_ai/cli 0.2.0-beta.167.1 → 0.2.0-beta.177.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 (55) hide show
  1. package/dist/agent-hooks/notis-agent-hook.mjs +10 -13
  2. package/dist/base-skills/notis-apps/references/design.md +14 -1
  3. package/dist/base-skills/notis-apps/references/sdk.md +1 -0
  4. package/dist/sdk/src/components/Dialog.tsx +46 -0
  5. package/{template/packages → dist}/sdk/src/index.ts +2 -0
  6. package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +10 -1
  7. package/{template/packages → dist}/sdk/src/styles.css +93 -74
  8. package/package.json +1 -1
  9. package/src/command-specs/diagnostics.js +15 -14
  10. package/src/runtime/app-platform.js +2 -2
  11. package/src/runtime/app-test-server.js +1 -1
  12. package/template/packages/sdk/tsconfig.json +0 -16
  13. /package/{template/packages → dist}/sdk/package.json +0 -0
  14. /package/{template/packages → dist}/sdk/src/agentContext.ts +0 -0
  15. /package/{template/packages → dist}/sdk/src/components/DocumentEditor.tsx +0 -0
  16. /package/{template/packages → dist}/sdk/src/components/Markdown.tsx +0 -0
  17. /package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +0 -0
  18. /package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +0 -0
  19. /package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
  20. /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  21. /package/{template/packages → dist}/sdk/src/components/NotisCommentBoundary.tsx +0 -0
  22. /package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +0 -0
  23. /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
  24. /package/{template/packages → dist}/sdk/src/components/Skeleton.tsx +0 -0
  25. /package/{template/packages → dist}/sdk/src/config.ts +0 -0
  26. /package/{template/packages → dist}/sdk/src/documents.ts +0 -0
  27. /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
  28. /package/{template/packages → dist}/sdk/src/hooks/useAgentContext.ts +0 -0
  29. /package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +0 -0
  30. /package/{template/packages → dist}/sdk/src/hooks/useCloudComputer.ts +0 -0
  31. /package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +0 -0
  32. /package/{template/packages → dist}/sdk/src/hooks/useDatabaseSchema.ts +0 -0
  33. /package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +0 -0
  34. /package/{template/packages → dist}/sdk/src/hooks/useDocument.ts +0 -0
  35. /package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +0 -0
  36. /package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +0 -0
  37. /package/{template/packages → dist}/sdk/src/hooks/useLongPressSelection.ts +0 -0
  38. /package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +0 -0
  39. /package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +0 -0
  40. /package/{template/packages → dist}/sdk/src/hooks/useNotisNavigation.ts +0 -0
  41. /package/{template/packages → dist}/sdk/src/hooks/useQuery.ts +0 -0
  42. /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
  43. /package/{template/packages → dist}/sdk/src/hooks/useToolQuery.ts +0 -0
  44. /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
  45. /package/{template/packages → dist}/sdk/src/hooks/useTopBarSearch.ts +0 -0
  46. /package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +0 -0
  47. /package/{template/packages → dist}/sdk/src/interactions/actions.ts +0 -0
  48. /package/{template/packages → dist}/sdk/src/interactions/visibility.ts +0 -0
  49. /package/{template/packages → dist}/sdk/src/interactions.ts +0 -0
  50. /package/{template/packages → dist}/sdk/src/provider.tsx +0 -0
  51. /package/{template/packages → dist}/sdk/src/queryCache.ts +0 -0
  52. /package/{template/packages → dist}/sdk/src/runtime.ts +0 -0
  53. /package/{template/packages → dist}/sdk/src/tailwind.ts +0 -0
  54. /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
  55. /package/{template/packages → dist}/sdk/src/vite.ts +0 -0
@@ -9050,7 +9050,7 @@ var BUNDLE_DIR = join4(OUTPUT_DIR, "bundle");
9050
9050
  var MANIFEST_FILE = join4(OUTPUT_DIR, "manifest.json");
9051
9051
  var METADATA_DIR = "metadata";
9052
9052
  var CLI_ROOT = resolve3(dirname3(fileURLToPath2(import.meta.url)), "../..");
9053
- var TEMPLATE_SDK_DIR = join4(CLI_ROOT, "template", "packages", "sdk");
9053
+ var TEMPLATE_SDK_DIR = join4(CLI_ROOT, "dist", "sdk");
9054
9054
  var NOTIS_APP_CATEGORIES = [
9055
9055
  "Productivity",
9056
9056
  "Sales & Marketing",
@@ -11533,7 +11533,7 @@ function resolveHarnessReactVersion(projectDir) {
11533
11533
  const candidates = [
11534
11534
  join5(projectDir, "node_modules", "@notis", "sdk", "package.json"),
11535
11535
  join5(REPO_ROOT, "packages", "sdk", "package.json"),
11536
- join5(CLI_ROOT2, "template", "packages", "sdk", "package.json")
11536
+ join5(CLI_ROOT2, "dist", "sdk", "package.json")
11537
11537
  ];
11538
11538
  for (const candidate of candidates) {
11539
11539
  const pkg = readJsonFile(candidate);
@@ -16633,7 +16633,7 @@ async function discoverSupabaseSqlTool(runtime) {
16633
16633
  return match;
16634
16634
  }
16635
16635
  function unwrapToolExecutionPayload(payload) {
16636
- return payload?.data?.results?.[0]?.response?.data ?? payload?.results?.[0]?.response?.data ?? payload?.data?.result ?? payload?.result ?? payload?.data ?? payload;
16636
+ return payload?.data?.data?.results?.[0]?.response?.data ?? payload?.data?.results?.[0]?.response?.data ?? payload?.results?.[0]?.response?.data ?? payload?.data?.result ?? payload?.result ?? payload?.data ?? payload;
16637
16637
  }
16638
16638
  function parseJsonCandidate(value) {
16639
16639
  if (typeof value !== "string") return null;
@@ -16642,20 +16642,17 @@ function parseJsonCandidate(value) {
16642
16642
  return JSON.parse(trimmed);
16643
16643
  } catch {
16644
16644
  const boundaryEnd = trimmed.lastIndexOf("</untrusted-data");
16645
- const boundaryStart = boundaryEnd >= 0 ? trimmed.lastIndexOf("<untrusted-data", boundaryEnd) : -1;
16646
- const contentStart = boundaryStart >= 0 ? trimmed.indexOf(">", boundaryStart) + 1 : -1;
16647
- if (contentStart > 0 && boundaryEnd > contentStart) {
16648
- const inner = trimmed.slice(contentStart, boundaryEnd).trim().replace(/^(?:\\r?\\n)+/, "").replace(/(?:\\r?\\n)+$/, "").trim();
16645
+ const boundaryName = boundaryEnd >= 0 ? trimmed.slice(boundaryEnd + 2, trimmed.indexOf(">", boundaryEnd)) : null;
16646
+ const opening = boundaryName ? `<${boundaryName}>` : null;
16647
+ for (let boundaryStart = opening ? trimmed.indexOf(opening) : -1; boundaryStart >= 0 && boundaryStart < boundaryEnd; boundaryStart = trimmed.indexOf(opening, boundaryStart + opening.length)) {
16648
+ const contentStart = boundaryStart + opening.length;
16649
+ const inner = trimmed.slice(contentStart, boundaryEnd).trim().replace(/^(?:(?:\\r)?\\n)+/, "").replace(/(?:(?:\\r)?\\n)+$/, "").trim();
16649
16650
  try {
16650
16651
  return JSON.parse(inner);
16651
- } catch (error) {
16652
- const nested = parseJsonCandidate(inner);
16653
- if (nested !== null) return nested;
16654
- throw usageError(
16655
- `Supabase diagnostic rows were not valid JSON: ${error instanceof Error ? error.message : String(error)}; prefix=${JSON.stringify(inner.slice(0, 100))}`
16656
- );
16652
+ } catch {
16657
16653
  }
16658
16654
  }
16655
+ if (boundaryEnd >= 0) throw usageError("Supabase diagnostic rows were not valid JSON.");
16659
16656
  const arrayMatch = trimmed.match(/(\[[\s\S]*\])/);
16660
16657
  if (arrayMatch) {
16661
16658
  try {
@@ -15,12 +15,25 @@
15
15
  - Let Notis own navigation, folder trees, and search. Use `PageHeading`,
16
16
  `NativeSelect`, `.list-row`, and `useTopBarSearch` instead of duplicating chrome.
17
17
  - Prefer inline optimistic edits for simple changes, with rollback on failure.
18
- Use a dialog for multi-field edits or destructive confirmation.
18
+ Use SDK `Dialog` for multi-field edits or destructive confirmation; keep its actions app-owned.
19
19
 
20
20
  Build enforces the existing design rules and reports violations by file/line.
21
21
  Use those diagnostics to fix specific problems; passing them does not establish
22
22
  that the design is good. See [troubleshooting](troubleshooting.md) when needed.
23
23
 
24
+ ### Controls and side panels
25
+
26
+ - **Keep controls with what they control.** Filters, counts, and Create belong
27
+ above the list; record actions belong inside the detail panel. Only controls
28
+ that affect both panes belong above both.
29
+ - **Align sibling panes.** Start the list and detail panel at the same height,
30
+ with consistent header spacing and control sizes.
31
+ - **Keep secondary actions quiet.** Put occasional destructive actions in an
32
+ overflow menu, retaining their confirmation step.
33
+
34
+ For example, a ticket view has two side-by-side panes: **Tickets / New ticket →
35
+ filters → list**, and **ticket ID / actions / close → details**.
36
+
24
37
  ### Look at the result
25
38
 
26
39
  Run build and verification, then inspect screenshots of the affected view at a
@@ -23,6 +23,7 @@ already installs `ShortcutProvider`; app code should not add a second provider.
23
23
  | `NotisSelectionBoundary` | `(NotisSelectionBoundaryProps) => ReactElement` | Attach structured, explicitly untrusted app/resource/selection context to selected content and copy operations |
24
24
  | `SelectionCheckbox` / `SelectionMarquee` | components | Standard selection controls backed by `useCollectionInteractions` |
25
25
  | `MultiSelectActionBar` | component | Standard bulk actions with pending/disabled state and shortcut support |
26
+ | `Dialog` | `{ open, onClose, title, description?, role?, children }` | Themed native modal with top-layer stacking, focus handling and background shortcut isolation. Use `role="alertdialog"` for destructive confirmation; put the safe action first. |
26
27
 
27
28
  Import headless collection action types and helpers from
28
29
  `@notis/sdk/interactions`. Keep an open detail view synchronized with
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useId, useRef, type ReactNode } from 'react';
4
+
5
+ export interface DialogProps {
6
+ open: boolean;
7
+ /** Request dismissal. Keep open unchanged to prevent dismissal while saving. */
8
+ onClose: () => void;
9
+ title: ReactNode;
10
+ description?: ReactNode;
11
+ role?: 'dialog' | 'alertdialog';
12
+ children: ReactNode;
13
+ }
14
+
15
+ /** Native top-layer dialog; content and actions remain app-owned. */
16
+ export function Dialog({ open, onClose, title, description, role = 'dialog', children }: DialogProps) {
17
+ const ref = useRef<HTMLDialogElement>(null);
18
+ const titleId = useId();
19
+ const descriptionId = useId();
20
+
21
+ useEffect(() => {
22
+ if (!open) return;
23
+ const dialog = ref.current!;
24
+ dialog.showModal();
25
+ return () => dialog.close();
26
+ }, [open]);
27
+
28
+ return (
29
+ <dialog
30
+ ref={ref}
31
+ className="notis-dialog"
32
+ role={role}
33
+ aria-labelledby={titleId}
34
+ aria-describedby={description == null ? undefined : descriptionId}
35
+ onCancel={(event) => {
36
+ event.preventDefault();
37
+ onClose();
38
+ }}
39
+ onKeyDown={(event) => event.stopPropagation()}
40
+ >
41
+ <h2 id={titleId} className="notis-dialog-title">{title}</h2>
42
+ {description != null && <p id={descriptionId} className="notis-dialog-description">{description}</p>}
43
+ {children}
44
+ </dialog>
45
+ );
46
+ }
@@ -72,6 +72,8 @@ export {
72
72
  } from './documents';
73
73
 
74
74
  // Multi-select components
75
+ export { Dialog } from './components/Dialog';
76
+ export type { DialogProps } from './components/Dialog';
75
77
  export { MultiSelectActionBar } from './components/MultiSelectActionBar';
76
78
  export { MultiSelectCheckbox, MultiSelectCheckbox as SelectionCheckbox } from './components/MultiSelectCheckbox';
77
79
  export { MultiSelectDragOverlay, MultiSelectDragOverlay as SelectionMarquee } from './components/MultiSelectDragOverlay';
@@ -215,6 +215,14 @@ function consumeShortcut(event: KeyboardEvent, definition: ShortcutDefinition):
215
215
  void definition.onTrigger(event);
216
216
  }
217
217
 
218
+ // Native dialogs make the background inert, but document capture listeners still
219
+ // receive their keyboard events. Leave those events to the dialog and its controls.
220
+ function isDialogEvent(event: KeyboardEvent): boolean {
221
+ return event.composedPath().some((target) =>
222
+ target instanceof HTMLElement && target.matches('dialog.notis-dialog[open]'),
223
+ );
224
+ }
225
+
218
226
  export function ShortcutProvider({ children }: { children: ReactNode }) {
219
227
  const parentRegistry = useContext(ShortcutContext);
220
228
  if (parentRegistry) return <>{children}</>;
@@ -280,7 +288,7 @@ function registerFallbackShortcut(input: Omit<ShortcutRegistration, 'id' | 'orde
280
288
  registrations,
281
289
  nextId: 1,
282
290
  dispatch: (event) => {
283
- if (event.defaultPrevented) return;
291
+ if (event.defaultPrevented || isDialogEvent(event)) return;
284
292
  const editable = isEditableShortcutEvent(event);
285
293
  const token = chordToken(eventChord(event));
286
294
  for (const registration of applicableRegistrations(registrations.values())) {
@@ -370,6 +378,7 @@ function ShortcutProviderRoot({ children }: { children: ReactNode }) {
370
378
 
371
379
  useEffect(() => {
372
380
  const handleKeyDown = (event: KeyboardEvent) => {
381
+ if (event.defaultPrevented || isDialogEvent(event)) return;
373
382
  const editable = isEditableShortcutEvent(event);
374
383
  if (!editable && !event.repeat && event.key === '?' && !event.metaKey && !event.ctrlKey && !event.altKey) {
375
384
  event.preventDefault();
@@ -8,94 +8,113 @@
8
8
  * The portal injects the live Notis theme variables at render time.
9
9
  */
10
10
 
11
+ /* Top-layer surfaces must remain distinct from the app's flat content panels. */
12
+ dialog.notis-dialog {
13
+ position: fixed;
14
+ inset: 0;
15
+ margin: auto;
16
+ box-sizing: border-box;
17
+ width: min(28rem, calc(100% - 2rem));
18
+ max-height: calc(100dvh - 2rem);
19
+ overflow: auto;
20
+ padding: 1.25rem;
21
+ border: 1px solid hsl(var(--border));
22
+ border-radius: 0.75rem;
23
+ background: hsl(var(--popover));
24
+ color: hsl(var(--popover-foreground));
25
+ box-shadow: 0 16px 48px rgb(0 0 0 / 0.2);
26
+ }
27
+
28
+ dialog.notis-dialog::backdrop { background: rgb(0 0 0 / 0.45); }
29
+ .notis-dialog-title { margin: 0; font-size: 1rem; font-weight: 600; line-height: 1.5; }
30
+ .notis-dialog-description { margin: 0.5rem 0 0; color: hsl(var(--muted-foreground)); font-size: 0.875rem; line-height: 1.5; }
31
+
11
32
  /* Framework-neutral styles: the app owns its Tailwind entry point. This file
12
- * is also consumed in shadow roots and by apps on older Tailwind versions. */
13
- @layer base {
14
- * {
15
- border-color: hsl(var(--border));
16
- }
17
- [data-notis-app-root] {
18
- color: hsl(var(--foreground));
19
- display: block;
20
- min-height: 100%;
21
- }
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%;
22
43
  }
23
44
 
24
- @layer components {
25
- .notis-app-shell {
26
- margin-inline: auto;
27
- width: 100%;
28
- max-width: var(--portal-page-max-width);
29
- padding: 1.5rem 1rem;
30
- }
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
+ }
31
51
 
32
- /* Flat panel: no border or shadow around items. */
33
- .notis-app-surface {
34
- border-radius: 1rem;
35
- background-color: hsl(var(--muted));
36
- color: hsl(var(--card-foreground));
37
- }
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
+ }
38
58
 
39
- .list-row {
40
- border-radius: 0.75rem;
41
- background-color: hsl(var(--muted));
42
- padding: 0.75rem 1rem;
43
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
44
- transition-duration: 150ms;
45
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
46
- }
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
+ }
47
67
 
48
- .list-row-selected {
49
- background-color: hsl(var(--primary) / 0.06);
50
- }
68
+ .list-row-selected {
69
+ background-color: hsl(var(--primary) / 0.06);
70
+ }
51
71
 
52
- /* Split pages are full-bleed; only the list pane carries a hairline. */
53
- .notis-app-split {
54
- display: flex;
55
- height: 100%;
56
- min-height: 0;
57
- width: 100%;
58
- max-width: none;
59
- flex-direction: column;
60
- }
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
+ }
61
81
 
62
- .notis-app-pane-list {
63
- width: 100%;
64
- flex-shrink: 0;
65
- background-color: hsl(var(--muted) / 0.4);
66
- }
82
+ .notis-app-pane-list {
83
+ width: 100%;
84
+ flex-shrink: 0;
85
+ background-color: hsl(var(--muted) / 0.4);
86
+ }
67
87
 
68
- .notis-app-pane-detail {
69
- min-width: 0;
70
- flex: 1 1 0%;
71
- background-color: hsl(var(--background));
72
- }
88
+ .notis-app-pane-detail {
89
+ min-width: 0;
90
+ flex: 1 1 0%;
91
+ background-color: hsl(var(--background));
92
+ }
73
93
 
74
- .notis-app-section {
75
- padding: 1.25rem;
76
- }
94
+ .notis-app-section {
95
+ padding: 1.25rem;
96
+ }
77
97
 
78
- @media (min-width: 768px) {
79
- .notis-app-shell { padding-block: 2rem; }
80
- .notis-app-section { padding: 1.5rem; }
81
- }
98
+ @media (min-width: 768px) {
99
+ .notis-app-shell { padding-block: 2rem; }
100
+ .notis-app-section { padding: 1.5rem; }
101
+ }
82
102
 
83
- @media (min-width: 1024px) {
84
- .list-row { background-color: transparent; }
85
- .list-row:hover { background-color: hsl(var(--muted) / 0.6); }
86
- .list-row-selected,
87
- .list-row-selected:hover { background-color: hsl(var(--primary) / 0.06); }
88
- .notis-app-split { flex-direction: row; }
89
- .notis-app-pane-list {
90
- width: 20rem;
91
- border-right-width: 1px;
92
- border-right-color: hsl(var(--border));
93
- }
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));
94
113
  }
114
+ }
95
115
 
96
- @media (min-width: 1280px) {
97
- .notis-app-pane-list { width: 24rem; }
98
- }
116
+ @media (min-width: 1280px) {
117
+ .notis-app-pane-list { width: 24rem; }
99
118
  }
100
119
 
101
120
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notis_ai/cli",
3
- "version": "0.2.0-beta.167.1",
3
+ "version": "0.2.0-beta.177.1",
4
4
  "description": "Agent-first Notis CLI for apps and generic tool execution",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 boundaryStart = boundaryEnd >= 0
78
- ? trimmed.lastIndexOf('<untrusted-data', boundaryEnd)
79
- : -1;
80
- const contentStart = boundaryStart >= 0 ? trimmed.indexOf('>', boundaryStart) + 1 : -1;
81
- if (contentStart > 0 && boundaryEnd > contentStart) {
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 (error) {
94
- const nested = parseJsonCandidate(inner);
95
- if (nested !== null) return nested;
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 {
@@ -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',
@@ -1847,7 +1847,7 @@ function listFilesRecursive(dir, base = dir, results = []) {
1847
1847
  /**
1848
1848
  * Keep an app's embedded `packages/sdk` copy identical to the SDK this CLI
1849
1849
  * 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
1850
+ * the bundled artifact generated from packages/sdk), so an app never edits it; before
1851
1851
  * this sync existed every app silently ran whatever SDK snapshot it was
1852
1852
  * scaffolded with and never received hook or style updates.
1853
1853
  *
@@ -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);
@@ -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
File without changes
File without changes