m3triq 0.2.9 → 0.2.11
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/README.md +19 -2
- package/dist/cli.js +3 -1
- package/dist/client.d.ts +15 -0
- package/dist/client.js +22 -0
- package/dist/commands/msa.d.ts +2 -0
- package/dist/commands/msa.js +91 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,8 +84,25 @@ m3t md results <job-id>
|
|
|
84
84
|
## Structure Prediction
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
m3t predict esmfold MKFLILLFNILCL...
|
|
88
|
-
m3t predict alphafold2 MKFLILLFNILCL... # Accurate (~
|
|
87
|
+
m3t predict esmfold MKFLILLFNILCL... # Fast (~10s, max 1024aa)
|
|
88
|
+
m3t predict alphafold2 MKFLILLFNILCL... # Accurate (~15-20min, max 2048aa)
|
|
89
|
+
|
|
90
|
+
# ESMFold2-Fast — monomer or multi-chain complex (self-hosted A100, max 2048aa total).
|
|
91
|
+
# Beats AlphaFold3 on antibody-antigen DockQ from single sequence; returns pLDDT/pTM/ipTM.
|
|
92
|
+
m3t predict esmfold2 --sequence MKFLILLFNILCL... # monomer
|
|
93
|
+
m3t predict esmfold2 --chain A:EVQL... --chain B:DIQM... # complex (antibody-antigen, PPI)
|
|
94
|
+
m3t predict esmfold2-batch inputs.json # fold N complexes (results in input order)
|
|
95
|
+
|
|
96
|
+
# Boltz-2 — biomolecular complex (protein + DNA/RNA + ligand) with binding affinity
|
|
97
|
+
m3t predict boltz2 --protein MKFL... --ligand "CC(=O)O" --rna GGUC...
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Protein Embeddings
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# ESMC-6B per-residue embeddings + pseudo-perplexity (lower = more "natural" sequence)
|
|
104
|
+
m3t embed esmc --sequence MKFLILLFNILCL... # single sequence
|
|
105
|
+
m3t embed esmc --sequence SEQ1 --sequence SEQ2 # batch (repeatable, max 32)
|
|
89
106
|
```
|
|
90
107
|
|
|
91
108
|
## Protein Design
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerSessionCommands } from './commands/sessions.js';
|
|
|
10
10
|
import { registerJobCommands } from './commands/jobs.js';
|
|
11
11
|
import { registerDockingCommands } from './commands/docking.js';
|
|
12
12
|
import { registerPredictCommands } from './commands/predict.js';
|
|
13
|
+
import { registerMsaCommands } from './commands/msa.js';
|
|
13
14
|
import { registerChemblCommands } from './commands/chembl.js';
|
|
14
15
|
import { registerSandboxCommands } from './commands/sandbox.js';
|
|
15
16
|
import { registerDesignCommands } from './commands/design.js';
|
|
@@ -24,7 +25,7 @@ const program = new Command();
|
|
|
24
25
|
program
|
|
25
26
|
.name('m3t')
|
|
26
27
|
.description('M3TRIQ — protein-ligand analysis from the terminal')
|
|
27
|
-
.version('0.2.
|
|
28
|
+
.version('0.2.11')
|
|
28
29
|
.option('--json', 'Output as JSON (machine-readable)')
|
|
29
30
|
.hook('preAction', (thisCommand) => {
|
|
30
31
|
const opts = thisCommand.optsWithGlobals();
|
|
@@ -37,6 +38,7 @@ registerSessionCommands(program);
|
|
|
37
38
|
registerJobCommands(program);
|
|
38
39
|
registerDockingCommands(program);
|
|
39
40
|
registerPredictCommands(program);
|
|
41
|
+
registerMsaCommands(program);
|
|
40
42
|
registerChemblCommands(program);
|
|
41
43
|
registerSandboxCommands(program);
|
|
42
44
|
registerDesignCommands(program);
|
package/dist/client.d.ts
CHANGED
|
@@ -81,6 +81,21 @@ export declare class M3triqClient {
|
|
|
81
81
|
}): Promise<{
|
|
82
82
|
job_id: string;
|
|
83
83
|
}>;
|
|
84
|
+
createMsaJob(params: {
|
|
85
|
+
project_id: string;
|
|
86
|
+
chains: {
|
|
87
|
+
id: string;
|
|
88
|
+
sequence: string;
|
|
89
|
+
}[];
|
|
90
|
+
depth: 'fast' | 'deep';
|
|
91
|
+
pair: boolean;
|
|
92
|
+
title?: string;
|
|
93
|
+
}): Promise<{
|
|
94
|
+
job_id: string;
|
|
95
|
+
task_id?: string;
|
|
96
|
+
depth?: string;
|
|
97
|
+
n_chains?: number;
|
|
98
|
+
}>;
|
|
84
99
|
createBoltz2Job(params: Boltz2JobParams): Promise<{
|
|
85
100
|
job_id: string;
|
|
86
101
|
}>;
|
package/dist/client.js
CHANGED
|
@@ -126,6 +126,28 @@ export class M3triqClient {
|
|
|
126
126
|
async createEsmfold2BatchJob(params) {
|
|
127
127
|
return this.request('POST', '/api/jobs/create_esmfold2_batch/', params);
|
|
128
128
|
}
|
|
129
|
+
async createMsaJob(params) {
|
|
130
|
+
// MSA generation is an async job created via the internal prediction endpoint
|
|
131
|
+
// (same path Boltz-2 uses). The server's is_msa branch keys off job_type.
|
|
132
|
+
const url = `${this.baseUrl}/api/jobs/create_prediction_internal/`;
|
|
133
|
+
const res = await fetch(url, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { ...this.headers, 'X-Internal-Service': 'true' },
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
project_id: params.project_id,
|
|
138
|
+
job_type: 'msa_generation',
|
|
139
|
+
title: params.title,
|
|
140
|
+
chains: params.chains,
|
|
141
|
+
depth: params.depth,
|
|
142
|
+
pair: params.pair,
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
if (!res.ok) {
|
|
146
|
+
const text = await res.text();
|
|
147
|
+
throw new Error(`API error ${res.status}: ${text.substring(0, 200)}`);
|
|
148
|
+
}
|
|
149
|
+
return res.json();
|
|
150
|
+
}
|
|
129
151
|
async createBoltz2Job(params) {
|
|
130
152
|
// Boltz-2 prediction is run client-side via the agents MCP, then saved here as a completed job.
|
|
131
153
|
// Mirrors the RFantibody internal flow but with status='completed' and result_data already populated.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createClient, getConsoleUrl } from '../cli.js';
|
|
3
|
+
import { requireProject } from '../config.js';
|
|
4
|
+
import { output } from '../output.js';
|
|
5
|
+
import { jobUrl, maybeOpenBrowser } from '../url.js';
|
|
6
|
+
// m3t msa — generate a multiple-sequence alignment (a3m) for one or more chains.
|
|
7
|
+
// fast = AlphaFold2-NIM reduced DBs; deep = self-hosted ColabFold (UniRef30 +
|
|
8
|
+
// species-paired) on an A100. Produces a downloadable .a3m artifact per chain.
|
|
9
|
+
export function registerMsaCommands(program) {
|
|
10
|
+
program.command('msa')
|
|
11
|
+
.description('Generate a multiple-sequence alignment (a3m) — per-chain and (for complexes) species-paired')
|
|
12
|
+
.option('--chain <id:seq>', 'Chain as "id:sequence" or "id:@path". Repeatable for multi-chain (paired) MSAs.', collectArg, [])
|
|
13
|
+
.option('--sequence <seq>', 'Single-chain shortcut (chain id "A"). Use --chain for multi-chain.')
|
|
14
|
+
.option('--depth <tier>', 'MSA depth: "fast" (reduced DBs, ~quick) or "deep" (ColabFold UniRef30 + paired, A100)', 'fast')
|
|
15
|
+
.option('--pair', 'Compute species-paired MSA across chains (complexes). Default on for ≥2 distinct chains.')
|
|
16
|
+
.option('--no-pair', 'Skip paired MSA (per-chain only).')
|
|
17
|
+
.option('--name <name>', 'Custom job title')
|
|
18
|
+
.action(async (opts) => {
|
|
19
|
+
await runMsa(opts);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function runMsa(opts) {
|
|
23
|
+
const depth = (opts.depth || 'fast').toLowerCase();
|
|
24
|
+
if (depth !== 'fast' && depth !== 'deep') {
|
|
25
|
+
process.stderr.write(`Error: --depth must be "fast" or "deep", got "${opts.depth}".\n`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const chains = [];
|
|
29
|
+
for (const raw of opts.chain) {
|
|
30
|
+
const colon = raw.indexOf(':');
|
|
31
|
+
if (colon <= 0) {
|
|
32
|
+
process.stderr.write(`Error: --chain must be "id:sequence" or "id:@path", got "${raw}".\n`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const id = raw.slice(0, colon).trim();
|
|
36
|
+
const seq = resolveSequence(raw.slice(colon + 1));
|
|
37
|
+
if (!id) {
|
|
38
|
+
process.stderr.write('Error: --chain id cannot be empty.\n');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
chains.push({ id, sequence: seq });
|
|
42
|
+
}
|
|
43
|
+
if (chains.length === 0 && opts.sequence) {
|
|
44
|
+
chains.push({ id: 'A', sequence: resolveSequence(opts.sequence) });
|
|
45
|
+
}
|
|
46
|
+
if (chains.length === 0) {
|
|
47
|
+
process.stderr.write('Error: provide at least one --chain "id:seq" or --sequence "<seq>".\n');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
// pair only matters for ≥2 distinct chains; let the server make the final call too.
|
|
51
|
+
const distinct = new Set(chains.map(c => c.sequence));
|
|
52
|
+
const pair = opts.pair && chains.length >= 2 && distinct.size >= 2;
|
|
53
|
+
const project = requireProject();
|
|
54
|
+
const client = createClient();
|
|
55
|
+
const consoleUrl = getConsoleUrl();
|
|
56
|
+
const result = await client.createMsaJob({
|
|
57
|
+
project_id: project.id,
|
|
58
|
+
chains,
|
|
59
|
+
depth: depth,
|
|
60
|
+
pair,
|
|
61
|
+
title: opts.name,
|
|
62
|
+
});
|
|
63
|
+
const url = jobUrl(consoleUrl, project.id, result.job_id);
|
|
64
|
+
maybeOpenBrowser(url);
|
|
65
|
+
const timeEst = depth === 'deep'
|
|
66
|
+
? '~3-10 min (ColabFold A100; +cold start if scaled to zero)'
|
|
67
|
+
: '~2-5 min (reduced DBs; +cold start if scaled to zero)';
|
|
68
|
+
const lines = [
|
|
69
|
+
`MSA job queued (${chains.length} chain${chains.length > 1 ? 's' : ''}, depth=${depth}${pair ? ', paired' : ''})`,
|
|
70
|
+
`Job ID: ${result.job_id.substring(0, 8)}`,
|
|
71
|
+
`Estimated: ${timeEst}`,
|
|
72
|
+
`Output: one .a3m artifact per chain (Files tab)`,
|
|
73
|
+
`View: ${url}`,
|
|
74
|
+
];
|
|
75
|
+
output({ job_id: result.job_id, n_chains: chains.length, depth, pair, url }, lines.join('\n'));
|
|
76
|
+
}
|
|
77
|
+
function collectArg(value, prev) {
|
|
78
|
+
return [...prev, value];
|
|
79
|
+
}
|
|
80
|
+
function resolveSequence(input) {
|
|
81
|
+
// @path → read file (FASTA-aware: strip header/whitespace)
|
|
82
|
+
if (input.startsWith('@')) {
|
|
83
|
+
const path = input.slice(1);
|
|
84
|
+
if (!fs.existsSync(path)) {
|
|
85
|
+
throw new Error(`Sequence file not found: ${path}`);
|
|
86
|
+
}
|
|
87
|
+
const raw = fs.readFileSync(path, 'utf-8');
|
|
88
|
+
return raw.split('\n').filter(line => !line.startsWith('>')).join('').replace(/\s+/g, '').toUpperCase();
|
|
89
|
+
}
|
|
90
|
+
return input.replace(/\s+/g, '').toUpperCase();
|
|
91
|
+
}
|