@diskd-ai/sdk
v5.1.14
Published
SDK for DiskD platform APIs (Drive, LLM Router, Agent Hub, MCP Hub, Telegram Userbot, Web Navigator)
Readme
@diskd-ai/sdk
Unified TypeScript SDK for the Upgraide platform APIs.
All services are accessible via the diskd factory, which provides a consistent
diskd.auth.* + namespaced service pattern across diskd.os.*, diskd.platform.*,
and diskd.utils.*:
import { diskd } from '@diskd-ai/sdk';
const auth = diskd.auth.apiKey({ workspaceId: '...' });
const drive = diskd.os.drive({ version: 'v1', auth });
const sessions = diskd.platform.sessions({
auth,
scope: { scopeType: 'project', projectId: 'proj-1' },
});
const crontab = diskd.platform.crontab({
auth,
scope: { scopeType: 'project', projectId: 'proj-1' },
});
const db = diskd.os.database({ auth, dbName: '...', schema: { ... } });
const ds = diskd.os.datasource({ auth, dbName: '...', entities: [...] });
const llm = diskd.os.llm({ auth });
const agents = diskd.os.agents({ auth, workspaceId: '...' });
const mcp = diskd.os.mcp({ auth, workspaceId: '...' });
const messages = diskd.os.messagesStore({ auth });
const routines = diskd.platform.routines({ auth });
const operatives = diskd.platform.operatives({ auth });
const calendar = diskd.platform.calendar({ auth });
const tg = diskd.utils.tgUserBot({ auth, workspaceId: '...' });
const webNav = diskd.utils.webNavigator({ auth, workspaceId: '...' });Installation
Configure
.npmrcin your project (or~/.npmrc):@diskd:registry=https://gitlab.iosya.com/api/v4/projects/80/packages/npm/ //gitlab.iosya.com/api/v4/projects/80/packages/npm/:_authToken=${NPM_TOKEN}Set
NPM_TOKENto a GitLab personal access token withread_apiscope:export NPM_TOKEN=glpat-xxxxxxxxxxxxInstall:
bun add @diskd-ai/sdk
Install / build (repo)
cd mono/platform-api
bun install
bun run buildAuthentication
The SDK supports two authentication modes via the AuthModule interface.
External clients (OAuth2)
Use diskd.auth.credentials() for OAuth2 service-account or PKCE browser flows:
import { diskd } from '@diskd-ai/sdk';
const auth = await diskd.auth.credentials({
scopes: ['openid'],
keyfilePath: 'credentials.json',
});
const drive = diskd.os.drive({ version: 'v1', auth });
const sessions = diskd.platform.sessions({
auth,
scope: { scopeType: 'project', projectId: 'proj-1' },
});
const crontab = diskd.platform.crontab({
auth,
scope: { scopeType: 'project', projectId: 'proj-1' },
});Internal services (API key)
Use diskd.auth.apiKey() for service-to-service communication within the cluster:
import { diskd } from '@diskd-ai/sdk';
const auth = diskd.auth.apiKey({ workspaceId: process.env.WORKSPACE_ID! });
const drive = diskd.os.drive({ version: 'v1', auth });diskd.auth.apiKey() reads APIS_API_KEY from the environment and fails fast when
either APIS_API_KEY or APIS_BASE_URL is missing.
Both auth modes produce identical client instances.
Environment variables
All resource APIs resolve from the centralized gateway base URL:
| Env Variable | Default |
|--------------|---------|
| APIS_BASE_URL | https://apis.diskd.local:8080 |
| APIS_API_KEY | none |
The gateway is the single resource entrypoint. Public gateway URLs follow the
versioned convention https://apis.example/v1/{namespace}/{module}. The SDK
derives API paths from the same namespace structure as the public SDK surface
and lets the gateway handle API orchestration and auth strategy.
Derived default paths:
/v1/os/drive/v1/os/database/v1/os/llm/v1/os/agents/v1/os/mcp/v1/platform/sessions/v1/platform/crontab/v1/platform/operatives/v1/platform/projects/v1/platform/routines/v1/platform/events/v1/platform/calendar/v1/utils/tg-userbot/v1/utils/web-navigator
You can still override a client with an explicit url, but the default mode is
the centralized gateway.
Gateway Decision
This SDK does not treat resource APIs as independently-discovered hosts.
The canonical model is one centralized apis gateway behind APIS_BASE_URL.
That means:
- no per-service default env vars such as
LLM_ROUTER_BASE_URL,AGENT_HUB_BASE_URL, orMCP_HUB_BASE_URL - resource clients derive their route from
APIS_BASE_URLplus a namespace-derived path prefix - the gateway is responsible for request routing, API orchestration, and auth-strategy handling
Per-client url remains available only as an explicit override.
Drive API
Path operations
await drive.init();
const entries = await drive.list({ path: '/' });
const dir = await drive.create({ dirName: 'my-folder' });
await drive.rename({ inode: dir.inode, newName: 'renamed-folder' });
await drive.delete({ inodes: [dir.inode], recursive: true });
const resolved = await drive.resolve({ inodes: ['inode1', 'inode2'] });
await drive.updateMetadata({ inode: 'abc', metadata: { key: 'value' } });
await drive.updateAttributes({ inode: 'abc', attributes: ['pinned'] });Upload (buffer + stream)
// Buffer upload
const result = await drive.upload.file({
name: 'hello.txt',
data: new TextEncoder().encode('Hello, world!'),
mimeType: 'text/plain',
onProgress: (uploaded, total) => console.log(`${uploaded}/${total}`),
});
// Stream upload (large files)
const result = await drive.upload.file({
name: 'large.bin',
stream: readableStream,
size: 1_000_000_000,
sha256Root: 'precomputed-hex',
});Download (streaming)
const file = await drive.download.file({
inode: 'abc123',
onProgress: (downloaded, total) => console.log(`${downloaded}/${total}`),
});
await file.stream.pipeTo(writableStream);File metadata, disk usage, tools
const meta = await drive.files.metadata({ inode: 'abc' });
const usage = await drive.diskUsage();
const ls = await drive.tools.ls({ path: '/', recursive: true });
const grep = await drive.tools.grep({ pattern: 'TODO' });Sessions
The SDK exposes these session methods on diskd.platform.sessions({ auth, scope }):
startopensavelistdeletemessage
const session = await sessions.start({
title: 'Deployment help',
});
await session.append([
sessions.message({ role: 'user', content: 'How do I deploy to production?' }),
]);
const sessionList = await sessions.list();Crontab scheduler
The SDK exposes these scheduler methods on diskd.platform.crontab({ auth, scope, timezone? }).
If timezone is omitted, the SDK uses the caller runtime timezone by default.
savegetgetStatuslistJobsrunJobcreateJob
await crontab.createJob({
job: {
jobId: '01JABCD2FGH3JK4MNP5QRST6VW',
enabled: true,
schedule: {
minute: '*/5',
hour: '*',
dayOfMonth: '*',
month: '*',
dayOfWeek: '*',
},
request: {
method: 'POST',
url: 'https://example.internal/hooks/sync',
payload: {
kind: 'json',
value: { source: 'sdk-example' },
},
},
},
});
const status = await crontab.getStatus();See examples/node/drive-upload-download.ts, examples/node/drive-session-external.ts, and examples/node/drive-crontab.ts.
Messages Store API
Channel-agnostic message storage on top of Drive. Each workspace mailbox is one
SQLite file under /Mailboxes/<mailboxId>.mailbox; attachment bytes live in
Drive under /Mailboxes/<mailboxId>/<per-message-folder>/. The wire is the
messages_store/* JSON-RPC namespace served by drive.
Use it to persist email (IMAP / JMAP), Telegram, WhatsApp, or any other
channel where messages live in folders inside per-account mailboxes. Message
payload is opaque JSON; the store never inspects it.
The client exposes four boundaries -- mailboxes, folders, messages, attachments
-- via a functional scoping pattern: each level returns a client that
captures its identifiers in a closure, so callers never repeat
(mailboxId, folderId, externalId) on every call.
const messagesStore = diskd.os.messagesStore({ auth });
const mailbox = messagesStore.mailbox({ mailboxId: 'gmail-acme' });
const folder = mailbox.folder({ folderId: 'INBOX' });
const message = folder.message({ externalId: 'imap-uid-1001' });Mailboxes
// Allocate the mailbox SQLite file under /Mailboxes/.
// metadata is opaque JSON, stashed on the underlying drive_databases record.
const created = await messagesStore.createMailbox({
mailboxId: 'gmail-acme',
displayName: '[email protected]',
metadata: { protocol: 'imap', host: 'imap.gmail.com' },
});
// → { mailboxId, dbInode, drivePath: '/Mailboxes/gmail-acme.mailbox' }
// Bind a mailbox-scoped client; subsequent calls don't repeat mailboxId.
const mailbox = messagesStore.mailbox({ mailboxId: 'gmail-acme' });
// Idempotent SQLite-schema bootstrap. Required before any folder/message ops.
await mailbox.init();
// Workspace-scoped enumeration.
const all = await messagesStore.listMailboxes();
// → readonly { mailboxId, displayName, dbInode, recordCount,
// sizeBytes, updatedAt }[]
// Cascade-delete (mailbox file + per-mailbox attachment subtree).
await mailbox.delete();Folders
folderId is opaque (caller-chosen) -- IMAP folder name, JMAP id, Telegram
chat_id, etc. Folder metadata is the natural place for protocol-specific
sync state (UIDVALIDITY/UIDNEXT/HIGHESTMODSEQ for IMAP, JMAP state,
Telegram pts, etc.). The store never reads it.
// Idempotent upsert. created=true on first call, false thereafter.
await mailbox.upsertFolder({
folderId: 'INBOX',
displayName: 'Inbox',
metadata: { uidvalidity: 12345, uidnext: 1101 },
});
// List folders in this mailbox.
const folders = await mailbox.listFolders();
// Bind a folder-scoped client.
const folder = mailbox.folder({ folderId: 'INBOX' });
// Update display name / sync metadata via the scoped client (folderId implicit).
await folder.upsert({
displayName: 'Inbox',
metadata: { uidvalidity: 12345, uidnext: 1200 },
});
// Read one folder.
const summary = await folder.get();
// → { folderId, displayName, metadata, messageCount, updatedAt }
// Cascade-delete folder + messages + per-message attachments.
const result = await folder.delete();
// → { folderId, deleted, deletedMessageCount }Messages
externalId is the caller's idempotency key within the folder (IMAP UID
stringified, Telegram message_id, JMAP id, ...). payload is opaque JSON
the store never inspects.
A successful upsertBatch response means the batch is durable in S3 -- the
SQLite head is committed before the call returns.
// Bulk insert-or-update by externalId.
const ub = await folder.upsertBatch({
items: [
{
externalId: 'imap-uid-1001',
payload: {
subject: 'Welcome to upgraide',
from: '[email protected]',
receivedAt: '2026-04-28T13:21:32Z',
labels: ['inbox', 'unread'],
},
},
{ externalId: 'imap-uid-1002', payload: { subject: 'Your weekly digest' } },
],
});
// → { inserted: 2, updated: 0 }
// Cursor-paginated read.
let cursor: string | null = null;
do {
const page = await folder.listMessages({ limit: 100, cursor: cursor ?? undefined });
for (const m of page.items) {
// m.externalId, m.payload, m.createdAt, m.updatedAt
}
cursor = page.nextCursor;
} while (cursor);
// Single-message lookup.
const msg = await folder.getMessage({ externalId: 'imap-uid-1001' });
// Bulk delete; missing ids are silently skipped.
const del = await folder.deleteBatch({
externalIds: ['imap-uid-1001', 'imap-uid-1002'],
});
// → { deleted: 2 }Attachments
Attachments follow Drive's upload-intent contract (start → PUT bytes →
commit) but are scoped to a single message. The per-message Drive folder is
created lazily on the first uploadStart call.
const message = folder.message({ externalId: 'imap-uid-1001' });
// 1. Begin upload -- get an intent + presigned URL.
const intent = await message.attachments.uploadStart({
attachmentId: 'att-1',
filename: 'invoice.pdf',
contentType: 'application/pdf',
sizeBytes: 12345,
});
// 2. PUT the bytes to intent.uploadUrl with header X-Upload-Intent-Id.
// (See Drive upload examples for streaming/buffer modes.)
const putRes = await fetch(intent.uploadUrl, {
method: 'PUT',
headers: { 'X-Upload-Intent-Id': intent.intentId, 'Content-Type': 'application/pdf' },
body: pdfBytes,
});
const { etag } = (await putRes.json()) as { etag: string };
// 3. Commit -- registers the attachment row.
await message.attachments.uploadCommit({
attachmentId: 'att-1',
intentId: intent.intentId,
etag,
});
// Enumerate this message's attachments.
const list = await message.attachments.list();
// Presigned download URL (with explicit expiresAt).
const dl = await message.attachments.downloadUrl({ attachmentId: 'att-1' });
// Delete the attachment row + its Drive file.
await message.attachments.delete({ attachmentId: 'att-1' });Auth modes
Same as the rest of the SDK -- both work:
// API key (internal services / Tilt / port-forward).
const auth = diskd.auth.apiKey({ workspaceId: 'ws-...' });
// OAuth2 client-credentials (external clients).
const auth = await diskd.auth.credentials({
scopes: ['openid'],
keyfilePath: '.agents/credentials.json',
});
const messagesStore = diskd.os.messagesStore({ auth });Workspace identity is always auth-derived (X-Workspace-Id header from API
key, ext.workspace_id claim from OAuth JWT) -- never sent on the wire as a
parameter. See examples/node/messages-store-example.ts for a full
end-to-end walk-through.
Routines API
REST client for managing routines (automated workflows) scoped to profile or project:
const routines = diskd.platform.routines({ auth });
// List routines in a scope
const all = await routines.list({ scope: 'workspace' });
const projectRoutines = await routines.list({ scope: 'project', projectName: 'my-project' });
// Get by slug
const routine = await routines.get({ slug: 'daily-summary', scope: 'workspace' });
// Create
const created = await routines.create({
name: 'Daily Summary',
scope: 'workspace',
operativeSlug: 'research-agent',
triggerType: 'rhythm',
trigger: { cron: '0 9 * * *' },
steps: [{ id: 'step-1', name: 'Summarize', action: 'summarize', order: 0 }],
});
// Update
const updated = await routines.update(
'daily-summary',
{ status: 'paused' },
{ scopeType: 'workspace' },
);
// Delete
await routines.delete({ slug: 'daily-summary', scope: 'workspace' });Operatives API
REST client for managing operatives (AI agents) with attached files, skills, and MCP tools:
const ops = diskd.platform.operatives({ auth });
// List operatives in a project
const list = await ops.list({ projectId: 'proj-1' });
// Get by id or slug
const operative = await ops.get('op-01');
const bySlug = await ops.getBySlug({ projectId: 'proj-1', slug: 'research-agent' });
// Create
const created = await ops.create({
projectId: 'proj-1',
name: 'Research Agent',
engine: 'deep',
engineProvider: 'anthropic',
engineModel: 'claude-4',
});
// Update
await ops.update('op-01', {
orders: 'You are a research assistant focused on academic papers.',
fileAccess: 'selected',
});
// Delete
await ops.delete('op-01');Operative files (Drive knowledge sources)
Attach Drive files from the operative's project chroot as knowledge sources:
// Attach files (paths relative to project chroot)
await ops.files.add('op-01', { paths: ['/docs/knowledge-base', '/docs/readme.md'] });
// List attached files
const files = await ops.files.list('op-01');
// Detach a file
await ops.files.remove('op-01', files[0].id);Operative skills
// Attach skills
await ops.skills.add('op-01', { refIds: ['web-search', 'code-review'] });
// List attached skills
const skills = await ops.skills.list('op-01');
// Detach a skill
await ops.skills.remove('op-01', skills[0].id);Operative MCP tools
// Attach MCP tools
await ops.tools.add('op-01', { selectors: ['github/search_repos', 'slack/send_message'] });
// List attached tools
const tools = await ops.tools.list('op-01');
// Detach a tool
await ops.tools.remove('op-01', tools[0].id);Calendar API
REST client for workspace calendar management -- events, attendees, note links, attachments, and settings:
const calendar = diskd.platform.calendar({ auth });
// Accounts and events
const accounts = await calendar.listAccounts();
const events = await calendar.listEvents({
startAfter: '2026-03-01T00:00:00Z',
startBefore: '2026-03-31T23:59:59Z',
});
// Event CRUD
const event = await calendar.createEvent({
calendarId: accounts[0].calendars[0].id,
title: 'Sprint Planning',
startAt: '2026-03-25T10:00:00Z',
endAt: '2026-03-25T11:00:00Z',
});
await calendar.updateEvent(event.id, {
title: 'Sprint Planning (updated)',
metadata: { timeBlockCategory: 'meeting' },
});
await calendar.deleteEvent(event.id);Attendees
const attendee = await calendar.attendees.add(event.id, {
email: '[email protected]',
role: 'required',
});
await calendar.attendees.updateRsvp(event.id, attendee.id, 'yes');
await calendar.attendees.remove(event.id, attendee.id);Note links
const link = await calendar.noteLinks.add(event.id, {
noteDiskPath: '/Projects/sprint/notes/planning.md',
title: 'Planning Notes',
linkType: 'context',
});
await calendar.noteLinks.remove(event.id, link.id);Attachments
const attachment = await calendar.attachments.add(event.id, {
type: 'url',
title: 'Meeting Recording',
url: 'https://meet.example.com/recording/123',
});
await calendar.attachments.remove(event.id, attachment.id);Event metadata
Events support an extensible metadata JSONB field for cross-domain data:
await calendar.updateEvent(event.id, {
metadata: {
timeBlockCategory: 'focus',
linkedNotes: [
{ noteDiskPath: '/docs/spec.md', title: 'Spec', linkType: 'context' },
],
},
});Settings
const settings = await calendar.getSettings();
await calendar.updateSettings({
weekStartDay: 0,
defaultView: 'month',
timezone: 'America/New_York',
});Drive Database API
JSON-RPC client for Drive's SQLite-backed database operations:
const drive = diskd.os.drive({ version: 'v1', auth });
// Create a database with schema
const db = await drive.db.create({
name: 'myapp.workspace-123.main',
schema: {
users: {
id: { type: 'TEXT', primaryKey: true },
name: { type: 'TEXT', notNull: true },
email: { type: 'TEXT', notNull: true },
},
},
});
// Insert rows
await drive.db.insert({
name: 'myapp.workspace-123.main',
table: 'users',
rows: [{ id: '1', name: 'Alice', email: '[email protected]' }],
});
// Query with parameters
const result = await drive.db.query({
name: 'myapp.workspace-123.main',
sql: 'SELECT * FROM users WHERE id = ?',
parameters: ['1'],
});
// Commit, metadata, drop, resolve
await drive.db.commit({ name: 'myapp.workspace-123.main' });
const meta = await drive.db.metadata({ name: 'myapp.workspace-123.main' });
const resolved = await drive.db.resolveByInode({ dbInode: db.dbInode });Drive Repository (CRUD pattern)
Higher-level database + table-scoped repository pattern -- ideal for services that use Drive DB as their persistence layer:
// Create database with schema
const db = diskd.os.database({
auth,
dbName: 'shop.workspace-123.main',
dbType: 'database',
schema: {
users: { id: { type: 'TEXT', primaryKey: true }, name: { type: 'TEXT', notNull: true } },
orders: { id: { type: 'TEXT', primaryKey: true }, user_id: { type: 'TEXT' }, total: { type: 'INTEGER' } },
},
});
await db.ensureCreated();
// Get table-scoped repositories
const users = db.repository('users');
const orders = db.repository('orders');
// Insert
await users.insert([{ id: 'u1', name: 'Alice' }, { id: 'u2', name: 'Bob' }]);
// Find with where, orderBy, limit, offset
const results = await users.find({
where: { name: 'Alice' },
orderBy: { column: 'name', direction: 'ASC' },
limit: 10,
});
// Find one (returns null if not found)
const alice = await users.findOne({ id: 'u1' });
// Count
const total = await orders.count();
const pending = await orders.count({ status: 'pending' });
// Update
await orders.update({ where: { id: 'o1' }, set: { status: 'shipped' } });
// Delete
await orders.deleteRows({ status: 'cancelled' });
// Raw SQL at database level for joins and complex queries
const summary = await db.query(`
SELECT u.name, SUM(o.total) AS revenue
FROM users u JOIN orders o ON o.user_id = u.id
GROUP BY u.id ORDER BY revenue DESC
`);
// Commit and metadata (database-level operations)
await db.commit();
const meta = await db.metadata();See examples/node/drive-db-repository-example.ts.
TypeORM Driver (diskd.os.datasource())
Use TypeORM entities, relations, and repositories against Drive DB. SQL is routed
through Drive DB JSON-RPC, and TypeORM's transaction lifecycle maps to Drive DB's
commit/rollback semantics. Requires typeorm as a peer dependency.
Installation
npm install @diskd-ai/sdk typeormUsage
import { diskd } from '@diskd-ai/sdk';
import { Entity, PrimaryColumn, Column } from 'typeorm';
// Define entities
@Entity({ name: 'users' })
class User {
@PrimaryColumn({ type: 'varchar', length: 26 })
id!: string;
@Column({ type: 'varchar' })
name!: string;
@Column({ type: 'varchar' })
email!: string;
}
// Create DataSource backed by Drive DB
process.env.APIS_BASE_URL ??= 'https://apis.diskd.local:8080';
const auth = diskd.auth.apiKey({ workspaceId: 'workspace-123' });
const ds = diskd.os.datasource({
auth,
url: `${process.env.APIS_BASE_URL}/v1/os/database/api/v1`,
dbName: 'shop.workspace-123.main',
entities: [User],
synchronize: true,
});
await ds.initialize();
// Standard TypeORM repository operations
const userRepo = ds.getRepository(User);
await userRepo.save({ id: 'u1', name: 'Alice', email: '[email protected]' });
const alice = await userRepo.findOneBy({ id: 'u1' });
const users = await userRepo.find({ order: { name: 'ASC' } });
// Persist to S3 (flush WAL)
await ds.driver.commit();
// Rollback discards uncommitted changes (revert to last commit)
await ds.driver.driveRollback();Transaction mapping
| TypeORM operation | Drive DB action |
|------------------------|----------------------------------------|
| BEGIN TRANSACTION | No-op (writes auto-accumulate in WAL) |
| COMMIT | drive.db.commit() -- flush WAL to S3 |
| ROLLBACK | drive.db.rollback() -- discard WAL |
Limitations (v1)
- No nested transactions / savepoints (deferred to v2)
- Affected row count returns 0 (each JSON-RPC call is a separate SQLite connection; works fine for ULID-based entities)
- Schema introspection from live database is limited;
synchronize: truegenerates DDL directly
See examples/node/typeorm-drive-example.ts and docs/typeorm-driver-design.md.
LLM Router API
JSON-RPC 2.0 + NDJSON streaming for multi-provider LLM completions:
const llm = diskd.os.llm({ auth });
// Non-streaming completion
const result = await llm.completions.create({
provider: 'openai', model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello' }],
maxTokens: 64,
});
// Streaming
for await (const chunk of llm.completions.stream(params)) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}
// Models, embeddings, OCR, audio transcription
const models = await llm.models.listAll();
const embeddings = await llm.embeddings.create({ provider: 'openai', model: 'text-embedding-3-small', input: ['text'] });See examples/node/llm-router-example.ts.
Agent Hub API
SSE streaming with StreamProtocolHandler + StreamProtocolFetcher for agent invocation:
import { diskd, StreamProtocolHandler } from '@diskd-ai/sdk';
const agents = diskd.os.agents({ auth, workspaceId: '...' });
// List agents and models
const agentList = await agents.agents.list();
const models = await agents.agents.getSupportedModels('assistant');
const billing = await agents.billing.getAliases();
// Invoke with fluent stream handling
const handler = new StreamProtocolHandler()
.on('response.output_text.delta', (e) => process.stdout.write(e.delta))
.on('response.completed', (e) => console.log('done', e.response.usage))
.on('response.failed', (e) => console.error(e.response.error.message))
.on('error', (e) => console.error(e.message));
const stream = await agents.invoke({
agentName: 'assistant',
query: 'Hello',
agentOptions: { maxTokens: 256 },
});
stream.map((event) => handler.handle(event))
.stop(() => console.log('stream closed'))
.catch((err) => console.error(err));Stream protocol events include text deltas, function calls/results, content parts (images, files, audio), web/file search lifecycle, external sources, plan updates, notifications, and error/completion signals.
See examples/node/agent-hub-example.ts.
MCP Hub API
REST client for MCP server catalog, registry, and integrations:
const mcp = diskd.os.mcp({ auth, workspaceId: '...' });
// Catalog
const catalog = await mcp.catalog.list({ search: 'github' });
const details = await mcp.catalog.getServerDetails(serverId);
// Registry (installed servers)
const registry = await mcp.registry.list();
const added = await mcp.registry.addServer({ catalogServerId: '...' });
await mcp.registry.toggleTool(serverId, toolId, false);
const logs = await mcp.registry.getServerLogs(serverId, { limit: 10 });
await mcp.registry.deleteServer(serverId);
// Env vars, connection settings, remote servers
await mcp.registry.upsertEnvVar(serverId, { key: 'TOKEN', value: '...' });
await mcp.registry.addRemoteServer({ name: 'My MCP', url: '...', authType: 'pat' });See examples/node/mcp-hub-example.ts.
Telegram Userbot API
REST client for Telegram channel resolution, importing, and message retrieval:
const tg = diskd.utils.tgUserBot({ auth, workspaceId: '...' });
// Resolve channel (public, no auth required)
const resolved = await tg.channels.resolve('durov');
// Channel operations
const channels = await tg.channels.list();
await tg.channels.add({ channelIdentifier: '@mychannel', limit: 1000 });
await tg.channels.sync({ telegramId: -1001234567890 });
// Messages and stats
const messages = await tg.channels.getMessages(channelId, { limit: 50, searchText: 'keyword' });
const stats = await tg.channels.getStats(channelId);
const status = await tg.channels.getStatus(channelId);
// Tasks
const tasks = await tg.tasks.list();
await tg.tasks.cancel(taskUuid);See examples/node/tg-userbot-example.ts.
Web Navigator API
REST client for URL resolution and web scraping jobs:
const webNav = diskd.utils.webNavigator({ auth, workspaceId: '...' });
// Resolve URL metadata
const meta = await webNav.resolve({ url: 'https://example.com' });
// Submit scrape job
const job = await webNav.scrape.submit({ url: 'https://example.com', depth: 1, maxPages: 10 });
const status = await webNav.scrape.getStatus(job.jobId);
const result = await webNav.scrape.getResult(job.jobId);
await webNav.scrape.cancel(job.jobId);See examples/node/web-navigator-example.ts.
Web quickstart (Vite + PKCE)
Use @diskd-ai/sdk/browser and a standard OAuth2 Authorization Code + PKCE redirect.
Runnable example: examples/web/ (see examples/README.md).
Publishing a new version
Bump the version in
package.json:npm version patch # 0.3.0 -> 0.3.1 npm version minor # 0.3.0 -> 0.4.0 npm version major # 0.3.0 -> 1.0.0Push the commit and tag:
git push gitlab main --tagsThe GitLab CI pipeline triggers on
v*.*.*tags and automatically:- Builds the project
- Runs unit tests and typecheck
- Publishes to the GitLab Package Registry
Verify at:
https://gitlab.iosya.com/upgraide-v2/platform-api/-/packages
Docs and examples
- Quickstart:
docs/sdk-quickstart.md - Examples:
examples/README.md - Design:
docs/drive-session-sdk-design.md
