@notis_ai/cli 0.2.7 → 0.2.9

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 (183) hide show
  1. package/README.md +56 -2
  2. package/dist/scaffolds/notis-database/app/page.tsx +38 -41
  3. package/dist/scaffolds/notis-database/lib/types.ts +5 -7
  4. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  5. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  6. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  7. package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
  8. package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
  9. package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +1839 -0
  10. package/dist/scaffolds/notis-database/notis.config.ts +48 -1
  11. package/dist/scaffolds/notis-database/package-lock.json +3935 -0
  12. package/dist/scaffolds/notis-database/package.json +2 -1
  13. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/package.json +4 -0
  14. package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  15. package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +60 -0
  16. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -2
  17. package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +196 -0
  18. package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +229 -0
  19. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  20. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +78 -0
  21. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +121 -0
  22. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useMultiSelect.ts +38 -2
  23. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  24. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/index.ts +30 -1
  25. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/runtime.ts +76 -17
  26. package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
  27. package/dist/scaffolds/notis-database/src/mock-runtime.ts +4 -1
  28. package/dist/scaffolds/notis-journal/CHANGELOG.md +4 -0
  29. package/dist/scaffolds/notis-journal/app/page.tsx +1 -0
  30. package/dist/scaffolds/notis-journal/package-lock.json +4615 -0
  31. package/dist/scaffolds/notis-journal/package.json +2 -2
  32. package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +52 -1
  33. package/dist/scaffolds/notis-notes/app/page.tsx +39 -46
  34. package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +596 -0
  35. package/dist/scaffolds/notis-notes/lib/visible-properties.ts +144 -0
  36. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  37. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  38. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  39. package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
  40. package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
  41. package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +752 -0
  42. package/dist/scaffolds/notis-notes/notis.config.ts +48 -3
  43. package/dist/scaffolds/notis-notes/package-lock.json +4636 -0
  44. package/dist/scaffolds/notis-notes/package.json +7 -3
  45. package/dist/scaffolds/notis-notes/packages/sdk/package.json +36 -0
  46. package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  47. package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +60 -0
  48. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
  49. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  50. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  51. package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +196 -0
  52. package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +229 -0
  53. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +41 -0
  54. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  55. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +78 -0
  56. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +121 -0
  57. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
  58. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +34 -0
  59. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
  60. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +64 -0
  61. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +56 -0
  62. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
  63. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  64. package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +83 -0
  65. package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +43 -0
  66. package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +220 -0
  67. package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +186 -0
  68. package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +15 -0
  69. package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +56 -0
  70. package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +15 -0
  71. package/dist/scaffolds/notis-random/CHANGELOG.md +12 -2
  72. package/dist/scaffolds/notis-random/app/history/page.tsx +5 -4
  73. package/dist/scaffolds/notis-random/app/page.tsx +141 -74
  74. package/dist/scaffolds/notis-random/components/ui/button.tsx +50 -0
  75. package/dist/scaffolds/notis-random/components/ui/card.tsx +16 -0
  76. package/dist/scaffolds/notis-random/components/ui/input.tsx +23 -0
  77. package/dist/scaffolds/notis-random/lib/notis-tools.ts +26 -7
  78. package/dist/scaffolds/notis-random/lib/rng.ts +179 -19
  79. package/dist/scaffolds/notis-random/lib/roll-record.ts +103 -16
  80. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  81. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  82. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  83. package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
  84. package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
  85. package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +753 -0
  86. package/dist/scaffolds/notis-random/notis.config.ts +47 -3
  87. package/dist/scaffolds/notis-random/package-lock.json +4513 -0
  88. package/dist/scaffolds/notis-random/package.json +6 -2
  89. package/dist/scaffolds/notis-random/packages/sdk/package.json +36 -0
  90. package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  91. package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +60 -0
  92. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
  93. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  94. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  95. package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +196 -0
  96. package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +229 -0
  97. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +41 -0
  98. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  99. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +78 -0
  100. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +121 -0
  101. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
  102. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +34 -0
  103. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
  104. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +64 -0
  105. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +56 -0
  106. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
  107. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  108. package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +83 -0
  109. package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +43 -0
  110. package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +220 -0
  111. package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +186 -0
  112. package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +15 -0
  113. package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +56 -0
  114. package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +15 -0
  115. package/dist/scaffolds/notis-random/tailwind.config.ts +8 -1
  116. package/dist/scaffolds.json +8 -17
  117. package/package.json +2 -1
  118. package/skills/notis-apps/SKILL.md +438 -144
  119. package/skills/notis-apps/cli.md +16 -1
  120. package/skills/notis-cli/SKILL.md +11 -4
  121. package/skills/notis-onboarding/BRIEF.md +93 -0
  122. package/skills/notis-query/cli.md +1 -1
  123. package/src/cli.js +50 -12
  124. package/src/command-specs/apps.js +309 -41
  125. package/src/command-specs/auth.js +107 -0
  126. package/src/command-specs/index.js +4 -0
  127. package/src/command-specs/meta.js +68 -6
  128. package/src/command-specs/onboarding.js +304 -0
  129. package/src/command-specs/tools.js +8 -4
  130. package/src/runtime/app-dev-server.js +3 -2
  131. package/src/runtime/app-dev-sessions.js +14 -40
  132. package/src/runtime/app-platform.js +65 -4
  133. package/src/runtime/desktop-auth.js +86 -17
  134. package/src/runtime/errors.js +1 -0
  135. package/src/runtime/oauth.js +1105 -0
  136. package/src/runtime/output.js +16 -2
  137. package/src/runtime/profiles.js +542 -18
  138. package/src/runtime/telemetry.js +92 -0
  139. package/src/runtime/transport.js +66 -11
  140. package/template/.harness/index.html.tmpl +30 -2
  141. package/template/packages/sdk/src/config.ts +38 -1
  142. package/dist/scaffolds/notis-affiliate-prospects/CHANGELOG.md +0 -7
  143. package/dist/scaffolds/notis-affiliate-prospects/README.md +0 -20
  144. package/dist/scaffolds/notis-affiliate-prospects/app/globals.css +0 -42
  145. package/dist/scaffolds/notis-affiliate-prospects/app/layout.tsx +0 -7
  146. package/dist/scaffolds/notis-affiliate-prospects/app/page.tsx +0 -248
  147. package/dist/scaffolds/notis-affiliate-prospects/app/prospects/page.tsx +0 -134
  148. package/dist/scaffolds/notis-affiliate-prospects/app/segments/page.tsx +0 -108
  149. package/dist/scaffolds/notis-affiliate-prospects/components/ui/card.tsx +0 -35
  150. package/dist/scaffolds/notis-affiliate-prospects/index.html +0 -12
  151. package/dist/scaffolds/notis-affiliate-prospects/lib/affiliate-data.ts +0 -218
  152. package/dist/scaffolds/notis-affiliate-prospects/lib/demo-prospects.ts +0 -52
  153. package/dist/scaffolds/notis-affiliate-prospects/lib/notis-tools.ts +0 -100
  154. package/dist/scaffolds/notis-affiliate-prospects/lib/utils.ts +0 -25
  155. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-1.png +0 -0
  156. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-2.png +0 -0
  157. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-3.png +0 -0
  158. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-fixtures.json +0 -187
  159. package/dist/scaffolds/notis-affiliate-prospects/notis.config.ts +0 -65
  160. package/dist/scaffolds/notis-affiliate-prospects/package.json +0 -32
  161. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/config.ts +0 -90
  162. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useDatabase.ts +0 -76
  163. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useUpsertDocument.ts +0 -50
  164. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/styles.css +0 -38
  165. package/dist/scaffolds/notis-affiliate-prospects/postcss.config.mjs +0 -6
  166. package/dist/scaffolds/notis-affiliate-prospects/src/dev-main.tsx +0 -72
  167. package/dist/scaffolds/notis-affiliate-prospects/src/mock-runtime.ts +0 -242
  168. package/dist/scaffolds/notis-affiliate-prospects/tailwind.config.ts +0 -51
  169. package/dist/scaffolds/notis-affiliate-prospects/tsconfig.json +0 -23
  170. package/dist/scaffolds/notis-affiliate-prospects/vite.config.ts +0 -11
  171. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
  172. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  173. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useBackend.ts +0 -0
  174. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotis.ts +0 -0
  175. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotisNavigation.ts +0 -0
  176. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTool.ts +0 -0
  177. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTools.ts +0 -0
  178. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTopBarSearch.ts +0 -0
  179. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/provider.tsx +0 -0
  180. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/ui.ts +0 -0
  181. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/vite.ts +0 -0
  182. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/tsconfig.json +0 -0
  183. /package/dist/scaffolds/{notis-affiliate-prospects → notis-random}/components.json +0 -0
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
3
  import { dirname, join, parse as parsePath } from 'node:path';
5
4
  import { CONFIG_DIR } from './profiles.js';
6
5
 
@@ -9,37 +8,6 @@ export const APP_DEV_SESSIONS_FILE = DEFAULT_APP_DEV_SESSIONS_FILE;
9
8
  export const APP_DEV_SESSIONS_VERSION = 1;
10
9
  export const APP_DEV_SESSION_MOUNT_ACKS_VERSION = 1;
11
10
 
12
- /**
13
- * Walk up from `startDir` looking for a workspace-scoped dev-sessions registry.
14
- *
15
- * The Notis desktop (and Conductor) reads a PER-WORKSPACE registry at
16
- * `<workspace>/.context/app-dev-sessions.json` (it points its own `apps dev`
17
- * at it via `NOTIS_APP_DEV_SESSIONS_FILE`). When a coding agent runs
18
- * `apps dev` manually it does not inherit that env var, so its session would
19
- * otherwise land in the global `~/.notis` registry and never appear in the
20
- * desktop's Local development sidebar. Resolving the nearest `.context`
21
- * registry keeps agent-run and desktop-run sessions in the same file.
22
- *
23
- * Returns the registry path if a `.context/` directory is found in `startDir`
24
- * or an ancestor (stopping at the home directory / filesystem root), else null.
25
- */
26
- export function findWorkspaceAppDevSessionsFile(startDir = process.cwd()) {
27
- let dir = startDir;
28
- const home = homedir();
29
- const { root } = parsePath(dir);
30
- // Bounded walk; `.context` lives at the workspace root, never above $HOME.
31
- for (let depth = 0; depth < 64; depth += 1) {
32
- if (existsSync(join(dir, '.context'))) {
33
- return join(dir, '.context', 'app-dev-sessions.json');
34
- }
35
- if (dir === home || dir === root) break;
36
- const parent = dirname(dir);
37
- if (parent === dir) break;
38
- dir = parent;
39
- }
40
- return null;
41
- }
42
-
43
11
  export function getAppDevSessionsFile(filePath) {
44
12
  if (filePath) {
45
13
  return filePath;
@@ -48,10 +16,6 @@ export function getAppDevSessionsFile(filePath) {
48
16
  if (typeof envPath === 'string' && envPath.trim()) {
49
17
  return envPath.trim();
50
18
  }
51
- const workspaceFile = findWorkspaceAppDevSessionsFile();
52
- if (workspaceFile) {
53
- return workspaceFile;
54
- }
55
19
  return DEFAULT_APP_DEV_SESSIONS_FILE;
56
20
  }
57
21
 
@@ -173,7 +137,7 @@ function normalizeMountAcknowledgements(raw) {
173
137
  typeof acknowledgement.devSlug === 'string' &&
174
138
  typeof acknowledgement.mountNonce === 'string' &&
175
139
  typeof acknowledgement.acknowledgedAt === 'string' &&
176
- acknowledgement.stage === 'listed',
140
+ ['listed', 'rendered'].includes(acknowledgement.stage),
177
141
  );
178
142
  }
179
143
 
@@ -218,12 +182,13 @@ export function removeAppDevSessionMountAcknowledgements(sessionId, sessionsFile
218
182
  return writeAppDevSessionMountAcknowledgements(registry, sessionsFilePath);
219
183
  }
220
184
 
221
- function mountAcknowledgementKey(value) {
222
- return `${value.sessionId}:${value.appId}:${value.devSlug}:${value.mountNonce}`;
185
+ function mountAcknowledgementKey(value, fallbackStage = 'listed') {
186
+ return `${value.sessionId}:${value.appId}:${value.devSlug}:${value.mountNonce}:${value.stage || fallbackStage}`;
223
187
  }
224
188
 
225
189
  export async function waitForAppDevSessionMountAcknowledgements(expectedSessions, options = {}) {
226
190
  const expected = Array.isArray(expectedSessions) ? expectedSessions : [expectedSessions];
191
+ const stage = options.stage === 'rendered' ? 'rendered' : 'listed';
227
192
  const timeoutMs = Number.isFinite(options.timeoutMs) ? Math.max(0, options.timeoutMs) : 15_000;
228
193
  const pollIntervalMs = Number.isFinite(options.pollIntervalMs)
229
194
  ? Math.max(1, options.pollIntervalMs)
@@ -237,7 +202,9 @@ export async function waitForAppDevSessionMountAcknowledgements(expectedSessions
237
202
  while (true) {
238
203
  const registry = readAppDevSessionMountAcknowledgements(options.sessionsFilePath);
239
204
  const acknowledgedKeys = new Set(registry.acknowledgements.map(mountAcknowledgementKey));
240
- const missing = expected.filter((session) => !acknowledgedKeys.has(mountAcknowledgementKey(session)));
205
+ const missing = expected.filter(
206
+ (session) => !acknowledgedKeys.has(mountAcknowledgementKey(session, stage)),
207
+ );
241
208
  if (missing.length === 0) {
242
209
  return { mounted: true, missing: [], acknowledgements: registry.acknowledgements };
243
210
  }
@@ -249,3 +216,10 @@ export async function waitForAppDevSessionMountAcknowledgements(expectedSessions
249
216
  await sleep(Math.min(pollIntervalMs, timeoutMs - elapsedMs));
250
217
  }
251
218
  }
219
+
220
+ export function waitForAppDevSessionRenderAcknowledgements(expectedSessions, options = {}) {
221
+ return waitForAppDevSessionMountAcknowledgements(expectedSessions, {
222
+ ...options,
223
+ stage: 'rendered',
224
+ });
225
+ }
@@ -231,7 +231,13 @@ export function detectProjectWarnings(projectDir, appConfig = null) {
231
231
  warnings.push('Missing Tailwind config.');
232
232
  }
233
233
 
234
- if (appConfig && (!Array.isArray(appConfig.databases) || appConfig.databases.length === 0)) {
234
+ const readsWorkspaceDatabases =
235
+ appConfig?.capabilities?.workspaceDatabases === 'read';
236
+ if (
237
+ appConfig
238
+ && !readsWorkspaceDatabases
239
+ && (!Array.isArray(appConfig.databases) || appConfig.databases.length === 0)
240
+ ) {
235
241
  warnings.push('No database references declared in notis.config.ts.');
236
242
  }
237
243
 
@@ -490,13 +496,24 @@ export async function runProjectScript({ projectDir, scriptName, env = {}, stdio
490
496
  stdio,
491
497
  env: { ...process.env, ...env },
492
498
  });
499
+ let capturedOutput = '';
500
+ for (const stream of [child.stdout, child.stderr]) {
501
+ stream?.on('data', (chunk) => {
502
+ capturedOutput += chunk.toString();
503
+ });
504
+ }
493
505
  child.on('error', rejectPromise);
494
506
  child.on('exit', (code) => {
495
507
  if (code === 0) {
496
508
  resolvePromise();
497
509
  return;
498
510
  }
499
- rejectPromise(new Error(`npm run ${scriptName} failed with exit code ${code}`));
511
+ const detail = capturedOutput.trim();
512
+ rejectPromise(
513
+ new Error(
514
+ `npm run ${scriptName} failed with exit code ${code}${detail ? `:\n${detail}` : ''}`,
515
+ ),
516
+ );
500
517
  });
501
518
  });
502
519
  }
@@ -707,7 +724,14 @@ export function generateManifest(appConfig, projectDir) {
707
724
  return entry;
708
725
  });
709
726
 
710
- const databases = appConfig.databases || [];
727
+ // Database entries may be a bare slug (structure only) or an object opting
728
+ // into shipping rows to installers. Normalize to the manifest's snake_case.
729
+ const databases = (appConfig.databases || []).map((entry) => {
730
+ if (typeof entry === 'string') return entry;
731
+ if (!entry || typeof entry !== 'object' || !entry.slug) return entry;
732
+ const seedDocuments = entry.seedDocuments ?? entry.seed_documents;
733
+ return seedDocuments === true ? { slug: entry.slug, seed_documents: true } : entry.slug;
734
+ });
711
735
  const categories = normalizeCategories(appConfig.categories || []);
712
736
  const metadata = { screenshots: resolveListingScreenshots(projectDir, appConfig) };
713
737
  const appSlug = safeKebab(appConfig.name);
@@ -786,12 +810,29 @@ export function generateManifest(appConfig, projectDir) {
786
810
  css: 'bundle/app.css',
787
811
  },
788
812
  databases,
813
+ capabilities: normalizeAppCapabilities(appConfig.capabilities),
789
814
  tools: appConfig.tools || [],
790
815
  skills,
791
816
  onboarding: appConfig.onboarding || null,
792
817
  };
793
818
  }
794
819
 
820
+ /**
821
+ * Keeps only capabilities the platform actually understands, at the exact
822
+ * values it accepts. An unknown key or value is dropped rather than passed
823
+ * through, so a typo can never reach the server as a permission grant.
824
+ */
825
+ export function normalizeAppCapabilities(capabilities) {
826
+ if (!capabilities || typeof capabilities !== 'object') {
827
+ return {};
828
+ }
829
+ const normalized = {};
830
+ if (capabilities.workspaceDatabases === 'read') {
831
+ normalized.workspaceDatabases = 'read';
832
+ }
833
+ return normalized;
834
+ }
835
+
795
836
  export function resolveConfiguredAppSkills(appConfig, projectDir) {
796
837
  const configured = Array.isArray(appConfig.skills) ? appConfig.skills : [];
797
838
  const projectRoot = resolve(projectDir);
@@ -831,13 +872,14 @@ export function resolveConfiguredAppSkills(appConfig, projectDir) {
831
872
  /**
832
873
  * Build the app bundle: generate entry file, run `vite build`, package into .notis/output/.
833
874
  */
834
- export async function buildArtifact(projectDir) {
875
+ export async function buildArtifact(projectDir, { stdio = 'inherit' } = {}) {
835
876
  await prepareArtifactBuild(projectDir);
836
877
 
837
878
  // Run Vite build
838
879
  await runProjectScript({
839
880
  projectDir,
840
881
  scriptName: 'build',
882
+ stdio,
841
883
  });
842
884
 
843
885
  // Verify the canonical `.notis/output/bundle` packaging contract.
@@ -948,8 +990,13 @@ export function scaffoldProject({ projectDir, appName, fromSlug = null }) {
948
990
  if (existsSync(pkgPath)) {
949
991
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
950
992
  pkg.name = appName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
993
+ // A scaffold creates a new app identity, even when its source comes from a
994
+ // versioned Store example. New apps must therefore start their own release
995
+ // history instead of inheriting the example app's registry version.
996
+ pkg.notisAppVersion = '0.1.0';
951
997
  ensureScaffoldLocalSdk(projectDir, pkg);
952
998
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
999
+ normalizeScaffoldLockfile(projectDir, pkg);
953
1000
  }
954
1001
 
955
1002
  // Update notis.config.ts with the app name
@@ -983,6 +1030,20 @@ export function scaffoldProject({ projectDir, appName, fromSlug = null }) {
983
1030
  return { projectDir };
984
1031
  }
985
1032
 
1033
+ function normalizeScaffoldLockfile(projectDir, pkg) {
1034
+ const lockPath = join(projectDir, 'package-lock.json');
1035
+ if (!existsSync(lockPath)) {
1036
+ return;
1037
+ }
1038
+
1039
+ const lockfile = JSON.parse(readFileSync(lockPath, 'utf-8'));
1040
+ lockfile.name = pkg.name;
1041
+ if (lockfile.packages?.['']) {
1042
+ lockfile.packages[''].name = pkg.name;
1043
+ }
1044
+ writeFileSync(lockPath, JSON.stringify(lockfile, null, 2) + '\n');
1045
+ }
1046
+
986
1047
  function ensureScaffoldLocalSdk(projectDir, pkg) {
987
1048
  let shouldInstallLocalSdk = false;
988
1049
  for (const dependencyGroup of ['dependencies', 'devDependencies']) {
@@ -24,7 +24,15 @@ function defaultDesktopAppName(apiBase) {
24
24
  }
25
25
  }
26
26
 
27
- export function getDesktopAuthRecovery(runtime) {
27
+ /**
28
+ * Recovery hints for an unusable desktop-managed credential.
29
+ *
30
+ * `mode` distinguishes the two cases an agent has to act on differently:
31
+ * "expired" means a session existed and the desktop app can renew it, while
32
+ * "missing" means this machine was never signed in — telling that caller to
33
+ * renew something is misleading.
34
+ */
35
+ export function getDesktopAuthRecovery(runtime, { mode = 'expired' } = {}) {
28
36
  const desktopRunning = isPidRunning(runtime.desktopPid);
29
37
  const appName = runtime.desktopAppName || defaultDesktopAppName(runtime.apiBase);
30
38
  let command = 'Start the Notis desktop app';
@@ -32,25 +40,78 @@ export function getDesktopAuthRecovery(runtime) {
32
40
  command = `open -a ${quoteShellArgument(appName)}`;
33
41
  }
34
42
 
35
- return {
36
- desktopRunning,
37
- appName,
38
- hints: [
39
- {
40
- command,
41
- reason: desktopRunning
42
- ? `Bring ${appName} forward so it can renew CLI authentication, then retry`
43
- : `Start ${appName} to renew expired CLI authentication, then retry`,
44
- },
45
- {
46
- command: 'notis doctor',
47
- reason: 'Retry the auth and API checks after the desktop app is ready',
48
- },
49
- ],
50
- };
43
+ let reason;
44
+ if (mode === 'missing') {
45
+ reason = desktopRunning
46
+ ? `Sign in to ${appName} to authenticate the CLI on this machine, then retry`
47
+ : `Install and sign in to ${appName} to authenticate the CLI on this machine, then retry`;
48
+ } else {
49
+ reason = desktopRunning
50
+ ? `Bring ${appName} forward so it can renew CLI authentication, then retry`
51
+ : `Start ${appName} to renew expired CLI authentication, then retry`;
52
+ }
53
+
54
+ const hints = [{ command, reason }];
55
+ if (mode === 'missing') {
56
+ // Leads the list: a machine that was never signed in may not even have an
57
+ // account yet, and `notis start` covers both cases.
58
+ hints.unshift({
59
+ command: 'notis login',
60
+ reason: 'Sign in or create an account in the browser and authorize this machine',
61
+ });
62
+ }
63
+ hints.push({
64
+ command: 'notis doctor',
65
+ reason: 'Retry the auth and API checks after the desktop app is ready',
66
+ });
67
+
68
+ return { desktopRunning, appName, hints };
69
+ }
70
+
71
+ /**
72
+ * Block until the desktop app writes an unexpired JWT into the CLI profile.
73
+ *
74
+ * Polls the local config file only — never the server. The desktop app is what
75
+ * mints the credential, and `transport.js` re-reads the profile before every
76
+ * request, so this loop exists purely so the caller's process can wait rather
77
+ * than fail and make the user re-run the command.
78
+ */
79
+ export async function waitForDesktopAuth({
80
+ loadConfig,
81
+ getJwt,
82
+ isJwtExpired,
83
+ profileName = 'default',
84
+ timeoutMs = 300_000,
85
+ intervalMs = 2_000,
86
+ onTick,
87
+ } = {}) {
88
+ const deadline = Date.now() + timeoutMs;
89
+ for (;;) {
90
+ const jwt = getJwt(loadConfig(), profileName);
91
+ if (jwt && !isJwtExpired(jwt)) {
92
+ return jwt;
93
+ }
94
+ if (Date.now() >= deadline) {
95
+ return null;
96
+ }
97
+ if (onTick) onTick(Math.max(0, deadline - Date.now()));
98
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
99
+ }
51
100
  }
52
101
 
53
102
  export function createExpiredAuthError(runtime) {
103
+ if (runtime.credentialKind === 'oauth') {
104
+ return new CliError({
105
+ code: 'auth_expired',
106
+ message: 'Notis CLI OAuth authentication has expired',
107
+ exitCode: EXIT_CODES.auth,
108
+ details: { credential_source: 'oauth' },
109
+ hints: [
110
+ { command: 'notis login', reason: 'Authorize a new scoped CLI credential' },
111
+ { command: 'notis doctor', reason: 'Inspect the active credential state' },
112
+ ],
113
+ });
114
+ }
54
115
  if (runtime.credentialSource === 'env') {
55
116
  return new CliError({
56
117
  code: 'auth_expired',
@@ -81,6 +142,14 @@ export function createExpiredAuthError(runtime) {
81
142
  }
82
143
 
83
144
  export function createInvalidAuthHints(runtime) {
145
+ if (runtime?.credentialKind === 'oauth') {
146
+ return [
147
+ {
148
+ command: 'notis login',
149
+ reason: 'Authorize a new scoped CLI credential',
150
+ },
151
+ ];
152
+ }
84
153
  if (runtime?.credentialSource === 'env') {
85
154
  return [
86
155
  {
@@ -6,6 +6,7 @@ export const EXIT_CODES = {
6
6
  conflict: 5,
7
7
  backend: 6,
8
8
  unexpected: 7,
9
+ payment: 8,
9
10
  };
10
11
 
11
12
  export class CliError extends Error {