lyra-core 0.1.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.
Files changed (131) hide show
  1. package/README.md +24 -0
  2. package/package.json +65 -0
  3. package/src/brain/compaction.test.ts +156 -0
  4. package/src/brain/compaction.ts +131 -0
  5. package/src/brain/demo-endpoint.ts +13 -0
  6. package/src/brain/frozen-prefix.test.ts +235 -0
  7. package/src/brain/frozen-prefix.ts +320 -0
  8. package/src/brain/history-persist.test.ts +129 -0
  9. package/src/brain/history-persist.ts +154 -0
  10. package/src/brain/index.ts +44 -0
  11. package/src/brain/openai-brain.test.ts +61 -0
  12. package/src/brain/openai-brain.ts +544 -0
  13. package/src/brain/sanitize.test.ts +27 -0
  14. package/src/brain/sanitize.ts +23 -0
  15. package/src/brain/stub.ts +20 -0
  16. package/src/brain/types.ts +129 -0
  17. package/src/chain.ts +35 -0
  18. package/src/claude-plugins/discovery.test.ts +71 -0
  19. package/src/claude-plugins/discovery.ts +152 -0
  20. package/src/claude-plugins/index.ts +6 -0
  21. package/src/claude-plugins/types.ts +38 -0
  22. package/src/commands/index.ts +16 -0
  23. package/src/commands/registry.test.ts +186 -0
  24. package/src/commands/registry.ts +255 -0
  25. package/src/config.ts +201 -0
  26. package/src/economy/index.ts +6 -0
  27. package/src/events/index.ts +4 -0
  28. package/src/events/listeners.ts +37 -0
  29. package/src/events/queue.test.ts +43 -0
  30. package/src/events/queue.ts +63 -0
  31. package/src/events/router.ts +42 -0
  32. package/src/events/types.ts +28 -0
  33. package/src/format.ts +13 -0
  34. package/src/index.test.ts +6 -0
  35. package/src/index.ts +314 -0
  36. package/src/locks.test.ts +259 -0
  37. package/src/locks.ts +233 -0
  38. package/src/mcp/discovery.test.ts +97 -0
  39. package/src/mcp/discovery.ts +150 -0
  40. package/src/mcp/index.ts +10 -0
  41. package/src/mcp/manager.test.ts +97 -0
  42. package/src/mcp/manager.ts +110 -0
  43. package/src/mcp/stdio-client.ts +154 -0
  44. package/src/mcp/types.ts +44 -0
  45. package/src/memory/edit.test.ts +41 -0
  46. package/src/memory/edit.ts +53 -0
  47. package/src/memory/encryption.test.ts +68 -0
  48. package/src/memory/encryption.ts +94 -0
  49. package/src/memory/fs-util.ts +15 -0
  50. package/src/memory/index-file.ts +74 -0
  51. package/src/memory/index-sync.test.ts +81 -0
  52. package/src/memory/index-sync.ts +99 -0
  53. package/src/memory/index.ts +58 -0
  54. package/src/memory/list-tool.ts +105 -0
  55. package/src/memory/pack-blob.test.ts +90 -0
  56. package/src/memory/pack-blob.ts +120 -0
  57. package/src/memory/pack-gather.test.ts +110 -0
  58. package/src/memory/pack-gather.ts +112 -0
  59. package/src/memory/parser.test.ts +29 -0
  60. package/src/memory/parser.ts +20 -0
  61. package/src/memory/path-drift.test.ts +71 -0
  62. package/src/memory/read-tool-fallback.test.ts +54 -0
  63. package/src/memory/read-tool.ts +198 -0
  64. package/src/memory/save-tool.test.ts +193 -0
  65. package/src/memory/save-tool.ts +189 -0
  66. package/src/memory/scan.test.ts +24 -0
  67. package/src/memory/scan.ts +63 -0
  68. package/src/memory/topic.ts +32 -0
  69. package/src/memory/types.ts +49 -0
  70. package/src/migration/index.ts +6 -0
  71. package/src/migration/option3-crypto.test.ts +106 -0
  72. package/src/migration/option3-crypto.ts +143 -0
  73. package/src/pairing.test.ts +212 -0
  74. package/src/pairing.ts +285 -0
  75. package/src/paths.ts +70 -0
  76. package/src/permission/dangerous.ts +105 -0
  77. package/src/permission/env-redact.ts +54 -0
  78. package/src/permission/index.ts +16 -0
  79. package/src/permission/path-guard.ts +114 -0
  80. package/src/permission/permission.test.ts +299 -0
  81. package/src/permission/service.ts +191 -0
  82. package/src/plugins/context.ts +226 -0
  83. package/src/plugins/hooks.ts +81 -0
  84. package/src/plugins/index.ts +24 -0
  85. package/src/plugins/plugins.test.ts +196 -0
  86. package/src/plugins/tool-search.ts +49 -0
  87. package/src/public/card.test.ts +70 -0
  88. package/src/public/card.ts +67 -0
  89. package/src/runtime/activity.ts +29 -0
  90. package/src/runtime/index.ts +7 -0
  91. package/src/runtime/runtime.test.ts +55 -0
  92. package/src/runtime/runtime.ts +126 -0
  93. package/src/sandbox/credentials.ts +25 -0
  94. package/src/sandbox/docker.ts +389 -0
  95. package/src/sandbox/factory.ts +99 -0
  96. package/src/sandbox/index.ts +15 -0
  97. package/src/sandbox/linux.ts +141 -0
  98. package/src/sandbox/local.ts +19 -0
  99. package/src/sandbox/macos.ts +71 -0
  100. package/src/sandbox/sandbox.test.ts +386 -0
  101. package/src/sandbox/seatbelt-profile.ts +139 -0
  102. package/src/sandbox/types.ts +129 -0
  103. package/src/skills/index.ts +8 -0
  104. package/src/skills/scanner.test.ts +137 -0
  105. package/src/skills/scanner.ts +257 -0
  106. package/src/skills/triggers.test.ts +77 -0
  107. package/src/skills/triggers.ts +78 -0
  108. package/src/skills/types.ts +37 -0
  109. package/src/storage/encryption.test.ts +30 -0
  110. package/src/storage/encryption.ts +87 -0
  111. package/src/storage/factory.ts +31 -0
  112. package/src/storage/index.ts +11 -0
  113. package/src/storage/local-stub.ts +70 -0
  114. package/src/storage/sqlite.test.ts +29 -0
  115. package/src/storage/sqlite.ts +95 -0
  116. package/src/storage/types.ts +21 -0
  117. package/src/tools/escalation.test.ts +348 -0
  118. package/src/tools/escalation.ts +200 -0
  119. package/src/tools/index.ts +11 -0
  120. package/src/tools/registry.test.ts +70 -0
  121. package/src/tools/registry.ts +152 -0
  122. package/src/tools/types.ts +65 -0
  123. package/src/tools/zod-helpers.test.ts +63 -0
  124. package/src/tools/zod-helpers.ts +36 -0
  125. package/src/tools/zod-schema.ts +99 -0
  126. package/src/wallet/drain.test.ts +41 -0
  127. package/src/wallet/drain.ts +76 -0
  128. package/src/wallet/eoa.ts +61 -0
  129. package/src/wallet/index.ts +14 -0
  130. package/src/wallet/keystore.test.ts +17 -0
  131. package/src/wallet/keystore.ts +50 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Phase 9.1 skills surface. Mirrors Claude Code's SKILL.md frontmatter so
3
+ * imports.claudeCode picks up the entire ~/.claude ecosystem free.
4
+ */
5
+ export type SkillSource = 'lyra' | 'lyra-plugin' | 'claude-code' | 'claude-plugin'
6
+
7
+ export interface SkillFrontmatter {
8
+ /** Unique name used by the brain to reference this skill. Required. */
9
+ name: string
10
+ /** One-line summary the brain sees in the skill index. Required. */
11
+ description: string
12
+ version?: string
13
+ license?: string
14
+ /** Comma-separated globs (e.g. `*.test.ts,*.spec.ts`) that auto-trigger the skill on fs.* paths. */
15
+ filePattern?: string
16
+ /** Regex (string) that auto-triggers the skill on shell.run commands. */
17
+ bashPattern?: string
18
+ /**
19
+ * Claude Code commands set this to distinguish slash-only invocations from
20
+ * model-invokable skills. Skills omit it; commands set it (any value).
21
+ */
22
+ argumentHint?: string
23
+ }
24
+
25
+ export interface SkillRef {
26
+ /** `<source-prefix>:<dir-name>` (e.g. `lyra:dogfood`, `claude-code:tmux`). */
27
+ id: string
28
+ /** Display name from frontmatter (falls back to directory name). */
29
+ name: string
30
+ description: string
31
+ /** Absolute path to SKILL.md. */
32
+ path: string
33
+ source: SkillSource
34
+ /** When set, marketplace > plugin > version triple from `~/.claude/plugins/cache/...` paths. */
35
+ pluginCoord?: { marketplace: string; plugin: string; version: string }
36
+ frontmatter: SkillFrontmatter
37
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { decrypt, encrypt, packEnvelope, unpackEnvelope } from './encryption'
3
+
4
+ describe('encryption', () => {
5
+ test('round-trips a short message', () => {
6
+ const plaintext = new TextEncoder().encode('hello lyra')
7
+ const env = encrypt(plaintext, 'testpass1234')
8
+ const out = decrypt(env, 'testpass1234')
9
+ expect(new TextDecoder().decode(out)).toBe('hello lyra')
10
+ })
11
+
12
+ test('wrong passphrase fails', () => {
13
+ const env = encrypt(new TextEncoder().encode('secret'), 'right-password')
14
+ expect(() => decrypt(env, 'wrong-password')).toThrow()
15
+ })
16
+
17
+ test('packs + unpacks round-trip', () => {
18
+ const env = encrypt(new TextEncoder().encode('pack me'), 'pw')
19
+ const packed = packEnvelope(env)
20
+ const unpacked = unpackEnvelope(packed)
21
+ expect(Array.from(unpacked.salt)).toEqual(Array.from(env.salt))
22
+ expect(Array.from(unpacked.iv)).toEqual(Array.from(env.iv))
23
+ expect(Array.from(unpacked.tag)).toEqual(Array.from(env.tag))
24
+ expect(Array.from(unpacked.ciphertext)).toEqual(Array.from(env.ciphertext))
25
+ })
26
+
27
+ test('unpack throws on truncated input', () => {
28
+ expect(() => unpackEnvelope(new Uint8Array(20))).toThrow('envelope shorter than header')
29
+ })
30
+ })
@@ -0,0 +1,87 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto'
2
+
3
+ const SCRYPT_N = 1 << 15
4
+ const SCRYPT_R = 8
5
+ const SCRYPT_P = 1
6
+ const SCRYPT_MAXMEM = 64 * 1024 * 1024
7
+ const KEY_LEN = 32
8
+ const IV_LEN = 12
9
+ const TAG_LEN = 16
10
+ const SALT_LEN = 16
11
+
12
+ /**
13
+ * AES-256-GCM symmetric encryption, scrypt-derived key from a passphrase.
14
+ * MVP pattern: each agent has one symmetric key derived from the operator
15
+ * passphrase. Same scrypt parameters as the wallet keystore for consistency.
16
+ *
17
+ * Post-MVP: replace with TEE-sealed key for /agent/ partition + ECIES to
18
+ * operator pubkey for /user/ partition (section 22 wallet architecture).
19
+ */
20
+
21
+ export interface EncryptedEnvelope {
22
+ /** Random 16-byte salt used to derive the symmetric key. */
23
+ salt: Uint8Array
24
+ /** Random 12-byte GCM IV. */
25
+ iv: Uint8Array
26
+ /** 16-byte GCM auth tag. */
27
+ tag: Uint8Array
28
+ /** Ciphertext. */
29
+ ciphertext: Uint8Array
30
+ }
31
+
32
+ export function encrypt(plaintext: Uint8Array, passphrase: string): EncryptedEnvelope {
33
+ const salt = randomBytes(SALT_LEN)
34
+ const iv = randomBytes(IV_LEN)
35
+ const key = scryptSync(passphrase, salt, KEY_LEN, {
36
+ N: SCRYPT_N,
37
+ r: SCRYPT_R,
38
+ p: SCRYPT_P,
39
+ maxmem: SCRYPT_MAXMEM,
40
+ })
41
+ const cipher = createCipheriv('aes-256-gcm', key, iv)
42
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()])
43
+ const tag = cipher.getAuthTag()
44
+ return {
45
+ salt,
46
+ iv,
47
+ tag: new Uint8Array(tag),
48
+ ciphertext: new Uint8Array(ciphertext),
49
+ }
50
+ }
51
+
52
+ export function decrypt(envelope: EncryptedEnvelope, passphrase: string): Uint8Array {
53
+ const key = scryptSync(passphrase, envelope.salt, KEY_LEN, {
54
+ N: SCRYPT_N,
55
+ r: SCRYPT_R,
56
+ p: SCRYPT_P,
57
+ maxmem: SCRYPT_MAXMEM,
58
+ })
59
+ const decipher = createDecipheriv('aes-256-gcm', key, envelope.iv)
60
+ decipher.setAuthTag(envelope.tag)
61
+ const plaintext = Buffer.concat([decipher.update(envelope.ciphertext), decipher.final()])
62
+ return new Uint8Array(plaintext)
63
+ }
64
+
65
+ /** Pack envelope into a single byte buffer for storage: salt || iv || tag || ciphertext. */
66
+ export function packEnvelope(envelope: EncryptedEnvelope): Uint8Array {
67
+ const total = SALT_LEN + IV_LEN + TAG_LEN + envelope.ciphertext.length
68
+ const out = new Uint8Array(total)
69
+ out.set(envelope.salt, 0)
70
+ out.set(envelope.iv, SALT_LEN)
71
+ out.set(envelope.tag, SALT_LEN + IV_LEN)
72
+ out.set(envelope.ciphertext, SALT_LEN + IV_LEN + TAG_LEN)
73
+ return out
74
+ }
75
+
76
+ /** Unpack a packed envelope back into its fields. */
77
+ export function unpackEnvelope(packed: Uint8Array): EncryptedEnvelope {
78
+ if (packed.length < SALT_LEN + IV_LEN + TAG_LEN) {
79
+ throw new Error('envelope shorter than header')
80
+ }
81
+ return {
82
+ salt: packed.slice(0, SALT_LEN),
83
+ iv: packed.slice(SALT_LEN, SALT_LEN + IV_LEN),
84
+ tag: packed.slice(SALT_LEN + IV_LEN, SALT_LEN + IV_LEN + TAG_LEN),
85
+ ciphertext: packed.slice(SALT_LEN + IV_LEN + TAG_LEN),
86
+ }
87
+ }
@@ -0,0 +1,31 @@
1
+ import { join } from 'node:path'
2
+ import { agentPaths } from '../paths'
3
+ import { SqliteStorage } from './sqlite'
4
+ import type { Storage } from './types'
5
+
6
+ let singleton: SqliteStorage | null = null
7
+
8
+ /**
9
+ * Shared, content-addressed local store at `~/.lyra/storage.sqlite`.
10
+ * Blobs are addressed by their `0x`+sha256 CID, so a single store serves all
11
+ * agents (a blob put by one is fetchable by hash from any). KV/log entries are
12
+ * namespaced by streamId. Replaces the prior decentralized storage backend.
13
+ */
14
+ export function getStorage(): Storage {
15
+ if (!singleton) {
16
+ singleton = new SqliteStorage(join(agentPaths.root, 'storage.sqlite'))
17
+ }
18
+ return singleton
19
+ }
20
+
21
+ /**
22
+ * Back-compat shim for the old "download blob by on-chain root hash" call.
23
+ * Blobs are content-addressed (rootHash === CID), so this is just `getBlob`.
24
+ * The network arg is ignored; kept so existing call sites need no reshaping.
25
+ */
26
+ export async function downloadBlobByRoot(
27
+ _network: unknown,
28
+ rootHash: string,
29
+ ): Promise<Uint8Array | null> {
30
+ return getStorage().getBlob(rootHash)
31
+ }
@@ -0,0 +1,11 @@
1
+ export type { Storage } from './types'
2
+ export { LocalStubStorage } from './local-stub'
3
+ export { SqliteStorage } from './sqlite'
4
+ export { getStorage, downloadBlobByRoot } from './factory'
5
+ export {
6
+ encrypt,
7
+ decrypt,
8
+ packEnvelope,
9
+ unpackEnvelope,
10
+ type EncryptedEnvelope,
11
+ } from './encryption'
@@ -0,0 +1,70 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { join } from 'node:path'
4
+ import type { Storage } from './types'
5
+
6
+ /**
7
+ * Local-disk Storage stub. Layout under `${root}/storage-stub`:
8
+ * kv/<streamId>/<url-encoded-key> — latest value
9
+ * log/<streamId>.jsonl — append-only JSONL
10
+ * blob/<cid> — immutable by content hash
11
+ */
12
+ export class LocalStubStorage implements Storage {
13
+ constructor(private readonly root: string) {}
14
+
15
+ private kvPath(stream: string, key: string): string {
16
+ return join(this.root, 'storage-stub', 'kv', stream, encodeURIComponent(key))
17
+ }
18
+
19
+ private logPath(stream: string): string {
20
+ return join(this.root, 'storage-stub', 'log', `${stream}.jsonl`)
21
+ }
22
+
23
+ private blobPath(cid: string): string {
24
+ return join(this.root, 'storage-stub', 'blob', cid)
25
+ }
26
+
27
+ async putKV(stream: string, key: string, value: Uint8Array): Promise<void> {
28
+ const p = this.kvPath(stream, key)
29
+ await mkdir(join(p, '..'), { recursive: true })
30
+ await writeFile(p, value)
31
+ }
32
+
33
+ async getKV(stream: string, key: string): Promise<Uint8Array | null> {
34
+ return await readOrNull(this.kvPath(stream, key))
35
+ }
36
+
37
+ async appendLog(stream: string, entry: Uint8Array): Promise<string> {
38
+ const p = this.logPath(stream)
39
+ await mkdir(join(p, '..'), { recursive: true })
40
+ const cid = cidOf(entry)
41
+ const line = JSON.stringify({ cid, hex: Buffer.from(entry).toString('hex'), ts: Date.now() })
42
+ await appendFile(p, `${line}\n`)
43
+ return cid
44
+ }
45
+
46
+ async putBlob(bytes: Uint8Array): Promise<string> {
47
+ const cid = cidOf(bytes)
48
+ const p = this.blobPath(cid)
49
+ await mkdir(join(p, '..'), { recursive: true })
50
+ await writeFile(p, bytes)
51
+ return cid
52
+ }
53
+
54
+ async getBlob(cid: string): Promise<Uint8Array | null> {
55
+ return await readOrNull(this.blobPath(cid))
56
+ }
57
+ }
58
+
59
+ async function readOrNull(path: string): Promise<Uint8Array | null> {
60
+ try {
61
+ return new Uint8Array(await readFile(path))
62
+ } catch (e) {
63
+ if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null
64
+ throw e
65
+ }
66
+ }
67
+
68
+ function cidOf(bytes: Uint8Array): string {
69
+ return `0x${createHash('sha256').update(bytes).digest('hex')}`
70
+ }
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { SqliteStorage } from './sqlite'
3
+
4
+ const enc = (s: string) => new TextEncoder().encode(s)
5
+ const dec = (b: Uint8Array | null) => (b ? new TextDecoder().decode(b) : null)
6
+
7
+ describe('SqliteStorage', () => {
8
+ it('round-trips a content-addressed blob', async () => {
9
+ const s = new SqliteStorage(':memory:')
10
+ const cid = await s.putBlob(enc('hello sui'))
11
+ expect(cid).toMatch(/^0x[0-9a-f]{64}$/)
12
+ // Same bytes -> same CID (content addressed, idempotent).
13
+ expect(await s.putBlob(enc('hello sui'))).toBe(cid)
14
+ expect(dec(await s.getBlob(cid))).toBe('hello sui')
15
+ expect(await s.getBlob('0xdeadbeef')).toBeNull()
16
+ s.close()
17
+ })
18
+
19
+ it('stores + overwrites KV and appends to the log', async () => {
20
+ const s = new SqliteStorage(':memory:')
21
+ await s.putKV('stream1', 'k', enc('v1'))
22
+ await s.putKV('stream1', 'k', enc('v2'))
23
+ expect(dec(await s.getKV('stream1', 'k'))).toBe('v2')
24
+ expect(await s.getKV('stream1', 'missing')).toBeNull()
25
+ const cid = await s.appendLog('stream1', enc('entry'))
26
+ expect(cid).toMatch(/^0x[0-9a-f]{64}$/)
27
+ s.close()
28
+ })
29
+ })
@@ -0,0 +1,95 @@
1
+ import { Database } from 'bun:sqlite'
2
+ import { createHash } from 'node:crypto'
3
+ import { mkdirSync } from 'node:fs'
4
+ import { dirname } from 'node:path'
5
+ import type { Storage } from './types'
6
+
7
+ /**
8
+ * SQLite-backed Storage (via `bun:sqlite`). Replaces the prior decentralized
9
+ * blob backend with a local, zero-infra store — ideal for the agent's
10
+ * encrypted memory and easy to demo. Implements the same three primitives:
11
+ * - KV: mutable value per (stream, key)
12
+ * - Log: append-only entries, each addressed by content CID
13
+ * - Blob: immutable, content-addressed bytes
14
+ *
15
+ * CID convention matches LocalStubStorage: `0x` + sha256(bytes) hex.
16
+ */
17
+ export class SqliteStorage implements Storage {
18
+ private readonly db: Database
19
+
20
+ /** @param path SQLite file path (defaults to in-memory). Parent dir is created. */
21
+ constructor(path = ':memory:') {
22
+ if (path !== ':memory:') {
23
+ mkdirSync(dirname(path), { recursive: true })
24
+ }
25
+ this.db = new Database(path, { create: true })
26
+ this.db.run('PRAGMA journal_mode = WAL;')
27
+ this.db.run(
28
+ `CREATE TABLE IF NOT EXISTS kv (
29
+ stream TEXT NOT NULL,
30
+ key TEXT NOT NULL,
31
+ value BLOB NOT NULL,
32
+ PRIMARY KEY (stream, key)
33
+ );`,
34
+ )
35
+ this.db.run(
36
+ `CREATE TABLE IF NOT EXISTS log (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ stream TEXT NOT NULL,
39
+ cid TEXT NOT NULL,
40
+ entry BLOB NOT NULL,
41
+ ts INTEGER NOT NULL
42
+ );`,
43
+ )
44
+ this.db.run('CREATE INDEX IF NOT EXISTS log_stream_idx ON log (stream, id);')
45
+ this.db.run(
46
+ `CREATE TABLE IF NOT EXISTS blob (
47
+ cid TEXT PRIMARY KEY,
48
+ bytes BLOB NOT NULL
49
+ );`,
50
+ )
51
+ }
52
+
53
+ async putKV(stream: string, key: string, value: Uint8Array): Promise<void> {
54
+ this.db
55
+ .query('INSERT OR REPLACE INTO kv (stream, key, value) VALUES (?, ?, ?)')
56
+ .run(stream, key, value)
57
+ }
58
+
59
+ async getKV(stream: string, key: string): Promise<Uint8Array | null> {
60
+ const row = this.db
61
+ .query('SELECT value FROM kv WHERE stream = ? AND key = ?')
62
+ .get(stream, key) as { value: Uint8Array } | null
63
+ return row ? new Uint8Array(row.value) : null
64
+ }
65
+
66
+ async appendLog(stream: string, entry: Uint8Array): Promise<string> {
67
+ const cid = cidOf(entry)
68
+ this.db
69
+ .query('INSERT INTO log (stream, cid, entry, ts) VALUES (?, ?, ?, ?)')
70
+ .run(stream, cid, entry, Date.now())
71
+ return cid
72
+ }
73
+
74
+ async putBlob(bytes: Uint8Array): Promise<string> {
75
+ const cid = cidOf(bytes)
76
+ this.db.query('INSERT OR IGNORE INTO blob (cid, bytes) VALUES (?, ?)').run(cid, bytes)
77
+ return cid
78
+ }
79
+
80
+ async getBlob(cid: string): Promise<Uint8Array | null> {
81
+ const row = this.db.query('SELECT bytes FROM blob WHERE cid = ?').get(cid) as {
82
+ bytes: Uint8Array
83
+ } | null
84
+ return row ? new Uint8Array(row.bytes) : null
85
+ }
86
+
87
+ /** Close the underlying database handle. */
88
+ close(): void {
89
+ this.db.close()
90
+ }
91
+ }
92
+
93
+ function cidOf(bytes: Uint8Array): string {
94
+ return `0x${createHash('sha256').update(bytes).digest('hex')}`
95
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Storage interface abstracting Sui Storage's three primitives as used by lyra:
3
+ * - KV: mutable key→value per namespace
4
+ * - Log: append-only, returns CID per entry
5
+ * - Blob: immutable bytes, content-addressed
6
+ *
7
+ * Phase 1 ships a local-disk stub. Phase 5 ships the real @0gfoundation/0g-ts-sdk
8
+ * backend + on-chain-event replay for KV reads (per verified architecture).
9
+ */
10
+ export interface Storage {
11
+ /** Put a value into a named stream under a key. */
12
+ putKV(streamId: string, key: string, value: Uint8Array): Promise<void>
13
+ /** Get the latest value for (streamId, key) or null. */
14
+ getKV(streamId: string, key: string): Promise<Uint8Array | null>
15
+ /** Append an entry to a stream's log. Returns CID (rootHash) of the entry. */
16
+ appendLog(streamId: string, entry: Uint8Array): Promise<string>
17
+ /** Upload immutable bytes, returns content CID. */
18
+ putBlob(bytes: Uint8Array): Promise<string>
19
+ /** Retrieve bytes by CID. */
20
+ getBlob(cid: string): Promise<Uint8Array | null>
21
+ }