@pleri/olam-cli 0.1.206 → 0.1.208

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.
@@ -1,4 +1,4 @@
1
1
  {
2
- "bundledAt": "2026-06-02T12:45:34.001Z",
2
+ "bundledAt": "2026-06-03T04:32:31.735Z",
3
3
  "kgFirstSha": "29a9ccce1b115d049e375c4a90eb5cf7c123e610e2d0590270a4db2cdbc64a28"
4
4
  }
@@ -118,7 +118,7 @@ spec:
118
118
  # k3d), started by `olam upgrade` Step 0.7 — not inside this Pod.
119
119
  containers:
120
120
  - name: olam-host-cp
121
- image: ghcr.io/pleri/olam-host-cp@sha256:f11d34247171fba77439e3877ba8b781b857d1eb3f1c983bac2621dd7a254fef
121
+ image: ghcr.io/pleri/olam-host-cp@sha256:ddeb5b82c7a1d72d78070b299ea2266683e71ff832c5b399fe25ba28f2285d6f
122
122
  imagePullPolicy: IfNotPresent
123
123
  securityContext:
124
124
  runAsNonRoot: true
@@ -70,7 +70,7 @@ spec:
70
70
  mountPath: /data
71
71
  containers:
72
72
  - name: olam-auth-service
73
- image: ghcr.io/pleri/olam-auth@sha256:10879610c8930d06e2201171ca98249da756461fbbca7540face7593de3f2516
73
+ image: ghcr.io/pleri/olam-auth@sha256:238ef8b58198684f84ea7c2bf91b23a3ac216660a40f8ef6b4db11c83e555b3a
74
74
  imagePullPolicy: IfNotPresent
75
75
  securityContext:
76
76
  runAsNonRoot: true
@@ -61,7 +61,7 @@ spec:
61
61
  mountPath: /data
62
62
  containers:
63
63
  - name: olam-kg-service
64
- image: ghcr.io/pleri/olam-kg-service@sha256:4fc5f7257d2fa8e11c07e5436a68edd0353c0d503739b9e90f7736e5b336a8fa
64
+ image: ghcr.io/pleri/olam-kg-service@sha256:8fa1ef59a27f9295b7f7375ccda8e1efa110c9a309603301c073076e919e35c3
65
65
  imagePullPolicy: IfNotPresent
66
66
  securityContext:
67
67
  runAsNonRoot: true
@@ -68,7 +68,7 @@ spec:
68
68
  mountPath: /data
69
69
  containers:
70
70
  - name: olam-mcp-auth-service
71
- image: ghcr.io/pleri/olam-mcp-auth@sha256:0780d597d7eb7d124eb998cf8e56649e375a594e4181ddb181f8b1cb2d443701
71
+ image: ghcr.io/pleri/olam-mcp-auth@sha256:56964905f2da39c19f462b5e5f5f84e74a930c9a70a4518463606ce91276272b
72
72
  imagePullPolicy: IfNotPresent
73
73
  securityContext:
74
74
  runAsNonRoot: true
@@ -70,7 +70,7 @@ spec:
70
70
  # bootstrap-placeholder comment + run `npm run refresh:manifest-digests`
71
71
  # once ghcr.io/pleri/olam-memory-service has a real published digest.
72
72
  # bootstrap-placeholder: pre-publish; refresh after first release
73
- image: ghcr.io/pleri/olam-memory-service@sha256:4b2742280eb0437e3865e603d1d148e28408a1ec8aa4a7dd2c726cd3d31ee8dd
73
+ image: ghcr.io/pleri/olam-memory-service@sha256:581f763fcdc2e7e056c805883efa5e510ce460c6c2096445a909f35fcb38f04b
74
74
  imagePullPolicy: IfNotPresent
75
75
  securityContext:
76
76
  runAsNonRoot: true
@@ -60,6 +60,7 @@ import { homedir } from 'node:os';
60
60
  import { join } from 'node:path';
61
61
  import { randomBytes } from 'node:crypto';
62
62
  import { fileURLToPath } from 'node:url';
63
+ import { isAgentMemoryEnabled } from './agent-memory-gate.mjs';
63
64
 
64
65
  /** Max bytes of captured text persisted per candidate. Keeps queue files small. */
65
66
  export const MAX_CAPTURED_CHARS = 4000;
@@ -315,6 +316,9 @@ function readContentField(obj) {
315
316
  }
316
317
 
317
318
  async function main() {
319
+ // Gate: memory is off by default — exit silently when not enabled.
320
+ if (!isAgentMemoryEnabled()) return;
321
+
318
322
  let event = {};
319
323
  try {
320
324
  event = JSON.parse(readFileSync(0, 'utf-8'));
@@ -33,6 +33,7 @@ import { existsSync, readFileSync, realpathSync } from 'node:fs';
33
33
  import { homedir } from 'node:os';
34
34
  import { join } from 'node:path';
35
35
  import { fileURLToPath } from 'node:url';
36
+ import { isAgentMemoryEnabled } from './agent-memory-gate.mjs';
36
37
 
37
38
  // Built-in tool-event filter: only fire on tool calls where recall has
38
39
  // signal. Bash + Edit + MultiEdit + Read on substantive paths qualify;
@@ -190,6 +191,9 @@ export function formatAdditionalContext(response, prompt) {
190
191
  }
191
192
 
192
193
  async function main() {
194
+ // Gate: memory is off by default — exit silently when not enabled.
195
+ if (!isAgentMemoryEnabled()) return;
196
+
193
197
  let event = {};
194
198
  try {
195
199
  event = JSON.parse(readFileSync(0, 'utf-8'));
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * agentmemory-save.mjs — PostToolUse hook: persist agent-written memory notes.
4
+ *
5
+ * AM_SENTINEL=olam-agent-memory-hook-v2
6
+ *
7
+ * This is the FILE-BASED port of the legacy inline python SAVE blob that used
8
+ * to live directly in ~/.claude/settings.json under
9
+ * `AM_SENTINEL=olam-agent-memory-hook-v1; python3 -c '…'`. The installer
10
+ * (`olam hooks install-all`, invoked by `olam skills sync`) copies this file to
11
+ * ~/.claude/scripts/hooks/ and registers a settings.json entry pointing at it,
12
+ * REPLACING the inline blob. Keeping the logic in a real source file means it
13
+ * is version-controlled, testable, bundled into @pleri/olam-cli, and owned by
14
+ * sync — not stranded as a string in the operator's settings.
15
+ *
16
+ * Behaviour (byte-faithful port of the v1 blob):
17
+ * - Reads the PostToolUse JSON payload from stdin.
18
+ * - Acts ONLY when tool_name === "Write" AND the written file_path matches
19
+ * /\/\.claude\/projects\/.*\/memory\/.*\.md$/ ; otherwise bails (exit 0).
20
+ * - Requires OLAM_AGENT_MEMORY_BEARER (trimmed, non-empty); else bails.
21
+ * - Parses YAML-ish frontmatter delimited by leading "---":
22
+ * description: → title (default "memory") ← keyed on description, per v1
23
+ * type: → type (default "fact")
24
+ * remainder → body
25
+ * - POSTs {title, content: body, type} to
26
+ * <OLAM_AGENT_MEMORY_URL|default>/agentmemory/remember
27
+ * with Authorization: Bearer <bearer>, ~3s timeout.
28
+ * - On success prints to STDERR the green status line:
29
+ * [🧠⇡ Memory saved] "<title first 60 chars>"
30
+ * - Fail-open: ANY error → silent, exit 0. Never throws to the caller.
31
+ *
32
+ * Node built-ins only (global fetch + AbortController, available on Node ≥18).
33
+ */
34
+
35
+ import { readFileSync } from 'node:fs';
36
+
37
+ const DEFAULT_URL = 'https://atlas-agent-memory.atlas-kitchen.workers.dev';
38
+ const MEMORY_PATH_RE = /\/\.claude\/projects\/.*\/memory\/.*\.md$/;
39
+ const TIMEOUT_MS = 3000;
40
+
41
+ /** Read all of stdin as a UTF-8 string. */
42
+ function readStdin() {
43
+ try {
44
+ return readFileSync(0, 'utf8');
45
+ } catch {
46
+ return '';
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Parse leading "---"-delimited frontmatter. Returns {title, type, body}.
52
+ * Mirrors the v1 blob: title comes from `description:`, type from `type:`.
53
+ */
54
+ function parseNote(raw) {
55
+ let title = 'memory';
56
+ let type = 'fact';
57
+ let body = raw;
58
+ if (raw.startsWith('---')) {
59
+ // Split into [before-first-delim, frontmatter, …rest]. JS's limited split
60
+ // truncates the remainder, so slice manually to keep the full body.
61
+ const firstDelimEnd = 3; // length of leading "---"
62
+ const secondDelim = raw.indexOf('---', firstDelimEnd);
63
+ if (secondDelim !== -1) {
64
+ const fm = raw.slice(firstDelimEnd, secondDelim);
65
+ body = raw.slice(secondDelim + 3).trim();
66
+ for (const ln of fm.split('\n')) {
67
+ const s = ln.trim();
68
+ if (s.startsWith('description:')) {
69
+ title = stripQuotes(s.slice(s.indexOf(':') + 1).trim());
70
+ } else if (s.startsWith('type:')) {
71
+ type = stripQuotes(s.slice(s.indexOf(':') + 1).trim());
72
+ }
73
+ }
74
+ }
75
+ }
76
+ return { title, type, body };
77
+ }
78
+
79
+ function stripQuotes(v) {
80
+ return v.replace(/^["']/, '').replace(/["']$/, '');
81
+ }
82
+
83
+ async function main() {
84
+ const payloadRaw = readStdin();
85
+ let payload;
86
+ try {
87
+ payload = JSON.parse(payloadRaw);
88
+ } catch {
89
+ return; // not parseable → fail-open
90
+ }
91
+
92
+ const toolName = payload?.tool_name ?? '';
93
+ const filePath = payload?.tool_input?.file_path ?? '';
94
+ if (toolName !== 'Write' || !MEMORY_PATH_RE.test(filePath)) return;
95
+
96
+ const bearer = (process.env['OLAM_AGENT_MEMORY_BEARER'] ?? '').trim();
97
+ if (!bearer) return;
98
+
99
+ let raw;
100
+ try {
101
+ raw = readFileSync(filePath, 'utf8');
102
+ } catch {
103
+ return;
104
+ }
105
+
106
+ const { title, type, body } = parseNote(raw);
107
+ const base = (process.env['OLAM_AGENT_MEMORY_URL'] ?? DEFAULT_URL).replace(/\/$/, '');
108
+ const url = `${base}/agentmemory/remember`;
109
+
110
+ const controller = new AbortController();
111
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
112
+ try {
113
+ const resp = await fetch(url, {
114
+ method: 'POST',
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ Authorization: `Bearer ${bearer}`,
118
+ 'User-Agent': 'olam-agent-memory-hook/2',
119
+ },
120
+ body: JSON.stringify({ title, content: body, type }),
121
+ signal: controller.signal,
122
+ });
123
+ // Treat any non-throwing response as delivered (the v1 blob did the same —
124
+ // it printed on a non-exception urlopen regardless of status code).
125
+ if (resp) {
126
+ const label = title.slice(0, 60);
127
+ process.stderr.write(`\x1b[32m[\u{1F9E0}\u{21E1} Memory saved]\x1b[0m "${label}"\n`);
128
+ }
129
+ } catch {
130
+ // Fail-open: network error / timeout / abort → silent.
131
+ } finally {
132
+ clearTimeout(timer);
133
+ }
134
+ }
135
+
136
+ main().catch(() => {
137
+ /* fail-open: never propagate to the caller */
138
+ });
@@ -40,6 +40,30 @@ const fs = require('fs');
40
40
  const os = require('os');
41
41
  const path = require('path');
42
42
 
43
+ // ---------------------------------------------------------------------------
44
+ // Opt-in gate — inline re-implementation of agent-memory-gate.mjs contract
45
+ // (CJS cannot import ESM at the top level; inline ensures same fail-closed
46
+ // semantics without a dynamic import or a build step).
47
+ // ---------------------------------------------------------------------------
48
+ const _GATE_TRUTHY_ENV = new Set(['1', 'true', 'on']);
49
+ const _GATE_TRUTHY_FILE = new Set(['true', '1']);
50
+ function _isAgentMemoryEnabled() {
51
+ const envVal = process.env['OLAM_AGENT_MEMORY'];
52
+ if (envVal !== undefined && envVal.length > 0) {
53
+ return _GATE_TRUTHY_ENV.has(envVal.trim().toLowerCase());
54
+ }
55
+ try {
56
+ const fromEnv = process.env['OLAM_HOME'];
57
+ const olamHome = (fromEnv && fromEnv.length > 0) ? fromEnv : path.join(os.homedir(), '.olam');
58
+ const flagPath = path.join(olamHome, 'agent-memory-enabled');
59
+ const raw = fs.readFileSync(flagPath, 'utf8').trim().toLowerCase();
60
+ return _GATE_TRUTHY_FILE.has(raw);
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+
43
67
  // Resolve the bridge URL: operator env wins, then the `olam memory connect`
44
68
  // artifact (~/.olam/memory-connection.json), then the LIVE memory-service
45
69
  // Worker. The prior default (olam-agent-memory.ernestcodes.workers.dev) is a
@@ -276,6 +300,9 @@ async function recall(query, limit = 5) {
276
300
  }
277
301
 
278
302
  async function main() {
303
+ // Gate: memory is off by default — exit silently when not enabled.
304
+ if (!_isAgentMemoryEnabled()) return;
305
+
279
306
  const raw = require('fs').readFileSync(0, 'utf8');
280
307
  let event = {};
281
308
  try { event = JSON.parse(raw); } catch (_) {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pleri/olam-cli",
3
- "version": "0.1.206",
3
+ "version": "0.1.208",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "olam": "./bin/olam.cjs"