attesso 0.9.0 → 0.10.0

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
@@ -22,10 +22,10 @@ are appended, and writes mode and path changes to the same files the hook reads:
22
22
 
23
23
  ```sh
24
24
  npx attesso ui
25
- npx attesso ui --port 0 --no-open
25
+ npx attesso ui --no-open
26
26
  ```
27
27
 
28
- The server binds `127.0.0.1`, mints a per-process token, and serves the page from
28
+ The server binds `127.0.0.1:8197`, mints a per-process token, and serves the page from
29
29
  the installed package. It has no account, no hosted data, and no dependency on a
30
30
  snapshot file. `Ctrl-C` stops it.
31
31
 
package/bin/attesso.mjs CHANGED
@@ -78,6 +78,7 @@ const USAGE = `attesso: a local, signed ledger of what your agents attempted
78
78
  attesso mode [observe|enforce|off] read or set the active hook mode
79
79
  attesso paths [--json|--set FILE] read or replace the filesystem scope
80
80
  attesso ui [--port N] [--no-open] local control surface for the signed ledger
81
+ (default 127.0.0.1:8197)
81
82
  attesso policy pull [--url URL] [--apply] [--json]
82
83
  verify the hosted rule bundle and optionally
83
84
  write rules.json (key from ATTESSO_API_KEY)
@@ -697,11 +698,12 @@ switch (command) {
697
698
  case 'ui': {
698
699
  try {
699
700
  const options = flags(rest);
700
- const requested = options.port === undefined ? 0 : Number(options.port);
701
+ const requested = options.port === undefined ? 8197 : Number(options.port);
701
702
  if (!Number.isInteger(requested) || requested < 0 || requested > 65535) {
702
703
  console.error('ui: --port must be an integer from 0 to 65535');
703
704
  process.exit(2);
704
705
  }
706
+ requireLedger();
705
707
  const ui = await startUi({
706
708
  dir: DIR,
707
709
  cwd: process.cwd(),
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "attesso",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Bounds: decide an AI agent's next tool call before it runs, with a signed record. Free and unlimited locally, $0.00085 per synced decision.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "bin": {
8
- "attesso": "./bin/attesso.mjs",
9
- "attesso-mcp": "./bin/attesso-mcp.mjs",
10
- "attesso-control-mcp": "./bin/attesso-control-mcp.mjs"
8
+ "attesso": "bin/attesso.mjs",
9
+ "attesso-mcp": "bin/attesso-mcp.mjs",
10
+ "attesso-control-mcp": "bin/attesso-control-mcp.mjs"
11
11
  },
12
12
  "files": [
13
13
  "bin",
package/src/connect.mjs CHANGED
@@ -78,12 +78,15 @@ function wiredHarnesses(harnesses, { dir, cwd }) {
78
78
  });
79
79
  }
80
80
 
81
- function instructions({ mode, wired, manual }) {
81
+ function instructions({ initialized, mode, wired, manual }) {
82
+ if (!initialized) return ['run `attesso init` first'];
82
83
  const next = [];
83
84
  if (!mode) next.push('run `attesso connect --write --mode observe` from the agent');
84
85
  else if (mode === 'off') next.push('run `attesso mode observe` to start recording');
85
86
  if (manual.length) next.push('wire the listed harnesses by hand, or use the generic hook contract');
86
- if (mode && !wired.length) next.push('run `attesso connect --write --mode observe` from the agent');
87
+ if (mode && !wired.length && !manual.length) {
88
+ next.push('no agent harness detected; wire the generic hook contract or run `attesso connect` from an agent project');
89
+ }
87
90
  return next;
88
91
  }
89
92
 
@@ -101,7 +104,7 @@ export function status({ dir, cwd = process.cwd(), home = homedir() } = {}) {
101
104
  wired: wired.map((harness) => harness.key),
102
105
  manual: manual.map((harness) => harness.key),
103
106
  connected: initialized && mode !== null && mode !== 'off' && wired.length > 0,
104
- next: instructions({ mode, wired, manual }),
107
+ next: instructions({ initialized, mode, wired, manual }),
105
108
  };
106
109
  }
107
110
 
@@ -163,6 +166,6 @@ export function connect({
163
166
  wired: wired.map((result) => result.key),
164
167
  manual: results.filter((result) => result.manual || result.error || result.reason?.includes('merge by hand')).map((result) => result.key),
165
168
  agent_context: agentContext,
166
- next: instructions({ mode: effectiveMode, wired, manual: results.filter((result) => result.manual || result.error) }),
169
+ next: instructions({ initialized: isInitialised(ledgerDir), mode: effectiveMode, wired, manual: results.filter((result) => result.manual || result.error) }),
167
170
  };
168
171
  }
@@ -69,6 +69,7 @@
69
69
  .badge{border:1px solid #ead6b3;background:#fbf5e8;color:#7a4b12;padding:2px 6px;font-size:10px}
70
70
  .actions{display:flex;gap:7px;align-items:center}
71
71
  .button{border:1px solid #d0d5dd;background:#fff;color:var(--body);padding:7px 11px;font-size:12px;cursor:pointer}
72
+ .button:disabled{opacity:.4;cursor:default}
72
73
  .button.primary{border-color:var(--ink);background:var(--ink);color:#fff}
73
74
  .button:hover{background:#f2f4f7}
74
75
  .page{max-width:1440px;padding:24px 30px 48px}
@@ -303,7 +304,7 @@
303
304
  <div class="topbar">
304
305
  <div class="crumbs"><span>Bounds</span><span>/</span><span class="current">local</span><span class="badge">ledger</span></div>
305
306
  <div class="actions">
306
- <button class="button" type="button" id="export-draft">Export draft</button>
307
+ <button class="button" type="button" id="export-draft" title="Set a rule on a block first" disabled>Export draft</button>
307
308
  <button class="button" type="button" id="all-calls">All calls</button>
308
309
  </div>
309
310
  </div>
@@ -687,9 +688,12 @@
687
688
  el('agent-action').textContent = 'Manage agent';
688
689
  } else {
689
690
  const wired = Array.isArray(agentData.wired) ? agentData.wired.length : 0;
691
+ const recorded = days.reduce((total, day) => total + summarize(day).calls, 0);
690
692
  el('agent-sub').textContent = wired
691
693
  ? 'Hook is ' + (agentData.mode || 'not configured') + ' - connect any agent to start observing'
692
- : 'No harness wired - connect an agent to start observing';
694
+ : recorded > 0
695
+ ? fmt(recorded) + ' recorded calls; no harness wired'
696
+ : 'No harness wired - connect an agent to start observing';
693
697
  el('agent-action').textContent = 'Connect agent';
694
698
  }
695
699
  renderAgentPanel();
@@ -1044,6 +1048,7 @@
1044
1048
  closePopover();
1045
1049
  applyFlowFilter();
1046
1050
  renderTokens(day);
1051
+ updateExportDraft();
1047
1052
  }
1048
1053
 
1049
1054
  function renderV1Flow(day, preserveCenter = null) {
@@ -1132,6 +1137,7 @@
1132
1137
  closePopover();
1133
1138
  applyFlowFilter();
1134
1139
  renderTokens(day);
1140
+ updateExportDraft();
1135
1141
  }
1136
1142
 
1137
1143
  function applyFlowFilter() {
@@ -1328,6 +1334,7 @@
1328
1334
  if (allOn) drafts.delete(key); else drafts.set(key, state);
1329
1335
  }
1330
1336
  updateMarkerDraft(marker);
1337
+ updateExportDraft();
1331
1338
  const popover = el('flow-popover');
1332
1339
  if (popover && !popover.hidden) {
1333
1340
  const draft = popover.querySelector('#fp-block-draft');
@@ -1347,6 +1354,7 @@
1347
1354
  if (!row) continue;
1348
1355
  if (next === 'unset') drafts.delete(String(row[0])); else drafts.set(String(row[0]), next);
1349
1356
  }
1357
+ updateExportDraft();
1350
1358
  const marker = selectedMarker || markerBySeq.get(key);
1351
1359
  if (marker) {
1352
1360
  marker.classList.remove('draft-allow', 'draft-deny', 'draft-ask');
@@ -1382,6 +1390,10 @@
1382
1390
  async function renderDay(day) {
1383
1391
  if (!calls[day] && !v1) return;
1384
1392
  const request = ++dayRequest;
1393
+ if (selectedDay !== day && drafts.size > 0) {
1394
+ drafts.clear();
1395
+ updateExportDraft();
1396
+ }
1385
1397
  selectedDay = day;
1386
1398
  callsDay = day;
1387
1399
  callsPage = 0;
@@ -1414,7 +1426,15 @@
1414
1426
  renderFlow(selectedDay, centerMinute);
1415
1427
  }
1416
1428
 
1429
+ function updateExportDraft() {
1430
+ const button = el('export-draft');
1431
+ if (!button) return;
1432
+ button.disabled = drafts.size === 0;
1433
+ button.title = drafts.size === 0 ? 'Set a rule on a block first' : '';
1434
+ }
1435
+
1417
1436
  function exportDraft() {
1437
+ if (drafts.size === 0) return;
1418
1438
  const output = {
1419
1439
  generated_at: new Date().toISOString(),
1420
1440
  day: selectedDay,