@north-light/sdk 0.3.478 → 0.3.480
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/init-command.js +24 -2
- package/dist/skill/northlight/SKILL.md +5 -3
- package/dist/templates/server.ts +5 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -3
package/dist/init-command.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import { mkdir, open, readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { appendFile, mkdir, open, readFile, stat } from 'node:fs/promises';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { dirname, join, relative } from 'node:path';
|
|
6
6
|
import { assetPath } from './assets.js';
|
|
@@ -65,6 +65,25 @@ async function createFile(path, text, mode = 0o644) {
|
|
|
65
65
|
await file.close();
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
/** The `.gitignore` lines that already keep `local/` (the key and env file) out of git. */
|
|
69
|
+
const IGNORES_LOCAL = new Set(['local', 'local/', '/local', '/local/', 'local/*', '/local/*', 'local/**', '/local/**']);
|
|
70
|
+
/** Makes sure `<root>/.gitignore` ignores `local/`: creates it, or appends the entry once. Returns what it did. */
|
|
71
|
+
async function ensureLocalIgnored(root) {
|
|
72
|
+
const path = join(root, '.gitignore');
|
|
73
|
+
const current = await readFile(path, 'utf8').catch((error) => {
|
|
74
|
+
if (error.code === 'ENOENT')
|
|
75
|
+
return undefined;
|
|
76
|
+
throw error;
|
|
77
|
+
});
|
|
78
|
+
if (current === undefined) {
|
|
79
|
+
await createFile(path, 'local/\n');
|
|
80
|
+
return 'created';
|
|
81
|
+
}
|
|
82
|
+
if (current.split(/\r?\n/).some((line) => IGNORES_LOCAL.has(line.trim())))
|
|
83
|
+
return 'present';
|
|
84
|
+
await appendFile(path, `${current === '' || current.endsWith('\n') ? '' : '\n'}local/\n`);
|
|
85
|
+
return 'added';
|
|
86
|
+
}
|
|
68
87
|
/** Runs `npm i --save-exact @north-light/sdk@<this version>` in `cwd` (no shell). */
|
|
69
88
|
function installSdk(cwd) {
|
|
70
89
|
const result = spawnSync('npm', ['i', '--save-exact', `@north-light/sdk@${SDK_VERSION}`], { cwd, stdio: ['ignore', 2, 2] });
|
|
@@ -125,6 +144,8 @@ export async function runInit(argv, options = {}) {
|
|
|
125
144
|
for (const [name, text] of Object.entries(scaffold))
|
|
126
145
|
await createFile(join(root, name), text, name === ENV_FILE ? 0o600 : 0o644);
|
|
127
146
|
}
|
|
147
|
+
// Before the key exists, so a key is never on disk in a directory git would pick up. A new app's scaffold already wrote it.
|
|
148
|
+
const ignored = existingProject ? await ensureLocalIgnored(root) : 'present';
|
|
128
149
|
let publicJwk;
|
|
129
150
|
if (!(await exists(keyPath)))
|
|
130
151
|
publicJwk = JSON.stringify((await createKeyFile(keyPath)).publicJwk);
|
|
@@ -134,7 +155,8 @@ export async function runInit(argv, options = {}) {
|
|
|
134
155
|
say([
|
|
135
156
|
existingProject ? `Added @north-light/sdk@${SDK_VERSION}, a signing key and the \`northlight\` skill to ${shown(root)}.` : `Created a Northlight app in ${shown(root)}:`,
|
|
136
157
|
...(existingProject ? [] : [' package.json server.ts .gitignore local/app.env', '']),
|
|
137
|
-
|
|
158
|
+
...(ignored === 'present' ? [] : [`${ignored === 'created' ? 'Created' : 'Updated'} ${shown(join(root, '.gitignore'))} so git ignores local/.`]),
|
|
159
|
+
`Private key: ${shown(keyPath)} (mode 0600, ignored by git). Never paste or commit it.`,
|
|
138
160
|
'Skill:',
|
|
139
161
|
...skillPaths.map((path) => ` ${globalSkill ? path : shown(path)}`),
|
|
140
162
|
'',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: northlight
|
|
3
3
|
description: "Use when building, debugging or shipping an app that signs people in with Northlight, starts runs on their Northlight runtime, or calls Northlight providers (sms, search, integrations, context, device) with @north-light/sdk."
|
|
4
4
|
metadata:
|
|
5
|
-
sdk-version: "0.3.
|
|
5
|
+
sdk-version: "0.3.480"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Building a Northlight app
|
|
@@ -11,7 +11,7 @@ An app on Northlight signs people in with their Northlight account, then starts
|
|
|
11
11
|
|
|
12
12
|
## First: check this skill matches the SDK
|
|
13
13
|
|
|
14
|
-
This skill is for SDK version `0.3.
|
|
14
|
+
This skill is for SDK version `0.3.480`. If the project has `node_modules/@north-light/sdk/package.json`, compare its `version` with that number. If they differ, run `npx @north-light/sdk skill` in the project, then read the rewritten `.agents/skills/northlight/SKILL.md` (or `.claude/skills/northlight/SKILL.md`) instead of this copy. That command needs no account and no crtr. A project that has no SDK yet starts with `npx @north-light/sdk@latest init <dir>`.
|
|
15
15
|
|
|
16
16
|
## Where things run
|
|
17
17
|
|
|
@@ -95,7 +95,9 @@ Keep `codeVerifier` and `nonce` per sign-in, keyed by `state`, and pass the same
|
|
|
95
95
|
|
|
96
96
|
```ts
|
|
97
97
|
const runtime = await oauth.client(userId); // a client on this person's runtime, from their stored connection
|
|
98
|
-
|
|
98
|
+
// The prompt is the instruction; the message stored with it joins the first turn, so `reply` can follow that turn.
|
|
99
|
+
const started = await runtime.runs.start({prompt: 'Reply with exactly the word: pong', message: 'ping', scopes: ['crtr:llm', 'crtr:act']});
|
|
100
|
+
const reply = await runtime.runs.reply(started.run_id, started.message!).collect(); // reply.text is what the agent said
|
|
99
101
|
const run = await runtime.runs.wait(started.run_id); // status is 'idle' once the reply is done
|
|
100
102
|
|
|
101
103
|
// Texts the signed-in person's own verified phone through the sms provider (50 per person per UTC day).
|
package/dist/templates/server.ts
CHANGED
|
@@ -65,11 +65,14 @@ const server = createServer(async (req, res) => {
|
|
|
65
65
|
|
|
66
66
|
if (url.pathname === '/run' && req.method === 'POST') {
|
|
67
67
|
const runtime = await oauth.client(userId); // a client on this person's runtime, from their stored connection
|
|
68
|
-
|
|
68
|
+
// The prompt is the instruction; the message stored with it joins the first turn, so `reply` can follow that turn.
|
|
69
|
+
const started = await runtime.runs.start({prompt: 'Reply with exactly the word: pong', message: 'ping', scopes: ['crtr:llm', 'crtr:act']});
|
|
70
|
+
const reply = await runtime.runs.reply(started.run_id, started.message!).collect(); // reply.text is what the agent said
|
|
69
71
|
const run = await runtime.runs.wait(started.run_id); // status is 'idle' once the reply is done
|
|
70
72
|
// Texts the signed-in person's own verified phone through the sms provider (50 per person per UTC day).
|
|
71
73
|
const sent = await runtime.providers.call({provider: 'sms', tool: 'send', arguments: {body: 'Hello from my first Northlight app.'}});
|
|
72
|
-
|
|
74
|
+
const {message_id} = sent as {message_id: string}; // the provider also returns a note meant for agents; it is not shown here
|
|
75
|
+
return page(res, 200, `<h1>Done</h1><p>The run finished with status <code>${escapeHtml(run.status)}</code> and replied <code>${escapeHtml(reply.text)}</code>. A text is on its way to your phone.</p><pre>${escapeHtml(JSON.stringify({run: run.run_id, status: run.status, reply: reply.text, message_id}, null, 2))}</pre><p><a href="/">Back</a></p>`);
|
|
73
76
|
}
|
|
74
77
|
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>`);
|
|
75
78
|
} catch (error) {
|
package/dist/version.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** This SDK's release version. Informational only: compatibility is the contract level, not this number. */
|
|
2
|
-
export declare const SDK_VERSION = "0.3.
|
|
2
|
+
export declare const SDK_VERSION = "0.3.480";
|
|
3
3
|
/** The release sent in `Crouter-Minimum-Runtime-Version` on every request. It is frozen, so a runtime from before the
|
|
4
4
|
* contract level existed (which only knows this header) refuses this SDK, while every later runtime accepts it. */
|
|
5
5
|
export declare const LEGACY_FLOOR = "0.3.423";
|
package/dist/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SDK_VERSION is written by scripts/release-version.mjs on every release; do not edit by hand.
|
|
2
2
|
/** This SDK's release version. Informational only: compatibility is the contract level, not this number. */
|
|
3
|
-
export const SDK_VERSION = '0.3.
|
|
3
|
+
export const SDK_VERSION = '0.3.480';
|
|
4
4
|
// LEGACY_FLOOR is written once by `scripts/contract.mjs release`, at the first release that carries the contract level
|
|
5
5
|
// (the `0.0.0` placeholder is replaced there and never again); do not edit by hand.
|
|
6
6
|
/** The release sent in `Crouter-Minimum-Runtime-Version` on every request. It is frozen, so a runtime from before the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.480",
|
|
4
4
|
"crouterContract": "1.7",
|
|
5
5
|
"description": "Typed Node and browser client for running crouter agents through the crtrd /v1 API.",
|
|
6
6
|
"type": "module",
|
|
@@ -35,14 +35,17 @@
|
|
|
35
35
|
"dist",
|
|
36
36
|
"README.md"
|
|
37
37
|
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20.6.0"
|
|
40
|
+
},
|
|
38
41
|
"sideEffects": false,
|
|
39
42
|
"scripts": {
|
|
40
43
|
"build": "tsc -p tsconfig.json && node scripts/build-skill.mjs && chmod 755 dist/keygen-cli.js",
|
|
41
44
|
"prepublishOnly": "node -e \"if (process.env.npm_config_tag !== 'next') { console.error('@north-light/sdk publishes only with --tag next; scripts/promote-sdk.mjs is the only mover of latest.'); process.exit(1); }\""
|
|
42
45
|
},
|
|
43
46
|
"dependencies": {
|
|
44
|
-
"@north-light/api": "0.3.
|
|
45
|
-
"@north-light/identity": "0.3.
|
|
47
|
+
"@north-light/api": "0.3.480",
|
|
48
|
+
"@north-light/identity": "0.3.480",
|
|
46
49
|
"jose": "^6.2.1"
|
|
47
50
|
},
|
|
48
51
|
"peerDependencies": {
|