sello 0.1.8 → 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 CHANGED
@@ -46,8 +46,15 @@ To scaffold a small HTTP route that emits receipts:
46
46
 
47
47
  ```bash
48
48
  npx --yes sello init-http-demo
49
+ npx --yes sello call-http-demo
49
50
  ```
50
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
+
51
58
  ## Why Sello?
52
59
 
53
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.
@@ -85,7 +92,7 @@ The implementation includes a local end-to-end demo, compact JWS token verificat
85
92
  |------|------|
86
93
  | Add Sello in a few lines | [SDK Quickstart](docs/sdk-quickstart.md) |
87
94
  | Emit your first receipt | `npx --yes sello dev`, then `npx --yes sello emit-demo` |
88
- | Wrap one HTTP route | `npx --yes sello init-http-demo` |
95
+ | Wrap one HTTP route | `npx --yes sello init-http-demo`, then `npx --yes sello call-http-demo` |
89
96
  | Try a wrapped tool locally | `node --run dev`, then `node --run example:tool` |
90
97
  | Try an MCP-style tool call | `node --run dev`, then `node --run example:mcp` |
91
98
  | See a minimal MCP integration | [examples/mcp-minimal-server.ts](examples/mcp-minimal-server.ts) |
@@ -142,7 +149,7 @@ For an MCP-shaped `tools/call` boundary, run `node --run example:mcp` instead of
142
149
 
143
150
  For a smaller production-shaped MCP example, see [examples/mcp-minimal-server.ts](examples/mcp-minimal-server.ts). It wraps one `tools/call` handler with `sello.service()` and leaves unknown tools unreceipted.
144
151
 
145
- For an installed-project bridge from demo to app, run `npx sello init-http-demo`. It writes a small dependency-free HTTP route that imports `sello`, reads the local dev config, verifies a bearer token, runs a handler, and emits a receipt.
152
+ For an installed-project bridge from demo to app, run `npx sello init-http-demo`. It writes a small dependency-free HTTP route that imports `sello`, reads the local dev config, verifies a bearer token, runs a handler, and emits a receipt. With the local log and route running, `npx sello call-http-demo` sends the demo request for you.
146
153
 
147
154
  ## The First 10 Minutes
148
155
 
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 { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
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";
@@ -81,6 +87,9 @@ try {
81
87
  case "emit-demo":
82
88
  await emitDemoCommand(process.argv.slice(3));
83
89
  break;
90
+ case "call-http-demo":
91
+ await callHttpDemoCommand(process.argv.slice(3));
92
+ break;
84
93
  case "init-demo":
85
94
  initDemoCommand(process.argv.slice(3));
86
95
  break;
@@ -160,7 +169,7 @@ async function devCommand(args ) {
160
169
  "calendar.example.com/mcp/v1";
161
170
  const logEndpoint = `http://localhost:${port}/api`;
162
171
  const logUrl = toCanonicalLogUrl(`http://localhost:${port}/api`);
163
- const state = createDevState({ serviceId, logUrl, logEndpoint });
172
+ const state = loadOrCreateDevState({ serviceId, logUrl, logEndpoint });
164
173
  saveDevState(state);
165
174
 
166
175
  if (dryRun) {
@@ -171,10 +180,12 @@ async function devCommand(args ) {
171
180
  }
172
181
 
173
182
  const log = new MockTransparencyLog(logUrl);
183
+ const logPath = devLogPath();
184
+ const loadedEntries = loadDevLogEntries(log, logPath);
174
185
  const registry = parseRegistry(textEncoder.encode(state.registryJson));
175
186
  const server = createServer(async (request, response) => {
176
187
  try {
177
- await handleDevRequest({ request, response, log, state, registry });
188
+ await handleDevRequest({ request, response, log, logPath, state, registry });
178
189
  } catch (error) {
179
190
  sendJson(response, 500, {
180
191
  error: error instanceof Error ? error.message : String(error),
@@ -184,6 +195,9 @@ async function devCommand(args ) {
184
195
 
185
196
  server.listen(port, () => {
186
197
  printDevConfig(port, state);
198
+ console.log("");
199
+ console.log(`Local dev log: ${logPath}`);
200
+ console.log(`Loaded ${devLogEntryCountLabel(loadedEntries)}.`);
187
201
  });
188
202
  }
189
203
 
@@ -237,6 +251,38 @@ async function emitDemoCommand(args ) {
237
251
  console.log(` ${actionViewerUrl(state)}`);
238
252
  }
239
253
 
254
+ async function callHttpDemoCommand(args ) {
255
+ const state = loadDevStateOrThrow(
256
+ "missing local Sello dev state. Run `sello dev` first, then run `sello call-http-demo` in another terminal from the same directory.",
257
+ );
258
+ const url = readFlag(args, "--url") ?? "http://localhost:8790/calendar/events";
259
+ const title = readFlag(args, "--title") ?? "Ship Sello";
260
+ const response = await fetch(url, {
261
+ method: "POST",
262
+ headers: {
263
+ authorization: `Bearer ${state.agentToken}`,
264
+ "content-type": "application/json",
265
+ },
266
+ body: JSON.stringify({ title }),
267
+ });
268
+ const responseText = await response.text();
269
+
270
+ if (!response.ok) {
271
+ throw new TypeError(
272
+ `HTTP route demo failed with HTTP ${response.status}: ${responseText.trim()}`,
273
+ );
274
+ }
275
+
276
+ console.log("Called Sello HTTP route demo.");
277
+ console.log(formatHttpDemoResponse(responseText));
278
+ console.log("");
279
+ console.log("View verified actions with:");
280
+ console.log(" sello actions");
281
+ console.log("");
282
+ console.log("Or open:");
283
+ console.log(` ${actionViewerUrl(state)}`);
284
+ }
285
+
240
286
  function initDemoCommand(args ) {
241
287
  const output = readFlag(args, "--output") ?? "emit-receipt.mjs";
242
288
  const force = args.includes("--force");
@@ -278,8 +324,7 @@ function initHttpDemoCommand(args ) {
278
324
  console.log(` node ${output}`);
279
325
  console.log("");
280
326
  console.log("Terminal 3: call the route and view the receipt");
281
- console.log(" TOKEN=$(node -e 'console.log(JSON.parse(require(\"fs\").readFileSync(\".sello/dev.json\", \"utf8\")).agentToken)')");
282
- console.log(" curl -sS -X POST http://localhost:8790/calendar/events -H \"authorization: Bearer $TOKEN\" -H \"content-type: application/json\" -d '{\"title\":\"Ship Sello\"}'");
327
+ console.log(" npx sello call-http-demo");
283
328
  console.log(" npx sello actions");
284
329
  console.log("");
285
330
  console.log("Then open http://localhost:8787/actions");
@@ -330,9 +375,10 @@ async function handleDevRequest(input
330
375
 
331
376
 
332
377
 
378
+
333
379
 
334
380
  ) {
335
- const { request, response, log, state, registry } = input;
381
+ const { request, response, log, logPath, state, registry } = input;
336
382
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
337
383
 
338
384
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
@@ -357,6 +403,7 @@ async function handleDevRequest(input
357
403
  decodeBase64url(body.envelope, "envelope"),
358
404
  typeof body.integratedTime === "string" ? body.integratedTime : undefined,
359
405
  );
406
+ appendDevLogEntry(logPath, entry);
360
407
  sendJson(response, 200, serializeEntry(entry));
361
408
  return;
362
409
  }
@@ -378,6 +425,24 @@ async function handleDevRequest(input
378
425
  sendJson(response, 404, { error: "not found" });
379
426
  }
380
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
+
381
446
  function verifyDevActions(input
382
447
 
383
448
 
@@ -493,6 +558,54 @@ function devStatePath() {
493
558
  return join(process.cwd(), ".sello", "dev.json");
494
559
  }
495
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
+
496
609
  function readFileBytes(path ) {
497
610
  return new Uint8Array(readFileSync(path));
498
611
  }
@@ -678,6 +791,7 @@ function printHelp() {
678
791
  console.log(`Usage:
679
792
  sello dev [--port 8787] [--service service-id] [--dry-run]
680
793
  sello emit-demo [--title title]
794
+ sello call-http-demo [--url http://localhost:8790/calendar/events] [--title title]
681
795
  sello init-demo [--output emit-receipt.mjs] [--force]
682
796
  sello init-http-demo [--output sello-http-route.mjs] [--force]
683
797
  sello actions [--token agent-token]
@@ -738,6 +852,14 @@ function actionViewerUrl(state ) {
738
852
  return `${endpoint.origin}/actions`;
739
853
  }
740
854
 
855
+ function formatHttpDemoResponse(responseText ) {
856
+ try {
857
+ return JSON.stringify(JSON.parse(responseText), null, 2);
858
+ } catch {
859
+ return responseText;
860
+ }
861
+ }
862
+
741
863
  function slug(value ) {
742
864
  return value
743
865
  .toLowerCase()
@@ -857,9 +979,8 @@ const server = createServer(async (request, response) => {
857
979
  server.listen(port, () => {
858
980
  console.log("Sello HTTP route demo running at http://localhost:" + port + "/calendar/events");
859
981
  console.log("");
860
- console.log("Call it with:");
861
- console.log(" TOKEN=$(node -e 'console.log(JSON.parse(require(\\\"fs\\\").readFileSync(\\\".sello/dev.json\\\", \\\"utf8\\\")).agentToken)')");
862
- console.log(" curl -sS -X POST http://localhost:" + port + "/calendar/events -H \\\"authorization: Bearer $TOKEN\\\" -H \\\"content-type: application/json\\\" -d '{\\\"title\\\":\\\"Ship Sello\\\"}'");
982
+ console.log("Call it from another terminal with:");
983
+ console.log(" npx sello call-http-demo");
863
984
  });
864
985
 
865
986
  async function readJson(request) {
@@ -41,7 +41,18 @@ To write a dependency-free HTTP route example into your project:
41
41
  npx --yes sello init-http-demo
42
42
  ```
43
43
 
44
- The route example imports `sello`, reads the local dev config, verifies a bearer token, runs one `POST /calendar/events` handler, and emits a receipt.
44
+ The route example imports `sello`, reads the local dev config, verifies a bearer token, runs one `POST /calendar/events` handler, and emits a receipt. With `npx sello dev` and the generated route running, call it with:
45
+
46
+ ```bash
47
+ npx --yes sello call-http-demo
48
+ npx --yes sello actions
49
+ ```
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.
45
56
 
46
57
  Inside this repo, start the local log and action viewer:
47
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sello",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Reference implementation of the Sello protocol for service-signed AI agent receipts.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
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 { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
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";
@@ -82,6 +88,9 @@ try {
82
88
  case "emit-demo":
83
89
  await emitDemoCommand(process.argv.slice(3));
84
90
  break;
91
+ case "call-http-demo":
92
+ await callHttpDemoCommand(process.argv.slice(3));
93
+ break;
85
94
  case "init-demo":
86
95
  initDemoCommand(process.argv.slice(3));
87
96
  break;
@@ -161,7 +170,7 @@ async function devCommand(args: string[]): Promise<void> {
161
170
  "calendar.example.com/mcp/v1";
162
171
  const logEndpoint = `http://localhost:${port}/api`;
163
172
  const logUrl = toCanonicalLogUrl(`http://localhost:${port}/api`);
164
- const state = createDevState({ serviceId, logUrl, logEndpoint });
173
+ const state = loadOrCreateDevState({ serviceId, logUrl, logEndpoint });
165
174
  saveDevState(state);
166
175
 
167
176
  if (dryRun) {
@@ -172,10 +181,12 @@ async function devCommand(args: string[]): Promise<void> {
172
181
  }
173
182
 
174
183
  const log = new MockTransparencyLog(logUrl);
184
+ const logPath = devLogPath();
185
+ const loadedEntries = loadDevLogEntries(log, logPath);
175
186
  const registry = parseRegistry(textEncoder.encode(state.registryJson));
176
187
  const server = createServer(async (request, response) => {
177
188
  try {
178
- await handleDevRequest({ request, response, log, state, registry });
189
+ await handleDevRequest({ request, response, log, logPath, state, registry });
179
190
  } catch (error) {
180
191
  sendJson(response, 500, {
181
192
  error: error instanceof Error ? error.message : String(error),
@@ -185,6 +196,9 @@ async function devCommand(args: string[]): Promise<void> {
185
196
 
186
197
  server.listen(port, () => {
187
198
  printDevConfig(port, state);
199
+ console.log("");
200
+ console.log(`Local dev log: ${logPath}`);
201
+ console.log(`Loaded ${devLogEntryCountLabel(loadedEntries)}.`);
188
202
  });
189
203
  }
190
204
 
@@ -238,6 +252,38 @@ async function emitDemoCommand(args: string[]): Promise<void> {
238
252
  console.log(` ${actionViewerUrl(state)}`);
239
253
  }
240
254
 
255
+ async function callHttpDemoCommand(args: string[]): Promise<void> {
256
+ const state = loadDevStateOrThrow(
257
+ "missing local Sello dev state. Run `sello dev` first, then run `sello call-http-demo` in another terminal from the same directory.",
258
+ );
259
+ const url = readFlag(args, "--url") ?? "http://localhost:8790/calendar/events";
260
+ const title = readFlag(args, "--title") ?? "Ship Sello";
261
+ const response = await fetch(url, {
262
+ method: "POST",
263
+ headers: {
264
+ authorization: `Bearer ${state.agentToken}`,
265
+ "content-type": "application/json",
266
+ },
267
+ body: JSON.stringify({ title }),
268
+ });
269
+ const responseText = await response.text();
270
+
271
+ if (!response.ok) {
272
+ throw new TypeError(
273
+ `HTTP route demo failed with HTTP ${response.status}: ${responseText.trim()}`,
274
+ );
275
+ }
276
+
277
+ console.log("Called Sello HTTP route demo.");
278
+ console.log(formatHttpDemoResponse(responseText));
279
+ console.log("");
280
+ console.log("View verified actions with:");
281
+ console.log(" sello actions");
282
+ console.log("");
283
+ console.log("Or open:");
284
+ console.log(` ${actionViewerUrl(state)}`);
285
+ }
286
+
241
287
  function initDemoCommand(args: string[]): void {
242
288
  const output = readFlag(args, "--output") ?? "emit-receipt.mjs";
243
289
  const force = args.includes("--force");
@@ -279,8 +325,7 @@ function initHttpDemoCommand(args: string[]): void {
279
325
  console.log(` node ${output}`);
280
326
  console.log("");
281
327
  console.log("Terminal 3: call the route and view the receipt");
282
- console.log(" TOKEN=$(node -e 'console.log(JSON.parse(require(\"fs\").readFileSync(\".sello/dev.json\", \"utf8\")).agentToken)')");
283
- console.log(" curl -sS -X POST http://localhost:8790/calendar/events -H \"authorization: Bearer $TOKEN\" -H \"content-type: application/json\" -d '{\"title\":\"Ship Sello\"}'");
328
+ console.log(" npx sello call-http-demo");
284
329
  console.log(" npx sello actions");
285
330
  console.log("");
286
331
  console.log("Then open http://localhost:8787/actions");
@@ -330,10 +375,11 @@ async function handleDevRequest(input: {
330
375
  request: IncomingMessage;
331
376
  response: ServerResponse;
332
377
  log: MockTransparencyLog;
378
+ logPath: string;
333
379
  state: DevState;
334
380
  registry: ReturnType<typeof parseRegistry>;
335
381
  }): Promise<void> {
336
- const { request, response, log, state, registry } = input;
382
+ const { request, response, log, logPath, state, registry } = input;
337
383
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
338
384
 
339
385
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
@@ -358,6 +404,7 @@ async function handleDevRequest(input: {
358
404
  decodeBase64url(body.envelope, "envelope"),
359
405
  typeof body.integratedTime === "string" ? body.integratedTime : undefined,
360
406
  );
407
+ appendDevLogEntry(logPath, entry);
361
408
  sendJson(response, 200, serializeEntry(entry));
362
409
  return;
363
410
  }
@@ -379,6 +426,24 @@ async function handleDevRequest(input: {
379
426
  sendJson(response, 404, { error: "not found" });
380
427
  }
381
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
+
382
447
  function verifyDevActions(input: {
383
448
  log: MockTransparencyLog;
384
449
  state: DevState;
@@ -494,6 +559,54 @@ function devStatePath(): string {
494
559
  return join(process.cwd(), ".sello", "dev.json");
495
560
  }
496
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
+
497
610
  function readFileBytes(path: string): Uint8Array {
498
611
  return new Uint8Array(readFileSync(path));
499
612
  }
@@ -679,6 +792,7 @@ function printHelp(): void {
679
792
  console.log(`Usage:
680
793
  sello dev [--port 8787] [--service service-id] [--dry-run]
681
794
  sello emit-demo [--title title]
795
+ sello call-http-demo [--url http://localhost:8790/calendar/events] [--title title]
682
796
  sello init-demo [--output emit-receipt.mjs] [--force]
683
797
  sello init-http-demo [--output sello-http-route.mjs] [--force]
684
798
  sello actions [--token agent-token]
@@ -739,6 +853,14 @@ function actionViewerUrl(state: DevState): string {
739
853
  return `${endpoint.origin}/actions`;
740
854
  }
741
855
 
856
+ function formatHttpDemoResponse(responseText: string): string {
857
+ try {
858
+ return JSON.stringify(JSON.parse(responseText), null, 2);
859
+ } catch {
860
+ return responseText;
861
+ }
862
+ }
863
+
742
864
  function slug(value: string): string {
743
865
  return value
744
866
  .toLowerCase()
@@ -858,9 +980,8 @@ const server = createServer(async (request, response) => {
858
980
  server.listen(port, () => {
859
981
  console.log("Sello HTTP route demo running at http://localhost:" + port + "/calendar/events");
860
982
  console.log("");
861
- console.log("Call it with:");
862
- console.log(" TOKEN=$(node -e 'console.log(JSON.parse(require(\\\"fs\\\").readFileSync(\\\".sello/dev.json\\\", \\\"utf8\\\")).agentToken)')");
863
- console.log(" curl -sS -X POST http://localhost:" + port + "/calendar/events -H \\\"authorization: Bearer $TOKEN\\\" -H \\\"content-type: application/json\\\" -d '{\\\"title\\\":\\\"Ship Sello\\\"}'");
983
+ console.log("Call it from another terminal with:");
984
+ console.log(" npx sello call-http-demo");
864
985
  });
865
986
 
866
987
  async function readJson(request) {