@north-light/cli 0.3.473 → 0.3.474
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/builtin-memory/crouter-sdk/README.md +3 -46
- package/dist/builtin-memory/crouter-sdk/client.md +2 -231
- package/dist/builtin-memory/crouter-sdk/errors.md +9 -50
- package/dist/builtin-memory/crouter-sdk/getting-started.md +2 -64
- package/dist/builtin-memory/crouter-sdk/guides/README.md +1 -3
- package/dist/builtin-memory/crouter-sdk/guides/typed-extraction.md +1 -1
- package/dist/builtin-memory/crouter-sdk/streaming.md +1 -70
- package/dist/builtin-memory/northlight-apps/INDEX.md +15 -0
- package/dist/builtin-memory/northlight-apps/cli.md +41 -0
- package/dist/builtin-memory/northlight-apps/coding-agents.md +75 -0
- package/dist/builtin-memory/northlight-apps/concepts/apps-and-sign-in.md +109 -0
- package/dist/builtin-memory/northlight-apps/concepts/connections.md +109 -0
- package/dist/builtin-memory/northlight-apps/concepts/providers.md +76 -0
- package/dist/builtin-memory/northlight-apps/concepts/runs.md +101 -0
- package/dist/builtin-memory/northlight-apps/concepts/scopes-and-grants.md +64 -0
- package/dist/builtin-memory/northlight-apps/concepts/testing-mode.md +54 -0
- package/dist/builtin-memory/northlight-apps/errors.md +119 -0
- package/dist/builtin-memory/northlight-apps/guides/background-runs.md +80 -0
- package/dist/builtin-memory/northlight-apps/guides/files-and-documents.md +61 -0
- package/dist/builtin-memory/northlight-apps/guides/go-live.md +39 -0
- package/dist/builtin-memory/northlight-apps/guides/grant-changes.md +100 -0
- package/dist/builtin-memory/northlight-apps/guides/manage-your-app.md +77 -0
- package/dist/builtin-memory/northlight-apps/guides/many-users.md +63 -0
- package/dist/builtin-memory/northlight-apps/guides/stream-a-run.md +84 -0
- package/dist/builtin-memory/{crouter-sdk/testing.md → northlight-apps/guides/test-your-app.md} +6 -6
- package/dist/builtin-memory/northlight-apps/guides/typed-results.md +99 -0
- package/dist/builtin-memory/northlight-apps/how-it-fits.md +49 -0
- package/dist/builtin-memory/northlight-apps/providers/README.md +39 -0
- package/dist/builtin-memory/northlight-apps/providers/context.md +34 -0
- package/dist/builtin-memory/northlight-apps/providers/device.md +28 -0
- package/dist/builtin-memory/northlight-apps/providers/integrations.md +28 -0
- package/dist/builtin-memory/northlight-apps/providers/search.md +31 -0
- package/dist/builtin-memory/northlight-apps/providers/sms.md +39 -0
- package/dist/builtin-memory/northlight-apps/quickstart.md +210 -0
- package/docs/apps/guides/background-runs.md +1 -1
- package/docs/apps/guides/manage-your-app.md +1 -1
- package/docs/cli/README.md +1 -0
- package/docs/cli/join-northlight.md +58 -0
- package/docs/sdk/README.md +3 -46
- package/docs/sdk/client.md +2 -231
- package/docs/sdk/errors.md +8 -49
- package/docs/sdk/getting-started.md +2 -64
- package/docs/sdk/guides/README.md +1 -3
- package/docs/sdk/guides/typed-extraction.md +1 -1
- package/docs/sdk/streaming.md +1 -70
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
- package/dist/builtin-memory/crouter-sdk/error-envelope.md +0 -38
- package/dist/builtin-memory/crouter-sdk/guides/apps-on-northlight.md +0 -423
- package/dist/builtin-memory/crouter-sdk/guides/apps-with-many-users.md +0 -61
- package/dist/builtin-memory/crouter-sdk/memory.md +0 -49
- package/docs/sdk/error-envelope.md +0 -36
- package/docs/sdk/guides/apps-on-northlight.md +0 -419
- package/docs/sdk/guides/apps-with-many-users.md +0 -59
- package/docs/sdk/memory.md +0 -47
- package/docs/sdk/testing.md +0 -191
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When you need Quick setup, this knowledge should be read
|
|
4
|
+
because From no account to a running app that signs a person in with
|
|
5
|
+
Northlight, starts a run on their runtime and sends them a text.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Quick setup
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx @north-light/sdk@latest init my-app
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This command creates a small Node server, a signing key and an env file. About ten minutes later you have an app on your laptop that signs you in with Northlight, starts an agent run on your own Northlight runtime, and texts your phone. The app runs on your machine and talks to Northlight's production directory at `https://account.northlight.ai`. There is no local Northlight stack to run, and you do not need crouter.
|
|
15
|
+
|
|
16
|
+
You need Node.js 22.19 or later and a phone that can receive iMessage.
|
|
17
|
+
|
|
18
|
+
Using a coding agent? Run the same `init` command, or tell your agent: *Read https://sdk.docs.northlight.ai/SKILL.md and follow it.* See [[northlight-apps/coding-agents]].
|
|
19
|
+
|
|
20
|
+
Each step below ends with what you should see when it worked. A step marked "(not yet checked on production)" is written from the source code and has not been run end to end against `account.northlight.ai`.
|
|
21
|
+
|
|
22
|
+
## 1. Create an account
|
|
23
|
+
|
|
24
|
+
Open `https://account.northlight.ai/signup` and create your account. Signup texts a confirmation code to your phone, and that text is sent over iMessage. A number that cannot receive iMessage gets "We couldn't send the text", and retrying does not help. One phone number belongs to one account.
|
|
25
|
+
|
|
26
|
+
You should see: your account's page after you enter the code. Opening `https://account.northlight.ai/console` while signed in shows the console, where your apps are listed. Checked on production: `/signup` redirects to the phone step at `/signup/phone`, and `/console` while signed out redirects to sign-in. Completing signup and the signed-in console are not yet checked on production.
|
|
27
|
+
|
|
28
|
+
## 2. Scaffold the app
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @north-light/sdk@latest init my-app
|
|
32
|
+
cd my-app
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`init` writes these files into `my-app/`:
|
|
36
|
+
|
|
37
|
+
| File | What it is |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `package.json` | Pins `@north-light/sdk` to the exact version of the CLI you ran, and adds `tsx`. |
|
|
40
|
+
| `server.ts` | The server shown at the end of this page. |
|
|
41
|
+
| `local/app-key.json` | Your app's private signing key, mode `0600`. Never commit it or paste it anywhere. |
|
|
42
|
+
| `local/app.env` | The settings the server reads: `CROUTER_ISSUER`, `APP_ORIGIN`, an empty `CROUTER_CLIENT_ID=` and `CROUTER_KEY_FILE`. |
|
|
43
|
+
| `.gitignore` | Keeps `local/` and `node_modules/` out of git. |
|
|
44
|
+
| `.agents/skills/northlight/SKILL.md` and `.claude/skills/northlight/SKILL.md` | The Northlight skill for your coding agent. |
|
|
45
|
+
|
|
46
|
+
You should see, on the terminal: `Created a Northlight app in my-app:` with the file names, the path of the private key, the next steps in order, and last, on its own line, the **public key** as one line of JSON:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{"kty":"EC","x":"…","y":"…","crv":"P-256","kid":"…","alg":"ES256","use":"sig"}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Copy that line. You paste it into the console in the next step. If a file already exists, `init` refuses, names it and exits non-zero without writing anything.
|
|
53
|
+
|
|
54
|
+
Checked: this output and these files, from the SDK source built on this branch in an empty directory with no crtr on `PATH`. `npx @north-light/sdk@latest` fetches the npm `latest` release, and that release does not include `init` until the site goes live (not yet checked on production).
|
|
55
|
+
|
|
56
|
+
## 3. Register the app in the console
|
|
57
|
+
|
|
58
|
+
Open `https://account.northlight.ai/console/new`. The page is titled **Register an app**. Fill in:
|
|
59
|
+
|
|
60
|
+
| Field | Value |
|
|
61
|
+
|---|---|
|
|
62
|
+
| **Name** | Shown to people on the consent page and in their account. |
|
|
63
|
+
| **Origin URL** | `http://localhost:3000` |
|
|
64
|
+
| **Logo URL** | Required. A square image shown beside your app's name. |
|
|
65
|
+
| **Why your app needs access** | One sentence. It is quoted on the consent page as your words. |
|
|
66
|
+
| **Redirect URIs** | `http://localhost:3000/auth/callback`, one URL per line. Sign-in returns only to a URI listed here, matched exactly. `localhost` and `127.0.0.1` are different URIs, and the port counts. |
|
|
67
|
+
| **Public key (optional)** | The JSON line `init` printed. |
|
|
68
|
+
|
|
69
|
+
Choose **Register app**.
|
|
70
|
+
|
|
71
|
+
You should see: a page titled "*your app's name* is registered" with a **Client ID** and, under it, the key ID of the key you pasted. Copy the client ID. If it says "Next: add your app's public key on the Sign-in tab", the key was left out: open the app's **Sign-in** tab and choose **Add key**. The app starts asking for `crtr:llm` and `crtr:act`.
|
|
72
|
+
|
|
73
|
+
The field labels are from the console's source. Pasting a key at registration is not yet checked on production.
|
|
74
|
+
|
|
75
|
+
## 4. Add the permission to text
|
|
76
|
+
|
|
77
|
+
On the app's page, open the **What it asks for** tab and choose **Add permissions**. Add `sms:send` from the list.
|
|
78
|
+
|
|
79
|
+
You should see: `sms:send` under what your app asks for, next to `crtr:llm` and `crtr:act`. The server in this guide asks for all three, so without `sms:send` the consent page does not offer it and the text in step 6 is refused. Take scope strings from this list. Do not invent them. See [[northlight-apps/concepts/scopes-and-grants]].
|
|
80
|
+
|
|
81
|
+
Not yet checked on production.
|
|
82
|
+
|
|
83
|
+
## 5. Write the client ID into the env file
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx @north-light/sdk env --client-id <client id>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You should see:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
Set in local/app.env:
|
|
93
|
+
CROUTER_ISSUER=https://account.northlight.ai
|
|
94
|
+
CROUTER_CLIENT_ID=<client id>
|
|
95
|
+
APP_ORIGIN=http://localhost:3000
|
|
96
|
+
Key file: local/app-key.json (set CROUTER_KEY_FILE to change it).
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`local/app.env` now has all four settings. The command makes no network call and keeps every other line in the file. Add `--origin <url>` if your app does not run on `http://localhost:3000`.
|
|
100
|
+
|
|
101
|
+
Checked from the SDK source built on this branch.
|
|
102
|
+
|
|
103
|
+
## 6. Run it
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm i
|
|
107
|
+
node --env-file=local/app.env --import tsx server.ts
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
(`npm start` runs the same command.) You should see `Listening on http://localhost:3000`.
|
|
111
|
+
|
|
112
|
+
Open `http://localhost:3000/auth/login`. Your browser goes to `account.northlight.ai`. Sign in if asked, read the consent page, and choose Allow. You land back on `http://localhost:3000/` with "Signed in" and your Northlight user id. Press **Start a run and send me a text**. The page answers "Done", shows the run's status `idle`, and your phone receives "Hello from my first Northlight app." from Northlight's number.
|
|
113
|
+
|
|
114
|
+
What was checked: the server starts, `/` shows the sign-in link, and `/auth/login` answers `303` to `https://account.northlight.ai/authorize?client_id=…&scope=openid+profile+crtr%3Allm+crtr%3Aact+sms%3Asend…&code_challenge_method=S256…`. The consent page, the callback, the run and the text are not yet checked on production.
|
|
115
|
+
|
|
116
|
+
If `/` shows "One step left", `CROUTER_CLIENT_ID` is empty: repeat step 5 and restart. If a call fails with `426 runtime_version_unsupported`, your SDK is newer than the Northlight runtimes. Install the previous version with `npm i --save-exact @north-light/sdk@<version>` and tell support@northlight.ai.
|
|
117
|
+
|
|
118
|
+
## 7. What next
|
|
119
|
+
|
|
120
|
+
- Only you can sign in until you add testers: see [[northlight-apps/concepts/testing-mode]].
|
|
121
|
+
- Keep each person's sign-in across restarts and processes: see [[northlight-apps/concepts/connections]].
|
|
122
|
+
- See what `server.ts` does and where each piece runs: [[northlight-apps/how-it-fits]].
|
|
123
|
+
|
|
124
|
+
## The server `init` writes
|
|
125
|
+
|
|
126
|
+
This is `examples/apps/quickstart-server.ts`. The SDK reads no environment: the server passes the values in.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// The server `npx @north-light/sdk init` writes as server.ts: sign a person in with Northlight, then start a run on
|
|
130
|
+
// their runtime and send them a text. It keeps sign-in state in memory, so it is a sketch for one process, not production.
|
|
131
|
+
import {createServer, type IncomingMessage, type ServerResponse} from 'node:http';
|
|
132
|
+
import {randomBytes} from 'node:crypto';
|
|
133
|
+
import {readFile} from 'node:fs/promises';
|
|
134
|
+
import {MemoryConnectionStore, OAuth2Client, authorizationRefusalOf} from '@north-light/sdk';
|
|
135
|
+
|
|
136
|
+
const origin = process.env.APP_ORIGIN ?? 'http://localhost:3000';
|
|
137
|
+
|
|
138
|
+
const store = new MemoryConnectionStore(); // one process only; use a persistent ConnectionStore in production
|
|
139
|
+
const oauth = new OAuth2Client({
|
|
140
|
+
clientId: process.env.CROUTER_CLIENT_ID ?? '',
|
|
141
|
+
issuer: process.env.CROUTER_ISSUER ?? 'https://account.northlight.ai',
|
|
142
|
+
redirectUri: `${origin}/auth/callback`,
|
|
143
|
+
privateKey: JSON.parse(await readFile(process.env.CROUTER_KEY_FILE ?? 'local/app-key.json', 'utf8')),
|
|
144
|
+
store,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const pending = new Map<string, {codeVerifier: string; nonce: string}>(); // sign-ins in flight, keyed by state
|
|
148
|
+
const sessions = new Map<string, string>(); // session id -> userId; use a real session store in production
|
|
149
|
+
|
|
150
|
+
const escapeHtml = (text: string) => text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
151
|
+
const page = (res: ServerResponse, status: number, body: string) => {
|
|
152
|
+
res.writeHead(status, {'content-type': 'text/html; charset=utf-8'}).end(`<!doctype html><meta charset="utf-8"><title>Northlight app</title><body style="font-family:system-ui;max-width:40rem;margin:3rem auto">${body}`);
|
|
153
|
+
};
|
|
154
|
+
const userOf = (req: IncomingMessage) => {
|
|
155
|
+
const sid = /(?:^|; )sid=([\w-]+)/.exec(req.headers.cookie ?? '')?.[1];
|
|
156
|
+
return sid ? sessions.get(sid) : undefined;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const server = createServer(async (req, res) => {
|
|
160
|
+
const url = new URL(req.url ?? '/', origin);
|
|
161
|
+
try {
|
|
162
|
+
if (!process.env.CROUTER_CLIENT_ID) {
|
|
163
|
+
return page(res, 200, '<h1>One step left</h1><p>Register the app in the console, then run <code>npx @north-light/sdk env --client-id <client id></code> and restart this server.</p>');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (url.pathname === '/auth/login') {
|
|
167
|
+
const {url: authorizeUrl, codeVerifier, state, nonce} = await oauth.authorizeUrl({
|
|
168
|
+
scopes: ['openid', 'profile', 'crtr:llm', 'crtr:act', 'sms:send'],
|
|
169
|
+
state: randomBytes(16).toString('base64url'),
|
|
170
|
+
});
|
|
171
|
+
pending.set(state, {codeVerifier, nonce});
|
|
172
|
+
return void res.writeHead(303, {location: authorizeUrl}).end();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (url.pathname === '/auth/callback') {
|
|
176
|
+
if (authorizationRefusalOf(url)?.code === 'app_in_testing') {
|
|
177
|
+
return page(res, 403, '<h1>This app is still in testing</h1><p>Ask its owner to add you on the Testers tab in the console.</p>');
|
|
178
|
+
}
|
|
179
|
+
const state = url.searchParams.get('state') ?? '';
|
|
180
|
+
const code = url.searchParams.get('code');
|
|
181
|
+
const saved = pending.get(state);
|
|
182
|
+
if (!saved || !code) return page(res, 400, '<p>Start sign-in again at <a href="/auth/login">/auth/login</a>.</p>');
|
|
183
|
+
pending.delete(state);
|
|
184
|
+
const connection = await oauth.exchangeCode({code, codeVerifier: saved.codeVerifier, state, expectedState: state, nonce: saved.nonce});
|
|
185
|
+
await store.save(connection);
|
|
186
|
+
const sid = randomBytes(16).toString('base64url');
|
|
187
|
+
sessions.set(sid, connection.userId);
|
|
188
|
+
return void res.writeHead(303, {location: '/', 'set-cookie': `sid=${sid}; HttpOnly; SameSite=Lax; Path=/`}).end();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const userId = userOf(req);
|
|
192
|
+
if (!userId) return page(res, 200, '<h1>Northlight app</h1><p><a href="/auth/login">Sign in with Northlight</a></p>');
|
|
193
|
+
|
|
194
|
+
if (url.pathname === '/run' && req.method === 'POST') {
|
|
195
|
+
const runtime = await oauth.client(userId); // a client on this person's runtime, from their stored connection
|
|
196
|
+
const started = await runtime.runs.start({prompt: 'Reply with exactly the word: pong', scopes: ['crtr:llm', 'crtr:act']});
|
|
197
|
+
const run = await runtime.runs.wait(started.run_id); // status is 'idle' once the reply is done
|
|
198
|
+
// Texts the signed-in person's own verified phone through the sms provider (50 per person per UTC day).
|
|
199
|
+
const sent = await runtime.providers.call({provider: 'sms', tool: 'send', arguments: {body: 'Hello from my first Northlight app.'}});
|
|
200
|
+
return page(res, 200, `<h1>Done</h1><p>The run finished with status <code>${escapeHtml(run.status)}</code>. A text is on its way to your phone.</p><pre>${escapeHtml(JSON.stringify({run: run.run_id, outcome: run.outcome, report: run.last_report, text: sent}, null, 2))}</pre><p><a href="/">Back</a></p>`);
|
|
201
|
+
}
|
|
202
|
+
return page(res, 200, `<h1>Signed in</h1><p>You are <code>${escapeHtml(userId)}</code>.</p><form method="post" action="/run"><button>Start a run and send me a text</button></form>`);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(error);
|
|
205
|
+
return page(res, 500, `<h1>Something failed</h1><pre>${escapeHtml(error instanceof Error ? error.message : String(error))}</pre>`);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
server.listen(Number(new URL(origin).port) || 3000, () => console.log(`Listening on ${origin}`));
|
|
210
|
+
```
|
|
@@ -68,7 +68,7 @@ app.post('/run-notices', async (req, res) => {
|
|
|
68
68
|
|
|
69
69
|
### Set the run notice URL
|
|
70
70
|
|
|
71
|
-
The console has no field for this yet. Until it does, set it with crtr on a machine joined to your Northlight account (`crtr sys join`; see [joining a machine](https://crouter.docs.northlight.ai/docs/
|
|
71
|
+
The console has no field for this yet. Until it does, set it with crtr on a machine joined to your Northlight account (`crtr sys join`; see [joining a machine](https://crouter.docs.northlight.ai/docs/cli/join-northlight) in the crouter docs):
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
crtr cloud apps set-run-notice-url --app-id <client id> --url https://app.example.com/run-notices
|
|
@@ -55,7 +55,7 @@ Once your app is live you can keep developing on your laptop against production
|
|
|
55
55
|
|
|
56
56
|
## Two things the console can't do yet
|
|
57
57
|
|
|
58
|
-
Deleting an app and setting its run notice URL are not in the console today. Both need `crtr`, the Northlight command line, on a machine joined to your Northlight account. Joining is a one-time setup described in [the crouter docs](https://crouter.docs.northlight.ai/docs/
|
|
58
|
+
Deleting an app and setting its run notice URL are not in the console today. Both need `crtr`, the Northlight command line, on a machine joined to your Northlight account. Joining is a one-time setup described in [the crouter docs](https://crouter.docs.northlight.ai/docs/cli/join-northlight): install `@north-light/cli`, then run `crtr sys join` with a token from `https://account.northlight.ai/account/machines`. Joining rewrites the local crtr home and has no unjoin, so join from a home you can throw away. These steps are the only part of building an app that needs crtr.
|
|
59
59
|
|
|
60
60
|
### Delete an app
|
|
61
61
|
|
package/docs/cli/README.md
CHANGED
|
@@ -14,4 +14,5 @@ Use these pages to start and return to an agent conversation, see the work runni
|
|
|
14
14
|
| [Canvas and dashboard](./canvas-and-dashboard.md) | See the node graph, find work that needs attention, and reopen a node. |
|
|
15
15
|
| [Human inbox](./human-inbox.md) | Answer decisions, review documents, and follow a live display. |
|
|
16
16
|
| [Documents and preferences](./documents-and-preferences.md) | Save knowledge and instructions that should shape later agents. |
|
|
17
|
+
| [Join a machine to your Northlight account](./join-northlight.md) | Run `crtr cloud apps` commands as you, from a joined laptop. |
|
|
17
18
|
| [The official plugin marketplace](./official-marketplace.md) | Find a plugin, see what it needs, and install it. |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Join a machine to your Northlight account
|
|
3
|
+
description: Join a laptop to your Northlight account with crtr sys join so crtr cloud apps commands run as you, and the crtr cloud apps command list.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Join a machine to your Northlight account
|
|
7
|
+
|
|
8
|
+
Joining makes this machine one of your Northlight account's machines, so `crtr cloud …` commands run as you. App builders need it for the two things the console cannot do yet: deleting an app and setting its run notice URL ([Manage your app](https://sdk.docs.northlight.ai/docs/guides/manage-your-app#two-things-the-console-cant-do-yet)). Everything else about building an app is on the [SDK docs site](https://sdk.docs.northlight.ai/docs/quickstart).
|
|
9
|
+
|
|
10
|
+
Create your account at `https://account.northlight.ai/signup` first. Then install the CLI (`npm i -g @north-light/cli`). Joining needs `crtr` 0.3.436 or later (older versions need `crtr sys daemon stop` first and fail if a viewer is open); `crtr --version` shows yours.
|
|
11
|
+
|
|
12
|
+
Then join your laptop to the account:
|
|
13
|
+
|
|
14
|
+
1. Open `https://account.northlight.ai/account/machines` and choose **Create join token**. A token needs a sign-in within the last 10 minutes, so the page asks you to sign in again if yours is older.
|
|
15
|
+
2. The page shows the join command with the token filled in. On your laptop, run it from a plain terminal: `crtr sys join --directory https://account.northlight.ai --token <token>`. Leave your crtr windows open: join stops the daemon itself, holds it down while it writes your identity (so an open viewer cannot restart it early), and starts it again on the joined settings. Run it outside any crtr node, because a node's engine stops with the daemon; from inside one, join refuses with `join_inside_node`. Add `--name <label>` to name the machine on the page. The token works once and expires at the time the page shows; the page shows it only once.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
crtr sys join --directory https://account.northlight.ai --token <token> --name my-laptop
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
It answers with `registered: true`, the machine id (`runtime:<id>`), your user id and `daemon: restarted` (`daemon: started` when crtr was not running: join starts the daemon, and its first grant sync installs `crtr cloud …` and your other provider plugins). Your open viewers and nodes reconnect on their own.
|
|
22
|
+
3. Check it: `crtr cloud apps mine` lists the apps you manage. Before you have joined, it answers `not_joined` and prints these same steps.
|
|
23
|
+
|
|
24
|
+
Things to know before you join:
|
|
25
|
+
|
|
26
|
+
- **Joining rewrites your crtr home.** Everything in your everyday crtr home that belonged to the local user (`user:local`) becomes your Northlight user, the next time the daemon starts. If you would rather not do that to your everyday home, join from a home you can throw away.
|
|
27
|
+
- **There is no unjoin.** The only undo is removing the machine on `/account/machines`.
|
|
28
|
+
- **Removing the machine stops it.** Once you choose **Remove** next to a machine, it gets no new tokens at once and the ones it holds expire within 5 minutes. After that its `cloud` commands answer `machine_revoked`: "This machine was removed from your Northlight account." It stays that way until you create a new join token and run `crtr sys join` again. Files on the computer stay on it.
|
|
29
|
+
- **Your hosted machine is unchanged.** `/account/machines` lists it as "Northlight (hosted)", the default. Your chat agent at `https://chat.northlight.ai` still runs `crtr cloud apps …` as before.
|
|
30
|
+
|
|
31
|
+
## `crtr cloud apps`
|
|
32
|
+
|
|
33
|
+
Every app-management leaf of Northlight's `cloud` provider runs as you and needs a joined machine. The console does the same work for all of them except `delete` and `set-run-notice-url`. `--app-id` takes `app:<client id>` or the bare client id.
|
|
34
|
+
|
|
35
|
+
Your Northlight chat agent at `https://chat.northlight.ai` can run these commands as you, on your hosted runtime. It can list and change an app (`mine` and `set-scopes` were checked on production). It cannot delete one. Generate keys on your laptop and give an agent only a public JWK, never the private key.
|
|
36
|
+
|
|
37
|
+
| Command | What it does |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `crtr cloud apps mine` | Lists the apps you manage. |
|
|
40
|
+
| `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). The `--redirect-uri` flag repeats. |
|
|
41
|
+
| `crtr cloud apps get --app-id <id>` | Shows the registration. |
|
|
42
|
+
| `crtr cloud apps update --app-id <id> --name … / --origin … / --logo … / --justification …` | Changes the profile fields you pass. |
|
|
43
|
+
| `crtr cloud apps set-redirect-uris --app-id <id> --redirect-uri …` | Replaces the whole redirect URI list; repeat the flag for each. |
|
|
44
|
+
| `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>`; `--requested` lists only what the app asks for now. For example `crtr cloud apps asks --app-id <id> --source sms`. |
|
|
45
|
+
| `crtr cloud apps set-scopes --app-id <id> --add … --remove …` | Edits the scope list the app has now; repeat `--add`/`--remove` per scope. Removing a scope that is not in the list is refused. Or replace the whole list with repeated `--scope`; `--clear` empties it. `crtr:act` needs `crtr:llm`. |
|
|
46
|
+
| `crtr cloud apps set-requires --app-id <id> --add … --remove …` | Edits the providers your app does not work without (for example `context`). Or replace the list with repeated `--provider`; `--clear` empties it. |
|
|
47
|
+
| `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. `keygen --app` calls it for you. |
|
|
48
|
+
| `crtr cloud apps keys list --app-id <id>` | Lists the keys with their `kid` and `active` or `retired` status. |
|
|
49
|
+
| `crtr cloud apps keys retire --app-id <id> --kid <kid>` | Retires a key so it no longer signs in. |
|
|
50
|
+
| `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 --price free` lists the app, so `cloud apps install` finds it; while the app is in testing it still stays out of the marketplace list. |
|
|
51
|
+
| `crtr cloud apps install --app-id <id>` | Texts an install link to the signed-in person. If they already hold a grant it answers `status: already_have` and the install URL instead of texting. The app must be listed. |
|
|
52
|
+
| `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. Not yet run on production. |
|
|
53
|
+
| `crtr cloud apps testers list --app-id <id>` | Lists the testers. |
|
|
54
|
+
| `crtr cloud apps testers remove --app-id <id> --email …` (or `--phone …`) | Removes a tester and revokes their grants for the app. Not yet run on production. |
|
|
55
|
+
| `crtr cloud apps set-run-notice-url --app-id <id> --url …` (or `--clear`) | Sets or clears the URL the runtime posts run notices to. https only, plain http on localhost for development, at most 512 characters. |
|
|
56
|
+
| `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`. |
|
|
57
|
+
|
|
58
|
+
Deleting an app removes every person's grant for it and cannot be undone. Each runtime deletes the app's runs, space and schedules, and the app's next refresh fails with `invalid_grant`. Apps that hold the provider role cannot be deleted.
|
package/docs/sdk/README.md
CHANGED
|
@@ -24,48 +24,9 @@ const run = await client.nodes.parse({
|
|
|
24
24
|
if (run.kind === 'result') console.log(run.output_parsed.root_cause);
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## Building an app for other people
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npx @north-light/sdk keygen --app <client id>
|
|
33
|
-
npx @north-light/sdk env --app <client id>
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`keygen` saves the private JWK to `local/app-key.json` (mode `0600`); keep that file out of git. With `--app`, it offers to add the public key to your registered app through your own `crtr`, as an additional key that never replaces the current one. Otherwise, add the public JWK printed on stdout with `crtr cloud apps keys add --app-id <client id> --jwk '<public JWK>'`. An app authenticates only with its key; the directory issues no client secret.
|
|
37
|
-
|
|
38
|
-
`env` reads the registration through `crtr cloud apps get` and writes `CROUTER_ISSUER`, `CROUTER_CLIENT_ID` and `APP_ORIGIN` into `local/app.env` (`--out` picks another file), keeping every other line. Both commands need this machine's `crtr` joined to your Northlight account; on a machine that isn't, they print a join step and exit 2. An app is configured from four variables:
|
|
39
|
-
|
|
40
|
-
| Variable | Value |
|
|
41
|
-
|---|---|
|
|
42
|
-
| `CROUTER_ISSUER` | The directory's issuer URL |
|
|
43
|
-
| `CROUTER_CLIENT_ID` | The app's client id |
|
|
44
|
-
| `APP_ORIGIN` | The app's origin; its redirect URI is `${APP_ORIGIN}/auth/callback` by convention |
|
|
45
|
-
| `CROUTER_KEY_FILE` | Path to the private JWK, default `local/app-key.json`. `env` never writes it |
|
|
46
|
-
|
|
47
|
-
The SDK reads no environment itself: load the values in your own config and pass them to `OAuth2Client`.
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
import {readFile} from 'node:fs/promises';
|
|
51
|
-
import {OAuth2Client} from '@north-light/sdk';
|
|
52
|
-
|
|
53
|
-
const privateKey = JSON.parse(await readFile(process.env.CROUTER_KEY_FILE ?? 'local/app-key.json', 'utf8'));
|
|
54
|
-
const oauth = new OAuth2Client({
|
|
55
|
-
clientId: process.env.CROUTER_CLIENT_ID!,
|
|
56
|
-
issuer: process.env.CROUTER_ISSUER!,
|
|
57
|
-
redirectUri: `${process.env.APP_ORIGIN}/auth/callback`,
|
|
58
|
-
privateKey,
|
|
59
|
-
store, // your ConnectionStore
|
|
60
|
-
});
|
|
61
|
-
const {url, state, codeVerifier, nonce} = await oauth.authorizeUrl({scopes: ['openid', 'crtr:llm', 'crtr:act'], state: sessionState});
|
|
62
|
-
// Save state, codeVerifier, nonce in the user's session, then redirect to url.
|
|
63
|
-
const connection = await oauth.exchangeCode({code: callbackCode, state: callbackState, expectedState: state, codeVerifier, nonce});
|
|
64
|
-
await store.save(connection);
|
|
65
|
-
const client = await oauth.client(connection.userId); // Refreshes through the store from here on.
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
The directory rejects the key until its public JWK is added to the app. To change keys later, follow [Rotating your key](./getting-started.md#rotating-your-key): add the new key, switch the app to it, verify a real sign-in, then retire the old key. An app can hold two active keys at once, so there is no downtime.
|
|
29
|
+
An app that signs people in with Northlight and runs agents on their runtimes is documented on the SDK docs site, which starts at [Quick setup](https://sdk.docs.northlight.ai/docs/quickstart). This section and the pages below cover driving a crouter daemon you run yourself.
|
|
69
30
|
|
|
70
31
|
## Pages
|
|
71
32
|
|
|
@@ -77,14 +38,10 @@ The directory rejects the key until its public JWK is added to the app. To chang
|
|
|
77
38
|
| [Streaming](./streaming.md) | The event table, `stream()` / `events()`, the activity helper, resume with `after`, `stream_gap` and `stream_dropped` |
|
|
78
39
|
| [Files](./files.md) | Absolute-path reads, writes, and one-level lists |
|
|
79
40
|
| [Bash](./bash.md) | One bounded command and its output result |
|
|
80
|
-
| [Memory](./memory.md) | `memory.list` and `memory.get`: an app reads the person's `public` documents with no scope |
|
|
81
41
|
| [Resource map](./resources.md) | Every namespace (including `customObjects`, `runs`, `memory`, `shares`, `providers`) with its routes, what is deliberately excluded, and the `client.request()` escape hatch |
|
|
82
|
-
| [Errors](./errors.md) | The
|
|
42
|
+
| [Errors](./errors.md) | The daemon's scoped-token and wire errors, identifier validation, and node retries |
|
|
83
43
|
| [Docker environment](./docker.md) | `start()`, `attach()`, `connection()` from `@north-light/env-docker` |
|
|
84
|
-
| [Testing your app](./testing.md) | `FakeCloud` from `@north-light/sdk/testing`: a fake directory and runtime your real `OAuth2Client` and `Crouter` talk to through `fetch` |
|
|
85
44
|
| [Release notes](https://sdk.docs.northlight.ai/docs/release-notes) | Behavior changes an existing app must act on, newest first |
|
|
86
|
-
| [Apps on Northlight](./guides/apps-on-northlight.md) | Building and registering an app against production (joining a laptop, `crtr cloud apps` registration, keys, env file, scopes, running locally, the `crtr cloud apps` commands); typed results from app runs with `runs.parse`, declines, bounding a run, `run_idle`, and waiting on a provider your app requires |
|
|
87
|
-
| [Apps with many users](./guides/apps-with-many-users.md) | Per-person runtime connections, app-owned user access, privacy, and spending |
|
|
88
45
|
|
|
89
46
|
## Phases
|
|
90
47
|
|