@north-light/cli 0.3.418 → 0.3.420
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/dist/api/dto/docs.d.ts +7 -0
- package/dist/api/dto/nodes.d.ts +1 -0
- package/dist/builtin-memory/crouter-sdk/README.md +3 -3
- package/dist/builtin-memory/crouter-sdk/client.md +16 -0
- package/dist/builtin-memory/crouter-sdk/getting-started.md +2 -2
- package/dist/builtin-memory/crouter-sdk/guides/README.md +1 -1
- package/dist/builtin-memory/crouter-sdk/guides/apps-on-crouter-cloud.md +222 -3
- package/dist/builtin-memory/crouter-sdk/release-notes.md +10 -0
- package/dist/builtin-memory/crouter-sdk/testing.md +23 -1
- package/dist/clients/attach/input/controller.d.ts +1 -0
- package/dist/clients/attach/input/controller.js +2 -2
- package/dist/clients/attach/render/chat-view.d.ts +44 -0
- package/dist/clients/attach/render/chat-view.js +3 -3
- package/dist/clients/attach/session/frames.d.ts +2 -0
- package/dist/clients/attach/session/frames.js +1 -1
- package/dist/clients/attach/session/input-wiring.d.ts +3 -0
- package/dist/clients/attach/session/input-wiring.js +1 -1
- package/dist/clients/attach/slash/dispatch.d.ts +4 -0
- package/dist/clients/attach/slash/dispatch.js +2 -2
- package/dist/clients/attach/viewer.js +567 -563
- package/dist/core/command-plugins/bundle.d.ts +3 -0
- package/dist/core/command-plugins/bundle.js +1 -1
- package/dist/core/config.js +1 -1
- package/dist/core/fs-utils.d.ts +7 -0
- package/dist/core/fs-utils.js +3 -3
- package/dist/core/graph/package-docs.d.ts +4 -1
- package/dist/core/graph/package-docs.js +5 -5
- package/dist/core/keybindings/catalog.d.ts +1 -1
- package/dist/core/keybindings/catalog.js +1 -1
- package/dist/core/manifest.d.ts +15 -0
- package/dist/core/manifest.js +1 -1
- package/dist/core/plugin-quarantine.d.ts +16 -0
- package/dist/core/plugin-quarantine.js +1 -0
- package/dist/core/providers/bundles.js +2 -2
- package/dist/core/runtime/broker/rebind.d.ts +5 -0
- package/dist/core/runtime/broker/rebind.js +1 -1
- package/dist/core/runtime/broker/turn-replies.d.ts +33 -0
- package/dist/core/runtime/broker/turn-replies.js +1 -0
- package/dist/core/runtime/broker-protocol.d.ts +12 -1
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/node-read.js +3 -3
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/dist/core/runtime/turn-reply-summary.d.ts +6 -0
- package/dist/core/runtime/turn-reply-summary.js +11 -0
- package/dist/core/user-settings.d.ts +15 -0
- package/dist/core/user-settings.js +1 -1
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/crtrd.js +6 -5
- package/dist/shared/turn-replies.d.ts +27 -0
- package/dist/shared/turn-replies.js +5 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -1
- package/docs/sdk/README.md +3 -3
- package/docs/sdk/client.md +16 -0
- package/docs/sdk/getting-started.md +2 -2
- package/docs/sdk/guides/README.md +1 -1
- package/docs/sdk/guides/apps-on-crouter-cloud.md +218 -1
- package/docs/sdk/release-notes.md +10 -0
- package/docs/sdk/testing.md +23 -1
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1,12 +1,229 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Apps on crouter cloud
|
|
3
|
-
description: When an app starts runs on a person's runtime and needs data back
|
|
3
|
+
description: When you are building and registering an app on Northlight, or an app starts runs on a person's runtime and needs data back, read this because it takes you from sign-up to a running app against production, and runs.parse returns a typed, validated result where the transcript is not a data channel.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Apps on crouter cloud
|
|
7
7
|
|
|
8
|
+
This page first walks through [building and registering an app](#build-and-register-an-app) against production, then covers [managing it with `crtr cloud apps`](#manage-an-app-with-crtr-cloud-apps), then runs.
|
|
9
|
+
|
|
8
10
|
An app holding a person's connection starts runs on their runtime with `runs.start`, follows them with `runs.events` or `runs.reply`, and reads them with `runs.get`. This page covers getting data back from a run. For connections and per-person access, see [Apps with many users](./apps-with-many-users.md).
|
|
9
11
|
|
|
12
|
+
## Build and register an app
|
|
13
|
+
|
|
14
|
+
This part takes you from nothing to an app running on your laptop against Northlight's production directory, `https://account.northlight.ai`. Your app server runs on `localhost`, you sign in to it through Northlight, and it calls your own runtime. There is no local Northlight stack to run.
|
|
15
|
+
|
|
16
|
+
You register and manage the app in the directory console, and you write the app's environment file by hand. You do not need `crtr` on your laptop. The `crtr cloud apps …` commands that automate these steps need a machine joined to your Northlight account, and production does not yet let a laptop join as an additional machine. [Manage an app with `crtr cloud apps`](#manage-an-app-with-crtr-cloud-apps) covers the two ways to use them today.
|
|
17
|
+
|
|
18
|
+
The example app texts you, so it asks for the `sms` provider's `sms:send` scope. You need Node.js 22.19 or later.
|
|
19
|
+
|
|
20
|
+
### 1. Sign up
|
|
21
|
+
|
|
22
|
+
Open `https://account.northlight.ai/signup` in a fresh browser session (a private window, or a browser profile you have not signed in to Northlight with) and create your account. Signup texts a confirmation code to your phone, and that text is sent over iMessage, so use a phone number that has iMessage. A number that cannot receive iMessage gets "We couldn't send the text", and retrying does not help. Finish the account: the console needs an account with a runtime, and the app you build calls that runtime.
|
|
23
|
+
|
|
24
|
+
### 2. Install the SDK
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
mkdir my-app && cd my-app
|
|
29
|
+
npm init -y
|
|
30
|
+
npm i @north-light/sdk@latest
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If a call fails with `426 runtime_version_unsupported`, the SDK is newer than the Northlight runtimes. This can happen briefly right after a new SDK release, until production runtimes are upgraded. Install the previous version (`npm i @north-light/sdk@<previous version>`) or wait, and tell Northlight at support@northlight.ai.
|
|
34
|
+
|
|
35
|
+
### 3. Generate the signing key
|
|
36
|
+
|
|
37
|
+
An app signs in with a key, not a client secret. From your app's folder:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx @north-light/sdk keygen --out local/app-key.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Run it **without `--app`**: that is the form that needs no `crtr`. It writes the private key to `local/app-key.json` (mode `0600`; keep it out of git) and prints the public JWK, one line of JSON like `{"kty":"EC","x":"…","y":"…","crv":"P-256","kid":"…","alg":"ES256","use":"sig"}`. Keep that line for step 4. Never paste the private key anywhere.
|
|
44
|
+
|
|
45
|
+
### 4. Register the app in the console
|
|
46
|
+
|
|
47
|
+
Open `https://account.northlight.ai/console`, choose **Register an app**, and fill in:
|
|
48
|
+
|
|
49
|
+
| Field | Value |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Name | Shown to people on the consent page and in their account. |
|
|
52
|
+
| Origin URL | Where your app runs, for example `http://localhost:3000`. `http://localhost:<port>` and `http://127.0.0.1:<port>` are accepted for development. |
|
|
53
|
+
| Logo URL | Required. A square image. |
|
|
54
|
+
| Why your app needs access | One sentence, quoted on the consent page as your words. |
|
|
55
|
+
| Redirect URIs | One per line, for example `http://localhost:3000/auth/callback`. Sign-in returns only to a URI listed here, matched exactly, so `localhost` and `127.0.0.1` are different URIs and the port counts. |
|
|
56
|
+
| Public key | Paste the public JWK from step 3. It is optional here; if you leave it empty, add it later with **Add key** on the app's **Sign-in** tab. |
|
|
57
|
+
|
|
58
|
+
Choose **Register app**. The app page shows the **Client ID**: copy it. Until the public key is added, `exchangeCode` throws an `AuthenticationError`.
|
|
59
|
+
|
|
60
|
+
### 5. Say what the app asks for
|
|
61
|
+
|
|
62
|
+
On the app page, open **What it asks for**. The app starts with `crtr:llm` and `crtr:act` ticked and can only ask for what is ticked. Tick **Send you text messages from Northlight's number** (under "Tools from providers", scope `sms:send`), then save. Production does not tick it for you, and without it the consent page does not offer it and `providers.call` for `sms` is refused. The consent sentence and the scope string appear together on each row; take scope strings from that page and do not invent them.
|
|
63
|
+
|
|
64
|
+
#### What each scope gates
|
|
65
|
+
|
|
66
|
+
A run you start can only do what the grant holds and the run's own `scopes` list allows. Narrowing a run's `scopes` removes abilities, so pick the smallest list that works.
|
|
67
|
+
|
|
68
|
+
| Scope | A run (or your server) with it can | Without it |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `crtr:llm` | Use the person's AI subscription for the run's model calls. List it in every run's `scopes`. | The run has no model access. |
|
|
71
|
+
| `crtr:act` | Start runs (`runs.start`) and use a shell: every bash call on the hosted (Linux) runtime needs it. | `runs.start` is refused with `403 scope_missing`. Ask for it in step 5 and list it in the run's `scopes`. |
|
|
72
|
+
| `crtr:schedule` | Create, pause, resume and run schedules. | Schedule calls are refused. |
|
|
73
|
+
| `crtr:memory:read:user`, `crtr:files:read:user:.`, `crtr:files:rw:user:.` | Read the person's documents beyond their public ones; read or write the person's files. | The run sees only the person's `public` documents and its own app space. |
|
|
74
|
+
| `sms:send` (a provider scope) | Call the `sms` provider's `send` tool, which texts the person's own verified phone. | `providers.call` is refused. |
|
|
75
|
+
|
|
76
|
+
#### Who can sign in: testing mode
|
|
77
|
+
|
|
78
|
+
A new app is in testing: it is registered as `verified: false`, only you and up to 100 testers can sign in to it, and it is not shown in the marketplace. You can sign in as yourself with no setup. To let someone else in, add them by the email or phone number on their Northlight account: `crtr cloud apps testers add --app-id <id> --email them@example.com` (or `--phone +15551234567`). An email matches only while it is verified on that account. They can sign in right away; nobody gets a message, so tell them. `testers list` shows who is on the list and `testers remove` takes someone off.
|
|
79
|
+
|
|
80
|
+
Anyone else who tries sees "<app> is still in testing", and your app's callback receives `error=access_denied` with `error_description=app_in_testing` instead of a `code`. [When sign-in is refused: apps in testing](../client.md#when-sign-in-is-refused-apps-in-testing) shows how to read it and what to show the person. Removing a tester stops their access at once, including their refresh tokens, and keeps their data; adding them back and signing in again restores it.
|
|
81
|
+
|
|
82
|
+
The `testers` leaves are `cloud` leaves like the rest, so they need a joined machine or your hosted chat agent (see [Manage an app with `crtr cloud apps`](#manage-an-app-with-crtr-cloud-apps)): a laptop cannot join production yet. Ask your chat agent to run the command, for example `crtr cloud apps testers add --app-id <id> --email them@example.com`.
|
|
83
|
+
|
|
84
|
+
When you are ready for everyone, email `support@northlight.ai` to have the app verified. Only Northlight staff can verify an app; registering never sets `verified: true`. Apps that existed before testing mode are already verified.
|
|
85
|
+
|
|
86
|
+
### 6. Write the environment file
|
|
87
|
+
|
|
88
|
+
Create `local/app.env` by hand with the Client ID from step 4:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
CROUTER_ISSUER=https://account.northlight.ai
|
|
92
|
+
CROUTER_CLIENT_ID=<client id>
|
|
93
|
+
APP_ORIGIN=http://localhost:3000
|
|
94
|
+
CROUTER_KEY_FILE=local/app-key.json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| Variable | Value |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `CROUTER_ISSUER` | `https://account.northlight.ai` |
|
|
100
|
+
| `CROUTER_CLIENT_ID` | The Client ID from the app page |
|
|
101
|
+
| `APP_ORIGIN` | The Origin URL you registered. The redirect URI is `${APP_ORIGIN}/auth/callback` by convention; it must match a registered redirect URI exactly. |
|
|
102
|
+
| `CROUTER_KEY_FILE` | Path to the private key from step 3. Default `local/app-key.json` |
|
|
103
|
+
|
|
104
|
+
The file holds no secret, but keep it out of git with the key. On a machine joined to your account, `npx @north-light/sdk env --app <client id>` writes the first three for you.
|
|
105
|
+
|
|
106
|
+
### 7. Run it locally
|
|
107
|
+
|
|
108
|
+
Create the config from those variables. The SDK reads no environment; your app passes the values in.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import {readFile} from 'node:fs/promises';
|
|
112
|
+
import {OAuth2Client, MemoryConnectionStore} from '@north-light/sdk';
|
|
113
|
+
|
|
114
|
+
const store = new MemoryConnectionStore(); // one process only; use a persistent ConnectionStore in production
|
|
115
|
+
const oauth = new OAuth2Client({
|
|
116
|
+
clientId: process.env.CROUTER_CLIENT_ID!,
|
|
117
|
+
issuer: process.env.CROUTER_ISSUER!,
|
|
118
|
+
redirectUri: `${process.env.APP_ORIGIN}/auth/callback`,
|
|
119
|
+
privateKey: JSON.parse(await readFile(process.env.CROUTER_KEY_FILE ?? 'local/app-key.json', 'utf8')),
|
|
120
|
+
store,
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Start the server with the file loaded, for example `node --env-file=local/app.env server.js`.
|
|
125
|
+
|
|
126
|
+
**Sign in.** The login route redirects to Northlight; the callback exchanges the code on your server and stores the person's connection. This example uses Express and an in-memory session, which are only a sketch:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import express from 'express';
|
|
130
|
+
import {randomBytes} from 'node:crypto';
|
|
131
|
+
|
|
132
|
+
const app = express();
|
|
133
|
+
const pending = new Map<string, {codeVerifier: string; nonce: string}>(); // keyed by state; use a real session
|
|
134
|
+
|
|
135
|
+
app.get('/auth/login', async (_req, res) => {
|
|
136
|
+
const {url, codeVerifier, state, nonce} = await oauth.authorizeUrl({
|
|
137
|
+
scopes: ['openid', 'profile', 'crtr:llm', 'crtr:act', 'sms:send'],
|
|
138
|
+
state: randomBytes(16).toString('base64url'),
|
|
139
|
+
});
|
|
140
|
+
pending.set(state, {codeVerifier, nonce});
|
|
141
|
+
res.redirect(303, url);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
app.get('/auth/callback', async (req, res) => {
|
|
145
|
+
const state = String(req.query.state);
|
|
146
|
+
const saved = pending.get(state);
|
|
147
|
+
if (!saved || typeof req.query.code !== 'string') return void res.status(400).send('Start sign-in again.');
|
|
148
|
+
pending.delete(state);
|
|
149
|
+
const connection = await oauth.exchangeCode({
|
|
150
|
+
code: req.query.code, codeVerifier: saved.codeVerifier,
|
|
151
|
+
state, expectedState: state, nonce: saved.nonce,
|
|
152
|
+
});
|
|
153
|
+
await store.save(connection);
|
|
154
|
+
res.redirect(303, `/?user=${encodeURIComponent(connection.userId)}`); // set your own session instead
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
app.listen(3000);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Open `http://localhost:3000/auth/login`. Northlight asks you to sign in and shows the consent page; choose Allow, and the browser returns to your callback with a `code` (and `iss`) in the query. Request only the scopes you ticked in step 5.
|
|
161
|
+
|
|
162
|
+
**Start a run and call the `sms` provider.** `oauth.client(userId)` opens a client on that person's runtime from their stored connection:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const runtime = await oauth.client(userId);
|
|
166
|
+
|
|
167
|
+
const started = await runtime.runs.start({prompt: 'Reply with exactly the word: pong', scopes: ['crtr:llm', 'crtr:act']});
|
|
168
|
+
const run = await runtime.runs.wait(started.run_id); // status 'idle' once the reply is done
|
|
169
|
+
|
|
170
|
+
// Text the signed-in person's own phone through the sms provider.
|
|
171
|
+
const sent = await runtime.providers.call({
|
|
172
|
+
provider: 'sms',
|
|
173
|
+
tool: 'send',
|
|
174
|
+
arguments: {body: 'Hello from my first Northlight app.'},
|
|
175
|
+
});
|
|
176
|
+
// sent.message_id is the message's id
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`send` takes a required `body` (and an optional `file`). It has no recipient argument: it texts the verified phone of the person who signed in to your app, from Northlight's number, and allows at most 50 texts per person per UTC day. `runtime.providers.tools('sms')` lists the provider's tools. [Getting a result back from a run](#getting-a-result-back-from-a-run) covers `runs.parse`.
|
|
180
|
+
|
|
181
|
+
If a run or call throws because the person's connection ended, handle it in one place with `onRefusal`; see [Apps with many users](./apps-with-many-users.md).
|
|
182
|
+
|
|
183
|
+
### 8. Test without a network
|
|
184
|
+
|
|
185
|
+
Test your app with `FakeCloud` from `@north-light/sdk/testing`. Your real `OAuth2Client` and `Crouter` talk to it through `fetch`, so tests never touch production or send a text. See [Testing your app](../testing.md).
|
|
186
|
+
|
|
187
|
+
## Manage an app with `crtr cloud apps`
|
|
188
|
+
|
|
189
|
+
Everything in the console has a `crtr cloud apps` command: they are leaves of Northlight's `cloud` provider, and they run as you. Two things to know today:
|
|
190
|
+
|
|
191
|
+
- **A laptop cannot run them yet.** The `cloud` commands appear on a machine joined to your Northlight account, and production does not yet let a laptop join as an additional machine. That is coming. Until it ships, do the steps above in the console and write the env file by hand. Do not follow older instructions to open `/account/machines` or to run `crtr sys join` against production: that page does not exist yet. `keygen --app` and `npx @north-light/sdk env` need the same joined machine, so use `keygen` without `--app`, as in step 3.
|
|
192
|
+
- **Your Northlight chat agent can run them.** Open `https://chat.northlight.ai`, and ask your chat agent to run the command. It runs as you on your hosted runtime and prints the real output. Verified on production: `register`, `set-scopes`, `keys add`, `get`, `mine`, `asks`, `set-listing`, `set-requires` and `update`. For `keys add`, generate the key on your laptop as in step 3 and give the agent only the public JWK. Never ask it to generate your key: it would live on the hosted disk. If a command fails once with `launcher refused launch_failed`, run it again.
|
|
193
|
+
|
|
194
|
+
For example, ask the agent: "Run `crtr cloud apps register --name Texty --origin http://localhost:3000 --logo https://example.com/logo.png --justification "Texts you a summary of your day." --redirect-uri http://localhost:3000/auth/callback` and show me the raw output." Then `set-scopes --app-id <id> --scope crtr:llm --scope crtr:act --scope sms:send` and `keys add --app-id <id> --jwk '<public JWK>'`.
|
|
195
|
+
|
|
196
|
+
`--app-id` takes `app:<client id>` or the bare client id. The leaves:
|
|
197
|
+
|
|
198
|
+
| Command | What it does |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `crtr cloud apps mine` | Lists the apps you manage. |
|
|
201
|
+
| `crtr cloud apps register --name … --origin … --logo … --justification … --redirect-uri …` | Registers an app and answers its registration, including `env` (the issuer, client id and origin for step 6). The `--redirect-uri` flag repeats. |
|
|
202
|
+
| `crtr cloud apps get --app-id <id>` | Shows the registration. |
|
|
203
|
+
| `crtr cloud apps update --app-id <id> --name … / --origin … / --logo … / --justification …` | Changes the profile fields you pass. |
|
|
204
|
+
| `crtr cloud apps set-redirect-uris --app-id <id> --redirect-uri …` | Replaces the whole redirect URI list; repeat the flag for each. |
|
|
205
|
+
| `crtr cloud apps asks --app-id <id>` | Lists what an app may ask for, with whether it asks now. Large sources such as `integrations` print as a count. Narrow it with `--query sms:send`, `--source sms`, `--group providers\|apps\|agents\|space`, or `--query <text>`; for example `crtr cloud apps asks --app-id <id> --source sms`. |
|
|
206
|
+
| `crtr cloud apps set-scopes --app-id <id> --scope …` | Replaces the scope list. Repeat `--scope` for every scope; `--clear` empties it; `crtr:act` needs `crtr:llm`. |
|
|
207
|
+
| `crtr cloud apps set-requires --app-id <id> --provider …` | Names providers your app does not work without (for example `context`); `--clear` empties it. |
|
|
208
|
+
| `crtr cloud apps keys add --app-id <id> --jwk '<public JWK>'` | Adds a signing key beside any existing one. Answers `created: false` if that key is already on the app. |
|
|
209
|
+
| `crtr cloud apps set-listing --app-id <id> --login-start-url … --text-welcome …` (also `--listed`, `--price`) | Sets the listing fields; pass at least one. A loopback `--login-start-url` is accepted. `--listed true` was not run during this guide's checks. |
|
|
210
|
+
| `crtr cloud apps testers add --app-id <id> --email …` (or `--phone …`) | Lets that person sign in while the app is in testing; at most 100. |
|
|
211
|
+
| `crtr cloud apps testers list --app-id <id>` | Lists the testers. |
|
|
212
|
+
| `crtr cloud apps testers remove --app-id <id> --email …` (or `--phone …`) | Removes a tester and revokes their grants for the app. |
|
|
213
|
+
| `crtr cloud apps delete --app-id <id> --confirm <client id>` | Deletes the app and removes every person's grant for it. Only you at a joined machine's terminal can run it: an agent, including your chat agent, is refused with `provider_refused`. |
|
|
214
|
+
|
|
215
|
+
### Deleting an app
|
|
216
|
+
|
|
217
|
+
The console has no control for deleting an app. `crtr cloud apps delete` is the way, and it runs only from a joined machine's terminal, which a laptop cannot be yet. Until then, email `support@northlight.ai` with the client id: Northlight staff can delete an app with an operator command. Deleting removes every person's grant for the app, so each runtime deletes the app's runs, space and schedules, and the app's next refresh for them fails with `invalid_grant`. It cannot be undone, and apps that hold the provider role cannot be deleted. Reusing an app for tests is usually simpler: change its redirect URIs and keys instead.
|
|
218
|
+
|
|
219
|
+
### Let people find and install it
|
|
220
|
+
|
|
221
|
+
`login_start_url` is where Northlight starts sign-in for a person who installs your app from a link. In the console, open **Profile**, and under **Marketplace** set **Sign-in start URL**, **Text welcome** (one or two plain sentences, at most 120 characters, no links) and **Listed in the marketplace**. Your `/auth/login` route from step 7 is a valid start URL. Agents can install an app for people by text only while it is listed.
|
|
222
|
+
|
|
223
|
+
### Rotate keys
|
|
224
|
+
|
|
225
|
+
Add a new key beside the old one, switch, verify with a real sign-in, then retire the old key. Use `keygen --out local/app-key-next.json` without `--app`, paste its public JWK with **Add key** on the **Sign-in** tab, and retire the old one there. [Rotating your key](../getting-started.md#rotating-your-key) has the full sequence.
|
|
226
|
+
|
|
10
227
|
## Getting a result back from a run
|
|
11
228
|
|
|
12
229
|
Pass an `output_schema` and call `runs.parse`. The runtime arms the schema on the run's root agent. The agent must submit a value that validates against it. The run settles with that value, and `runs.parse` returns it as `output_parsed`, typed from your schema:
|
|
@@ -7,6 +7,16 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
|
|
|
7
7
|
|
|
8
8
|
Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
|
|
9
9
|
|
|
10
|
+
## 0.3.419
|
|
11
|
+
|
|
12
|
+
### Testing mode: `app_in_testing` (additive)
|
|
13
|
+
|
|
14
|
+
Not breaking: no type or wire shape an app reads is removed or changed.
|
|
15
|
+
|
|
16
|
+
- An app Northlight has not verified is in testing: only its admin and named testers can sign in. A refused sign-in redirects with `error=access_denied`, `error_description=app_in_testing`; a refused refresh answers `invalid_grant` `app_in_testing`. The refresh refusal is a new `Refusal` with `code: 'app_in_testing'` and `kind: 'paused'`, like `grant_suspended`: keep the person's data. If you `switch` exhaustively on `refusal.code`, add the case. See [When a refresh is refused](./client.md#when-a-refresh-is-refused).
|
|
17
|
+
- New `authorizationRefusalOf(callback)` reads the sign-in refusal from the callback's query and returns `AuthorizationRefusal` (`{code: 'app_in_testing', error: 'access_denied', state}`), else `null`. See [When sign-in is refused](./client.md#when-sign-in-is-refused-apps-in-testing).
|
|
18
|
+
- `@north-light/sdk/testing`: `cloud.testing` (`enable`, `disable`, `active`, `addTester`, `removeTester`, `testers`, `allowed`), `cloud.authorize(userId, {redirectUri, state})`, `user.setAccount({email, emailVerified, phone})`, and `'app_in_testing'` in `FakeRefusalCode`. The fake directory now serves `grant_type=authorization_code` for codes `cloud.authorize` issued. See [Testing mode](./testing.md#testing-mode).
|
|
19
|
+
|
|
10
20
|
## 0.3.415
|
|
11
21
|
|
|
12
22
|
### Monthly spend cap enforced; `usage.get` (additive; one behavior change)
|
package/docs/sdk/testing.md
CHANGED
|
@@ -87,12 +87,34 @@ Idempotency follows the runtime too: the same `Idempotency-Key` with the same bo
|
|
|
87
87
|
|
|
88
88
|
## Refusals
|
|
89
89
|
|
|
90
|
-
- `user.refuse(code)` leaves the grant alone: the directory refuses each refresh with `code` (`grant_removed`, `grant_suspended`, `refresh_token_expired`, `refresh_token_reused`, `grant_revoked`), and tokens issued before the call get 401, so the next call refreshes and is refused. `refuse('ok')` clears it.
|
|
90
|
+
- `user.refuse(code)` leaves the grant alone: the directory refuses each refresh with `code` (`grant_removed`, `grant_suspended`, `app_in_testing`, `refresh_token_expired`, `refresh_token_reused`, `grant_revoked`), and tokens issued before the call get 401, so the next call refreshes and is refused. `refuse('ok')` clears it.
|
|
91
91
|
- `grant.revoke('grant_removed')` deactivates the grant: calls answer 401 `grant_revoked`, held reads wake to it, open streams get an error frame, and the next refresh is refused with that code.
|
|
92
92
|
- `user.expireTokens()` forces a refresh without a refusal.
|
|
93
93
|
|
|
94
94
|
The real `OAuth2Client` handles all of it: your `onRefusal` runs with the refusal, the stored connection is removed, and the thrown error has `refusalOf(e)` set. `await cloud.refusalError(code)` returns that exact error for unit tests that need the value.
|
|
95
95
|
|
|
96
|
+
## Testing mode
|
|
97
|
+
|
|
98
|
+
An unverified third-party app is *in testing* on crouter cloud: only its admin and up to 100 named testers can sign in, and anyone else is refused. `cloud.testing` puts the fake's app in that state so your app can test the refusal.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
cloud.user('user:tess').setAccount({ email: 'tess@example.com' }); // the identifiers the account holds
|
|
102
|
+
cloud.testing.addTester({ email: 'Tess@Example.com' }); // or {phone: '+15551234567'}; false if already present
|
|
103
|
+
cloud.testing.enable({ admin: 'user:me' }); // in testing; revokes active grants of people not allowed
|
|
104
|
+
|
|
105
|
+
const back = cloud.authorize('user:stranger', { redirectUri, state }); // the redirect the browser follows back to your app
|
|
106
|
+
authorizationRefusalOf(back); // {code: 'app_in_testing', error: 'access_denied', state}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- `cloud.testing.enable({admin?})` / `disable()` / `active`. `enable` returns the user ids whose active grants it revoked.
|
|
110
|
+
- `addTester({email} | {phone})` stores the entry (email trimmed and lowercased, phone E.164). The 101st entry throws. `removeTester(ref)` returns `{removed, revoked}`: a removed tester's active grant is revoked, not deleted, and the fake keeps their runs.
|
|
111
|
+
- `cloud.testing.allowed(userId)`: not in testing, the admin, or a tester entry matches the account: an `email` entry needs `emailVerified` (default true when `email` is set), a `phone` entry matches `setAccount({phone})`.
|
|
112
|
+
- A refresh for an active grant whose person is not allowed answers `invalid_grant` `app_in_testing`, and so does a refresh after the grant was revoked this way. `oauth.client(id)` rejects with an `AuthenticationError` (`code: 'app_in_testing'`, `refusalOf(e).kind === 'paused'`) and `onRefusal` runs. Keep the person's data.
|
|
113
|
+
- `cloud.authorize(userId, {redirectUri, state, scopes?})` stands in for the browser round trip. An allowed person gets an active grant and a redirect with `code` and `state`; give `code` to `oauth.exchangeCode`. A person who is not allowed gets `error=access_denied`, `error_description=app_in_testing` and `state`, and nothing is written. A person revoked by `removeTester` who is allowed again gets access back by `authorize`.
|
|
114
|
+
- When a person's email or phone stops matching (`setAccount`), nothing is revoked; their next refresh and `authorize` are refused.
|
|
115
|
+
|
|
116
|
+
In your callback handler, check `authorizationRefusalOf(url)` before reading `code`.
|
|
117
|
+
|
|
96
118
|
## Memory and providers
|
|
97
119
|
|
|
98
120
|
`user.memory.put({name, body, privacy = 'public', kind = 'knowledge', ...})` and `remove(name)`. Without `crtr:memory:manage:user` in the grant, only `public` documents are listed and others read as 404, as on the runtime. List items carry the full `MemoryListItem` fields.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.420",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
},
|
|
135
135
|
"license": "UNLICENSED",
|
|
136
136
|
"dependencies": {
|
|
137
|
-
"@north-light/identity": "0.3.
|
|
137
|
+
"@north-light/identity": "0.3.420",
|
|
138
138
|
"@earendil-works/pi-agent-core": "1.1.0",
|
|
139
139
|
"@earendil-works/pi-ai": "1.1.0",
|
|
140
140
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.420",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/cli",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.420",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"workspaces": [
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@earendil-works/pi-ai": "1.1.0",
|
|
19
19
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
20
20
|
"@earendil-works/pi-tui": "1.1.0",
|
|
21
|
-
"@north-light/identity": "0.3.
|
|
21
|
+
"@north-light/identity": "0.3.420",
|
|
22
22
|
"cron-parser": "^5.6.0",
|
|
23
23
|
"esbuild": "^0.27.7",
|
|
24
24
|
"jose": "^6.2.12",
|
|
@@ -12095,17 +12095,17 @@
|
|
|
12095
12095
|
},
|
|
12096
12096
|
"packages/crouter-api": {
|
|
12097
12097
|
"name": "@north-light/api",
|
|
12098
|
-
"version": "0.3.
|
|
12098
|
+
"version": "0.3.420",
|
|
12099
12099
|
"license": "UNLICENSED"
|
|
12100
12100
|
},
|
|
12101
12101
|
"packages/crouter-env-docker": {
|
|
12102
12102
|
"name": "@north-light/env-docker",
|
|
12103
|
-
"version": "0.3.
|
|
12103
|
+
"version": "0.3.420",
|
|
12104
12104
|
"license": "UNLICENSED"
|
|
12105
12105
|
},
|
|
12106
12106
|
"packages/crouter-identity": {
|
|
12107
12107
|
"name": "@north-light/identity",
|
|
12108
|
-
"version": "0.3.
|
|
12108
|
+
"version": "0.3.420",
|
|
12109
12109
|
"license": "UNLICENSED",
|
|
12110
12110
|
"dependencies": {
|
|
12111
12111
|
"jose": "^6.2.1"
|
|
@@ -12113,20 +12113,20 @@
|
|
|
12113
12113
|
},
|
|
12114
12114
|
"packages/crouter-plugin": {
|
|
12115
12115
|
"name": "@north-light/plugin",
|
|
12116
|
-
"version": "0.3.
|
|
12116
|
+
"version": "0.3.420",
|
|
12117
12117
|
"license": "UNLICENSED",
|
|
12118
12118
|
"dependencies": {
|
|
12119
|
-
"@north-light/api": "0.3.
|
|
12120
|
-
"@north-light/identity": "0.3.
|
|
12119
|
+
"@north-light/api": "0.3.420",
|
|
12120
|
+
"@north-light/identity": "0.3.420"
|
|
12121
12121
|
}
|
|
12122
12122
|
},
|
|
12123
12123
|
"packages/crouter-sdk": {
|
|
12124
12124
|
"name": "@north-light/sdk",
|
|
12125
|
-
"version": "0.3.
|
|
12125
|
+
"version": "0.3.420",
|
|
12126
12126
|
"license": "UNLICENSED",
|
|
12127
12127
|
"dependencies": {
|
|
12128
|
-
"@north-light/api": "0.3.
|
|
12129
|
-
"@north-light/identity": "0.3.
|
|
12128
|
+
"@north-light/api": "0.3.420",
|
|
12129
|
+
"@north-light/identity": "0.3.420",
|
|
12130
12130
|
"jose": "^6.2.1"
|
|
12131
12131
|
},
|
|
12132
12132
|
"bin": {
|