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 CHANGED
@@ -84,8 +84,25 @@ m3t md results <job-id>
84
84
  ## Structure Prediction
85
85
 
86
86
  ```bash
87
- m3t predict esmfold MKFLILLFNILCL... # Fast (~10s, max 1024aa)
88
- m3t predict alphafold2 MKFLILLFNILCL... # Accurate (~5min, max 2048aa)
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.9')
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,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerMsaCommands(program: Command): void;
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "m3triq",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "M3TRIQ \u2014 protein-ligand analysis from the terminal",
5
5
  "type": "module",
6
6
  "bin": {