m3triq 0.1.0

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.
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerSandboxCommands(program: Command): void;
@@ -0,0 +1,37 @@
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
+ export function registerSandboxCommands(program) {
7
+ program
8
+ .command('run')
9
+ .argument('<script>', 'Python script file path (- for stdin)')
10
+ .option('--packages <list>', 'Comma-separated pip packages', (v) => v.split(','))
11
+ .option('--save <name>', 'Save results as a project dataset with this name')
12
+ .option('--timeout <seconds>', 'Timeout in seconds', parseInt)
13
+ .option('--name <name>', 'Script name shown in UI')
14
+ .description('Run a Python script in secure sandbox')
15
+ .action(async (scriptPath, opts) => {
16
+ const project = requireProject();
17
+ const client = createClient();
18
+ const consoleUrl = getConsoleUrl();
19
+ const script = scriptPath === '-'
20
+ ? fs.readFileSync(0, 'utf-8')
21
+ : fs.readFileSync(scriptPath, 'utf-8');
22
+ const scriptName = opts.name || (scriptPath === '-' ? 'CLI Script' : scriptPath.split('/').pop());
23
+ const result = await client.createSandboxJob({
24
+ project_id: project.id,
25
+ script,
26
+ script_name: scriptName,
27
+ packages: opts.packages || [],
28
+ timeout: opts.timeout || 600,
29
+ save_to_dataset: !!opts.save,
30
+ dataset_name: opts.save,
31
+ });
32
+ const url = jobUrl(consoleUrl, project.id, result.job_id);
33
+ maybeOpenBrowser(url);
34
+ const data = { job_id: result.job_id, script_name: scriptName, url };
35
+ output(data, `Sandbox job created\nJob ID: ${result.job_id.substring(0, 8)}\nScript: ${scriptName}`);
36
+ });
37
+ }
@@ -0,0 +1,34 @@
1
+ export interface M3triqConfig {
2
+ api_key?: string;
3
+ api_url?: string;
4
+ agents_url?: string;
5
+ console_url?: string;
6
+ active_project?: string;
7
+ active_project_name?: string;
8
+ }
9
+ interface LocalProjectConfig {
10
+ project_id: string;
11
+ project_name?: string;
12
+ }
13
+ export declare function loadConfig(): M3triqConfig;
14
+ export declare function saveConfig(config: M3triqConfig): void;
15
+ export declare function loadLocalProject(): LocalProjectConfig | null;
16
+ /** Save .m3triq in the current working directory */
17
+ export declare function saveLocalProject(projectId: string, projectName?: string): void;
18
+ /**
19
+ * Resolution order for project:
20
+ * 1. Local .m3triq (cwd or parent)
21
+ * 2. Global ~/.m3triq/config.json
22
+ *
23
+ * Resolution order for api_key/urls:
24
+ * 1. Environment variables
25
+ * 2. Global config
26
+ * 3. Defaults
27
+ */
28
+ export declare function getEffectiveConfig(): M3triqConfig;
29
+ export declare function requireApiKey(): string;
30
+ export declare function requireProject(): {
31
+ id: string;
32
+ name?: string;
33
+ };
34
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,101 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ const LOCAL_CONFIG_FILE = '.m3triq';
5
+ // ── Global config (~/.m3triq/config.json) ───────────────────────
6
+ function configDir() {
7
+ const dir = path.join(os.homedir(), '.m3triq');
8
+ fs.mkdirSync(dir, { recursive: true });
9
+ return dir;
10
+ }
11
+ function configPath() {
12
+ return path.join(configDir(), 'config.json');
13
+ }
14
+ export function loadConfig() {
15
+ try {
16
+ const raw = fs.readFileSync(configPath(), 'utf-8');
17
+ return JSON.parse(raw);
18
+ }
19
+ catch {
20
+ return {};
21
+ }
22
+ }
23
+ export function saveConfig(config) {
24
+ const json = JSON.stringify(config, null, 2) + '\n';
25
+ fs.writeFileSync(configPath(), json, { mode: 0o600 });
26
+ }
27
+ // ── Local project config (.m3triq in cwd or parent) ─────────────
28
+ /** Walk up from cwd to find a .m3triq file, like how git finds .git */
29
+ function findLocalConfig() {
30
+ let dir = process.cwd();
31
+ const root = path.parse(dir).root;
32
+ while (true) {
33
+ const candidate = path.join(dir, LOCAL_CONFIG_FILE);
34
+ if (fs.existsSync(candidate))
35
+ return candidate;
36
+ const parent = path.dirname(dir);
37
+ if (parent === dir || parent === root)
38
+ return null;
39
+ dir = parent;
40
+ }
41
+ }
42
+ export function loadLocalProject() {
43
+ const configFile = findLocalConfig();
44
+ if (!configFile)
45
+ return null;
46
+ try {
47
+ const raw = fs.readFileSync(configFile, 'utf-8');
48
+ return JSON.parse(raw);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ /** Save .m3triq in the current working directory */
55
+ export function saveLocalProject(projectId, projectName) {
56
+ const config = { project_id: projectId };
57
+ if (projectName)
58
+ config.project_name = projectName;
59
+ const filePath = path.join(process.cwd(), LOCAL_CONFIG_FILE);
60
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
61
+ }
62
+ // ── Effective config (local > global > env) ─────────────────────
63
+ /**
64
+ * Resolution order for project:
65
+ * 1. Local .m3triq (cwd or parent)
66
+ * 2. Global ~/.m3triq/config.json
67
+ *
68
+ * Resolution order for api_key/urls:
69
+ * 1. Environment variables
70
+ * 2. Global config
71
+ * 3. Defaults
72
+ */
73
+ export function getEffectiveConfig() {
74
+ const file = loadConfig();
75
+ const local = loadLocalProject();
76
+ return {
77
+ api_key: process.env.M3TRIQ_API_KEY || file.api_key,
78
+ api_url: process.env.M3TRIQ_API_URL || file.api_url || 'https://server.m3triq.com',
79
+ agents_url: process.env.M3TRIQ_AGENTS_URL || file.agents_url || 'https://agents.m3triq.com',
80
+ console_url: process.env.M3TRIQ_CONSOLE_URL || file.console_url || 'https://console.m3triq.com',
81
+ // Local project takes precedence over global
82
+ active_project: local?.project_id || file.active_project,
83
+ active_project_name: local?.project_name || file.active_project_name,
84
+ };
85
+ }
86
+ export function requireApiKey() {
87
+ const config = getEffectiveConfig();
88
+ if (!config.api_key) {
89
+ process.stderr.write('Error: No API key configured. Run: m3t config --key <your-key>\n');
90
+ process.exit(1);
91
+ }
92
+ return config.api_key;
93
+ }
94
+ export function requireProject() {
95
+ const config = getEffectiveConfig();
96
+ if (!config.active_project) {
97
+ process.stderr.write('Error: No active project.\n Run: m3t use <id> (sets .m3triq in current directory)\n Or: m3t use --global <id> (sets globally in ~/.m3triq/config.json)\n');
98
+ process.exit(1);
99
+ }
100
+ return { id: config.active_project, name: config.active_project_name };
101
+ }
@@ -0,0 +1,8 @@
1
+ export declare function setJsonMode(enabled: boolean): void;
2
+ export declare function isJsonMode(): boolean;
3
+ /** Output data. JSON mode → structured JSON. Human mode → formatted text. */
4
+ export declare function output(data: unknown, humanText: string): void;
5
+ /** Error output. Always stderr. */
6
+ export declare function error(message: string): void;
7
+ /** Format aligned table from rows. */
8
+ export declare function formatTable(headers: string[], rows: string[][]): string;
package/dist/output.js ADDED
@@ -0,0 +1,27 @@
1
+ let jsonMode = false;
2
+ export function setJsonMode(enabled) {
3
+ jsonMode = enabled;
4
+ }
5
+ export function isJsonMode() {
6
+ return jsonMode;
7
+ }
8
+ /** Output data. JSON mode → structured JSON. Human mode → formatted text. */
9
+ export function output(data, humanText) {
10
+ if (jsonMode) {
11
+ process.stdout.write(JSON.stringify(data, null, 2) + '\n');
12
+ }
13
+ else {
14
+ process.stdout.write(humanText + '\n');
15
+ }
16
+ }
17
+ /** Error output. Always stderr. */
18
+ export function error(message) {
19
+ process.stderr.write(`Error: ${message}\n`);
20
+ }
21
+ /** Format aligned table from rows. */
22
+ export function formatTable(headers, rows) {
23
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map(r => (r[i] || '').length)));
24
+ const header = headers.map((h, i) => h.padEnd(widths[i])).join(' ');
25
+ const lines = rows.map(r => r.map((c, i) => (c || '').padEnd(widths[i])).join(' '));
26
+ return [header, ...lines].join('\n');
27
+ }
@@ -0,0 +1,71 @@
1
+ export interface Project {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ created_at: string;
6
+ }
7
+ export interface Job {
8
+ id: string;
9
+ job_type: string;
10
+ title: string;
11
+ status: string;
12
+ progress_percentage: number;
13
+ current_step: string;
14
+ result_data: Record<string, unknown> | null;
15
+ created_at: string;
16
+ completed_at: string | null;
17
+ }
18
+ export interface DockingParams {
19
+ project_id: string;
20
+ title: string;
21
+ protein_pdb: string;
22
+ ligand_smiles: string;
23
+ scoring_function?: 'vina' | 'gnina';
24
+ exhaustiveness?: number;
25
+ center_x: number;
26
+ center_y: number;
27
+ center_z: number;
28
+ size_x?: number;
29
+ size_y?: number;
30
+ size_z?: number;
31
+ num_modes?: number;
32
+ }
33
+ export interface BatchDockingParams {
34
+ project_id: string;
35
+ title: string;
36
+ protein_pdb: string;
37
+ ligand_smiles_list: string[];
38
+ scoring_function?: 'vina' | 'gnina';
39
+ center_x: number;
40
+ center_y: number;
41
+ center_z: number;
42
+ size_x?: number;
43
+ size_y?: number;
44
+ size_z?: number;
45
+ }
46
+ export interface StructurePredictionParams {
47
+ project_id: string;
48
+ sequence: string;
49
+ method?: 'esmfold' | 'alphafold2';
50
+ name?: string;
51
+ }
52
+ export interface DiffDockParams {
53
+ project_id: string;
54
+ ligand_smiles: string;
55
+ protein_pdb: string;
56
+ num_samples?: number;
57
+ }
58
+ export interface SandboxParams {
59
+ project_id: string;
60
+ script: string;
61
+ script_name?: string;
62
+ packages?: string[];
63
+ timeout?: number;
64
+ save_to_dataset?: boolean;
65
+ dataset_name?: string;
66
+ }
67
+ export interface McpCallResult {
68
+ error?: string;
69
+ result?: unknown;
70
+ [key: string]: unknown;
71
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/url.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare function jobUrl(consoleUrl: string, projectId: string, jobId: string): string;
2
+ export declare function projectUrl(consoleUrl: string, projectId: string): string;
3
+ export declare function openInBrowser(url: string): void;
4
+ /** Open browser only when stdout is a TTY (not piping) */
5
+ export declare function maybeOpenBrowser(url: string): void;
package/dist/url.js ADDED
@@ -0,0 +1,21 @@
1
+ import { exec } from 'node:child_process';
2
+ export function jobUrl(consoleUrl, projectId, jobId) {
3
+ const shortProject = projectId.substring(0, 8);
4
+ const shortJob = jobId.substring(0, 8);
5
+ return `${consoleUrl}/?project=${shortProject}&view=jobs&job=${shortJob}`;
6
+ }
7
+ export function projectUrl(consoleUrl, projectId) {
8
+ const shortProject = projectId.substring(0, 8);
9
+ return `${consoleUrl}/?project=${shortProject}`;
10
+ }
11
+ export function openInBrowser(url) {
12
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
13
+ exec(`${cmd} "${url}"`);
14
+ }
15
+ /** Open browser only when stdout is a TTY (not piping) */
16
+ export function maybeOpenBrowser(url) {
17
+ if (process.stdout.isTTY) {
18
+ openInBrowser(url);
19
+ process.stderr.write(`Opened: ${url}\n`);
20
+ }
21
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "m3triq",
3
+ "version": "0.1.0",
4
+ "description": "M3TRIQ — protein-ligand analysis from the terminal",
5
+ "type": "module",
6
+ "bin": {
7
+ "m3t": "./dist/cli.js"
8
+ },
9
+ "main": "dist/cli.js",
10
+ "keywords": ["bioinformatics", "chembl", "docking", "protein", "ligand", "drug-discovery", "cli"],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/khai-m3triq/m3t_mono.git",
14
+ "directory": "m3triq_cli"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsc --watch",
19
+ "prepublishOnly": "tsc"
20
+ },
21
+ "dependencies": {
22
+ "commander": "^13.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.0.0",
26
+ "typescript": "^5.7.0"
27
+ },
28
+ "files": ["dist"],
29
+ "license": "MIT"
30
+ }