@notis_ai/cli 0.2.0-beta.113.1 → 0.2.0-beta.116.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 +15 -0
- package/dist/scaffolds/notis-database/app/page.tsx +38 -41
- package/dist/scaffolds/notis-database/lib/types.ts +5 -7
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +1839 -0
- package/dist/scaffolds/notis-database/notis.config.ts +48 -1
- package/dist/scaffolds/notis-database/package-lock.json +3935 -0
- package/dist/scaffolds/notis-database/package.json +2 -1
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/package.json +4 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -2
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useMultiSelect.ts +38 -2
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/index.ts +30 -1
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/runtime.ts +76 -17
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +4 -1
- package/dist/scaffolds/notis-journal/CHANGELOG.md +4 -0
- package/dist/scaffolds/notis-journal/app/page.tsx +1 -0
- package/dist/scaffolds/notis-journal/package-lock.json +4615 -0
- package/dist/scaffolds/notis-journal/package.json +2 -2
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +52 -1
- package/dist/scaffolds/notis-notes/app/page.tsx +39 -46
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +596 -0
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +144 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +752 -0
- package/dist/scaffolds/notis-notes/notis.config.ts +48 -3
- package/dist/scaffolds/notis-notes/package-lock.json +4636 -0
- package/dist/scaffolds/notis-notes/package.json +7 -3
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-random/CHANGELOG.md +12 -2
- package/dist/scaffolds/notis-random/app/history/page.tsx +5 -4
- package/dist/scaffolds/notis-random/app/page.tsx +141 -74
- package/dist/scaffolds/notis-random/components/ui/button.tsx +50 -0
- package/dist/scaffolds/notis-random/components/ui/card.tsx +16 -0
- package/dist/scaffolds/notis-random/components/ui/input.tsx +23 -0
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +26 -7
- package/dist/scaffolds/notis-random/lib/rng.ts +179 -19
- package/dist/scaffolds/notis-random/lib/roll-record.ts +103 -16
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +753 -0
- package/dist/scaffolds/notis-random/notis.config.ts +47 -3
- package/dist/scaffolds/notis-random/package-lock.json +4513 -0
- package/dist/scaffolds/notis-random/package.json +6 -2
- package/dist/scaffolds/notis-random/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-random/tailwind.config.ts +8 -1
- package/dist/scaffolds.json +8 -17
- package/package.json +2 -1
- package/skills/notis-apps/SKILL.md +438 -144
- package/skills/notis-apps/cli.md +15 -0
- package/skills/notis-cli/SKILL.md +8 -4
- package/skills/notis-onboarding/BRIEF.md +93 -0
- package/src/cli.js +16 -0
- package/src/command-specs/apps.js +290 -41
- package/src/command-specs/index.js +2 -0
- package/src/command-specs/onboarding.js +216 -0
- package/src/command-specs/tools.js +8 -4
- package/src/runtime/app-dev-server.js +3 -2
- package/src/runtime/app-dev-sessions.js +14 -40
- package/src/runtime/app-platform.js +65 -4
- package/src/runtime/desktop-auth.js +66 -17
- package/src/runtime/errors.js +1 -0
- package/src/runtime/profiles.js +27 -6
- package/src/runtime/telemetry.js +92 -0
- package/src/runtime/transport.js +12 -0
- package/template/.harness/index.html.tmpl +30 -2
- package/template/packages/sdk/src/config.ts +38 -1
- package/dist/scaffolds/notis-affiliate-prospects/CHANGELOG.md +0 -7
- package/dist/scaffolds/notis-affiliate-prospects/README.md +0 -20
- package/dist/scaffolds/notis-affiliate-prospects/app/globals.css +0 -42
- package/dist/scaffolds/notis-affiliate-prospects/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-affiliate-prospects/app/page.tsx +0 -248
- package/dist/scaffolds/notis-affiliate-prospects/app/prospects/page.tsx +0 -134
- package/dist/scaffolds/notis-affiliate-prospects/app/segments/page.tsx +0 -108
- package/dist/scaffolds/notis-affiliate-prospects/components/ui/card.tsx +0 -35
- package/dist/scaffolds/notis-affiliate-prospects/index.html +0 -12
- package/dist/scaffolds/notis-affiliate-prospects/lib/affiliate-data.ts +0 -218
- package/dist/scaffolds/notis-affiliate-prospects/lib/demo-prospects.ts +0 -52
- package/dist/scaffolds/notis-affiliate-prospects/lib/notis-tools.ts +0 -100
- package/dist/scaffolds/notis-affiliate-prospects/lib/utils.ts +0 -25
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-fixtures.json +0 -187
- package/dist/scaffolds/notis-affiliate-prospects/notis.config.ts +0 -65
- package/dist/scaffolds/notis-affiliate-prospects/package.json +0 -32
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/config.ts +0 -90
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useDatabase.ts +0 -76
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useUpsertDocument.ts +0 -50
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/styles.css +0 -38
- package/dist/scaffolds/notis-affiliate-prospects/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-affiliate-prospects/src/dev-main.tsx +0 -72
- package/dist/scaffolds/notis-affiliate-prospects/src/mock-runtime.ts +0 -242
- package/dist/scaffolds/notis-affiliate-prospects/tailwind.config.ts +0 -51
- package/dist/scaffolds/notis-affiliate-prospects/tsconfig.json +0 -23
- package/dist/scaffolds/notis-affiliate-prospects/vite.config.ts +0 -11
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useBackend.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotis.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotisNavigation.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTool.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTools.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTopBarSearch.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/provider.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/ui.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/vite.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/tsconfig.json +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-random}/components.json +0 -0
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
collectArtifactFiles,
|
|
33
33
|
collectSourceFiles,
|
|
34
34
|
resolveConfiguredAppSkills,
|
|
35
|
+
normalizeAppCapabilities,
|
|
35
36
|
directDeploy,
|
|
36
37
|
pullAppSource,
|
|
37
38
|
} from '../runtime/app-platform.js';
|
|
@@ -43,10 +44,12 @@ import {
|
|
|
43
44
|
runHarnessRoute,
|
|
44
45
|
} from '../runtime/agent-browser.js';
|
|
45
46
|
import {
|
|
47
|
+
getAppDevSessionsFile,
|
|
46
48
|
heartbeatAppDevSession,
|
|
47
49
|
removeAppDevSession,
|
|
48
50
|
upsertAppDevSessions,
|
|
49
51
|
waitForAppDevSessionMountAcknowledgements,
|
|
52
|
+
waitForAppDevSessionRenderAcknowledgements,
|
|
50
53
|
} from '../runtime/app-dev-sessions.js';
|
|
51
54
|
import { getAvailablePort } from '../runtime/ports.js';
|
|
52
55
|
import { getCliMode } from '../runtime/cli-mode.js';
|
|
@@ -66,6 +69,7 @@ const ENSURE_DEV_APP_INSTALLATION_TOOL = 'LOCAL_NOTIS_ENSURE_DEV_APP_INSTALLATIO
|
|
|
66
69
|
const GET_APP_TOOL = 'LOCAL_NOTIS_GET_APP';
|
|
67
70
|
const LIST_APPS_TOOL = 'LOCAL_NOTIS_LIST_APPS';
|
|
68
71
|
const CREATE_APP_TOOL = 'LOCAL_NOTIS_CREATE_APP';
|
|
72
|
+
const DUPLICATE_APP_TOOL = 'LOCAL_NOTIS_DUPLICATE_APP';
|
|
69
73
|
const SAVE_APP_FILES_TOOL = 'LOCAL_NOTIS_SAVE_APP_FILES';
|
|
70
74
|
|
|
71
75
|
// ---------------------------------------------------------------------------
|
|
@@ -138,13 +142,24 @@ function decodeJwtSub(jwt) {
|
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
|
|
141
|
-
function
|
|
142
|
-
|
|
145
|
+
export function developmentDesktopOpenCommand(
|
|
146
|
+
url,
|
|
147
|
+
{
|
|
148
|
+
platform = process.platform,
|
|
149
|
+
appName = null,
|
|
150
|
+
bundleId = null,
|
|
151
|
+
scheme = 'notis',
|
|
152
|
+
} = {},
|
|
153
|
+
) {
|
|
143
154
|
let command;
|
|
144
155
|
let args;
|
|
145
156
|
if (platform === 'darwin') {
|
|
146
157
|
command = 'open';
|
|
147
|
-
args =
|
|
158
|
+
args = bundleId && scheme === 'notis'
|
|
159
|
+
? ['-b', bundleId, url]
|
|
160
|
+
: appName && scheme === 'notis'
|
|
161
|
+
? ['-a', appName, url]
|
|
162
|
+
: [url];
|
|
148
163
|
} else if (platform === 'win32') {
|
|
149
164
|
command = 'cmd';
|
|
150
165
|
args = ['/c', 'start', '', url];
|
|
@@ -152,10 +167,14 @@ function openInBrowser(url) {
|
|
|
152
167
|
command = 'xdg-open';
|
|
153
168
|
args = [url];
|
|
154
169
|
}
|
|
170
|
+
return { command, args };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function openInBrowser(url, options = {}) {
|
|
174
|
+
const { command, args } = developmentDesktopOpenCommand(url, options);
|
|
155
175
|
try {
|
|
156
|
-
const child = spawn(command, args, { stdio: 'ignore'
|
|
176
|
+
const child = spawn(command, args, { stdio: 'ignore' });
|
|
157
177
|
child.on('error', () => {});
|
|
158
|
-
child.unref();
|
|
159
178
|
} catch {
|
|
160
179
|
// Non-fatal. The URL is printed in the CLI output.
|
|
161
180
|
}
|
|
@@ -205,21 +224,55 @@ function pickDefaultRouteSlug(manifest) {
|
|
|
205
224
|
|
|
206
225
|
const DESKTOP_DEEP_LINK_SCHEME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
207
226
|
|
|
208
|
-
export function resolveDevelopmentDesktopScheme(env = process.env) {
|
|
227
|
+
export function resolveDevelopmentDesktopScheme(env = process.env, worktreeRuntime = null) {
|
|
209
228
|
// Mirror the desktop app's own scheme resolution (electron main + forge config):
|
|
210
229
|
// local dev launches register `notis-dev`, while installed prod/beta builds claim
|
|
211
230
|
// `notis`. Hardcoding `notis` here is what made `apps dev` open the installed
|
|
212
231
|
// prod/beta app instead of the local dev app.
|
|
213
|
-
const scheme = (
|
|
232
|
+
const scheme = (
|
|
233
|
+
worktreeRuntime?.desktop_deep_link_scheme
|
|
234
|
+
|| env.NOTIS_DESKTOP_DEEP_LINK_SCHEME
|
|
235
|
+
|| ''
|
|
236
|
+
).trim();
|
|
214
237
|
return DESKTOP_DEEP_LINK_SCHEME_PATTERN.test(scheme) ? scheme : 'notis';
|
|
215
238
|
}
|
|
216
239
|
|
|
240
|
+
export function resolveDevelopmentDesktopAppName(runtime = {}) {
|
|
241
|
+
const explicit = String(
|
|
242
|
+
runtime.desktopAppName
|
|
243
|
+
|| runtime.worktreeRuntime?.desktop_app_name
|
|
244
|
+
|| '',
|
|
245
|
+
).trim();
|
|
246
|
+
if (explicit) {
|
|
247
|
+
return explicit;
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
return new URL(runtime.apiBase).hostname === 'api-beta.notis.ai'
|
|
251
|
+
? 'Notis Beta'
|
|
252
|
+
: 'Notis';
|
|
253
|
+
} catch {
|
|
254
|
+
return 'Notis';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function resolveDevelopmentDesktopBundleId(runtime = {}) {
|
|
259
|
+
return resolveDevelopmentDesktopAppName(runtime) === 'Notis Beta'
|
|
260
|
+
? 'ai.notis.desktop.beta'
|
|
261
|
+
: 'ai.notis.desktop';
|
|
262
|
+
}
|
|
263
|
+
|
|
217
264
|
export function buildDevelopmentDesktopUrl(appHref = null, scheme = 'notis') {
|
|
218
265
|
const route = String(appHref || '/store').replace(/^\/+/, '');
|
|
219
266
|
const normalizedScheme = DESKTOP_DEEP_LINK_SCHEME_PATTERN.test(scheme) ? scheme : 'notis';
|
|
220
267
|
return `${normalizedScheme}://${route || 'store'}`;
|
|
221
268
|
}
|
|
222
269
|
|
|
270
|
+
export function buildMountedDevelopmentDesktopUrl(appHref, scheme, sessionId) {
|
|
271
|
+
const url = new URL(buildDevelopmentDesktopUrl(appHref, scheme));
|
|
272
|
+
url.searchParams.set('notis_dev_session', sessionId);
|
|
273
|
+
return url.toString();
|
|
274
|
+
}
|
|
275
|
+
|
|
223
276
|
export function shouldOpenDevelopmentTab(options = {}) {
|
|
224
277
|
// Commander stores the negatable `--no-open` flag as `options.open === false`;
|
|
225
278
|
// it never sets `options.noOpen`. Reading the non-existent `noOpen` key meant
|
|
@@ -228,8 +281,17 @@ export function shouldOpenDevelopmentTab(options = {}) {
|
|
|
228
281
|
return options.open !== false;
|
|
229
282
|
}
|
|
230
283
|
|
|
231
|
-
function
|
|
232
|
-
|
|
284
|
+
export function buildDevelopmentAppHref({
|
|
285
|
+
appSlug,
|
|
286
|
+
appId,
|
|
287
|
+
devSlug,
|
|
288
|
+
targetAppId = null,
|
|
289
|
+
targetAppSlug = null,
|
|
290
|
+
manifest,
|
|
291
|
+
}) {
|
|
292
|
+
const routeAppId = `${targetAppId || appId}__local_dev__${devSlug}`;
|
|
293
|
+
const routeAppSlug = targetAppSlug || devSlug || appSlug;
|
|
294
|
+
const originlessBase = `/apps/${routeAppSlug}-${routeAppId}`;
|
|
233
295
|
const routeSlug = pickDefaultRouteSlug(manifest);
|
|
234
296
|
return routeSlug ? `${originlessBase}/${routeSlug}` : originlessBase;
|
|
235
297
|
}
|
|
@@ -377,22 +439,35 @@ function assertHarnessResult(result, route, databaseSlugs) {
|
|
|
377
439
|
details: error,
|
|
378
440
|
});
|
|
379
441
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
442
|
+
const declaredDatabaseSet = new Set(databaseSlugs);
|
|
443
|
+
const databaseQueries = (result.runtimeCalls || []).filter(
|
|
444
|
+
(call) =>
|
|
445
|
+
call?.op === 'callTool'
|
|
446
|
+
&& localNotisToolSlug(call?.args?.name) === 'LOCAL_NOTIS_DATABASE_QUERY',
|
|
447
|
+
);
|
|
448
|
+
for (const call of databaseQueries) {
|
|
449
|
+
const databaseSlug = call?.args?.arguments?.database_slug;
|
|
450
|
+
if (databaseSlug && !declaredDatabaseSet.has(databaseSlug)) {
|
|
388
451
|
assertions.push({
|
|
389
452
|
ok: false,
|
|
390
|
-
code: '
|
|
391
|
-
message: `Route "${route.slug}"
|
|
453
|
+
code: 'undeclared_database_query',
|
|
454
|
+
message: `Route "${route.slug}" queried undeclared database "${databaseSlug}".`,
|
|
392
455
|
details: { databaseSlug },
|
|
393
456
|
});
|
|
394
457
|
}
|
|
395
458
|
}
|
|
459
|
+
const collectionDatabase = route?.collection?.database;
|
|
460
|
+
if (
|
|
461
|
+
collectionDatabase
|
|
462
|
+
&& !databaseQueries.some((call) => call?.args?.arguments?.database_slug === collectionDatabase)
|
|
463
|
+
) {
|
|
464
|
+
assertions.push({
|
|
465
|
+
ok: false,
|
|
466
|
+
code: 'missing_collection_database_query',
|
|
467
|
+
message: `Collection route "${route.slug}" did not query "${collectionDatabase}".`,
|
|
468
|
+
details: { databaseSlug: collectionDatabase },
|
|
469
|
+
});
|
|
470
|
+
}
|
|
396
471
|
return assertions;
|
|
397
472
|
}
|
|
398
473
|
|
|
@@ -440,6 +515,7 @@ function buildManifestForDev(appConfig) {
|
|
|
440
515
|
css: 'bundle/app.css',
|
|
441
516
|
},
|
|
442
517
|
databases: appConfig.databases || [],
|
|
518
|
+
capabilities: normalizeAppCapabilities(appConfig.capabilities),
|
|
443
519
|
tools: appConfig.tools || [],
|
|
444
520
|
skills: (appConfig.skills || []).map((skill) => ({
|
|
445
521
|
key: skill.key,
|
|
@@ -483,11 +559,12 @@ export async function ensureDevInstall({
|
|
|
483
559
|
const manifest = buildManifestForDev(appConfig);
|
|
484
560
|
const skills = resolveConfiguredAppSkills(appConfig, projectDir);
|
|
485
561
|
let linkedState = readLinkedState(projectDir);
|
|
562
|
+
let linkedApp = null;
|
|
486
563
|
if (linkedState?.app_id) {
|
|
487
|
-
|
|
488
|
-
if (
|
|
564
|
+
linkedApp = await getAccessibleApp(ctx.runtime, linkedState.app_id, runTool);
|
|
565
|
+
if (linkedApp?.manifest?.is_dev === true) {
|
|
489
566
|
const { app_id: legacyDevAppId, linked_at: _linkedAt, deployed_at: _deployedAt, version: _version, ...rest } = linkedState;
|
|
490
|
-
const devAppId = linkedState.dev_app_id ||
|
|
567
|
+
const devAppId = linkedState.dev_app_id || legacyDevAppId;
|
|
491
568
|
linkedState = {
|
|
492
569
|
...rest,
|
|
493
570
|
...(devAppId ? { dev_app_id: devAppId } : {}),
|
|
@@ -496,6 +573,7 @@ export async function ensureDevInstall({
|
|
|
496
573
|
} : {}),
|
|
497
574
|
};
|
|
498
575
|
writeLinkedState(projectDir, linkedState);
|
|
576
|
+
linkedApp = null;
|
|
499
577
|
}
|
|
500
578
|
}
|
|
501
579
|
const ensureArguments = buildEnsureDevInstallArguments({ appConfig, manifest, linkedState, skills });
|
|
@@ -528,6 +606,7 @@ export async function ensureDevInstall({
|
|
|
528
606
|
created: ensureResult.payload.created || false,
|
|
529
607
|
linkedAppId: linkedState?.app_id || null,
|
|
530
608
|
targetAppId: linkedState?.app_id || null,
|
|
609
|
+
targetAppSlug: linkedApp?.slug || null,
|
|
531
610
|
databaseMaterialization: ensureResult.payload.database_materialization || { created: [], unresolved: [] },
|
|
532
611
|
};
|
|
533
612
|
}
|
|
@@ -709,6 +788,15 @@ async function appsDevHandler(ctx) {
|
|
|
709
788
|
throw usageError('Could not determine the current user from the CLI auth token. Open the Notis desktop app, sign in, and retry.');
|
|
710
789
|
}
|
|
711
790
|
const apiBase = String(ctx.runtime.apiBase || '').replace(/\/$/, '');
|
|
791
|
+
const sessionsFilePath = getAppDevSessionsFile(
|
|
792
|
+
ctx.runtime.worktreeRuntime?.app_dev_sessions_file,
|
|
793
|
+
);
|
|
794
|
+
const desktopScheme = resolveDevelopmentDesktopScheme(
|
|
795
|
+
process.env,
|
|
796
|
+
ctx.runtime.worktreeRuntime,
|
|
797
|
+
);
|
|
798
|
+
const desktopAppName = resolveDevelopmentDesktopAppName(ctx.runtime);
|
|
799
|
+
const desktopBundleId = resolveDevelopmentDesktopBundleId(ctx.runtime);
|
|
712
800
|
const sessionId = randomUUID();
|
|
713
801
|
|
|
714
802
|
const candidates = [];
|
|
@@ -764,16 +852,25 @@ async function appsDevHandler(ctx) {
|
|
|
764
852
|
...app,
|
|
765
853
|
bundleBaseUrl,
|
|
766
854
|
mountNonce: randomUUID(),
|
|
767
|
-
appHref:
|
|
855
|
+
appHref: buildDevelopmentAppHref({
|
|
768
856
|
appSlug: app.slug,
|
|
769
857
|
appId: app.appId,
|
|
858
|
+
devSlug: app.devSlug,
|
|
859
|
+
targetAppId: app.targetAppId,
|
|
860
|
+
targetAppSlug: app.targetAppSlug,
|
|
770
861
|
manifest: app.manifest,
|
|
771
862
|
}),
|
|
772
863
|
};
|
|
773
864
|
});
|
|
774
865
|
const developmentTabUrl = buildDevelopmentDesktopUrl(
|
|
775
866
|
apps[0]?.appHref,
|
|
776
|
-
|
|
867
|
+
desktopScheme,
|
|
868
|
+
);
|
|
869
|
+
const desktopWakeUrl = buildDevelopmentDesktopUrl('/manage', desktopScheme);
|
|
870
|
+
const mountedDevelopmentTabUrl = buildMountedDevelopmentDesktopUrl(
|
|
871
|
+
apps[0]?.appHref,
|
|
872
|
+
desktopScheme,
|
|
873
|
+
sessionId,
|
|
777
874
|
);
|
|
778
875
|
const warnings = databaseMaterializationWarnings(apps);
|
|
779
876
|
|
|
@@ -786,6 +883,7 @@ async function appsDevHandler(ctx) {
|
|
|
786
883
|
userId: identity,
|
|
787
884
|
})),
|
|
788
885
|
port,
|
|
886
|
+
sessionsFilePath,
|
|
789
887
|
});
|
|
790
888
|
|
|
791
889
|
try {
|
|
@@ -802,7 +900,8 @@ async function appsDevHandler(ctx) {
|
|
|
802
900
|
projectDir: app.projectDir,
|
|
803
901
|
startedAt: now,
|
|
804
902
|
lastHeartbeatAt: now,
|
|
805
|
-
|
|
903
|
+
desktopAppName,
|
|
904
|
+
})), sessionsFilePath);
|
|
806
905
|
} catch (error) {
|
|
807
906
|
try {
|
|
808
907
|
await devServer.close();
|
|
@@ -814,7 +913,7 @@ async function appsDevHandler(ctx) {
|
|
|
814
913
|
|
|
815
914
|
let heartbeatTimer = setInterval(() => {
|
|
816
915
|
try {
|
|
817
|
-
heartbeatAppDevSession(sessionId, new Date().toISOString());
|
|
916
|
+
heartbeatAppDevSession(sessionId, new Date().toISOString(), sessionsFilePath);
|
|
818
917
|
} catch (error) {
|
|
819
918
|
const message = error instanceof Error ? error.message : String(error);
|
|
820
919
|
process.stderr.write(`[notis apps dev] heartbeat failed: ${message}\n`);
|
|
@@ -836,6 +935,8 @@ async function appsDevHandler(ctx) {
|
|
|
836
935
|
development_url: developmentTabUrl,
|
|
837
936
|
session_id: sessionId,
|
|
838
937
|
mount_status: 'serving',
|
|
938
|
+
render_status: 'waiting_for_route',
|
|
939
|
+
desktop_target: desktopAppName,
|
|
839
940
|
identity,
|
|
840
941
|
apps: apps.map((app) => ({
|
|
841
942
|
slug: app.devSlug,
|
|
@@ -853,36 +954,67 @@ async function appsDevHandler(ctx) {
|
|
|
853
954
|
warnings,
|
|
854
955
|
humanSummary: [
|
|
855
956
|
`Running apps dev against ${apiBase} as ${identity} (mode: ${mode})`,
|
|
957
|
+
`Target desktop: ${desktopAppName}`,
|
|
856
958
|
'',
|
|
857
959
|
`Open in desktop: ${developmentTabUrl}`,
|
|
858
960
|
'',
|
|
859
961
|
...apps.map((app) => ` ${app.name.padEnd(24)} ${app.bundleBaseUrl} -> ${app.appHref}`),
|
|
860
962
|
'',
|
|
861
|
-
`Serving locally; waiting for
|
|
963
|
+
`Serving locally; waiting for ${desktopAppName} to mount ${apps.length === 1 ? 'the app' : `${apps.length} apps`}.`,
|
|
862
964
|
'',
|
|
863
965
|
'Press Ctrl-C to stop.',
|
|
864
966
|
].join('\n'),
|
|
865
967
|
});
|
|
866
968
|
|
|
867
969
|
if (shouldOpenDevelopmentTab(ctx.options)) {
|
|
868
|
-
openInBrowser(
|
|
970
|
+
openInBrowser(desktopWakeUrl, {
|
|
971
|
+
appName: desktopAppName,
|
|
972
|
+
bundleId: desktopBundleId,
|
|
973
|
+
scheme: desktopScheme,
|
|
974
|
+
});
|
|
869
975
|
}
|
|
870
976
|
|
|
871
977
|
void (async () => {
|
|
872
|
-
let result = await waitForAppDevSessionMountAcknowledgements(expectedMountAcknowledgements
|
|
978
|
+
let result = await waitForAppDevSessionMountAcknowledgements(expectedMountAcknowledgements, {
|
|
979
|
+
sessionsFilePath,
|
|
980
|
+
});
|
|
873
981
|
if (!result.mounted) {
|
|
874
982
|
process.stderr.write(
|
|
875
|
-
`[notis apps dev] Serving locally, but
|
|
983
|
+
`[notis apps dev] Serving locally, but ${desktopAppName} has not acknowledged ${result.missing.length === 1 ? 'the app' : `${result.missing.length} apps`} in its Local development sidebar yet. Keep this command running and open ${desktopAppName}.\n`,
|
|
876
984
|
);
|
|
877
985
|
}
|
|
878
986
|
while (!result.mounted) {
|
|
879
987
|
result = await waitForAppDevSessionMountAcknowledgements(expectedMountAcknowledgements, {
|
|
988
|
+
sessionsFilePath,
|
|
880
989
|
timeoutMs: 60_000,
|
|
881
990
|
pollIntervalMs: 250,
|
|
882
991
|
});
|
|
883
992
|
}
|
|
884
993
|
process.stderr.write(
|
|
885
|
-
`[notis apps dev] Mounted in
|
|
994
|
+
`[notis apps dev] Mounted in ${desktopAppName}: ${apps.map((app) => app.name).join(', ')}.\n`,
|
|
995
|
+
);
|
|
996
|
+
if (shouldOpenDevelopmentTab(ctx.options)) {
|
|
997
|
+
openInBrowser(mountedDevelopmentTabUrl, {
|
|
998
|
+
appName: desktopAppName,
|
|
999
|
+
bundleId: desktopBundleId,
|
|
1000
|
+
scheme: desktopScheme,
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const firstAppRender = expectedMountAcknowledgements.slice(0, 1);
|
|
1005
|
+
let renderResult = await waitForAppDevSessionRenderAcknowledgements(firstAppRender, {
|
|
1006
|
+
sessionsFilePath,
|
|
1007
|
+
timeoutMs: 0,
|
|
1008
|
+
});
|
|
1009
|
+
while (!renderResult.mounted) {
|
|
1010
|
+
renderResult = await waitForAppDevSessionRenderAcknowledgements(firstAppRender, {
|
|
1011
|
+
sessionsFilePath,
|
|
1012
|
+
timeoutMs: 60_000,
|
|
1013
|
+
pollIntervalMs: 250,
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
process.stderr.write(
|
|
1017
|
+
`[notis apps dev] Rendered in ${desktopAppName}: ${apps[0].name}.\n`,
|
|
886
1018
|
);
|
|
887
1019
|
})().catch((error) => {
|
|
888
1020
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -899,7 +1031,7 @@ async function appsDevHandler(ctx) {
|
|
|
899
1031
|
heartbeatTimer = null;
|
|
900
1032
|
}
|
|
901
1033
|
try {
|
|
902
|
-
removeAppDevSession(sessionId);
|
|
1034
|
+
removeAppDevSession(sessionId, sessionsFilePath);
|
|
903
1035
|
} catch {
|
|
904
1036
|
// ignore cleanup failures during shutdown
|
|
905
1037
|
}
|
|
@@ -930,7 +1062,9 @@ async function appsBuildHandler(ctx) {
|
|
|
930
1062
|
throw usageError(`Project has problems:\n${problems.map((p) => ` - ${p}`).join('\n')}`);
|
|
931
1063
|
}
|
|
932
1064
|
|
|
933
|
-
const { manifest } = await buildArtifact(projectDir
|
|
1065
|
+
const { manifest } = await buildArtifact(projectDir, {
|
|
1066
|
+
stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit',
|
|
1067
|
+
});
|
|
934
1068
|
|
|
935
1069
|
return ctx.output.emitSuccess({
|
|
936
1070
|
command: ctx.spec.command_path.join(' '),
|
|
@@ -963,7 +1097,9 @@ async function appsVerifyHandler(ctx) {
|
|
|
963
1097
|
}
|
|
964
1098
|
|
|
965
1099
|
if (!ctx.options.skipBuild) {
|
|
966
|
-
await buildArtifact(projectDir
|
|
1100
|
+
await buildArtifact(projectDir, {
|
|
1101
|
+
stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit',
|
|
1102
|
+
});
|
|
967
1103
|
}
|
|
968
1104
|
|
|
969
1105
|
const manifest = readManifest(projectDir);
|
|
@@ -1175,7 +1311,9 @@ async function appsScreenshotHandler(ctx) {
|
|
|
1175
1311
|
}
|
|
1176
1312
|
|
|
1177
1313
|
if (!ctx.options.skipBuild) {
|
|
1178
|
-
await buildArtifact(projectDir
|
|
1314
|
+
await buildArtifact(projectDir, {
|
|
1315
|
+
stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit',
|
|
1316
|
+
});
|
|
1179
1317
|
}
|
|
1180
1318
|
|
|
1181
1319
|
const manifest = readManifest(projectDir);
|
|
@@ -1364,16 +1502,25 @@ async function appsScreenshotHandler(ctx) {
|
|
|
1364
1502
|
}
|
|
1365
1503
|
}
|
|
1366
1504
|
|
|
1505
|
+
export function buildLinkedAppState(existingState, appId, linkedAt = new Date().toISOString()) {
|
|
1506
|
+
return {
|
|
1507
|
+
...(existingState?.dev_app_id ? { dev_app_id: existingState.dev_app_id } : {}),
|
|
1508
|
+
...(existingState?.dev_linked_at ? { dev_linked_at: existingState.dev_linked_at } : {}),
|
|
1509
|
+
app_id: appId,
|
|
1510
|
+
linked_at: linkedAt,
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1367
1514
|
async function appsLinkHandler(ctx) {
|
|
1368
1515
|
const projectDir = resolveProjectDir(ctx.args.dir || '.');
|
|
1369
1516
|
const appId = ctx.args.appId;
|
|
1370
1517
|
|
|
1371
1518
|
await assertLinkTarget(ctx.runtime, appId);
|
|
1372
1519
|
|
|
1373
|
-
writeLinkedState(
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1520
|
+
writeLinkedState(
|
|
1521
|
+
projectDir,
|
|
1522
|
+
buildLinkedAppState(readLinkedState(projectDir), appId),
|
|
1523
|
+
);
|
|
1377
1524
|
|
|
1378
1525
|
return ctx.output.emitSuccess({
|
|
1379
1526
|
command: ctx.spec.command_path.join(' '),
|
|
@@ -1442,7 +1589,9 @@ async function appsDeployHandler(ctx) {
|
|
|
1442
1589
|
|
|
1443
1590
|
// Build if needed
|
|
1444
1591
|
if (!ctx.options.skipBuild) {
|
|
1445
|
-
await buildArtifact(projectDir
|
|
1592
|
+
await buildArtifact(projectDir, {
|
|
1593
|
+
stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit',
|
|
1594
|
+
});
|
|
1446
1595
|
}
|
|
1447
1596
|
|
|
1448
1597
|
// Direct deploy mode: upload to Supabase storage directly
|
|
@@ -1539,6 +1688,67 @@ function deployedAppVersion(app) {
|
|
|
1539
1688
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
1540
1689
|
}
|
|
1541
1690
|
|
|
1691
|
+
async function appsDuplicateHandler(ctx) {
|
|
1692
|
+
const projectDir = resolveProjectDir(ctx.args.dir || '.');
|
|
1693
|
+
// Either target an app explicitly, or duplicate whatever this project is
|
|
1694
|
+
// linked to, so `notis apps duplicate` works from inside a project.
|
|
1695
|
+
const appId = requireLinkedAppId(projectDir, ctx.options.appId);
|
|
1696
|
+
const idempotencyKey = nextIdempotencyKey(ctx.globalOptions);
|
|
1697
|
+
|
|
1698
|
+
const copyDocuments = ctx.options.copyDocuments || 'declared';
|
|
1699
|
+
if (!['declared', 'all', 'none'].includes(copyDocuments)) {
|
|
1700
|
+
throw usageError("--copy-documents must be one of: declared, all, none.");
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
const result = await runToolCommand({
|
|
1704
|
+
runtime: ctx.runtime,
|
|
1705
|
+
toolName: DUPLICATE_APP_TOOL,
|
|
1706
|
+
arguments_: {
|
|
1707
|
+
app_id: appId,
|
|
1708
|
+
...(ctx.options.name ? { name: ctx.options.name } : {}),
|
|
1709
|
+
copy_documents: copyDocuments,
|
|
1710
|
+
},
|
|
1711
|
+
mutating: true,
|
|
1712
|
+
idempotencyKey,
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
const payload = result.payload || {};
|
|
1716
|
+
if (payload.status === 'error') {
|
|
1717
|
+
throw usageError(`Could not duplicate app ${appId}: ${payload.message || 'unknown error'}`);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
const duplicated = payload.app || {};
|
|
1721
|
+
if (!duplicated.id) {
|
|
1722
|
+
throw usageError(`Could not duplicate app ${appId}: the backend did not return an app id.`);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
const data = {
|
|
1726
|
+
app_id: duplicated.id,
|
|
1727
|
+
name: duplicated.name,
|
|
1728
|
+
slug: duplicated.slug,
|
|
1729
|
+
duplicated_from_app_id: payload.duplicated_from_app_id || appId,
|
|
1730
|
+
copied_document_count: payload.copied_document_count ?? 0,
|
|
1731
|
+
portal_url: payload.portal_url,
|
|
1732
|
+
idempotency_key: idempotencyKey,
|
|
1733
|
+
// The duplicate owns brand new databases; nothing is shared with the source.
|
|
1734
|
+
databases: (payload.databases || []).map((database) => ({
|
|
1735
|
+
id: database.id,
|
|
1736
|
+
slug: database.slug,
|
|
1737
|
+
name: database.name,
|
|
1738
|
+
})),
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
return ctx.output.emitSuccess({
|
|
1742
|
+
command: ctx.spec.command_path.join(' '),
|
|
1743
|
+
data,
|
|
1744
|
+
humanSummary: `Duplicated app ${appId} as ${duplicated.name || duplicated.id}`,
|
|
1745
|
+
hints: payload.portal_url
|
|
1746
|
+
? [{ command: payload.portal_url, reason: 'Open the duplicated app in Portal' }]
|
|
1747
|
+
: [],
|
|
1748
|
+
meta: { mutating: true, idempotency_key: idempotencyKey },
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1542
1752
|
async function appsPublishHandler(ctx) {
|
|
1543
1753
|
if (ctx.options.confirmReady !== true) {
|
|
1544
1754
|
throw usageError(
|
|
@@ -1652,10 +1862,20 @@ async function appsDoctorHandler(ctx) {
|
|
|
1652
1862
|
? `Found ${problems.length} problems:\n${problems.map((p) => ` - ${p}`).join('\n')}`
|
|
1653
1863
|
: warnings.length
|
|
1654
1864
|
? `Healthy with ${warnings.length} warnings:\n${warnings.map((w) => ` - ${w}`).join('\n')}`
|
|
1655
|
-
: `Project is healthy.${linkedState
|
|
1865
|
+
: `Project is healthy.${doctorLinkSummary(linkedState)}`,
|
|
1656
1866
|
});
|
|
1657
1867
|
}
|
|
1658
1868
|
|
|
1869
|
+
export function doctorLinkSummary(linkedState) {
|
|
1870
|
+
if (linkedState?.app_id) {
|
|
1871
|
+
return ` Linked to app ${linkedState.app_id}.`;
|
|
1872
|
+
}
|
|
1873
|
+
if (linkedState?.dev_app_id) {
|
|
1874
|
+
return ` Local development app ${linkedState.dev_app_id} is active.`;
|
|
1875
|
+
}
|
|
1876
|
+
return ' Not linked.';
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1659
1879
|
// ---------------------------------------------------------------------------
|
|
1660
1880
|
// Command specs
|
|
1661
1881
|
// ---------------------------------------------------------------------------
|
|
@@ -1919,6 +2139,35 @@ export const appsCommandSpecs = [
|
|
|
1919
2139
|
backend_call: { type: 'http', name: 'portal_apps/publish' },
|
|
1920
2140
|
handler: appsPublishHandler,
|
|
1921
2141
|
},
|
|
2142
|
+
{
|
|
2143
|
+
command_path: ['apps', 'duplicate'],
|
|
2144
|
+
summary: 'Duplicate an app into an independent copy with its own databases.',
|
|
2145
|
+
when_to_use:
|
|
2146
|
+
'When the same app should run for a second purpose - a notes app for blog drafts alongside one for bookmarks. The copy shares no data with the source.',
|
|
2147
|
+
args_schema: {
|
|
2148
|
+
arguments: [
|
|
2149
|
+
{ token: '[dir]', key: 'dir', description: 'Project directory (default: current dir).' },
|
|
2150
|
+
],
|
|
2151
|
+
options: [
|
|
2152
|
+
{ flags: '--app-id <id>', description: 'App to duplicate. Defaults to the app this project is linked to.' },
|
|
2153
|
+
{ flags: '--name <name>', description: 'Name for the duplicate (default: the source name followed by "copy").' },
|
|
2154
|
+
{
|
|
2155
|
+
flags: '--copy-documents <mode>',
|
|
2156
|
+
description:
|
|
2157
|
+
"Which rows to copy: 'declared' (default, the starter content a fresh install would have), 'all', or 'none'.",
|
|
2158
|
+
},
|
|
2159
|
+
],
|
|
2160
|
+
},
|
|
2161
|
+
examples: [
|
|
2162
|
+
'notis apps duplicate --name "Blog"',
|
|
2163
|
+
'notis apps duplicate --app-id abc123 --name "Bookmarks" --copy-documents none',
|
|
2164
|
+
],
|
|
2165
|
+
mutates: true,
|
|
2166
|
+
idempotent: false,
|
|
2167
|
+
require_auth: true,
|
|
2168
|
+
backend_call: { type: 'tool', name: DUPLICATE_APP_TOOL },
|
|
2169
|
+
handler: appsDuplicateHandler,
|
|
2170
|
+
},
|
|
1922
2171
|
{
|
|
1923
2172
|
command_path: ['apps', 'doctor'],
|
|
1924
2173
|
summary: 'Check project health and readiness.',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appsCommandSpecs } from './apps.js';
|
|
2
2
|
import { toolsCommandSpecs } from './tools.js';
|
|
3
3
|
import { metaCommandSpecs } from './meta.js';
|
|
4
|
+
import { onboardingCommandSpecs } from './onboarding.js';
|
|
4
5
|
import { diagnosticCommandSpecs } from './diagnostics.js';
|
|
5
6
|
import { smokeCommandSpecs } from './smoke.js';
|
|
6
7
|
|
|
@@ -12,6 +13,7 @@ export const GROUP_SUMMARIES = {
|
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export const COMMAND_SPECS = [
|
|
16
|
+
...onboardingCommandSpecs,
|
|
15
17
|
...appsCommandSpecs,
|
|
16
18
|
...toolsCommandSpecs,
|
|
17
19
|
...diagnosticCommandSpecs,
|