@notis_ai/cli 0.2.9 → 0.2.11
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 +73 -9
- package/dist/scaffolds.json +1 -1
- package/package.json +1 -1
- package/skills/notis-apps/SKILL.md +3 -3
- package/skills/notis-apps/cli.md +1 -1
- package/skills/notis-cli/SKILL.md +62 -19
- package/skills/notis-onboarding/BRIEF.md +24 -5
- package/skills/notis-query/cli.md +1 -1
- package/src/cli.js +27 -2
- package/src/command-specs/apps.js +18 -10
- package/src/command-specs/auth.js +15 -20
- package/src/command-specs/diagnostics.js +6 -1
- package/src/command-specs/helpers.js +7 -1
- package/src/command-specs/index.js +3 -0
- package/src/command-specs/meta.js +29 -19
- package/src/command-specs/onboarding.js +122 -200
- package/src/command-specs/profile.js +358 -0
- package/src/runtime/app-platform.js +5 -5
- package/src/runtime/auth-recovery.js +100 -0
- package/src/runtime/cli-mode.generated.js +4 -3
- package/src/runtime/cli-mode.js +13 -8
- package/src/runtime/oauth.js +121 -30
- package/src/runtime/profiles.js +435 -223
- package/src/runtime/transport.js +84 -29
- package/src/runtime/desktop-auth.js +0 -162
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Agent-first Notis CLI for apps and generic tool execution.
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
Use the Notis CLI through NPX; do not rely on an installed `notis` command.
|
|
7
|
+
Use the Notis CLI through NPX; do not rely on an installed `notis` command. Run `notis login` once to authorize a scoped, revocable OAuth credential in the browser — that is how the CLI signs in everywhere, including on a machine that also runs Notis Desktop.
|
|
8
8
|
|
|
9
9
|
For CI, hosted agents, or internal scripts, pass a non-persisted token with `NOTIS_JWT=<token>`.
|
|
10
10
|
|
|
@@ -18,7 +18,24 @@ npx --package @notis_ai/cli@latest -- notis apps list
|
|
|
18
18
|
npx --package @notis_ai/cli@latest -- notis tools search "list Notis databases"
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Use `notis login --paste-code` for the HTTPS copy-paste fallback on a remote machine.
|
|
22
|
+
|
|
23
|
+
## Profiles
|
|
24
|
+
|
|
25
|
+
A profile is one account paired with one API endpoint. Every profile keeps its own credential, so switching between them never signs any of them out.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx --package @notis_ai/cli@latest -- notis login --profile work
|
|
29
|
+
npx --package @notis_ai/cli@latest -- notis profile list
|
|
30
|
+
npx --package @notis_ai/cli@latest -- notis profile use work
|
|
31
|
+
npx --package @notis_ai/cli@latest -- notis --profile default tools search "..."
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`notis logout` revokes and removes the OAuth grant for one profile; `--all-profiles` clears every one.
|
|
35
|
+
|
|
36
|
+
Credential precedence within the selected profile is: an active `./dev.sh` worktree credential, then `NOTIS_JWT`, then the profile's OAuth grant.
|
|
37
|
+
|
|
38
|
+
`./dev.sh` exposes its test account as a lease-backed `dev-<workspace>-<hash>` profile bound to its loopback backend. The credential stays in the worktree rather than the shared account config. That synthetic profile is the default only inside its active worktree; naming any stored profile with `--profile` runs against that real account instead.
|
|
22
39
|
|
|
23
40
|
The CLI defaults to `json` output in agent or non-TTY contexts and `table` output in interactive terminals.
|
|
24
41
|
|
|
@@ -27,7 +44,7 @@ The CLI defaults to `json` output in agent or non-TTY contexts and `table` outpu
|
|
|
27
44
|
- `--json` — Shortcut for `--output json`
|
|
28
45
|
- `--output <table|json|yaml|ndjson>` — Output mode override
|
|
29
46
|
- `--non-interactive` — Disable prompts
|
|
30
|
-
- `--profile <name>` —
|
|
47
|
+
- `--profile <name>` — Run as a stored profile instead of the active one
|
|
31
48
|
- `--api-base <url>` — Override the API base for one invocation
|
|
32
49
|
- `--timeout-ms <n>` — HTTP timeout in milliseconds
|
|
33
50
|
- `--idempotency-key <key>` — Override the generated idempotency key for mutating commands
|
|
@@ -36,37 +53,38 @@ The CLI defaults to `json` output in agent or non-TTY contexts and `table` outpu
|
|
|
36
53
|
|
|
37
54
|
### `npx --package @notis_ai/cli@latest -- notis login`
|
|
38
55
|
|
|
39
|
-
Authorize
|
|
56
|
+
Authorize a CLI profile in a browser with scoped OAuth access.
|
|
40
57
|
|
|
41
|
-
When to use:
|
|
58
|
+
When to use: Run this once per account you want the CLI to reach. Pass --profile to add a second account without signing the first one out.
|
|
42
59
|
|
|
43
60
|
Options:
|
|
44
61
|
- `--no-browser` — Print the authorization URL without opening a browser.
|
|
45
62
|
- `--print-url` — Print the authorization URL even when opening a browser.
|
|
46
63
|
- `--paste-code` — Use the copy-paste callback for SSH and headless machines.
|
|
47
|
-
- `--force` — Create an independent OAuth grant even when Desktop is signed in.
|
|
48
64
|
- `--timeout-seconds <n>` — How long to wait for authorization (default 300).
|
|
49
65
|
- `--scope <scope>` — OAuth permission to request (repeatable).
|
|
50
66
|
- `--code <code>` — Redeem the code shown in the browser after a non-interactive login.
|
|
51
67
|
|
|
52
68
|
Examples:
|
|
53
69
|
- `npx --package @notis_ai/cli@latest -- notis login`
|
|
70
|
+
- `npx --package @notis_ai/cli@latest -- notis login --profile work`
|
|
71
|
+
- `npx --package @notis_ai/cli@latest -- notis login --profile beta --api-base https://api-beta.notis.ai`
|
|
54
72
|
- `npx --package @notis_ai/cli@latest -- notis login --no-browser --print-url`
|
|
55
73
|
- `npx --package @notis_ai/cli@latest -- notis login --paste-code`
|
|
56
74
|
- `npx --package @notis_ai/cli@latest -- notis login --code 4f3c2b1a`
|
|
57
|
-
- `npx --package @notis_ai/cli@latest -- notis login --force`
|
|
58
75
|
|
|
59
76
|
### `npx --package @notis_ai/cli@latest -- notis logout`
|
|
60
77
|
|
|
61
|
-
Revoke and remove the
|
|
78
|
+
Revoke and remove the OAuth credential for one CLI profile.
|
|
62
79
|
|
|
63
|
-
When to use: Use this to disconnect
|
|
80
|
+
When to use: Use this to disconnect a single account. Other profiles keep their credentials unless you pass --all-profiles.
|
|
64
81
|
|
|
65
82
|
Options:
|
|
66
83
|
- `--all-profiles` — Remove OAuth credentials from every CLI profile.
|
|
67
84
|
|
|
68
85
|
Examples:
|
|
69
86
|
- `npx --package @notis_ai/cli@latest -- notis logout`
|
|
87
|
+
- `npx --package @notis_ai/cli@latest -- notis logout --profile work`
|
|
70
88
|
- `npx --package @notis_ai/cli@latest -- notis logout --all-profiles`
|
|
71
89
|
|
|
72
90
|
|
|
@@ -352,6 +370,52 @@ Examples:
|
|
|
352
370
|
- `npx --package @notis_ai/cli@latest -- notis tools link dataforseo --reconnect --credentials - < credentials.json`
|
|
353
371
|
|
|
354
372
|
|
|
373
|
+
## Profile Commands
|
|
374
|
+
|
|
375
|
+
### `npx --package @notis_ai/cli@latest -- notis profile list`
|
|
376
|
+
|
|
377
|
+
List every CLI profile with its account, API endpoint, and credential state.
|
|
378
|
+
|
|
379
|
+
When to use: Use this to see which accounts and environments this machine can reach before choosing one.
|
|
380
|
+
|
|
381
|
+
Examples:
|
|
382
|
+
- `npx --package @notis_ai/cli@latest -- notis profile list`
|
|
383
|
+
- `npx --package @notis_ai/cli@latest -- notis profile list --json`
|
|
384
|
+
|
|
385
|
+
### `npx --package @notis_ai/cli@latest -- notis profile use <name>`
|
|
386
|
+
|
|
387
|
+
Switch the default profile without signing any profile out.
|
|
388
|
+
|
|
389
|
+
When to use: Use this to change which account and API subsequent commands target. Every other profile keeps its credential.
|
|
390
|
+
|
|
391
|
+
Examples:
|
|
392
|
+
- `npx --package @notis_ai/cli@latest -- notis profile use work`
|
|
393
|
+
- `npx --package @notis_ai/cli@latest -- notis profile use default`
|
|
394
|
+
|
|
395
|
+
### `npx --package @notis_ai/cli@latest -- notis profile show [name]`
|
|
396
|
+
|
|
397
|
+
Show one profile in detail, including scopes and credential expiry.
|
|
398
|
+
|
|
399
|
+
When to use: Use this to inspect exactly which account and endpoint a profile resolves to.
|
|
400
|
+
|
|
401
|
+
Examples:
|
|
402
|
+
- `npx --package @notis_ai/cli@latest -- notis profile show`
|
|
403
|
+
- `npx --package @notis_ai/cli@latest -- notis profile show work --json`
|
|
404
|
+
|
|
405
|
+
### `npx --package @notis_ai/cli@latest -- notis profile remove <name>`
|
|
406
|
+
|
|
407
|
+
Delete a CLI profile from this machine.
|
|
408
|
+
|
|
409
|
+
When to use: Use this after logging a profile out. Removing a still-authorized profile requires --force and leaves the grant live server-side.
|
|
410
|
+
|
|
411
|
+
Options:
|
|
412
|
+
- `--force` — Discard a profile that still holds a credential.
|
|
413
|
+
|
|
414
|
+
Examples:
|
|
415
|
+
- `npx --package @notis_ai/cli@latest -- notis profile remove old-work`
|
|
416
|
+
- `npx --package @notis_ai/cli@latest -- notis profile remove old-work --force`
|
|
417
|
+
|
|
418
|
+
|
|
355
419
|
## Meta Commands
|
|
356
420
|
|
|
357
421
|
### `npx --package @notis_ai/cli@latest -- notis doctor`
|
package/dist/scaffolds.json
CHANGED
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ description: Design and package Notis apps. Use when users want an app that grou
|
|
|
7
7
|
|
|
8
8
|
Use this skill when the user wants a packaged Notis app -- task manager, CRM, dashboard, internal tool, etc. Notis apps are **Vite + React projects** that deploy into the Notis portal as installed apps for the current user or team.
|
|
9
9
|
|
|
10
|
-
Run the Notis CLI through NPX, for example `npx --package @notis_ai/cli@latest -- notis apps list`.
|
|
10
|
+
Run the Notis CLI through NPX, for example `npx --package @notis_ai/cli@latest -- notis apps list`. Sign the CLI in once with `notis login`; each account you authorize is a profile you can switch between with `notis profile use`. This `notis-apps` skill is delivered through normal Notis skill sync for the signed-in user, alongside other curated skills.
|
|
11
11
|
|
|
12
12
|
## How Apps Are Built
|
|
13
13
|
|
|
@@ -470,13 +470,13 @@ Runs the real desktop-local development workflow. The CLI should discover all ap
|
|
|
470
470
|
|
|
471
471
|
## Deploy Without Backend Server
|
|
472
472
|
|
|
473
|
-
If the
|
|
473
|
+
If the live API is unreachable, use `--direct`:
|
|
474
474
|
|
|
475
475
|
```bash
|
|
476
476
|
npx --package @notis_ai/cli@latest -- notis apps deploy --direct
|
|
477
477
|
```
|
|
478
478
|
|
|
479
|
-
This uploads the bundle and editable source snapshot directly to Supabase storage and updates the app manifest in the database, bypassing the
|
|
479
|
+
This uploads the bundle and editable source snapshot directly to Supabase storage and updates the app manifest in the database, bypassing the API server. The CLI auto-falls back to direct mode on network errors. Localhost backends are reserved for `/notis-tests` via `./dev.sh`; do not retarget the personal CLI lane at loopback from this skill.
|
|
480
480
|
|
|
481
481
|
## Testing
|
|
482
482
|
|
package/skills/notis-apps/cli.md
CHANGED
|
@@ -8,7 +8,7 @@ Important: `notis apps deploy` updates the linked installed app. It is not an ap
|
|
|
8
8
|
|
|
9
9
|
## Setup
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Run `npx --package @notis_ai/cli@latest -- notis login` to authorize the CLI. Run commands through NPX, for example `npx --package @notis_ai/cli@latest -- notis apps list`.
|
|
12
12
|
|
|
13
13
|
For CI, hosted agents, or internal scripts, pass a non-persisted token with `NOTIS_JWT=<token>` and use `--api-base <server-url>` when targeting a non-default server.
|
|
14
14
|
|
|
@@ -29,9 +29,30 @@ Use the registry-resolved published npm package everywhere:
|
|
|
29
29
|
|
|
30
30
|
- `npx --package @notis_ai/cli@latest -- notis ...`
|
|
31
31
|
|
|
32
|
-
Always use this NPX command form so the agent runs the current published CLI. In hosted shells, the CLI is pre-authenticated through `NOTIS_JWT
|
|
32
|
+
Always use this NPX command form so the agent runs the current published CLI. In hosted shells, the CLI is pre-authenticated through `NOTIS_JWT`. On a local machine the CLI authorizes itself in the browser with `notis login` — nothing else signs it in, and the Notis desktop app is not involved.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
This `notis-cli` skill is delivered through normal Notis skill sync for the signed-in user, alongside other curated skills.
|
|
35
|
+
|
|
36
|
+
## Profiles: accounts and endpoints
|
|
37
|
+
|
|
38
|
+
A profile is one account paired with one API endpoint. Profiles live side by side; switching between them never signs any of them out.
|
|
39
|
+
|
|
40
|
+
- `npx --package @notis_ai/cli@latest -- notis profile list` — every profile on this machine, with its endpoint, user, and whether it is signed in. The active one is marked.
|
|
41
|
+
- `npx --package @notis_ai/cli@latest -- notis profile use <name>` — change which account subsequent commands run as.
|
|
42
|
+
- `npx --package @notis_ai/cli@latest -- notis --profile <name> <command>` — run a single command as another account without changing the active one.
|
|
43
|
+
- `npx --package @notis_ai/cli@latest -- notis login --profile <name>` — add an account. The existing profiles keep their credentials.
|
|
44
|
+
- `npx --package @notis_ai/cli@latest -- notis logout` — sign out of the active profile only; pass `--all-profiles` to clear every one.
|
|
45
|
+
|
|
46
|
+
Read the profile before acting on the user's data. `notis whoami` reports the account and endpoint a command will hit; if that is not the account the user meant, switch profiles rather than proceeding.
|
|
47
|
+
|
|
48
|
+
### Working against a local `./dev.sh` backend
|
|
49
|
+
|
|
50
|
+
`./dev.sh` exposes its test account as a lease-backed profile (`dev-<workspace>-<hash>`) pointing at the local backend, and prints the name on startup. Its credential stays in that worktree and it is the automatic default there, so `notis ...` targets the local API and test user with no extra flags. It is not a stored account profile and cannot be selected outside the worktree.
|
|
51
|
+
|
|
52
|
+
Two rules follow from the dev credential being scoped to that local backend:
|
|
53
|
+
|
|
54
|
+
- A `dev-*` profile only exists while its `./dev.sh` lease is running. If it is stopped, the CLI fails with `dev_runtime_unavailable` instead of sending the test user's token to the live API.
|
|
55
|
+
- To reach a real account from inside a worktree — including when the local backend is wedged — name a real profile explicitly: `notis --profile <name> ...`. That is the supported escape hatch.
|
|
35
56
|
|
|
36
57
|
## Critical rule for missing tools
|
|
37
58
|
|
|
@@ -134,13 +155,13 @@ This is the main escape hatch for:
|
|
|
134
155
|
### Tool access workflow
|
|
135
156
|
|
|
136
157
|
1. List available toolkit namespaces:
|
|
137
|
-
- `npx --package @notis_ai/cli@latest -- notis tools toolkits`
|
|
158
|
+
- `npx --package @notis_ai/cli@latest -- notis tools toolkits --timeout-ms 90000`
|
|
138
159
|
2. Search for the capability you need using natural language:
|
|
139
|
-
- `npx --package @notis_ai/cli@latest -- notis tools search "<query>"`
|
|
160
|
+
- `npx --package @notis_ai/cli@latest -- notis tools search "<query>" --timeout-ms 90000`
|
|
140
161
|
- optionally add known field hints with `--known-fields "<key:value>"`
|
|
141
162
|
3. If needed, inspect the exact tool and parameter schema:
|
|
142
|
-
- `npx --package @notis_ai/cli@latest -- notis tools describe <tool-name
|
|
143
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec <tool-name> --get-schema`
|
|
163
|
+
- `npx --package @notis_ai/cli@latest -- notis tools describe <tool-name> --timeout-ms 90000`
|
|
164
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec <tool-name> --get-schema --timeout-ms 90000`
|
|
144
165
|
4. Validate arguments before execution when the tool is mutating or the schema is non-trivial:
|
|
145
166
|
- `npx --package @notis_ai/cli@latest -- notis tools exec <tool-name> --dry-run --arguments '<json>'`
|
|
146
167
|
5. Execute the tool:
|
|
@@ -151,6 +172,22 @@ This is the main escape hatch for:
|
|
|
151
172
|
- `npx --package @notis_ai/cli@latest -- notis tools link <toolkit>`
|
|
152
173
|
- For a revoked or invalid credential-based connection, reconnect with credential JSON on stdin: `npx --package @notis_ai/cli@latest -- notis tools link <toolkit> --reconnect --credentials -`
|
|
153
174
|
|
|
175
|
+
### Discovery latency and caching
|
|
176
|
+
|
|
177
|
+
The discovery bridge may query several connected MCP servers on a cold run and
|
|
178
|
+
can legitimately take longer than the CLI's general 30-second timeout. Always
|
|
179
|
+
use `--timeout-ms 90000` for `tools toolkits`, `tools search`, `tools describe`,
|
|
180
|
+
and schema-only discovery calls. If a discovery call returns `network_timeout`,
|
|
181
|
+
retry that same command once with `--timeout-ms 90000`; do not start a new
|
|
182
|
+
query, invent a tool name, or loop on the default 30-second command.
|
|
183
|
+
|
|
184
|
+
Discovery is idempotent but should be bounded: run the toolkit listing once per
|
|
185
|
+
task, run one natural-language search per distinct capability, and cache the
|
|
186
|
+
returned canonical tool names and schemas for the rest of the current turn.
|
|
187
|
+
After a successful search/schema response, call the returned canonical tool
|
|
188
|
+
directly (with a dry-run before mutations) instead of repeating the same
|
|
189
|
+
discovery request before every connected-service action.
|
|
190
|
+
|
|
154
191
|
### Tool access rules
|
|
155
192
|
|
|
156
193
|
- Never guess tool names. Discover them with `npx --package @notis_ai/cli@latest -- notis tools search` first.
|
|
@@ -241,7 +278,8 @@ When `LOCAL_NOTIS_DATABASE_LIST_DATABASES` or `LOCAL_NOTIS_DATABASE_GET_DATABASE
|
|
|
241
278
|
|
|
242
279
|
## Supporting commands
|
|
243
280
|
|
|
244
|
-
- `npx --package @notis_ai/cli@latest -- notis
|
|
281
|
+
- `npx --package @notis_ai/cli@latest -- notis whoami` — confirm which account and endpoint a command will target
|
|
282
|
+
- `npx --package @notis_ai/cli@latest -- notis doctor` — verify CLI config, auth, routing, and API reachability before relying on the CLI
|
|
245
283
|
- `npx --package @notis_ai/cli@latest -- notis describe <command...>` — get the exact command contract for first-class CLI commands
|
|
246
284
|
|
|
247
285
|
## Summary
|
|
@@ -255,26 +293,31 @@ Most importantly: if you do not currently have the tool you need, especially for
|
|
|
255
293
|
|
|
256
294
|
## Troubleshooting
|
|
257
295
|
|
|
258
|
-
### CLI returns `auth_expired`
|
|
296
|
+
### CLI returns `auth_expired` or `auth_missing`
|
|
297
|
+
|
|
298
|
+
The profile's browser authorization has lapsed or was never granted. Run
|
|
299
|
+
`notis login` (add `--profile <name>` when the failing profile is not the
|
|
300
|
+
active one) and have the user approve the browser prompt. In JSON/agent mode
|
|
301
|
+
the first hint is the exact command to run. Do not copy refresh tokens into
|
|
302
|
+
commands or try to mint a credential yourself.
|
|
259
303
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
`open -a 'Notis Beta'`) command when the owning desktop app is not running.
|
|
263
|
-
Wait for the app to restore the signed-in session, then rerun the original
|
|
264
|
-
command. Do not copy refresh tokens into commands or try to refresh the shared
|
|
265
|
-
desktop session yourself.
|
|
304
|
+
If the profile is a `dev-*` one, the fix is to restart `./dev.sh` in the
|
|
305
|
+
workspace it belongs to, or to switch to a real account profile.
|
|
266
306
|
|
|
267
307
|
### Deploy fails with "network_error" or "fetch failed"
|
|
268
308
|
|
|
269
|
-
The
|
|
309
|
+
The CLI defaults to the live Notis API (`https://api.notis.ai`, or
|
|
310
|
+
`https://api-beta.notis.ai` when the signed-in user is on beta). Solutions:
|
|
311
|
+
|
|
312
|
+
1. Run `npx --package @notis_ai/cli@latest -- notis doctor` and confirm `api_base` is a live Notis host
|
|
313
|
+
2. Use `--direct` for app deploys when you only need Supabase storage upload: `npx --package @notis_ai/cli@latest -- notis apps deploy --direct`
|
|
314
|
+
3. If auth looks stale, run `npx --package @notis_ai/cli@latest -- notis login` and retry
|
|
270
315
|
|
|
271
|
-
|
|
272
|
-
2. Start the backend server: `cd server && python3 main.py`
|
|
273
|
-
3. Check the API base: `npx --package @notis_ai/cli@latest -- notis doctor` shows the configured `api_base`
|
|
316
|
+
Localhost backends are a Notis-developer test lane only. Do not retarget the CLI at loopback from this skill — that path is owned by `./dev.sh`, which exposes its own lease-backed `dev-*` profile.
|
|
274
317
|
|
|
275
318
|
### `npx --package @notis_ai/cli@latest -- notis doctor` shows health/tool_roundtrip errors
|
|
276
319
|
|
|
277
|
-
The CLI health check pings the
|
|
320
|
+
The CLI health check pings the configured live API. App development commands that are `backend_call: local` (`init`, `build`, `verify`, `link`, `doctor`) work offline. `dev`, `pull`, `create`, `list`, and normal `deploy` need the live API; `deploy --direct` can bypass it when Supabase credentials are available.
|
|
278
321
|
|
|
279
322
|
### Stale bundle in the portal after deploy
|
|
280
323
|
|
|
@@ -10,6 +10,22 @@ npx --package @notis_ai/cli@latest -- notis tools exec <TOOL> --arguments '<json
|
|
|
10
10
|
|
|
11
11
|
Never invent tool names. `notis tools search "<what you need>"` finds them.
|
|
12
12
|
|
|
13
|
+
## 0. Read before you ask
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_GET_USER_SETTINGS --arguments '{}'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This returns `onboarding_complete`, the settings already on file, and
|
|
20
|
+
`missing_settings` — the only fields you still have a reason to ask about.
|
|
21
|
+
|
|
22
|
+
**If `onboarding_complete` is true, stop. Do not run this plan.** The account is
|
|
23
|
+
already set up; say so and get on with whatever the user actually asked for.
|
|
24
|
+
Running onboarding again re-asks a returning user their own name and tells them
|
|
25
|
+
you connected things that were connected long before you arrived.
|
|
26
|
+
|
|
27
|
+
Otherwise ask only for what is in `missing_settings`.
|
|
28
|
+
|
|
13
29
|
## 1. Collect the basics
|
|
14
30
|
|
|
15
31
|
The fields onboarding collects, wherever it runs. Both the conversational
|
|
@@ -36,7 +52,6 @@ Rules that hold on every surface:
|
|
|
36
52
|
* Save as soon as you have the basics rather than batching to the end — a user who
|
|
37
53
|
drops out halfway should not lose what they already told you.
|
|
38
54
|
|
|
39
|
-
|
|
40
55
|
You are in a terminal, so there is no phone number to infer a country from. Take
|
|
41
56
|
the language and time zone from the shell environment if you can read them, state
|
|
42
57
|
what you inferred, and let the user correct you. Then save:
|
|
@@ -74,6 +89,9 @@ If they connect nothing, fall back to installing a public app:
|
|
|
74
89
|
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_COMPLETE_TUTORIAL --arguments '{}'
|
|
75
90
|
```
|
|
76
91
|
|
|
92
|
+
Only if you actually ran this plan. If step 0 told you onboarding was already
|
|
93
|
+
complete, you skipped the plan and there is nothing to finish.
|
|
94
|
+
|
|
77
95
|
Until this runs, every message the user sends on every channel is routed to the
|
|
78
96
|
onboarding assistant — so an agent that collects everything and skips this leaves
|
|
79
97
|
them permanently stuck talking to an onboarding bot.
|
|
@@ -83,10 +101,11 @@ Verify with `LOCAL_NOTIS_GET_INTEGRATIONS_STATUS` and tell them what is connecte
|
|
|
83
101
|
## What this plan does not include
|
|
84
102
|
|
|
85
103
|
Do **not** call `LOCAL_NOTIS_INSERT_REMINDER` or any automation tool during
|
|
86
|
-
onboarding
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
onboarding: on most plans they fail at the worst possible moment. If the user
|
|
105
|
+
asks for a reminder or a recurring task, check what they actually have with
|
|
106
|
+
`LOCAL_NOTIS_GET_SUBSCRIPTION_STATUS` and answer from that rather than from a
|
|
107
|
+
guess about which tier includes what — the entitlement lives in the product, not
|
|
108
|
+
in this document.
|
|
90
109
|
|
|
91
110
|
What they *do* have: 1,000+ integrations through this CLI, skills that sync to
|
|
92
111
|
their coding agents, long-term memory, notes and databases, and the ability to
|
|
@@ -4,7 +4,7 @@ Use the generic `notis tools` workflow through NPX for native Notis Database ope
|
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Run `npx --package @notis_ai/cli@latest -- notis login` to authorize the CLI. Run commands through NPX, for example `npx --package @notis_ai/cli@latest -- notis tools search "list Notis databases"`.
|
|
8
8
|
|
|
9
9
|
For CI, hosted agents, or internal scripts, pass a non-persisted token with `NOTIS_JWT=<token>` and use `--api-base <server-url>` when targeting a non-default server.
|
|
10
10
|
|
package/src/cli.js
CHANGED
|
@@ -69,7 +69,28 @@ function ensureParentCommand(program, parentMap, parentPath) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function buildRuntime(globalOptions, spec) {
|
|
72
|
-
const
|
|
72
|
+
const localBackendTypes = new Set(['local', 'local_config', 'local_registry']);
|
|
73
|
+
const commandName = spec.command_path.join(' ');
|
|
74
|
+
const liveAppHarness = (
|
|
75
|
+
commandName === 'apps verify' || commandName === 'apps screenshot'
|
|
76
|
+
) && globalOptions.mode === 'live';
|
|
77
|
+
const runtime = resolveRuntimeProfile(globalOptions, {
|
|
78
|
+
requireAuth: spec.require_auth !== false,
|
|
79
|
+
allowUnknownProfile: spec.allow_unknown_profile === true,
|
|
80
|
+
allowUnavailableWorktree:
|
|
81
|
+
localBackendTypes.has(spec.backend_call?.type) && !liveAppHarness,
|
|
82
|
+
});
|
|
83
|
+
// A stopped local-only worktree may keep executing commands whose backend is
|
|
84
|
+
// entirely local, but it must not carry a shared live credential into those
|
|
85
|
+
// handlers or their post-command telemetry. Naming --profile explicitly is
|
|
86
|
+
// the deliberate escape hatch from the worktree boundary.
|
|
87
|
+
if (runtime.worktreeRuntimeUnavailable && runtime.profileSource !== 'explicit') {
|
|
88
|
+
runtime.jwt = undefined;
|
|
89
|
+
runtime.credentialKind = undefined;
|
|
90
|
+
runtime.credentialSource = undefined;
|
|
91
|
+
runtime.oauthAccessToken = undefined;
|
|
92
|
+
runtime.oauthRefreshToken = undefined;
|
|
93
|
+
}
|
|
73
94
|
return {
|
|
74
95
|
...runtime,
|
|
75
96
|
cliVersion: CLI_VERSION,
|
|
@@ -86,6 +107,7 @@ function buildErrorRuntime(globalOptions) {
|
|
|
86
107
|
...resolveRuntimeProfile(globalOptions, {
|
|
87
108
|
requireAuth: false,
|
|
88
109
|
includeDebugEntitlementOverride: false,
|
|
110
|
+
allowUnknownProfile: true,
|
|
89
111
|
}),
|
|
90
112
|
cliVersion: CLI_VERSION,
|
|
91
113
|
color: globalOptions.color !== false,
|
|
@@ -194,7 +216,10 @@ export function createProgram() {
|
|
|
194
216
|
.option('--quiet', 'Suppress non-essential human output')
|
|
195
217
|
.option('--verbose', 'Show extra human-readable diagnostics')
|
|
196
218
|
.option('--no-color', 'Disable ANSI color output')
|
|
197
|
-
|
|
219
|
+
// No default value: commander cannot tell a default apart from an explicit
|
|
220
|
+
// flag, and a defaulted "default" here silently overrode every
|
|
221
|
+
// `notis profile use`, pinning all commands to the default profile.
|
|
222
|
+
.option('--profile <name>', 'CLI profile to run as (defaults to the active profile)')
|
|
198
223
|
.option('--api-base <url>', 'Override the API base URL for this invocation')
|
|
199
224
|
.option('--timeout-ms <n>', 'HTTP timeout in milliseconds')
|
|
200
225
|
.option('--idempotency-key <key>', 'Override the generated idempotency key for mutating commands');
|
|
@@ -239,11 +239,7 @@ export function resolveDevelopmentDesktopScheme(env = process.env, worktreeRunti
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
export function resolveDevelopmentDesktopAppName(runtime = {}) {
|
|
242
|
-
const explicit = String(
|
|
243
|
-
runtime.desktopAppName
|
|
244
|
-
|| runtime.worktreeRuntime?.desktop_app_name
|
|
245
|
-
|| '',
|
|
246
|
-
).trim();
|
|
242
|
+
const explicit = String(runtime.worktreeRuntime?.desktop_app_name || '').trim();
|
|
247
243
|
if (explicit) {
|
|
248
244
|
return explicit;
|
|
249
245
|
}
|
|
@@ -561,6 +557,14 @@ export async function ensureDevInstall({
|
|
|
561
557
|
const skills = resolveConfiguredAppSkills(appConfig, projectDir);
|
|
562
558
|
let linkedState = readLinkedState(projectDir);
|
|
563
559
|
let linkedApp = null;
|
|
560
|
+
if (linkedState?.dev_app_id) {
|
|
561
|
+
const devApp = await getAccessibleApp(ctx.runtime, linkedState.dev_app_id, runTool);
|
|
562
|
+
if (!devApp || devApp.manifest?.is_dev !== true) {
|
|
563
|
+
const { dev_app_id: _devAppId, dev_linked_at: _devLinkedAt, ...rest } = linkedState;
|
|
564
|
+
linkedState = rest;
|
|
565
|
+
writeLinkedState(projectDir, linkedState);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
564
568
|
if (linkedState?.app_id) {
|
|
565
569
|
linkedApp = await getAccessibleApp(ctx.runtime, linkedState.app_id, runTool);
|
|
566
570
|
if (linkedApp?.manifest?.is_dev === true) {
|
|
@@ -631,7 +635,7 @@ async function getAccessibleApp(runtime, appId, runTool = runToolCommand) {
|
|
|
631
635
|
const result = await runTool({
|
|
632
636
|
runtime,
|
|
633
637
|
toolName: GET_APP_TOOL,
|
|
634
|
-
arguments_: { app_id: appId },
|
|
638
|
+
arguments_: { app_id: appId, include_documents: false },
|
|
635
639
|
});
|
|
636
640
|
if (result.payload?.app) {
|
|
637
641
|
return {
|
|
@@ -640,7 +644,11 @@ async function getAccessibleApp(runtime, appId, runTool = runToolCommand) {
|
|
|
640
644
|
};
|
|
641
645
|
}
|
|
642
646
|
const message = typeof result.payload?.message === 'string' ? result.payload.message : '';
|
|
643
|
-
|
|
647
|
+
const errorCode = result.payload?.code || result.payload?.error?.code;
|
|
648
|
+
if (
|
|
649
|
+
result.payload?.status === 'error'
|
|
650
|
+
&& (errorCode === 'app_not_found' || /^App not found\.?$/i.test(message.trim()))
|
|
651
|
+
) {
|
|
644
652
|
return null;
|
|
645
653
|
}
|
|
646
654
|
throw usageError(`Could not verify access to app ${appId}${message ? `: ${message}` : '.'}`);
|
|
@@ -786,7 +794,7 @@ async function appsDevHandler(ctx) {
|
|
|
786
794
|
const mode = getCliMode();
|
|
787
795
|
const identity = decodeJwtSub(ctx.runtime.jwt);
|
|
788
796
|
if (!identity) {
|
|
789
|
-
throw usageError('Could not determine the current user from the CLI
|
|
797
|
+
throw usageError('Could not determine the current user from the CLI credential. Run notis login and retry.');
|
|
790
798
|
}
|
|
791
799
|
const apiBase = String(ctx.runtime.apiBase || '').replace(/\/$/, '');
|
|
792
800
|
const sessionsFilePath = getAppDevSessionsFile(
|
|
@@ -1095,7 +1103,7 @@ async function appsVerifyHandler(ctx) {
|
|
|
1095
1103
|
throw usageError('Live verify mode requires a current OAuth grant. Run `notis login` and retry.');
|
|
1096
1104
|
}
|
|
1097
1105
|
if (!ctx.runtime.jwt) {
|
|
1098
|
-
throw usageError('Live verify mode requires CLI auth.
|
|
1106
|
+
throw usageError('Live verify mode requires CLI auth. Run notis login and retry.');
|
|
1099
1107
|
}
|
|
1100
1108
|
linkedState = readLinkedState(projectDir);
|
|
1101
1109
|
if (!linkedState?.app_id) {
|
|
@@ -1315,7 +1323,7 @@ async function appsScreenshotHandler(ctx) {
|
|
|
1315
1323
|
throw usageError('Live mode requires a current OAuth grant. Run `notis login` and retry.');
|
|
1316
1324
|
}
|
|
1317
1325
|
if (!ctx.runtime.jwt) {
|
|
1318
|
-
throw usageError('Live mode requires CLI auth.
|
|
1326
|
+
throw usageError('Live mode requires CLI auth. Run notis login and retry.');
|
|
1319
1327
|
}
|
|
1320
1328
|
linkedState = readLinkedState(projectDir);
|
|
1321
1329
|
if (!linkedState?.app_id) {
|
|
@@ -2,16 +2,6 @@ import { loginWithOAuth, logoutOAuth } from '../runtime/oauth.js';
|
|
|
2
2
|
|
|
3
3
|
async function loginHandler(ctx) {
|
|
4
4
|
const result = await loginWithOAuth(ctx.runtime, ctx.options, ctx.output);
|
|
5
|
-
if (result.desktopFastPath) {
|
|
6
|
-
return ctx.output.emitSuccess({
|
|
7
|
-
command: 'login',
|
|
8
|
-
data: {
|
|
9
|
-
authenticated: true,
|
|
10
|
-
credential_source: result.credentialSource || 'desktop',
|
|
11
|
-
},
|
|
12
|
-
humanSummary: 'Notis Desktop already provides a valid CLI credential.',
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
5
|
if (result.agentAuthorization) {
|
|
16
6
|
return ctx.output.emitSuccess({
|
|
17
7
|
command: 'login',
|
|
@@ -25,12 +15,16 @@ async function loginHandler(ctx) {
|
|
|
25
15
|
authenticated: true,
|
|
26
16
|
credential_source: 'oauth',
|
|
27
17
|
profile: ctx.runtime.profileName,
|
|
18
|
+
api_base: result.profile.api_base || ctx.runtime.apiBase,
|
|
28
19
|
user_id: result.profile.oauth_user_id,
|
|
29
20
|
scopes: result.profile.oauth_scopes,
|
|
30
21
|
access_expires_at: result.profile.oauth_access_expires_at,
|
|
31
22
|
refresh_expires_at: result.profile.oauth_refresh_expires_at,
|
|
32
23
|
},
|
|
33
24
|
humanSummary: `Notis CLI is authorized for profile "${ctx.runtime.profileName}".`,
|
|
25
|
+
hints: [
|
|
26
|
+
{ command: 'notis profile list', reason: 'See every account this machine can switch between' },
|
|
27
|
+
],
|
|
34
28
|
});
|
|
35
29
|
}
|
|
36
30
|
|
|
@@ -53,16 +47,15 @@ async function logoutHandler(ctx) {
|
|
|
53
47
|
export const authCommandSpecs = [
|
|
54
48
|
{
|
|
55
49
|
command_path: ['login'],
|
|
56
|
-
summary: 'Authorize
|
|
50
|
+
summary: 'Authorize a CLI profile in a browser with scoped OAuth access.',
|
|
57
51
|
when_to_use:
|
|
58
|
-
'
|
|
52
|
+
'Run this once per account you want the CLI to reach. Pass --profile to add a second account without signing the first one out.',
|
|
59
53
|
args_schema: {
|
|
60
54
|
arguments: [],
|
|
61
55
|
options: [
|
|
62
56
|
{ flags: '--no-browser', description: 'Print the authorization URL without opening a browser.' },
|
|
63
57
|
{ flags: '--print-url', description: 'Print the authorization URL even when opening a browser.' },
|
|
64
58
|
{ flags: '--paste-code', description: 'Use the copy-paste callback for SSH and headless machines.' },
|
|
65
|
-
{ flags: '--force', description: 'Create an independent OAuth grant even when Desktop is signed in.' },
|
|
66
59
|
{ flags: '--timeout-seconds <n>', description: 'How long to wait for authorization (default 300).' },
|
|
67
60
|
{ flags: '--scope <scope>', description: 'OAuth permission to request (repeatable).', collect: true },
|
|
68
61
|
{ flags: '--code <code>', description: 'Redeem the code shown in the browser after a non-interactive login.' },
|
|
@@ -70,37 +63,39 @@ export const authCommandSpecs = [
|
|
|
70
63
|
},
|
|
71
64
|
examples: [
|
|
72
65
|
'notis login',
|
|
66
|
+
'notis login --profile work',
|
|
67
|
+
'notis login --profile beta --api-base https://api-beta.notis.ai',
|
|
73
68
|
'notis login --no-browser --print-url',
|
|
74
69
|
'notis login --paste-code',
|
|
75
70
|
'notis login --code 4f3c2b1a',
|
|
76
|
-
'notis login --force',
|
|
77
71
|
],
|
|
78
72
|
output_schema:
|
|
79
|
-
'Returns credential_source, profile, user_id, scopes, and credential expiries; agent mode returns authorize_url and expires_in.',
|
|
73
|
+
'Returns credential_source, profile, api_base, user_id, scopes, and credential expiries; agent mode returns authorize_url and expires_in.',
|
|
80
74
|
mutates: true,
|
|
81
75
|
idempotent: true,
|
|
82
76
|
require_auth: false,
|
|
83
|
-
|
|
77
|
+
allow_unknown_profile: true,
|
|
78
|
+
related_commands: ['notis profile list', 'notis profile use', 'notis logout', 'notis doctor'],
|
|
84
79
|
backend_call: { type: 'oauth', name: 'authorization_code+pkce' },
|
|
85
80
|
handler: loginHandler,
|
|
86
81
|
},
|
|
87
82
|
{
|
|
88
83
|
command_path: ['logout'],
|
|
89
|
-
summary: 'Revoke and remove the
|
|
84
|
+
summary: 'Revoke and remove the OAuth credential for one CLI profile.',
|
|
90
85
|
when_to_use:
|
|
91
|
-
'Use this to disconnect
|
|
86
|
+
'Use this to disconnect a single account. Other profiles keep their credentials unless you pass --all-profiles.',
|
|
92
87
|
args_schema: {
|
|
93
88
|
arguments: [],
|
|
94
89
|
options: [
|
|
95
90
|
{ flags: '--all-profiles', description: 'Remove OAuth credentials from every CLI profile.' },
|
|
96
91
|
],
|
|
97
92
|
},
|
|
98
|
-
examples: ['notis logout', 'notis logout --all-profiles'],
|
|
93
|
+
examples: ['notis logout', 'notis logout --profile work', 'notis logout --all-profiles'],
|
|
99
94
|
output_schema: 'Returns oauth_connected=false and the profiles whose OAuth credentials were removed.',
|
|
100
95
|
mutates: true,
|
|
101
96
|
idempotent: true,
|
|
102
97
|
require_auth: false,
|
|
103
|
-
related_commands: ['notis login', 'notis
|
|
98
|
+
related_commands: ['notis login', 'notis profile list'],
|
|
104
99
|
backend_call: { type: 'oauth', name: 'revocation' },
|
|
105
100
|
handler: logoutHandler,
|
|
106
101
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { usageError } from '../runtime/errors.js';
|
|
4
4
|
import {
|
|
@@ -178,6 +178,11 @@ async function executeReadOnlySql(ctx, query, phase) {
|
|
|
178
178
|
tools: [{ tool_slug: toolName, arguments: { query } }],
|
|
179
179
|
},
|
|
180
180
|
mutating: false,
|
|
181
|
+
// Always a fresh key, never the operator's --idempotency-key: a diagnostic
|
|
182
|
+
// must observe current state rather than replay a cached response, and two
|
|
183
|
+
// different queries under one reused key would collide on the request hash.
|
|
184
|
+
idempotencyKey: randomUUID(),
|
|
185
|
+
sendIdempotencyKeyWhenReading: true,
|
|
181
186
|
});
|
|
182
187
|
if (result.payload?.successful === false || result.payload?.error) {
|
|
183
188
|
throw usageError(
|