m3triq 0.2.0 → 0.2.1

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/dist/cli.js CHANGED
@@ -17,11 +17,13 @@ import { registerFoodbCommands } from './commands/foodb.js';
17
17
  import { registerAdmetCommands } from './commands/admet.js';
18
18
  import { registerMdCommands } from './commands/md.js';
19
19
  import { registerZincCommands } from './commands/zinc.js';
20
+ import { registerCreditsCommands } from './commands/credits.js';
21
+ import { registerPricingCommands } from './commands/pricing.js';
20
22
  const program = new Command();
21
23
  program
22
24
  .name('m3t')
23
25
  .description('M3TRIQ — protein-ligand analysis from the terminal')
24
- .version('0.1.0')
26
+ .version('0.2.1')
25
27
  .option('--json', 'Output as JSON (machine-readable)')
26
28
  .hook('preAction', (thisCommand) => {
27
29
  const opts = thisCommand.optsWithGlobals();
@@ -42,6 +44,8 @@ registerFoodbCommands(program);
42
44
  registerAdmetCommands(program);
43
45
  registerMdCommands(program);
44
46
  registerZincCommands(program);
47
+ registerCreditsCommands(program);
48
+ registerPricingCommands(program);
45
49
  // Catch async errors from all command actions
46
50
  program.parseAsync().catch((err) => {
47
51
  const message = err.message || String(err);
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Job, Project, ChatSession, DockingParams, BatchDockingParams, DiffDockParams, StructurePredictionParams, SandboxParams, McpCallResult } from './types.js';
1
+ import type { Job, Project, ChatSession, DockingParams, BatchDockingParams, DiffDockParams, StructurePredictionParams, SandboxParams, McpCallResult, CreditQuota, CreditLogResponse, Boltz2JobParams, PricingCatalog } from './types.js';
2
2
  export declare class M3triqClient {
3
3
  private baseUrl;
4
4
  private apiKey;
@@ -39,9 +39,21 @@ export declare class M3triqClient {
39
39
  createStructurePrediction(params: StructurePredictionParams): Promise<{
40
40
  job_id: string;
41
41
  }>;
42
+ createBoltz2Job(params: Boltz2JobParams): Promise<{
43
+ job_id: string;
44
+ }>;
42
45
  createSandboxJob(params: SandboxParams): Promise<{
43
46
  job_id: string;
44
47
  }>;
48
+ getCredits(): Promise<CreditQuota>;
49
+ getCreditLog(params?: {
50
+ page?: number;
51
+ pageSize?: number;
52
+ event?: string;
53
+ since?: string;
54
+ until?: string;
55
+ }): Promise<CreditLogResponse>;
56
+ getPricing(): Promise<PricingCatalog>;
45
57
  }
46
58
  /**
47
59
  * Client for the agents service (MCP tool calls).
package/dist/client.js CHANGED
@@ -105,9 +105,55 @@ export class M3triqClient {
105
105
  : '/api/jobs/create_esmfold_prediction/';
106
106
  return this.request('POST', endpoint, params);
107
107
  }
108
+ async createBoltz2Job(params) {
109
+ // Boltz-2 prediction is run client-side via the agents MCP, then saved here as a completed job.
110
+ // Mirrors the RFantibody internal flow but with status='completed' and result_data already populated.
111
+ const url = `${this.baseUrl}/api/jobs/create_prediction_internal/`;
112
+ const res = await fetch(url, {
113
+ method: 'POST',
114
+ headers: {
115
+ ...this.headers,
116
+ 'X-Internal-Service': 'true',
117
+ },
118
+ body: JSON.stringify({
119
+ project_id: params.project_id,
120
+ job_type: 'boltz2_prediction',
121
+ title: params.title,
122
+ description: params.description ?? '',
123
+ result_data: params.result_data,
124
+ }),
125
+ });
126
+ if (!res.ok) {
127
+ const text = await res.text();
128
+ throw new Error(`API error ${res.status}: ${text.substring(0, 200)}`);
129
+ }
130
+ return res.json();
131
+ }
108
132
  async createSandboxJob(params) {
109
133
  return this.request('POST', '/api/jobs/create_sandbox_job/', params);
110
134
  }
135
+ // ── Credits ──────────────────────────────────────────────────
136
+ async getCredits() {
137
+ return this.request('GET', '/api/membership/user/credits/');
138
+ }
139
+ async getCreditLog(params = {}) {
140
+ const q = new URLSearchParams();
141
+ if (params.page)
142
+ q.set('page', String(params.page));
143
+ if (params.pageSize)
144
+ q.set('page_size', String(params.pageSize));
145
+ if (params.event)
146
+ q.set('event', params.event);
147
+ if (params.since)
148
+ q.set('since', params.since);
149
+ if (params.until)
150
+ q.set('until', params.until);
151
+ const qs = q.toString();
152
+ return this.request('GET', `/api/membership/user/credits/log/${qs ? `?${qs}` : ''}`);
153
+ }
154
+ async getPricing() {
155
+ return this.request('GET', '/api/membership/billing/pricing/');
156
+ }
111
157
  }
112
158
  /**
113
159
  * Client for the agents service (MCP tool calls).
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerCreditsCommands(program: Command): void;
@@ -0,0 +1,97 @@
1
+ import { createClient } from '../cli.js';
2
+ import { output, formatTable, error } from '../output.js';
3
+ function formatPercentage(pct) {
4
+ return `${pct.toFixed(1)}%`;
5
+ }
6
+ function progressBar(used, total, width = 30) {
7
+ if (total <= 0)
8
+ return '';
9
+ const ratio = Math.min(1, used / total);
10
+ const filled = Math.round(ratio * width);
11
+ const empty = width - filled;
12
+ return `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
13
+ }
14
+ function formatCredits(n) {
15
+ return n.toLocaleString();
16
+ }
17
+ function shortJobId(jobId) {
18
+ if (!jobId)
19
+ return '';
20
+ return jobId.split('-')[0] || jobId.substring(0, 8);
21
+ }
22
+ export function registerCreditsCommands(program) {
23
+ const credits = program
24
+ .command('credits')
25
+ .description('Show your account credit balance')
26
+ .action(async () => {
27
+ const client = createClient();
28
+ let q;
29
+ try {
30
+ q = await client.getCredits();
31
+ }
32
+ catch (e) {
33
+ error(e.message);
34
+ process.exit(1);
35
+ return;
36
+ }
37
+ const remainingClass = q.is_quota_exceeded ? '!! EXHAUSTED' : '';
38
+ const periodEnd = q.period_end ? new Date(q.period_end).toLocaleDateString() : '—';
39
+ const lines = [
40
+ `Tier: ${q.tier}`,
41
+ `Used: ${formatCredits(q.credits_used)} / ${formatCredits(q.monthly_credits)} ${progressBar(q.credits_used, q.monthly_credits)} ${formatPercentage(q.usage_percentage || 0)}`,
42
+ `Top-up: ${formatCredits(q.topup_credits)}`,
43
+ `Remaining: ${formatCredits(q.credits_remaining)} ${remainingClass}`.trimEnd(),
44
+ `Resets: ${periodEnd}`,
45
+ ];
46
+ output(q, lines.join('\n'));
47
+ });
48
+ credits
49
+ .command('log')
50
+ .description('Show your credit ledger (paginated)')
51
+ .option('--event <type>', 'Filter to event type: reserve|tokens|refund|reconcile|topup|reset')
52
+ .option('--since <date>', 'Events on or after this ISO date (yyyy-mm-dd)')
53
+ .option('--until <date>', 'Events on or before this ISO date (yyyy-mm-dd)')
54
+ .option('--page <n>', 'Page number (default 1)', '1')
55
+ .option('--limit <n>', 'Results per page (default 25, max 200)', '25')
56
+ .action(async (opts) => {
57
+ const client = createClient();
58
+ let response;
59
+ try {
60
+ response = await client.getCreditLog({
61
+ page: parseInt(opts.page, 10),
62
+ pageSize: parseInt(opts.limit, 10),
63
+ event: opts.event,
64
+ since: opts.since,
65
+ until: opts.until,
66
+ });
67
+ }
68
+ catch (e) {
69
+ error(e.message);
70
+ process.exit(1);
71
+ return;
72
+ }
73
+ if (response.results.length === 0) {
74
+ output(response, 'No credit events match these filters.');
75
+ return;
76
+ }
77
+ const rows = response.results.map(evt => {
78
+ const detail = evt.note
79
+ || (evt.metadata && (evt.metadata.job_type || evt.metadata.model))
80
+ || '';
81
+ const sign = evt.credits > 0 ? '-' : '+';
82
+ const date = new Date(evt.created_at).toISOString().replace('T', ' ').substring(0, 16);
83
+ return [
84
+ date,
85
+ evt.event,
86
+ `${sign}${Math.abs(evt.credits).toLocaleString()}`,
87
+ detail.toString().substring(0, 50),
88
+ shortJobId(evt.job_id),
89
+ ];
90
+ });
91
+ const page = parseInt(opts.page, 10);
92
+ const limit = parseInt(opts.limit, 10);
93
+ const totalPages = Math.max(1, Math.ceil(response.count / limit));
94
+ const footer = `\nPage ${page} of ${totalPages} • ${response.count.toLocaleString()} total events`;
95
+ output(response, formatTable(['Date (UTC)', 'Event', 'Credits', 'Detail', 'Job'], rows) + footer);
96
+ });
97
+ }
@@ -52,7 +52,7 @@ export function registerDockingCommands(program) {
52
52
  .argument('<smiles>', 'SMILES string of the ligand')
53
53
  .argument('<pdb>', 'PDB ID (e.g., 5NJ8) or path to .pdb file')
54
54
  .option('--exhaustiveness <n>', 'Search exhaustiveness', parseInt)
55
- .description('Dock with GNINA CNN scoring (Azure T4 GPU, ~6s/compound)')).action(async (smiles, pdb, opts) => {
55
+ .description('Dock with GNINA CNN scoring (GCP T4 GPU, ~3-4 min/job incl. GPU spin-up)')).action(async (smiles, pdb, opts) => {
56
56
  await runSiteDock(smiles, pdb, 'gnina', opts);
57
57
  });
58
58
  // m3t dock vina
@@ -60,7 +60,7 @@ export function registerDockingCommands(program) {
60
60
  .argument('<smiles>', 'SMILES string of the ligand')
61
61
  .argument('<pdb>', 'PDB ID (e.g., 5NJ8) or path to .pdb file')
62
62
  .option('--exhaustiveness <n>', 'Search exhaustiveness', parseInt)
63
- .description('Dock with AutoDock Vina (Cloud Run CPU, ~30s/compound)')).action(async (smiles, pdb, opts) => {
63
+ .description('Dock with AutoDock Vina (Cloud Run CPU, ~2-3 min/compound)')).action(async (smiles, pdb, opts) => {
64
64
  await runSiteDock(smiles, pdb, 'vina', opts);
65
65
  });
66
66
  // m3t dock diffdock
@@ -91,14 +91,14 @@ export function registerDockingCommands(program) {
91
91
  addSiteOptions(batch.command('gnina')
92
92
  .argument('<file>', 'Compounds file (CSV/JSON, - for stdin)')
93
93
  .argument('<pdb>', 'PDB ID (e.g., 5NJ8) or path to .pdb file')
94
- .description('Batch dock with GNINA (~6s/compound)')).action(async (file, pdb, opts) => {
94
+ .description('Batch dock with GNINA (GCP T4 GPU, ~3-4 min/job for small batches)')).action(async (file, pdb, opts) => {
95
95
  await runBatchDock(file, pdb, 'gnina', opts);
96
96
  });
97
97
  // m3t batch vina
98
98
  addSiteOptions(batch.command('vina')
99
99
  .argument('<file>', 'Compounds file (CSV/JSON, - for stdin)')
100
100
  .argument('<pdb>', 'PDB ID (e.g., 5NJ8) or path to .pdb file')
101
- .description('Batch dock with Vina (~30s/compound)')).action(async (file, pdb, opts) => {
101
+ .description('Batch dock with Vina (Cloud Run CPU, ~2-3 min/compound)')).action(async (file, pdb, opts) => {
102
102
  await runBatchDock(file, pdb, 'vina', opts);
103
103
  });
104
104
  }
@@ -124,7 +124,7 @@ async function runSiteDock(smiles, pdb, method, opts) {
124
124
  });
125
125
  const url = jobUrl(consoleUrl, project.id, result.job_id);
126
126
  maybeOpenBrowser(url);
127
- const timeEst = method === 'gnina' ? '~6s' : '~30s';
127
+ const timeEst = method === 'gnina' ? '~3-4 min (incl. GPU spin-up)' : '~2-3 min';
128
128
  const data = { job_id: result.job_id, method, url };
129
129
  output(data, `${method.toUpperCase()} docking job created\nJob ID: ${result.job_id.substring(0, 8)}\nEstimated: ${timeEst}`);
130
130
  }
@@ -150,8 +150,11 @@ async function runBatchDock(file, pdb, method, opts) {
150
150
  });
151
151
  const url = jobUrl(consoleUrl, project.id, result.job_id);
152
152
  maybeOpenBrowser(url);
153
- const perCompound = method === 'gnina' ? 6 : 30;
154
- const totalMin = Math.ceil((compounds.length * perCompound) / 60);
153
+ // GNINA is provisioning-bound (~constant per job for small batches); Vina
154
+ // scales per compound on CPU.
155
+ const totalMin = method === 'gnina'
156
+ ? Math.max(4, Math.ceil(compounds.length * 0.05))
157
+ : Math.ceil(compounds.length * 2.5);
155
158
  const data = { job_id: result.job_id, compounds: compounds.length, method, url };
156
159
  output(data, `${method.toUpperCase()} batch docking created\nJob ID: ${result.job_id.substring(0, 8)}\nCompounds: ${compounds.length}\nEstimated: ~${totalMin} minutes`);
157
160
  }
@@ -1,9 +1,10 @@
1
- import { createClient, getConsoleUrl } from '../cli.js';
1
+ import fs from 'node:fs';
2
+ import { createAgentsClient, createClient, getConsoleUrl } from '../cli.js';
2
3
  import { requireProject } from '../config.js';
3
4
  import { output } from '../output.js';
4
5
  import { jobUrl, maybeOpenBrowser } from '../url.js';
5
6
  export function registerPredictCommands(program) {
6
- const predict = program.command('predict').description('Predict 3D protein structure from sequence');
7
+ const predict = program.command('predict').description('Predict 3D structure from sequence (single protein or biomolecular complex)');
7
8
  // m3t predict esmfold
8
9
  predict.command('esmfold')
9
10
  .argument('<sequence>', 'Amino acid sequence (single letter codes, max 1024aa)')
@@ -16,10 +17,24 @@ export function registerPredictCommands(program) {
16
17
  predict.command('alphafold2')
17
18
  .argument('<sequence>', 'Amino acid sequence (single letter codes, max 2048aa)')
18
19
  .option('--name <name>', 'Name for the prediction')
19
- .description('High-accuracy prediction with AlphaFold2 (~5 minutes, scale-to-zero VM)')
20
+ .description('High-accuracy prediction with AlphaFold2 (~15-20 min, varies with length; scale-to-zero VM)')
20
21
  .action(async (sequence, opts) => {
21
22
  await runPredict(sequence, 'alphafold2', opts.name);
22
23
  });
24
+ // m3t predict boltz2
25
+ predict.command('boltz2')
26
+ .description('Predict biomolecular complex (protein + DNA/RNA + ligand) with binding affinity (Boltz-2 / NVIDIA NIM, ~1-5 min)')
27
+ .option('--protein <seq>', 'Protein sequence (or @path to file). Repeatable for multi-chain complexes.', collectArg, [])
28
+ .option('--dna <seq>', 'DNA sequence (or @path to file). Repeatable.', collectArg, [])
29
+ .option('--rna <seq>', 'RNA sequence (or @path to file). Repeatable.', collectArg, [])
30
+ .option('--ligand <smiles_or_ccd>', 'Ligand as SMILES or CCD code (e.g. ATP). Repeatable.', collectArg, [])
31
+ .option('--samples <n>', 'Number of structure samples (1-25, default: 1)', parseInt, 1)
32
+ .option('--recycling <n>', 'Recycling steps (1-10, default: 3)', parseInt, 3)
33
+ .option('--sampling <n>', 'Diffusion sampling steps (10-1000, default: 50)', parseInt, 50)
34
+ .option('--name <name>', 'Custom job title')
35
+ .action(async (opts) => {
36
+ await runBoltz2(opts);
37
+ });
23
38
  }
24
39
  async function runPredict(sequence, method, name) {
25
40
  const project = requireProject();
@@ -33,7 +48,123 @@ async function runPredict(sequence, method, name) {
33
48
  });
34
49
  const url = jobUrl(consoleUrl, project.id, result.job_id);
35
50
  maybeOpenBrowser(url);
36
- const timeEst = method === 'alphafold2' ? '~5 minutes' : '~10 seconds';
51
+ const timeEst = method === 'alphafold2' ? '~15-20 minutes (varies with length)' : '~10 seconds';
37
52
  const data = { job_id: result.job_id, method, sequence_length: sequence.length, url };
38
53
  output(data, `${method} prediction created\nJob ID: ${result.job_id.substring(0, 8)}\nLength: ${sequence.length} residues\nEstimated: ${timeEst}`);
39
54
  }
55
+ async function runBoltz2(opts) {
56
+ const polymers = [];
57
+ for (const seq of opts.protein)
58
+ polymers.push({ molecule_type: 'protein', sequence: resolveSequence(seq) });
59
+ for (const seq of opts.dna)
60
+ polymers.push({ molecule_type: 'dna', sequence: resolveSequence(seq) });
61
+ for (const seq of opts.rna)
62
+ polymers.push({ molecule_type: 'rna', sequence: resolveSequence(seq) });
63
+ if (polymers.length === 0) {
64
+ process.stderr.write('Error: At least one --protein, --dna, or --rna is required.\n');
65
+ process.exit(1);
66
+ }
67
+ const ligands = opts.ligand.map(parseLigand);
68
+ const project = requireProject();
69
+ const client = createClient();
70
+ const agents = createAgentsClient();
71
+ const consoleUrl = getConsoleUrl();
72
+ const summary = describeComplex(polymers, ligands);
73
+ process.stderr.write(`Boltz-2: ${summary}\n`);
74
+ process.stderr.write('Running prediction (NVIDIA NIM, ~1-5 min)...');
75
+ const startTime = Date.now();
76
+ const mcpData = await agents.callMcpTool('bionemo', 'predict_structure_boltz2', {
77
+ polymers,
78
+ ligands: ligands.length > 0 ? ligands : undefined,
79
+ diffusion_samples: opts.samples,
80
+ recycling_steps: opts.recycling,
81
+ sampling_steps: opts.sampling,
82
+ });
83
+ const result = mcpData;
84
+ if (result.error || !result.success) {
85
+ const msg = result.error ?? result.message ?? 'Unknown error';
86
+ process.stderr.write(`\nFailed: ${msg}\n`);
87
+ process.exit(1);
88
+ }
89
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
90
+ process.stderr.write(` done (${elapsed}s).\n`);
91
+ const title = opts.name ?? `Boltz-2: ${result.complex || summary}`;
92
+ const job = await client.createBoltz2Job({
93
+ project_id: project.id,
94
+ title,
95
+ description: `Boltz-2 biomolecular complex prediction. ${result.quality ?? ''}`.trim(),
96
+ result_data: {
97
+ complex: result.complex,
98
+ structure_data: result.structure_data,
99
+ output_format: result.output_format,
100
+ confidence_scores: result.confidence_scores,
101
+ best_confidence: result.best_confidence,
102
+ affinities: result.affinities,
103
+ metrics: result.metrics,
104
+ quality: result.quality,
105
+ num_structures_returned: result.num_structures_returned,
106
+ diffusion_samples: result.diffusion_samples,
107
+ model: 'boltz2',
108
+ },
109
+ });
110
+ const url = jobUrl(consoleUrl, project.id, job.job_id);
111
+ maybeOpenBrowser(url);
112
+ const lines = [
113
+ `Boltz-2 prediction complete in ${elapsed}s`,
114
+ `Job ID: ${job.job_id.substring(0, 8)}`,
115
+ `Complex: ${result.complex}`,
116
+ `Quality: ${result.quality ?? 'unknown'}`,
117
+ ];
118
+ if (result.best_confidence !== null && result.best_confidence !== undefined) {
119
+ lines.push(`Best confidence: ${result.best_confidence.toFixed(3)}`);
120
+ }
121
+ if (result.affinities && Object.keys(result.affinities).length > 0) {
122
+ lines.push(`Affinities: ${JSON.stringify(result.affinities)}`);
123
+ }
124
+ lines.push(`View: ${url}`);
125
+ output({
126
+ job_id: job.job_id,
127
+ elapsed_seconds: elapsed,
128
+ complex: result.complex,
129
+ quality: result.quality,
130
+ best_confidence: result.best_confidence,
131
+ affinities: result.affinities,
132
+ url,
133
+ }, lines.join('\n'));
134
+ }
135
+ function collectArg(value, prev) {
136
+ return [...prev, value];
137
+ }
138
+ function resolveSequence(input) {
139
+ // @path → read file (FASTA-aware: strip header/whitespace)
140
+ if (input.startsWith('@')) {
141
+ const path = input.slice(1);
142
+ if (!fs.existsSync(path)) {
143
+ throw new Error(`Sequence file not found: ${path}`);
144
+ }
145
+ const raw = fs.readFileSync(path, 'utf-8');
146
+ return raw
147
+ .split('\n')
148
+ .filter(line => !line.startsWith('>'))
149
+ .join('')
150
+ .replace(/\s+/g, '')
151
+ .toUpperCase();
152
+ }
153
+ return input.replace(/\s+/g, '').toUpperCase();
154
+ }
155
+ function parseLigand(input) {
156
+ // CCD codes are 1-5 uppercase letters/digits with no SMILES-specific chars.
157
+ // SMILES typically contain at least one of: ()=[]#+/\.@-/lowercase-atom-symbol.
158
+ const looksLikeCcd = /^[A-Z0-9]{1,5}$/.test(input);
159
+ const looksLikeSmiles = /[()\[\]=#@\\/.+-]/.test(input) || /[a-z]/.test(input);
160
+ if (looksLikeCcd && !looksLikeSmiles)
161
+ return { ccd_code: input };
162
+ return { smiles: input };
163
+ }
164
+ function describeComplex(polymers, ligands) {
165
+ const parts = polymers.map(p => `${p.molecule_type}(${p.sequence.length})`);
166
+ if (ligands.length > 0) {
167
+ parts.push(`ligand×${ligands.length}`);
168
+ }
169
+ return parts.join(' + ');
170
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerPricingCommands(program: Command): void;
@@ -0,0 +1,36 @@
1
+ import { createClient } from '../cli.js';
2
+ import { output, formatTable, error } from '../output.js';
3
+ export function registerPricingCommands(program) {
4
+ program
5
+ .command('pricing')
6
+ .description('Show what each operation costs in credits')
7
+ .action(async () => {
8
+ const client = createClient();
9
+ let p;
10
+ try {
11
+ p = await client.getPricing();
12
+ }
13
+ catch (e) {
14
+ error(e.message);
15
+ process.exit(1);
16
+ return;
17
+ }
18
+ const jobRows = p.jobs.map(j => [j.label, j.credits.toLocaleString(), j.unit]);
19
+ const mdRows = p.md_simulation.map(m => [m.label, m.credits.toLocaleString(), m.unit]);
20
+ const lines = [
21
+ 'What things cost (credits)',
22
+ '',
23
+ ` ${p.note}`,
24
+ '',
25
+ formatTable(['Service', 'Credits', 'Metering'], jobRows),
26
+ '',
27
+ formatTable(['Molecular dynamics', 'Credits', 'Duration'], mdRows),
28
+ '',
29
+ ` ${p.sandbox.label}: ${p.sandbox.credits.toLocaleString()} cr (${p.sandbox.unit})`,
30
+ ` ${p.ai.label}: ${p.ai.range} (${p.ai.unit})`,
31
+ '',
32
+ ' Runtime-metered jobs reserve an estimate up front and reconcile to actual usage.',
33
+ ];
34
+ output(p, lines.join('\n'));
35
+ });
36
+ }
package/dist/types.d.ts CHANGED
@@ -89,6 +89,46 @@ export interface DiffDockParams {
89
89
  protein_pdb: string;
90
90
  num_samples?: number;
91
91
  }
92
+ export type Boltz2MoleculeType = 'protein' | 'dna' | 'rna';
93
+ export interface Boltz2Polymer {
94
+ molecule_type: Boltz2MoleculeType;
95
+ sequence: string;
96
+ id?: string;
97
+ }
98
+ export interface Boltz2Ligand {
99
+ smiles?: string;
100
+ ccd_code?: string;
101
+ }
102
+ export interface Boltz2McpParams {
103
+ polymers: Boltz2Polymer[];
104
+ ligands?: Boltz2Ligand[];
105
+ recycling_steps?: number;
106
+ sampling_steps?: number;
107
+ diffusion_samples?: number;
108
+ step_scale?: number;
109
+ }
110
+ export interface Boltz2McpResult {
111
+ success?: boolean;
112
+ model?: string;
113
+ complex?: string;
114
+ output_format?: string;
115
+ diffusion_samples?: number;
116
+ num_structures_returned?: number;
117
+ structure_data?: string;
118
+ confidence_scores?: number[];
119
+ best_confidence?: number | null;
120
+ affinities?: Record<string, unknown> | null;
121
+ metrics?: Record<string, unknown> | null;
122
+ quality?: string;
123
+ message?: string;
124
+ error?: string;
125
+ }
126
+ export interface Boltz2JobParams {
127
+ project_id: string;
128
+ title: string;
129
+ result_data: Record<string, unknown>;
130
+ description?: string;
131
+ }
92
132
  export interface SandboxParams {
93
133
  project_id: string;
94
134
  script: string;
@@ -103,3 +143,48 @@ export interface McpCallResult {
103
143
  result?: unknown;
104
144
  [key: string]: unknown;
105
145
  }
146
+ export interface CreditUsageLogEntry {
147
+ event: string;
148
+ credits: number;
149
+ job_id?: string | null;
150
+ note?: string;
151
+ metadata?: Record<string, unknown>;
152
+ created_at: string;
153
+ }
154
+ export interface CreditQuota {
155
+ tier: string;
156
+ monthly_credits: number;
157
+ credits_used: number;
158
+ topup_credits: number;
159
+ credits_remaining: number;
160
+ usage_percentage: number;
161
+ period_start?: string;
162
+ period_end?: string;
163
+ is_quota_exceeded: boolean;
164
+ last_used?: string | null;
165
+ recent_events?: CreditUsageLogEntry[];
166
+ }
167
+ export interface CreditLogResponse {
168
+ count: number;
169
+ next: string | null;
170
+ previous: string | null;
171
+ results: CreditUsageLogEntry[];
172
+ }
173
+ export interface PricingItem {
174
+ label: string;
175
+ unit: string;
176
+ credits: number;
177
+ note?: string;
178
+ }
179
+ export interface PricingCatalog {
180
+ credit_usd: number;
181
+ note: string;
182
+ jobs: PricingItem[];
183
+ md_simulation: PricingItem[];
184
+ sandbox: PricingItem;
185
+ ai: {
186
+ label: string;
187
+ unit: string;
188
+ range: string;
189
+ };
190
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "m3triq",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "M3TRIQ — protein-ligand analysis from the terminal",
5
5
  "type": "module",
6
6
  "bin": {