Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions packages/engram-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# @engram/client

TypeScript SDK for [Engram](https://github.com/Dipraise1/Engram) — decentralized AI memory on Bittensor subnet 450.

Mirrors the Python `engram-subnet` SDK. Supports ingest, query, private namespaces with X25519 hybrid encryption, sr25519 request signing, and CID validation.

## Installation

```bash
npm install @engram/client
```

## Quick Start

```typescript
import { EngramClient } from "@engram/client";

const client = new EngramClient("http://127.0.0.1:8091");

// Store text
const cid = await client.ingest("The transformer architecture changed everything.");
console.log("Stored:", cid);

// Semantic search
const results = await client.query("attention mechanisms", top_k: 5);
for (const r of results) {
console.log(r.cid, r.score);
}

// Health check
const health = await client.health();
console.log("Status:", health.status);
```

## Private Namespaces

```typescript
const client = new EngramClient({
minerUrl: "http://127.0.0.1:8091",
namespace: "my-namespace",
namespaceKey: "my-secret-key",
});

// Data is encrypted client-side before sending to the miner
const cid = await client.ingest("This is private");
```

## sr25519 Signing

```typescript
import { generateKeypairFromSeed } from "@engram/client";

const keypair = generateKeypairFromSeed("my-seed-phrase");

const client = new EngramClient({
minerUrl: "http://127.0.0.1:8091",
keypair,
});
```

## API

### Core Methods

| Method | Description |
|--------|-------------|
| `ingest(text, metadata?)` | Embed and store text |
| `ingestEmbedding(vector, metadata?)` | Store pre-computed embedding |
| `query(text, top_k?, filter?)` | Semantic search |
| `queryByVector(vector, top_k?)` | Search by vector |
| `get(cid)` | Retrieve by CID |
| `delete(cid)` | Delete by CID |
| `list(opts?)` | List stored memories |
| `batchIngest(jsonl)` | Ingest JSONL data |
| `ingestConversation(messages, sessionId?)` | Store conversation |
| `ingestURL(url)` | Fetch and store URL |
| `health()` | Check miner liveness |
| `isOnline()` | Quick online check |

### Error Types

- `EngramError` — base
- `MinerOfflineError` — miner unreachable
- `IngestError` — ingest failed
- `QueryError` — query failed
- `InvalidCIDError` — CID validation failed

## Development

```bash
npm install
npm run build
npm test
```

## License

MIT
28 changes: 28 additions & 0 deletions packages/engram-client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@engram/client",
"version": "0.1.0",
"description": "TypeScript client for Engram — decentralized AI memory on Bittensor",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist/", "README.md", "LICENSE"],
"scripts": {
"build": "tsc",
"test": "vitest run",
"test:watch": "vitest",
"prepublish": "npm run build"
},
"keywords": ["engram", "bittensor", "ai", "memory", "vector-database", "subnet"],
"license": "MIT",
"dependencies": {
"@polkadot/util-crypto": "^12.0.0",
"@polkadot/keyring": "^12.0.0"
},
"devDependencies": {
"typescript": "^5.4.0",
"vitest": "^1.6.0",
"@types/node": "^20.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
62 changes: 62 additions & 0 deletions packages/engram-client/src/cid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Engram SDK — CID parsing and validation.
*
* CID format: <scheme>::<hash>
* Example: v1::a3f2b1c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0
*/

/** A parsed CID. */
export interface ParsedCID {
scheme: string;
hash: string;
encoded: string;
}

/**
* Parse and validate a CID string.
* @throws {Error} If the CID format is invalid.
*/
export function parseCID(cid: string): ParsedCID {
if (!cid || typeof cid !== "string") {
throw new Error(`Invalid CID: empty or non-string`);
}

const parts = cid.split("::");
if (parts.length !== 2) {
throw new Error(`Invalid CID format: expected 'scheme::hash', got '${cid}'`);
}

const [scheme, hash] = parts;

if (!scheme || scheme.length === 0) {
throw new Error(`Invalid CID: empty scheme`);
}

if (!hash || hash.length === 0) {
throw new Error(`Invalid CID: empty hash`);
}

// Hash should be hex-encoded (at minimum 8 chars for a real CID)
if (hash.length < 8) {
throw new Error(`Invalid CID: hash too short (${hash.length} chars)`);
}

const hexRegex = /^[0-9a-f]+$/i;
if (!hexRegex.test(hash)) {
throw new Error(`Invalid CID: hash is not valid hex`);
}

return { scheme, hash, encoded: cid };
}

/**
* Validate a CID string. Returns true if valid, false otherwise.
*/
export function isValidCID(cid: string): boolean {
try {
parseCID(cid);
return true;
} catch {
return false;
}
}
Loading