@notis_ai/cli 0.2.10 → 0.2.12
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 +1 -1
- package/skills/notis-apps/cli.md +1 -1
- package/skills/notis-cli/SKILL.md +56 -16
- 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 +7 -29
- package/src/command-specs/auth.js +15 -20
- 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/oauth.js +148 -44
- package/src/runtime/profiles.js +395 -221
- 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
|
|
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 holds its own OAuth grant: `notis login` authorizes one in the browser, and signing in to the Notis desktop app authorizes one automatically for that account. Either way the grant belongs to the CLI, which refreshes it without the desktop app running.
|
|
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,14 +293,16 @@ 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
|
|
|
@@ -271,9 +311,9 @@ The CLI defaults to the live Notis API (`https://api.notis.ai`, or
|
|
|
271
311
|
|
|
272
312
|
1. Run `npx --package @notis_ai/cli@latest -- notis doctor` and confirm `api_base` is a live Notis host
|
|
273
313
|
2. Use `--direct` for app deploys when you only need Supabase storage upload: `npx --package @notis_ai/cli@latest -- notis apps deploy --direct`
|
|
274
|
-
3. If auth looks stale,
|
|
314
|
+
3. If auth looks stale, run `npx --package @notis_ai/cli@latest -- notis login` and retry
|
|
275
315
|
|
|
276
|
-
Localhost backends are a Notis-developer test lane only. Do not retarget the CLI at loopback from this skill — that path is owned by
|
|
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.
|
|
277
317
|
|
|
278
318
|
### `npx --package @notis_ai/cli@latest -- notis doctor` shows health/tool_roundtrip errors
|
|
279
319
|
|
|
@@ -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
|
}
|
|
@@ -562,13 +558,7 @@ export async function ensureDevInstall({
|
|
|
562
558
|
let linkedState = readLinkedState(projectDir);
|
|
563
559
|
let linkedApp = null;
|
|
564
560
|
if (linkedState?.dev_app_id) {
|
|
565
|
-
|
|
566
|
-
// zero-row errors from LOCAL_NOTIS_GET_APP (.single()), not exact "App not found".
|
|
567
|
-
// Treat those as absence so ensure can clear and reprovision. Installed app_id
|
|
568
|
-
// verification below stays fail-closed on unstructured DB errors.
|
|
569
|
-
const devApp = await getAccessibleApp(ctx.runtime, linkedState.dev_app_id, runTool, {
|
|
570
|
-
treatPostgrestAbsenceAsMissing: true,
|
|
571
|
-
});
|
|
561
|
+
const devApp = await getAccessibleApp(ctx.runtime, linkedState.dev_app_id, runTool);
|
|
572
562
|
if (!devApp || devApp.manifest?.is_dev !== true) {
|
|
573
563
|
const { dev_app_id: _devAppId, dev_linked_at: _devLinkedAt, ...rest } = linkedState;
|
|
574
564
|
linkedState = rest;
|
|
@@ -641,15 +631,7 @@ function databaseMaterializationWarnings(apps) {
|
|
|
641
631
|
return warnings;
|
|
642
632
|
}
|
|
643
633
|
|
|
644
|
-
function
|
|
645
|
-
const text = String(message || '');
|
|
646
|
-
return /\bPGRST116\b/.test(text)
|
|
647
|
-
|| /\b0 rows\b/i.test(text)
|
|
648
|
-
|| /no rows returned/i.test(text);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
async function getAccessibleApp(runtime, appId, runTool = runToolCommand, options = {}) {
|
|
652
|
-
const { treatPostgrestAbsenceAsMissing = false } = options;
|
|
634
|
+
async function getAccessibleApp(runtime, appId, runTool = runToolCommand) {
|
|
653
635
|
const result = await runTool({
|
|
654
636
|
runtime,
|
|
655
637
|
toolName: GET_APP_TOOL,
|
|
@@ -665,11 +647,7 @@ async function getAccessibleApp(runtime, appId, runTool = runToolCommand, option
|
|
|
665
647
|
const errorCode = result.payload?.code || result.payload?.error?.code;
|
|
666
648
|
if (
|
|
667
649
|
result.payload?.status === 'error'
|
|
668
|
-
&& (
|
|
669
|
-
errorCode === 'app_not_found'
|
|
670
|
-
|| /^App not found\.?$/i.test(message.trim())
|
|
671
|
-
|| (treatPostgrestAbsenceAsMissing && isPostgrestNoRowsMessage(message))
|
|
672
|
-
)
|
|
650
|
+
&& (errorCode === 'app_not_found' || /^App not found\.?$/i.test(message.trim()))
|
|
673
651
|
) {
|
|
674
652
|
return null;
|
|
675
653
|
}
|
|
@@ -816,7 +794,7 @@ async function appsDevHandler(ctx) {
|
|
|
816
794
|
const mode = getCliMode();
|
|
817
795
|
const identity = decodeJwtSub(ctx.runtime.jwt);
|
|
818
796
|
if (!identity) {
|
|
819
|
-
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.');
|
|
820
798
|
}
|
|
821
799
|
const apiBase = String(ctx.runtime.apiBase || '').replace(/\/$/, '');
|
|
822
800
|
const sessionsFilePath = getAppDevSessionsFile(
|
|
@@ -1125,7 +1103,7 @@ async function appsVerifyHandler(ctx) {
|
|
|
1125
1103
|
throw usageError('Live verify mode requires a current OAuth grant. Run `notis login` and retry.');
|
|
1126
1104
|
}
|
|
1127
1105
|
if (!ctx.runtime.jwt) {
|
|
1128
|
-
throw usageError('Live verify mode requires CLI auth.
|
|
1106
|
+
throw usageError('Live verify mode requires CLI auth. Run notis login and retry.');
|
|
1129
1107
|
}
|
|
1130
1108
|
linkedState = readLinkedState(projectDir);
|
|
1131
1109
|
if (!linkedState?.app_id) {
|
|
@@ -1345,7 +1323,7 @@ async function appsScreenshotHandler(ctx) {
|
|
|
1345
1323
|
throw usageError('Live mode requires a current OAuth grant. Run `notis login` and retry.');
|
|
1346
1324
|
}
|
|
1347
1325
|
if (!ctx.runtime.jwt) {
|
|
1348
|
-
throw usageError('Live mode requires CLI auth.
|
|
1326
|
+
throw usageError('Live mode requires CLI auth. Run notis login and retry.');
|
|
1349
1327
|
}
|
|
1350
1328
|
linkedState = readLinkedState(projectDir);
|
|
1351
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
|
},
|
|
@@ -5,16 +5,19 @@ import { onboardingCommandSpecs } from './onboarding.js';
|
|
|
5
5
|
import { diagnosticCommandSpecs } from './diagnostics.js';
|
|
6
6
|
import { smokeCommandSpecs } from './smoke.js';
|
|
7
7
|
import { authCommandSpecs } from './auth.js';
|
|
8
|
+
import { profileCommandSpecs } from './profile.js';
|
|
8
9
|
|
|
9
10
|
export const GROUP_SUMMARIES = {
|
|
10
11
|
apps: 'Develop, deploy, and submit Notis Apps.',
|
|
11
12
|
tools: 'Discover and execute generic tools exposed through Notis.',
|
|
13
|
+
profile: 'Switch between signed-in accounts and their API endpoints.',
|
|
12
14
|
debug: 'Inspect effective runtime context, worker identity, and trace costs.',
|
|
13
15
|
smoke: 'Run deterministic connected-service smoke tests with guaranteed cleanup.',
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export const COMMAND_SPECS = [
|
|
17
19
|
...authCommandSpecs,
|
|
20
|
+
...profileCommandSpecs,
|
|
18
21
|
...onboardingCommandSpecs,
|
|
19
22
|
...appsCommandSpecs,
|
|
20
23
|
...toolsCommandSpecs,
|