sello 0.1.9 → 0.1.10
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 +6 -0
- package/dist/cli/sello.js +83 -4
- package/docs/sdk-quickstart.md +6 -0
- package/package.json +1 -1
- package/src/cli/sello.ts +83 -4
package/README.md
CHANGED
|
@@ -49,6 +49,12 @@ npx --yes sello init-http-demo
|
|
|
49
49
|
npx --yes sello call-http-demo
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
## What Just Happened?
|
|
53
|
+
|
|
54
|
+
`sello dev` created a local owner key, service key, token, registry, and transparency log. The demo tool or route verified the token before running the handler. After the handler ran, the service signed an encrypted receipt for the action it observed. The local log stored the encrypted receipt, not plaintext action details. `sello actions` fetched the receipt, verified the log entry and service signature, decrypted it with the owner key, and printed the owner's view.
|
|
55
|
+
|
|
56
|
+
Local dev state lives under `.sello/`. The dev log is stored as encrypted receipt entries in `.sello/dev-log.jsonl`, so receipts survive restarting `sello dev` while staying out of git.
|
|
57
|
+
|
|
52
58
|
## Why Sello?
|
|
53
59
|
|
|
54
60
|
Most agent logs are written by the same system whose behavior they describe. If the agent, runtime, or operator is compromised, those logs can be incomplete or false.
|
package/dist/cli/sello.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { createServer, } from "node:http";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
appendFileSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
5
11
|
import { dirname, join } from "node:path";
|
|
6
12
|
|
|
7
13
|
import { generateEd25519KeyPair } from "../cose/sign1.js";
|
|
@@ -163,7 +169,7 @@ async function devCommand(args ) {
|
|
|
163
169
|
"calendar.example.com/mcp/v1";
|
|
164
170
|
const logEndpoint = `http://localhost:${port}/api`;
|
|
165
171
|
const logUrl = toCanonicalLogUrl(`http://localhost:${port}/api`);
|
|
166
|
-
const state =
|
|
172
|
+
const state = loadOrCreateDevState({ serviceId, logUrl, logEndpoint });
|
|
167
173
|
saveDevState(state);
|
|
168
174
|
|
|
169
175
|
if (dryRun) {
|
|
@@ -174,10 +180,12 @@ async function devCommand(args ) {
|
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
const log = new MockTransparencyLog(logUrl);
|
|
183
|
+
const logPath = devLogPath();
|
|
184
|
+
const loadedEntries = loadDevLogEntries(log, logPath);
|
|
177
185
|
const registry = parseRegistry(textEncoder.encode(state.registryJson));
|
|
178
186
|
const server = createServer(async (request, response) => {
|
|
179
187
|
try {
|
|
180
|
-
await handleDevRequest({ request, response, log, state, registry });
|
|
188
|
+
await handleDevRequest({ request, response, log, logPath, state, registry });
|
|
181
189
|
} catch (error) {
|
|
182
190
|
sendJson(response, 500, {
|
|
183
191
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -187,6 +195,9 @@ async function devCommand(args ) {
|
|
|
187
195
|
|
|
188
196
|
server.listen(port, () => {
|
|
189
197
|
printDevConfig(port, state);
|
|
198
|
+
console.log("");
|
|
199
|
+
console.log(`Local dev log: ${logPath}`);
|
|
200
|
+
console.log(`Loaded ${devLogEntryCountLabel(loadedEntries)}.`);
|
|
190
201
|
});
|
|
191
202
|
}
|
|
192
203
|
|
|
@@ -364,9 +375,10 @@ async function handleDevRequest(input
|
|
|
364
375
|
|
|
365
376
|
|
|
366
377
|
|
|
378
|
+
|
|
367
379
|
|
|
368
380
|
) {
|
|
369
|
-
const { request, response, log, state, registry } = input;
|
|
381
|
+
const { request, response, log, logPath, state, registry } = input;
|
|
370
382
|
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
|
|
371
383
|
|
|
372
384
|
if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
|
|
@@ -391,6 +403,7 @@ async function handleDevRequest(input
|
|
|
391
403
|
decodeBase64url(body.envelope, "envelope"),
|
|
392
404
|
typeof body.integratedTime === "string" ? body.integratedTime : undefined,
|
|
393
405
|
);
|
|
406
|
+
appendDevLogEntry(logPath, entry);
|
|
394
407
|
sendJson(response, 200, serializeEntry(entry));
|
|
395
408
|
return;
|
|
396
409
|
}
|
|
@@ -412,6 +425,24 @@ async function handleDevRequest(input
|
|
|
412
425
|
sendJson(response, 404, { error: "not found" });
|
|
413
426
|
}
|
|
414
427
|
|
|
428
|
+
function loadOrCreateDevState(input
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
) {
|
|
433
|
+
const existing = loadDevStateIfPresent();
|
|
434
|
+
if (
|
|
435
|
+
existing &&
|
|
436
|
+
existing.serviceId === input.serviceId &&
|
|
437
|
+
existing.logUrl === input.logUrl &&
|
|
438
|
+
existing.logEndpoint === input.logEndpoint
|
|
439
|
+
) {
|
|
440
|
+
return existing;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return createDevState(input);
|
|
444
|
+
}
|
|
445
|
+
|
|
415
446
|
function verifyDevActions(input
|
|
416
447
|
|
|
417
448
|
|
|
@@ -527,6 +558,54 @@ function devStatePath() {
|
|
|
527
558
|
return join(process.cwd(), ".sello", "dev.json");
|
|
528
559
|
}
|
|
529
560
|
|
|
561
|
+
function devLogPath() {
|
|
562
|
+
return join(process.cwd(), ".sello", "dev-log.jsonl");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function loadDevLogEntries(log , path ) {
|
|
566
|
+
if (!existsSync(path)) {
|
|
567
|
+
return 0;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const lines = textDecoder
|
|
571
|
+
.decode(readFileBytes(path))
|
|
572
|
+
.split(/\r?\n/)
|
|
573
|
+
.filter((line) => line.trim().length > 0);
|
|
574
|
+
|
|
575
|
+
let loaded = 0;
|
|
576
|
+
for (const [index, line] of lines.entries()) {
|
|
577
|
+
let entry;
|
|
578
|
+
try {
|
|
579
|
+
entry = deserializeEntry(JSON.parse(line));
|
|
580
|
+
if (entry.logUrl !== log.logUrl) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
log.append(entry.envelope, entry.integratedTime);
|
|
584
|
+
loaded += 1;
|
|
585
|
+
} catch (error) {
|
|
586
|
+
throw new TypeError(
|
|
587
|
+
`invalid local dev log entry ${index + 1} in ${path}: ${
|
|
588
|
+
error instanceof Error ? error.message : String(error)
|
|
589
|
+
}`,
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return loaded;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function appendDevLogEntry(
|
|
598
|
+
path ,
|
|
599
|
+
entry ,
|
|
600
|
+
) {
|
|
601
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
602
|
+
appendFileSync(path, `${JSON.stringify(serializeEntry(entry))}\n`, { mode: 0o600 });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function devLogEntryCountLabel(count ) {
|
|
606
|
+
return count === 1 ? "1 encrypted receipt" : `${count} encrypted receipts`;
|
|
607
|
+
}
|
|
608
|
+
|
|
530
609
|
function readFileBytes(path ) {
|
|
531
610
|
return new Uint8Array(readFileSync(path));
|
|
532
611
|
}
|
package/docs/sdk-quickstart.md
CHANGED
|
@@ -48,6 +48,12 @@ npx --yes sello call-http-demo
|
|
|
48
48
|
npx --yes sello actions
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## What Just Happened?
|
|
52
|
+
|
|
53
|
+
`sello dev` created local development keys, a demo authorization token, a service registry, and a local transparency log. The wrapped tool or route verified the token before running your handler. After the handler returned, Sello signed an encrypted receipt for the observed action and submitted it to the local log. The log stored encrypted receipt data, not plaintext action details. `sello actions` used the owner key from local dev state to fetch, verify, decrypt, and print the action.
|
|
54
|
+
|
|
55
|
+
Local dev state lives under `.sello/`. The encrypted dev log is stored in `.sello/dev-log.jsonl`, so receipts survive restarting `sello dev` without being committed to git.
|
|
56
|
+
|
|
51
57
|
Inside this repo, start the local log and action viewer:
|
|
52
58
|
|
|
53
59
|
```bash
|
package/package.json
CHANGED
package/src/cli/sello.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --experimental-strip-types
|
|
2
2
|
|
|
3
3
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
appendFileSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
5
11
|
import { dirname, join } from "node:path";
|
|
6
12
|
|
|
7
13
|
import { generateEd25519KeyPair } from "../cose/sign1.ts";
|
|
@@ -164,7 +170,7 @@ async function devCommand(args: string[]): Promise<void> {
|
|
|
164
170
|
"calendar.example.com/mcp/v1";
|
|
165
171
|
const logEndpoint = `http://localhost:${port}/api`;
|
|
166
172
|
const logUrl = toCanonicalLogUrl(`http://localhost:${port}/api`);
|
|
167
|
-
const state =
|
|
173
|
+
const state = loadOrCreateDevState({ serviceId, logUrl, logEndpoint });
|
|
168
174
|
saveDevState(state);
|
|
169
175
|
|
|
170
176
|
if (dryRun) {
|
|
@@ -175,10 +181,12 @@ async function devCommand(args: string[]): Promise<void> {
|
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
const log = new MockTransparencyLog(logUrl);
|
|
184
|
+
const logPath = devLogPath();
|
|
185
|
+
const loadedEntries = loadDevLogEntries(log, logPath);
|
|
178
186
|
const registry = parseRegistry(textEncoder.encode(state.registryJson));
|
|
179
187
|
const server = createServer(async (request, response) => {
|
|
180
188
|
try {
|
|
181
|
-
await handleDevRequest({ request, response, log, state, registry });
|
|
189
|
+
await handleDevRequest({ request, response, log, logPath, state, registry });
|
|
182
190
|
} catch (error) {
|
|
183
191
|
sendJson(response, 500, {
|
|
184
192
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -188,6 +196,9 @@ async function devCommand(args: string[]): Promise<void> {
|
|
|
188
196
|
|
|
189
197
|
server.listen(port, () => {
|
|
190
198
|
printDevConfig(port, state);
|
|
199
|
+
console.log("");
|
|
200
|
+
console.log(`Local dev log: ${logPath}`);
|
|
201
|
+
console.log(`Loaded ${devLogEntryCountLabel(loadedEntries)}.`);
|
|
191
202
|
});
|
|
192
203
|
}
|
|
193
204
|
|
|
@@ -364,10 +375,11 @@ async function handleDevRequest(input: {
|
|
|
364
375
|
request: IncomingMessage;
|
|
365
376
|
response: ServerResponse;
|
|
366
377
|
log: MockTransparencyLog;
|
|
378
|
+
logPath: string;
|
|
367
379
|
state: DevState;
|
|
368
380
|
registry: ReturnType<typeof parseRegistry>;
|
|
369
381
|
}): Promise<void> {
|
|
370
|
-
const { request, response, log, state, registry } = input;
|
|
382
|
+
const { request, response, log, logPath, state, registry } = input;
|
|
371
383
|
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
|
|
372
384
|
|
|
373
385
|
if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
|
|
@@ -392,6 +404,7 @@ async function handleDevRequest(input: {
|
|
|
392
404
|
decodeBase64url(body.envelope, "envelope"),
|
|
393
405
|
typeof body.integratedTime === "string" ? body.integratedTime : undefined,
|
|
394
406
|
);
|
|
407
|
+
appendDevLogEntry(logPath, entry);
|
|
395
408
|
sendJson(response, 200, serializeEntry(entry));
|
|
396
409
|
return;
|
|
397
410
|
}
|
|
@@ -413,6 +426,24 @@ async function handleDevRequest(input: {
|
|
|
413
426
|
sendJson(response, 404, { error: "not found" });
|
|
414
427
|
}
|
|
415
428
|
|
|
429
|
+
function loadOrCreateDevState(input: {
|
|
430
|
+
serviceId: string;
|
|
431
|
+
logUrl: CanonicalLogUrl;
|
|
432
|
+
logEndpoint: string;
|
|
433
|
+
}): DevState {
|
|
434
|
+
const existing = loadDevStateIfPresent();
|
|
435
|
+
if (
|
|
436
|
+
existing &&
|
|
437
|
+
existing.serviceId === input.serviceId &&
|
|
438
|
+
existing.logUrl === input.logUrl &&
|
|
439
|
+
existing.logEndpoint === input.logEndpoint
|
|
440
|
+
) {
|
|
441
|
+
return existing;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return createDevState(input);
|
|
445
|
+
}
|
|
446
|
+
|
|
416
447
|
function verifyDevActions(input: {
|
|
417
448
|
log: MockTransparencyLog;
|
|
418
449
|
state: DevState;
|
|
@@ -528,6 +559,54 @@ function devStatePath(): string {
|
|
|
528
559
|
return join(process.cwd(), ".sello", "dev.json");
|
|
529
560
|
}
|
|
530
561
|
|
|
562
|
+
function devLogPath(): string {
|
|
563
|
+
return join(process.cwd(), ".sello", "dev-log.jsonl");
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function loadDevLogEntries(log: MockTransparencyLog, path: string): number {
|
|
567
|
+
if (!existsSync(path)) {
|
|
568
|
+
return 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const lines = textDecoder
|
|
572
|
+
.decode(readFileBytes(path))
|
|
573
|
+
.split(/\r?\n/)
|
|
574
|
+
.filter((line) => line.trim().length > 0);
|
|
575
|
+
|
|
576
|
+
let loaded = 0;
|
|
577
|
+
for (const [index, line] of lines.entries()) {
|
|
578
|
+
let entry;
|
|
579
|
+
try {
|
|
580
|
+
entry = deserializeEntry(JSON.parse(line));
|
|
581
|
+
if (entry.logUrl !== log.logUrl) {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
log.append(entry.envelope, entry.integratedTime);
|
|
585
|
+
loaded += 1;
|
|
586
|
+
} catch (error) {
|
|
587
|
+
throw new TypeError(
|
|
588
|
+
`invalid local dev log entry ${index + 1} in ${path}: ${
|
|
589
|
+
error instanceof Error ? error.message : String(error)
|
|
590
|
+
}`,
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return loaded;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function appendDevLogEntry(
|
|
599
|
+
path: string,
|
|
600
|
+
entry: ReturnType<MockTransparencyLog["append"]>,
|
|
601
|
+
): void {
|
|
602
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
603
|
+
appendFileSync(path, `${JSON.stringify(serializeEntry(entry))}\n`, { mode: 0o600 });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function devLogEntryCountLabel(count: number): string {
|
|
607
|
+
return count === 1 ? "1 encrypted receipt" : `${count} encrypted receipts`;
|
|
608
|
+
}
|
|
609
|
+
|
|
531
610
|
function readFileBytes(path: string): Uint8Array {
|
|
532
611
|
return new Uint8Array(readFileSync(path));
|
|
533
612
|
}
|