@notis_ai/cli 0.2.6 → 0.2.8
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 +17 -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-database/packages/sdk/package.json +36 -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-database/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- 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/useBackend.ts +41 -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-database/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +15 -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/components.json +20 -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 -6
- package/package.json +2 -1
- package/skills/notis-apps/SKILL.md +449 -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 +52 -7
- package/src/command-specs/apps.js +154 -21
- package/src/command-specs/diagnostics.js +674 -0
- package/src/command-specs/helpers.js +4 -1
- package/src/command-specs/index.js +8 -0
- package/src/command-specs/onboarding.js +216 -0
- package/src/command-specs/smoke.js +386 -0
- package/src/command-specs/tools.js +189 -5
- 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/output.js +21 -3
- package/src/runtime/profiles.js +214 -18
- package/src/runtime/telemetry.js +92 -0
- package/src/runtime/transport.js +29 -1
- package/template/.harness/index.html.tmpl +30 -2
- package/template/packages/sdk/src/config.ts +38 -1
package/skills/notis-apps/cli.md
CHANGED
|
@@ -232,6 +232,21 @@ Examples:
|
|
|
232
232
|
- `npx --package @notis_ai/cli@latest -- notis apps publish --confirm-ready`
|
|
233
233
|
- `npx --package @notis_ai/cli@latest -- notis apps publish ./my-app --confirm-ready`
|
|
234
234
|
|
|
235
|
+
### `npx --package @notis_ai/cli@latest -- notis apps duplicate [dir]`
|
|
236
|
+
|
|
237
|
+
Duplicate an app into an independent copy with its own databases.
|
|
238
|
+
|
|
239
|
+
When to use: 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.
|
|
240
|
+
|
|
241
|
+
Options:
|
|
242
|
+
- `--app-id <id>` — App to duplicate. Defaults to the app this project is linked to.
|
|
243
|
+
- `--name <name>` — Name for the duplicate (default: the source name followed by "copy").
|
|
244
|
+
- `--copy-documents <mode>` — Which rows to copy: 'declared' (default, the starter content a fresh install would have), 'all', or 'none'.
|
|
245
|
+
|
|
246
|
+
Examples:
|
|
247
|
+
- `npx --package @notis_ai/cli@latest -- notis apps duplicate --name "Blog"`
|
|
248
|
+
- `npx --package @notis_ai/cli@latest -- notis apps duplicate --app-id abc123 --name "Bookmarks" --copy-documents none`
|
|
249
|
+
|
|
235
250
|
### `npx --package @notis_ai/cli@latest -- notis apps doctor [dir]`
|
|
236
251
|
|
|
237
252
|
Check project health and readiness.
|
|
@@ -49,9 +49,9 @@ Treat the Notis CLI the same way you would treat a Composio-style tool router fl
|
|
|
49
49
|
|
|
50
50
|
Use this section when the goal is to create or update a Notis app from a local workspace.
|
|
51
51
|
|
|
52
|
-
Notis apps are Vite + React projects using `@notis/sdk`. The workflow is init or pull, dev, build, verify, create/link, deploy.
|
|
52
|
+
Notis apps are Vite + React projects using `@notis/sdk`. The workflow is init or pull, dev, build, verify, create/link, deploy, then an explicitly confirmed Store submission when requested.
|
|
53
53
|
|
|
54
|
-
Important: `deploy`
|
|
54
|
+
Important: `deploy` only updates the installed app artifact for the current user or team. Store submission is a separate, user-gated step. After the user explicitly confirms that the current App Details page and Store listing are ready, `apps publish --confirm-ready` submits the matching deployed version through the backend review flow.
|
|
55
55
|
|
|
56
56
|
### App development workflow
|
|
57
57
|
|
|
@@ -74,6 +74,8 @@ Important: `deploy` means updating the installed app artifact for the current us
|
|
|
74
74
|
- `npx --package @notis_ai/cli@latest -- notis apps deploy`
|
|
75
75
|
9. Check project health:
|
|
76
76
|
- `npx --package @notis_ai/cli@latest -- notis apps doctor`
|
|
77
|
+
10. Only after the user explicitly approves the current Store preview, submit the deployed version:
|
|
78
|
+
- `npx --package @notis_ai/cli@latest -- notis apps publish --confirm-ready`
|
|
77
79
|
|
|
78
80
|
### App development rules
|
|
79
81
|
|
|
@@ -82,7 +84,8 @@ Important: `deploy` means updating the installed app artifact for the current us
|
|
|
82
84
|
- Link before `deploy`, or pass `--app-id <id>` when intentionally deploying without writing local link state.
|
|
83
85
|
- Use `npx --package @notis_ai/cli@latest -- notis apps doctor` to diagnose configuration or dependency issues.
|
|
84
86
|
- Use `npx --package @notis_ai/cli@latest -- notis apps list` to discover existing app IDs before linking.
|
|
85
|
-
-
|
|
87
|
+
- Never treat deploy approval as Store approval. Set visibility to Team or Public first, then run `apps publish --confirm-ready` only after the user explicitly confirms the current App Details page and Store listing.
|
|
88
|
+
- `apps publish --confirm-ready` submits the deployed snapshot through the same backend review flow as App Details. It must reject missing confirmation, incomplete listing media, a local/deployed version mismatch, private visibility, or an existing pending review.
|
|
86
89
|
|
|
87
90
|
### App development command reference
|
|
88
91
|
|
|
@@ -96,9 +99,10 @@ Important: `deploy` means updating the installed app artifact for the current us
|
|
|
96
99
|
- `npx --package @notis_ai/cli@latest -- notis apps link` -- associate the project with a remote app
|
|
97
100
|
- `npx --package @notis_ai/cli@latest -- notis apps deploy` -- upload the artifact and editable source snapshot to the linked installed app in Notis
|
|
98
101
|
- `npx --package @notis_ai/cli@latest -- notis apps deploy --direct` -- deploy directly to Supabase storage, bypassing the backend server (auto-fallback when server is down)
|
|
102
|
+
- `npx --package @notis_ai/cli@latest -- notis apps publish --confirm-ready` -- submit the matching deployed version for Team or Public Store review after explicit user confirmation
|
|
99
103
|
- `npx --package @notis_ai/cli@latest -- notis apps doctor` -- run project diagnostics
|
|
100
104
|
|
|
101
|
-
App
|
|
105
|
+
App Details remains the visual review surface and offers the same Publish/Update action. The CLI command is for agents completing an already approved submission; it does not weaken the separate confirmation gate.
|
|
102
106
|
|
|
103
107
|
If the task is specifically about app structure, runtime behavior, or database/view packaging, pair this skill with the `notis-apps` skill. Use `notis-cli` for the command workflow and `notis-apps` for the product/runtime contract.
|
|
104
108
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
You are onboarding someone to Notis from inside their coding agent. You already
|
|
2
|
+
have a shell and an authenticated Notis CLI, so you do the setup for them and
|
|
3
|
+
report what you did.
|
|
4
|
+
|
|
5
|
+
Run every Notis operation as:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx --package @notis_ai/cli@latest -- notis tools exec <TOOL> --arguments '<json>'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Never invent tool names. `notis tools search "<what you need>"` finds them.
|
|
12
|
+
|
|
13
|
+
## 1. Collect the basics
|
|
14
|
+
|
|
15
|
+
The fields onboarding collects, wherever it runs. Both the conversational
|
|
16
|
+
onboarding assistant and the CLI brief reference this partial so the two paths
|
|
17
|
+
can never quietly collect different things.
|
|
18
|
+
|
|
19
|
+
All of these land through `LOCAL_NOTIS_SAVE_USER_SETTINGS`: `full_name` on the
|
|
20
|
+
user row, the rest merged into the `settings` blob.
|
|
21
|
+
|
|
22
|
+
| Field | Setting key | What it is for |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| First name | `full_name` | How Notis addresses the user. Deduce from the email before asking. |
|
|
25
|
+
| Occupation / role | `position` | Tailors examples and suggestions. |
|
|
26
|
+
| Language | `language` | The language Notis replies in. |
|
|
27
|
+
| Time zone | `timezone` | Anchors every scheduled and time-relative request. Ask; never guess silently. |
|
|
28
|
+
| Attribution | `attribution` | Where they heard about Notis. Asked once, never again. |
|
|
29
|
+
|
|
30
|
+
Rules that hold on every surface:
|
|
31
|
+
|
|
32
|
+
* Ask one question at a time. A wall of questions reads as a form, and people
|
|
33
|
+
abandon forms.
|
|
34
|
+
* Skip anything already known or confidently deducible, and say what you deduced
|
|
35
|
+
rather than asking the user to confirm a blank.
|
|
36
|
+
* Save as soon as you have the basics rather than batching to the end — a user who
|
|
37
|
+
drops out halfway should not lose what they already told you.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
You are in a terminal, so there is no phone number to infer a country from. Take
|
|
41
|
+
the language and time zone from the shell environment if you can read them, state
|
|
42
|
+
what you inferred, and let the user correct you. Then save:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_SAVE_USER_SETTINGS \
|
|
46
|
+
--arguments '{"full_name":"...","position":"...","language":"...","timezone":"..."}'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 2. Connect one app, then immediately read from it
|
|
50
|
+
|
|
51
|
+
Ask what they use day to day. Gmail, Google Calendar, Notion, Slack, Linear, and
|
|
52
|
+
their CRM are the common answers. For each:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx --package @notis_ai/cli@latest -- notis tools link <toolkit>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Give the user the returned URL and wait. Confirm with `notis tools toolkits`.
|
|
59
|
+
|
|
60
|
+
Then — and this is the step that makes onboarding land — run one read-only action
|
|
61
|
+
through the connection and show them the actual result: today's calendar events,
|
|
62
|
+
their most recent email threads, the databases in their Notion. A connected
|
|
63
|
+
integration is a claim; their own data on screen is proof.
|
|
64
|
+
|
|
65
|
+
If they connect nothing, fall back to installing a public app:
|
|
66
|
+
`LOCAL_NOTIS_LIST_PUBLIC_APP_STORE`, then `LOCAL_NOTIS_INSTALL_APP` with
|
|
67
|
+
`destination_type="personal"`.
|
|
68
|
+
|
|
69
|
+
## 3. Finish onboarding
|
|
70
|
+
|
|
71
|
+
**Do this. It is not optional.**
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_COMPLETE_TUTORIAL --arguments '{}'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Until this runs, every message the user sends on every channel is routed to the
|
|
78
|
+
onboarding assistant — so an agent that collects everything and skips this leaves
|
|
79
|
+
them permanently stuck talking to an onboarding bot.
|
|
80
|
+
|
|
81
|
+
Verify with `LOCAL_NOTIS_GET_INTEGRATIONS_STATUS` and tell them what is connected.
|
|
82
|
+
|
|
83
|
+
## What this plan does not include
|
|
84
|
+
|
|
85
|
+
Do **not** call `LOCAL_NOTIS_INSERT_REMINDER` or any automation tool during
|
|
86
|
+
onboarding. Reminders, automations, voice, and the messaging channels are paid
|
|
87
|
+
features; calling them here fails at the worst possible moment. If the user asks
|
|
88
|
+
for a reminder or a recurring task, say plainly that it needs an upgrade and offer
|
|
89
|
+
`LOCAL_NOTIS_GET_SUBSCRIPTION_STATUS` so they can see their plan.
|
|
90
|
+
|
|
91
|
+
What they *do* have: 1,000+ integrations through this CLI, skills that sync to
|
|
92
|
+
their coding agents, long-term memory, notes and databases, and the ability to
|
|
93
|
+
build Notis apps from this machine.
|
package/src/cli.js
CHANGED
|
@@ -5,7 +5,13 @@ import { Command } from 'commander';
|
|
|
5
5
|
import { COMMAND_SPECS, GROUP_SUMMARIES } from './command-specs/index.js';
|
|
6
6
|
import { OutputManager } from './runtime/output.js';
|
|
7
7
|
import { asCliError } from './runtime/errors.js';
|
|
8
|
-
import {
|
|
8
|
+
import { reportCliCommand } from './runtime/telemetry.js';
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_PROFILE,
|
|
11
|
+
resolveOutputMode,
|
|
12
|
+
resolveRuntimeProfile,
|
|
13
|
+
workspacePath,
|
|
14
|
+
} from './runtime/profiles.js';
|
|
9
15
|
|
|
10
16
|
// Read the version from package.json: the publish pipeline bumps the manifest
|
|
11
17
|
// (scripts/release-utils.js applyVersion), so a hardcoded string here goes
|
|
@@ -68,10 +74,39 @@ function buildRuntime(globalOptions, spec) {
|
|
|
68
74
|
...runtime,
|
|
69
75
|
cliVersion: CLI_VERSION,
|
|
70
76
|
color: globalOptions.color !== false,
|
|
77
|
+
quiet: Boolean(globalOptions.quiet),
|
|
78
|
+
verbose: Boolean(globalOptions.verbose),
|
|
71
79
|
workspacePath,
|
|
72
80
|
};
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
function buildErrorRuntime(globalOptions) {
|
|
84
|
+
try {
|
|
85
|
+
return {
|
|
86
|
+
...resolveRuntimeProfile(globalOptions, {
|
|
87
|
+
requireAuth: false,
|
|
88
|
+
includeDebugEntitlementOverride: false,
|
|
89
|
+
}),
|
|
90
|
+
cliVersion: CLI_VERSION,
|
|
91
|
+
color: globalOptions.color !== false,
|
|
92
|
+
quiet: Boolean(globalOptions.quiet),
|
|
93
|
+
verbose: Boolean(globalOptions.verbose),
|
|
94
|
+
workspacePath,
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return {
|
|
98
|
+
profileName: globalOptions.profile || DEFAULT_PROFILE,
|
|
99
|
+
apiBase: null,
|
|
100
|
+
outputMode: resolveOutputMode(globalOptions),
|
|
101
|
+
cliVersion: CLI_VERSION,
|
|
102
|
+
color: globalOptions.color !== false,
|
|
103
|
+
quiet: Boolean(globalOptions.quiet),
|
|
104
|
+
verbose: Boolean(globalOptions.verbose),
|
|
105
|
+
workspacePath,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
75
110
|
function attachSpec(program, parentMap, spec, specs) {
|
|
76
111
|
const parent = ensureParentCommand(program, parentMap, spec.command_path.slice(0, -1));
|
|
77
112
|
const leaf = spec.command_path[spec.command_path.length - 1];
|
|
@@ -96,6 +131,7 @@ function attachSpec(program, parentMap, spec, specs) {
|
|
|
96
131
|
}
|
|
97
132
|
|
|
98
133
|
command.action(async (...raw) => {
|
|
134
|
+
const startedAt = Date.now();
|
|
99
135
|
const commanderCommand = raw[raw.length - 1];
|
|
100
136
|
const argumentValues = raw.slice(0, -1);
|
|
101
137
|
const globalOptions = commanderCommand.optsWithGlobals();
|
|
@@ -118,15 +154,24 @@ function attachSpec(program, parentMap, spec, specs) {
|
|
|
118
154
|
output,
|
|
119
155
|
});
|
|
120
156
|
process.exitCode = typeof exitCode === 'number' ? exitCode : 0;
|
|
157
|
+
await reportCliCommand({
|
|
158
|
+
spec,
|
|
159
|
+
runtime,
|
|
160
|
+
result: process.exitCode === 0 ? 'success' : 'failed',
|
|
161
|
+
durationMs: Date.now() - startedAt,
|
|
162
|
+
error: process.exitCode === 0 ? null : { exitCode: process.exitCode },
|
|
163
|
+
});
|
|
121
164
|
} catch (error) {
|
|
122
|
-
const runtime =
|
|
123
|
-
...resolveRuntimeProfile(globalOptions, { requireAuth: false }),
|
|
124
|
-
cliVersion: CLI_VERSION,
|
|
125
|
-
color: globalOptions.color !== false,
|
|
126
|
-
workspacePath,
|
|
127
|
-
};
|
|
165
|
+
const runtime = buildErrorRuntime(globalOptions);
|
|
128
166
|
const output = new OutputManager(runtime);
|
|
129
167
|
const cliError = asCliError(error);
|
|
168
|
+
await reportCliCommand({
|
|
169
|
+
spec,
|
|
170
|
+
runtime,
|
|
171
|
+
result: 'failed',
|
|
172
|
+
durationMs: Date.now() - startedAt,
|
|
173
|
+
error: cliError,
|
|
174
|
+
});
|
|
130
175
|
process.exitCode = output.emitError({
|
|
131
176
|
command: spec.command_path.join(' '),
|
|
132
177
|
error: cliError,
|
|
@@ -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';
|
|
@@ -66,6 +67,7 @@ const ENSURE_DEV_APP_INSTALLATION_TOOL = 'LOCAL_NOTIS_ENSURE_DEV_APP_INSTALLATIO
|
|
|
66
67
|
const GET_APP_TOOL = 'LOCAL_NOTIS_GET_APP';
|
|
67
68
|
const LIST_APPS_TOOL = 'LOCAL_NOTIS_LIST_APPS';
|
|
68
69
|
const CREATE_APP_TOOL = 'LOCAL_NOTIS_CREATE_APP';
|
|
70
|
+
const DUPLICATE_APP_TOOL = 'LOCAL_NOTIS_DUPLICATE_APP';
|
|
69
71
|
const SAVE_APP_FILES_TOOL = 'LOCAL_NOTIS_SAVE_APP_FILES';
|
|
70
72
|
|
|
71
73
|
// ---------------------------------------------------------------------------
|
|
@@ -377,22 +379,35 @@ function assertHarnessResult(result, route, databaseSlugs) {
|
|
|
377
379
|
details: error,
|
|
378
380
|
});
|
|
379
381
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
382
|
+
const declaredDatabaseSet = new Set(databaseSlugs);
|
|
383
|
+
const databaseQueries = (result.runtimeCalls || []).filter(
|
|
384
|
+
(call) =>
|
|
385
|
+
call?.op === 'callTool'
|
|
386
|
+
&& localNotisToolSlug(call?.args?.name) === 'LOCAL_NOTIS_DATABASE_QUERY',
|
|
387
|
+
);
|
|
388
|
+
for (const call of databaseQueries) {
|
|
389
|
+
const databaseSlug = call?.args?.arguments?.database_slug;
|
|
390
|
+
if (databaseSlug && !declaredDatabaseSet.has(databaseSlug)) {
|
|
388
391
|
assertions.push({
|
|
389
392
|
ok: false,
|
|
390
|
-
code: '
|
|
391
|
-
message: `Route "${route.slug}"
|
|
393
|
+
code: 'undeclared_database_query',
|
|
394
|
+
message: `Route "${route.slug}" queried undeclared database "${databaseSlug}".`,
|
|
392
395
|
details: { databaseSlug },
|
|
393
396
|
});
|
|
394
397
|
}
|
|
395
398
|
}
|
|
399
|
+
const collectionDatabase = route?.collection?.database;
|
|
400
|
+
if (
|
|
401
|
+
collectionDatabase
|
|
402
|
+
&& !databaseQueries.some((call) => call?.args?.arguments?.database_slug === collectionDatabase)
|
|
403
|
+
) {
|
|
404
|
+
assertions.push({
|
|
405
|
+
ok: false,
|
|
406
|
+
code: 'missing_collection_database_query',
|
|
407
|
+
message: `Collection route "${route.slug}" did not query "${collectionDatabase}".`,
|
|
408
|
+
details: { databaseSlug: collectionDatabase },
|
|
409
|
+
});
|
|
410
|
+
}
|
|
396
411
|
return assertions;
|
|
397
412
|
}
|
|
398
413
|
|
|
@@ -440,6 +455,7 @@ function buildManifestForDev(appConfig) {
|
|
|
440
455
|
css: 'bundle/app.css',
|
|
441
456
|
},
|
|
442
457
|
databases: appConfig.databases || [],
|
|
458
|
+
capabilities: normalizeAppCapabilities(appConfig.capabilities),
|
|
443
459
|
tools: appConfig.tools || [],
|
|
444
460
|
skills: (appConfig.skills || []).map((skill) => ({
|
|
445
461
|
key: skill.key,
|
|
@@ -485,9 +501,9 @@ export async function ensureDevInstall({
|
|
|
485
501
|
let linkedState = readLinkedState(projectDir);
|
|
486
502
|
if (linkedState?.app_id) {
|
|
487
503
|
const linkedApp = await getAccessibleApp(ctx.runtime, linkedState.app_id, runTool);
|
|
488
|
-
if (
|
|
504
|
+
if (linkedApp?.manifest?.is_dev === true) {
|
|
489
505
|
const { app_id: legacyDevAppId, linked_at: _linkedAt, deployed_at: _deployedAt, version: _version, ...rest } = linkedState;
|
|
490
|
-
const devAppId = linkedState.dev_app_id ||
|
|
506
|
+
const devAppId = linkedState.dev_app_id || legacyDevAppId;
|
|
491
507
|
linkedState = {
|
|
492
508
|
...rest,
|
|
493
509
|
...(devAppId ? { dev_app_id: devAppId } : {}),
|
|
@@ -930,7 +946,9 @@ async function appsBuildHandler(ctx) {
|
|
|
930
946
|
throw usageError(`Project has problems:\n${problems.map((p) => ` - ${p}`).join('\n')}`);
|
|
931
947
|
}
|
|
932
948
|
|
|
933
|
-
const { manifest } = await buildArtifact(projectDir
|
|
949
|
+
const { manifest } = await buildArtifact(projectDir, {
|
|
950
|
+
stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit',
|
|
951
|
+
});
|
|
934
952
|
|
|
935
953
|
return ctx.output.emitSuccess({
|
|
936
954
|
command: ctx.spec.command_path.join(' '),
|
|
@@ -963,7 +981,9 @@ async function appsVerifyHandler(ctx) {
|
|
|
963
981
|
}
|
|
964
982
|
|
|
965
983
|
if (!ctx.options.skipBuild) {
|
|
966
|
-
await buildArtifact(projectDir
|
|
984
|
+
await buildArtifact(projectDir, {
|
|
985
|
+
stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit',
|
|
986
|
+
});
|
|
967
987
|
}
|
|
968
988
|
|
|
969
989
|
const manifest = readManifest(projectDir);
|
|
@@ -1175,7 +1195,9 @@ async function appsScreenshotHandler(ctx) {
|
|
|
1175
1195
|
}
|
|
1176
1196
|
|
|
1177
1197
|
if (!ctx.options.skipBuild) {
|
|
1178
|
-
await buildArtifact(projectDir
|
|
1198
|
+
await buildArtifact(projectDir, {
|
|
1199
|
+
stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit',
|
|
1200
|
+
});
|
|
1179
1201
|
}
|
|
1180
1202
|
|
|
1181
1203
|
const manifest = readManifest(projectDir);
|
|
@@ -1364,16 +1386,25 @@ async function appsScreenshotHandler(ctx) {
|
|
|
1364
1386
|
}
|
|
1365
1387
|
}
|
|
1366
1388
|
|
|
1389
|
+
export function buildLinkedAppState(existingState, appId, linkedAt = new Date().toISOString()) {
|
|
1390
|
+
return {
|
|
1391
|
+
...(existingState?.dev_app_id ? { dev_app_id: existingState.dev_app_id } : {}),
|
|
1392
|
+
...(existingState?.dev_linked_at ? { dev_linked_at: existingState.dev_linked_at } : {}),
|
|
1393
|
+
app_id: appId,
|
|
1394
|
+
linked_at: linkedAt,
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1367
1398
|
async function appsLinkHandler(ctx) {
|
|
1368
1399
|
const projectDir = resolveProjectDir(ctx.args.dir || '.');
|
|
1369
1400
|
const appId = ctx.args.appId;
|
|
1370
1401
|
|
|
1371
1402
|
await assertLinkTarget(ctx.runtime, appId);
|
|
1372
1403
|
|
|
1373
|
-
writeLinkedState(
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1404
|
+
writeLinkedState(
|
|
1405
|
+
projectDir,
|
|
1406
|
+
buildLinkedAppState(readLinkedState(projectDir), appId),
|
|
1407
|
+
);
|
|
1377
1408
|
|
|
1378
1409
|
return ctx.output.emitSuccess({
|
|
1379
1410
|
command: ctx.spec.command_path.join(' '),
|
|
@@ -1442,7 +1473,9 @@ async function appsDeployHandler(ctx) {
|
|
|
1442
1473
|
|
|
1443
1474
|
// Build if needed
|
|
1444
1475
|
if (!ctx.options.skipBuild) {
|
|
1445
|
-
await buildArtifact(projectDir
|
|
1476
|
+
await buildArtifact(projectDir, {
|
|
1477
|
+
stdio: ctx.output.isMachineMode() ? 'pipe' : 'inherit',
|
|
1478
|
+
});
|
|
1446
1479
|
}
|
|
1447
1480
|
|
|
1448
1481
|
// Direct deploy mode: upload to Supabase storage directly
|
|
@@ -1539,6 +1572,67 @@ function deployedAppVersion(app) {
|
|
|
1539
1572
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
1540
1573
|
}
|
|
1541
1574
|
|
|
1575
|
+
async function appsDuplicateHandler(ctx) {
|
|
1576
|
+
const projectDir = resolveProjectDir(ctx.args.dir || '.');
|
|
1577
|
+
// Either target an app explicitly, or duplicate whatever this project is
|
|
1578
|
+
// linked to, so `notis apps duplicate` works from inside a project.
|
|
1579
|
+
const appId = requireLinkedAppId(projectDir, ctx.options.appId);
|
|
1580
|
+
const idempotencyKey = nextIdempotencyKey(ctx.globalOptions);
|
|
1581
|
+
|
|
1582
|
+
const copyDocuments = ctx.options.copyDocuments || 'declared';
|
|
1583
|
+
if (!['declared', 'all', 'none'].includes(copyDocuments)) {
|
|
1584
|
+
throw usageError("--copy-documents must be one of: declared, all, none.");
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
const result = await runToolCommand({
|
|
1588
|
+
runtime: ctx.runtime,
|
|
1589
|
+
toolName: DUPLICATE_APP_TOOL,
|
|
1590
|
+
arguments_: {
|
|
1591
|
+
app_id: appId,
|
|
1592
|
+
...(ctx.options.name ? { name: ctx.options.name } : {}),
|
|
1593
|
+
copy_documents: copyDocuments,
|
|
1594
|
+
},
|
|
1595
|
+
mutating: true,
|
|
1596
|
+
idempotencyKey,
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
const payload = result.payload || {};
|
|
1600
|
+
if (payload.status === 'error') {
|
|
1601
|
+
throw usageError(`Could not duplicate app ${appId}: ${payload.message || 'unknown error'}`);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
const duplicated = payload.app || {};
|
|
1605
|
+
if (!duplicated.id) {
|
|
1606
|
+
throw usageError(`Could not duplicate app ${appId}: the backend did not return an app id.`);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const data = {
|
|
1610
|
+
app_id: duplicated.id,
|
|
1611
|
+
name: duplicated.name,
|
|
1612
|
+
slug: duplicated.slug,
|
|
1613
|
+
duplicated_from_app_id: payload.duplicated_from_app_id || appId,
|
|
1614
|
+
copied_document_count: payload.copied_document_count ?? 0,
|
|
1615
|
+
portal_url: payload.portal_url,
|
|
1616
|
+
idempotency_key: idempotencyKey,
|
|
1617
|
+
// The duplicate owns brand new databases; nothing is shared with the source.
|
|
1618
|
+
databases: (payload.databases || []).map((database) => ({
|
|
1619
|
+
id: database.id,
|
|
1620
|
+
slug: database.slug,
|
|
1621
|
+
name: database.name,
|
|
1622
|
+
})),
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1625
|
+
return ctx.output.emitSuccess({
|
|
1626
|
+
command: ctx.spec.command_path.join(' '),
|
|
1627
|
+
data,
|
|
1628
|
+
humanSummary: `Duplicated app ${appId} as ${duplicated.name || duplicated.id}`,
|
|
1629
|
+
hints: payload.portal_url
|
|
1630
|
+
? [{ command: payload.portal_url, reason: 'Open the duplicated app in Portal' }]
|
|
1631
|
+
: [],
|
|
1632
|
+
meta: { mutating: true, idempotency_key: idempotencyKey },
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1542
1636
|
async function appsPublishHandler(ctx) {
|
|
1543
1637
|
if (ctx.options.confirmReady !== true) {
|
|
1544
1638
|
throw usageError(
|
|
@@ -1652,10 +1746,20 @@ async function appsDoctorHandler(ctx) {
|
|
|
1652
1746
|
? `Found ${problems.length} problems:\n${problems.map((p) => ` - ${p}`).join('\n')}`
|
|
1653
1747
|
: warnings.length
|
|
1654
1748
|
? `Healthy with ${warnings.length} warnings:\n${warnings.map((w) => ` - ${w}`).join('\n')}`
|
|
1655
|
-
: `Project is healthy.${linkedState
|
|
1749
|
+
: `Project is healthy.${doctorLinkSummary(linkedState)}`,
|
|
1656
1750
|
});
|
|
1657
1751
|
}
|
|
1658
1752
|
|
|
1753
|
+
export function doctorLinkSummary(linkedState) {
|
|
1754
|
+
if (linkedState?.app_id) {
|
|
1755
|
+
return ` Linked to app ${linkedState.app_id}.`;
|
|
1756
|
+
}
|
|
1757
|
+
if (linkedState?.dev_app_id) {
|
|
1758
|
+
return ` Local development app ${linkedState.dev_app_id} is active.`;
|
|
1759
|
+
}
|
|
1760
|
+
return ' Not linked.';
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1659
1763
|
// ---------------------------------------------------------------------------
|
|
1660
1764
|
// Command specs
|
|
1661
1765
|
// ---------------------------------------------------------------------------
|
|
@@ -1919,6 +2023,35 @@ export const appsCommandSpecs = [
|
|
|
1919
2023
|
backend_call: { type: 'http', name: 'portal_apps/publish' },
|
|
1920
2024
|
handler: appsPublishHandler,
|
|
1921
2025
|
},
|
|
2026
|
+
{
|
|
2027
|
+
command_path: ['apps', 'duplicate'],
|
|
2028
|
+
summary: 'Duplicate an app into an independent copy with its own databases.',
|
|
2029
|
+
when_to_use:
|
|
2030
|
+
'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.',
|
|
2031
|
+
args_schema: {
|
|
2032
|
+
arguments: [
|
|
2033
|
+
{ token: '[dir]', key: 'dir', description: 'Project directory (default: current dir).' },
|
|
2034
|
+
],
|
|
2035
|
+
options: [
|
|
2036
|
+
{ flags: '--app-id <id>', description: 'App to duplicate. Defaults to the app this project is linked to.' },
|
|
2037
|
+
{ flags: '--name <name>', description: 'Name for the duplicate (default: the source name followed by "copy").' },
|
|
2038
|
+
{
|
|
2039
|
+
flags: '--copy-documents <mode>',
|
|
2040
|
+
description:
|
|
2041
|
+
"Which rows to copy: 'declared' (default, the starter content a fresh install would have), 'all', or 'none'.",
|
|
2042
|
+
},
|
|
2043
|
+
],
|
|
2044
|
+
},
|
|
2045
|
+
examples: [
|
|
2046
|
+
'notis apps duplicate --name "Blog"',
|
|
2047
|
+
'notis apps duplicate --app-id abc123 --name "Bookmarks" --copy-documents none',
|
|
2048
|
+
],
|
|
2049
|
+
mutates: true,
|
|
2050
|
+
idempotent: false,
|
|
2051
|
+
require_auth: true,
|
|
2052
|
+
backend_call: { type: 'tool', name: DUPLICATE_APP_TOOL },
|
|
2053
|
+
handler: appsDuplicateHandler,
|
|
2054
|
+
},
|
|
1922
2055
|
{
|
|
1923
2056
|
command_path: ['apps', 'doctor'],
|
|
1924
2057
|
summary: 'Check project health and readiness.',
|