sello 0.1.4 → 0.1.6

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/cli/sello.js CHANGED
@@ -44,6 +44,25 @@ import { createSelloService } from "../sdk/service.js";
44
44
 
45
45
 
46
46
 
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
47
66
 
48
67
 
49
68
  const textEncoder = new TextEncoder();
@@ -285,16 +304,17 @@ async function handleDevRequest(input
285
304
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
286
305
 
287
306
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
288
- const result = verifyReceipts({
289
- authorizationTokenBytes: textEncoder.encode(state.agentToken),
290
- trustedLogs: [log],
291
- registry,
292
- ownerPrivateKey: normalizeHpkePrivateKey(state.ownerKey),
293
- });
307
+ const result = verifyDevActions({ log, state, registry });
294
308
  sendHtml(response, renderActionsHtml(result));
295
309
  return;
296
310
  }
297
311
 
312
+ if (request.method === "GET" && url.pathname === "/api/actions") {
313
+ const result = verifyDevActions({ log, state, registry });
314
+ sendJson(response, 200, actionsViewModel(result));
315
+ return;
316
+ }
317
+
298
318
  if (request.method === "POST" && url.pathname === "/api/entries") {
299
319
  const body = await readJson(request);
300
320
  if (!isRecord(body) || typeof body.envelope !== "string") {
@@ -326,6 +346,19 @@ async function handleDevRequest(input
326
346
  sendJson(response, 404, { error: "not found" });
327
347
  }
328
348
 
349
+ function verifyDevActions(input
350
+
351
+
352
+
353
+ ) {
354
+ return verifyReceipts({
355
+ authorizationTokenBytes: textEncoder.encode(input.state.agentToken),
356
+ trustedLogs: [input.log],
357
+ registry: input.registry,
358
+ ownerPrivateKey: normalizeHpkePrivateKey(input.state.ownerKey),
359
+ });
360
+ }
361
+
329
362
  async function loadViewerRegistry(devState ) {
330
363
  if (process.env.SELLO_REGISTRY_PATH) {
331
364
  return parseRegistry(readFileBytes(process.env.SELLO_REGISTRY_PATH));
@@ -461,16 +494,40 @@ function printActions(result ) {
461
494
  }
462
495
  }
463
496
 
497
+ function actionsViewModel(result ) {
498
+ return {
499
+ receipts: result.receipts.map((record) => ({
500
+ integratedTime: record.integratedTime,
501
+ serviceIdentifier: record.serviceIdentifier,
502
+ actionType: record.receipt["action-type"],
503
+ resultStatus: record.receipt["result-status"],
504
+ status: record.status,
505
+ logUrl: record.logUrl,
506
+ logCompleteness: record.logCompleteness,
507
+ sameSecondActivity: record.sameSecondActivity,
508
+ })),
509
+ rejected: result.rejected.map((record) => ({
510
+ code: record.code,
511
+ message: record.message,
512
+ ...(record.logUrl === undefined ? {} : { logUrl: record.logUrl }),
513
+ ...(record.integratedTime === undefined ? {} : { integratedTime: record.integratedTime }),
514
+ })),
515
+ };
516
+ }
517
+
464
518
  function renderActionsHtml(result ) {
465
- const rows = result.receipts.map((record) => `
519
+ const view = actionsViewModel(result);
520
+ const actionCount = view.receipts.length;
521
+ const rejectedCount = view.rejected.length;
522
+ const rows = view.receipts.map((record) => `
466
523
  <tr>
467
524
  <td>${escapeHtml(record.integratedTime)}</td>
468
525
  <td>${escapeHtml(record.serviceIdentifier)}</td>
469
- <td>${escapeHtml(record.receipt["action-type"])}</td>
470
- <td>${escapeHtml(record.receipt["result-status"])}</td>
526
+ <td>${escapeHtml(record.actionType)}</td>
527
+ <td>${escapeHtml(record.resultStatus)}</td>
471
528
  <td>${escapeHtml(record.status)}</td>
472
529
  </tr>`).join("");
473
- const rejected = result.rejected.map((record) => `
530
+ const rejected = view.rejected.map((record) => `
474
531
  <li><strong>${escapeHtml(record.code)}</strong>: ${escapeHtml(record.message)}</li>`).join("");
475
532
 
476
533
  return `<!doctype html>
@@ -478,23 +535,48 @@ function renderActionsHtml(result ) {
478
535
  <head>
479
536
  <meta charset="utf-8">
480
537
  <meta name="viewport" content="width=device-width, initial-scale=1">
538
+ <meta http-equiv="refresh" content="2">
481
539
  <title>Sello Actions</title>
482
540
  <style>
483
541
  body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 40px; color: #17201d; }
542
+ header { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
543
+ h1 { margin: 0; font-size: 40px; line-height: 1.1; }
544
+ h2 { margin: 32px 0 8px; font-size: 20px; }
545
+ code { background: #eef3f0; border-radius: 4px; padding: 2px 6px; }
484
546
  table { border-collapse: collapse; width: 100%; margin-top: 16px; }
485
547
  th, td { border-bottom: 1px solid #d8dfdc; padding: 10px 8px; text-align: left; }
486
548
  th { color: #52615b; font-weight: 600; }
549
+ .summary { color: #52615b; font-size: 15px; margin: 0; white-space: nowrap; }
487
550
  .empty { color: #52615b; margin-top: 16px; }
551
+ .empty p { margin: 8px 0; }
552
+ @media (max-width: 720px) {
553
+ body { margin: 24px; }
554
+ header { display: block; }
555
+ h1 { font-size: 32px; }
556
+ .summary { margin-top: 8px; white-space: normal; }
557
+ table { display: block; overflow-x: auto; }
558
+ }
488
559
  </style>
489
560
  </head>
490
561
  <body>
491
- <h1>Sello Actions</h1>
492
- ${rows ? `<table><thead><tr><th>Integrated time</th><th>Service</th><th>Action</th><th>Result</th><th>Status</th></tr></thead><tbody>${rows}</tbody></table>` : `<p class="empty">No verified actions found yet.</p>`}
493
- ${rejected ? `<h2>Rejected receipts</h2><ul>${rejected}</ul>` : ""}
562
+ <header>
563
+ <h1>Sello Actions</h1>
564
+ <p class="summary">${escapeHtml(actionCountLabel(actionCount))}</p>
565
+ </header>
566
+ ${rows ? `<table><thead><tr><th>Integrated time</th><th>Service</th><th>Action</th><th>Result</th><th>Status</th></tr></thead><tbody>${rows}</tbody></table>` : `<section class="empty"><p>No verified actions yet.</p><p>In another terminal, run <code>npx sello emit-demo</code>.</p></section>`}
567
+ ${rejected ? `<h2>Rejected receipts</h2><p class="summary">${escapeHtml(rejectedCountLabel(rejectedCount))}</p><ul>${rejected}</ul>` : ""}
494
568
  </body>
495
569
  </html>`;
496
570
  }
497
571
 
572
+ function actionCountLabel(count ) {
573
+ return count === 1 ? "1 verified action" : `${count} verified actions`;
574
+ }
575
+
576
+ function rejectedCountLabel(count ) {
577
+ return count === 1 ? "1 rejected receipt" : `${count} rejected receipts`;
578
+ }
579
+
498
580
  function verifyHttpProof(entry ) {
499
581
  if (!isRecord(entry.proof)) {
500
582
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sello",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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
@@ -47,6 +47,25 @@ type DevState = {
47
47
  registryJson: string;
48
48
  };
49
49
 
50
+ type ActionsViewModel = {
51
+ receipts: {
52
+ integratedTime: string;
53
+ serviceIdentifier: string;
54
+ actionType: string;
55
+ resultStatus: string;
56
+ status: string;
57
+ logUrl: string;
58
+ logCompleteness: string;
59
+ sameSecondActivity: boolean;
60
+ }[];
61
+ rejected: {
62
+ code: string;
63
+ message: string;
64
+ logUrl?: string;
65
+ integratedTime?: string;
66
+ }[];
67
+ };
68
+
50
69
  const textEncoder = new TextEncoder();
51
70
  const textDecoder = new TextDecoder();
52
71
  const command = process.argv[2] ?? "help";
@@ -286,16 +305,17 @@ async function handleDevRequest(input: {
286
305
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
287
306
 
288
307
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
289
- const result = verifyReceipts({
290
- authorizationTokenBytes: textEncoder.encode(state.agentToken),
291
- trustedLogs: [log],
292
- registry,
293
- ownerPrivateKey: normalizeHpkePrivateKey(state.ownerKey),
294
- });
308
+ const result = verifyDevActions({ log, state, registry });
295
309
  sendHtml(response, renderActionsHtml(result));
296
310
  return;
297
311
  }
298
312
 
313
+ if (request.method === "GET" && url.pathname === "/api/actions") {
314
+ const result = verifyDevActions({ log, state, registry });
315
+ sendJson(response, 200, actionsViewModel(result));
316
+ return;
317
+ }
318
+
299
319
  if (request.method === "POST" && url.pathname === "/api/entries") {
300
320
  const body = await readJson(request);
301
321
  if (!isRecord(body) || typeof body.envelope !== "string") {
@@ -327,6 +347,19 @@ async function handleDevRequest(input: {
327
347
  sendJson(response, 404, { error: "not found" });
328
348
  }
329
349
 
350
+ function verifyDevActions(input: {
351
+ log: MockTransparencyLog;
352
+ state: DevState;
353
+ registry: ReturnType<typeof parseRegistry>;
354
+ }): ReturnType<typeof verifyReceipts> {
355
+ return verifyReceipts({
356
+ authorizationTokenBytes: textEncoder.encode(input.state.agentToken),
357
+ trustedLogs: [input.log],
358
+ registry: input.registry,
359
+ ownerPrivateKey: normalizeHpkePrivateKey(input.state.ownerKey),
360
+ });
361
+ }
362
+
330
363
  async function loadViewerRegistry(devState: DevState | undefined) {
331
364
  if (process.env.SELLO_REGISTRY_PATH) {
332
365
  return parseRegistry(readFileBytes(process.env.SELLO_REGISTRY_PATH));
@@ -462,16 +495,40 @@ function printActions(result: ReturnType<typeof verifyReceipts>): void {
462
495
  }
463
496
  }
464
497
 
498
+ function actionsViewModel(result: ReturnType<typeof verifyReceipts>): ActionsViewModel {
499
+ return {
500
+ receipts: result.receipts.map((record) => ({
501
+ integratedTime: record.integratedTime,
502
+ serviceIdentifier: record.serviceIdentifier,
503
+ actionType: record.receipt["action-type"],
504
+ resultStatus: record.receipt["result-status"],
505
+ status: record.status,
506
+ logUrl: record.logUrl,
507
+ logCompleteness: record.logCompleteness,
508
+ sameSecondActivity: record.sameSecondActivity,
509
+ })),
510
+ rejected: result.rejected.map((record) => ({
511
+ code: record.code,
512
+ message: record.message,
513
+ ...(record.logUrl === undefined ? {} : { logUrl: record.logUrl }),
514
+ ...(record.integratedTime === undefined ? {} : { integratedTime: record.integratedTime }),
515
+ })),
516
+ };
517
+ }
518
+
465
519
  function renderActionsHtml(result: ReturnType<typeof verifyReceipts>): string {
466
- const rows = result.receipts.map((record) => `
520
+ const view = actionsViewModel(result);
521
+ const actionCount = view.receipts.length;
522
+ const rejectedCount = view.rejected.length;
523
+ const rows = view.receipts.map((record) => `
467
524
  <tr>
468
525
  <td>${escapeHtml(record.integratedTime)}</td>
469
526
  <td>${escapeHtml(record.serviceIdentifier)}</td>
470
- <td>${escapeHtml(record.receipt["action-type"])}</td>
471
- <td>${escapeHtml(record.receipt["result-status"])}</td>
527
+ <td>${escapeHtml(record.actionType)}</td>
528
+ <td>${escapeHtml(record.resultStatus)}</td>
472
529
  <td>${escapeHtml(record.status)}</td>
473
530
  </tr>`).join("");
474
- const rejected = result.rejected.map((record) => `
531
+ const rejected = view.rejected.map((record) => `
475
532
  <li><strong>${escapeHtml(record.code)}</strong>: ${escapeHtml(record.message)}</li>`).join("");
476
533
 
477
534
  return `<!doctype html>
@@ -479,23 +536,48 @@ function renderActionsHtml(result: ReturnType<typeof verifyReceipts>): string {
479
536
  <head>
480
537
  <meta charset="utf-8">
481
538
  <meta name="viewport" content="width=device-width, initial-scale=1">
539
+ <meta http-equiv="refresh" content="2">
482
540
  <title>Sello Actions</title>
483
541
  <style>
484
542
  body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 40px; color: #17201d; }
543
+ header { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
544
+ h1 { margin: 0; font-size: 40px; line-height: 1.1; }
545
+ h2 { margin: 32px 0 8px; font-size: 20px; }
546
+ code { background: #eef3f0; border-radius: 4px; padding: 2px 6px; }
485
547
  table { border-collapse: collapse; width: 100%; margin-top: 16px; }
486
548
  th, td { border-bottom: 1px solid #d8dfdc; padding: 10px 8px; text-align: left; }
487
549
  th { color: #52615b; font-weight: 600; }
550
+ .summary { color: #52615b; font-size: 15px; margin: 0; white-space: nowrap; }
488
551
  .empty { color: #52615b; margin-top: 16px; }
552
+ .empty p { margin: 8px 0; }
553
+ @media (max-width: 720px) {
554
+ body { margin: 24px; }
555
+ header { display: block; }
556
+ h1 { font-size: 32px; }
557
+ .summary { margin-top: 8px; white-space: normal; }
558
+ table { display: block; overflow-x: auto; }
559
+ }
489
560
  </style>
490
561
  </head>
491
562
  <body>
492
- <h1>Sello Actions</h1>
493
- ${rows ? `<table><thead><tr><th>Integrated time</th><th>Service</th><th>Action</th><th>Result</th><th>Status</th></tr></thead><tbody>${rows}</tbody></table>` : `<p class="empty">No verified actions found yet.</p>`}
494
- ${rejected ? `<h2>Rejected receipts</h2><ul>${rejected}</ul>` : ""}
563
+ <header>
564
+ <h1>Sello Actions</h1>
565
+ <p class="summary">${escapeHtml(actionCountLabel(actionCount))}</p>
566
+ </header>
567
+ ${rows ? `<table><thead><tr><th>Integrated time</th><th>Service</th><th>Action</th><th>Result</th><th>Status</th></tr></thead><tbody>${rows}</tbody></table>` : `<section class="empty"><p>No verified actions yet.</p><p>In another terminal, run <code>npx sello emit-demo</code>.</p></section>`}
568
+ ${rejected ? `<h2>Rejected receipts</h2><p class="summary">${escapeHtml(rejectedCountLabel(rejectedCount))}</p><ul>${rejected}</ul>` : ""}
495
569
  </body>
496
570
  </html>`;
497
571
  }
498
572
 
573
+ function actionCountLabel(count: number): string {
574
+ return count === 1 ? "1 verified action" : `${count} verified actions`;
575
+ }
576
+
577
+ function rejectedCountLabel(count: number): string {
578
+ return count === 1 ? "1 rejected receipt" : `${count} rejected receipts`;
579
+ }
580
+
499
581
  function verifyHttpProof(entry: ReturnType<typeof deserializeEntry>): boolean {
500
582
  if (!isRecord(entry.proof)) {
501
583
  return false;