mantisai-cli 3.0.0 → 3.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.
- package/README.md +2 -1
- package/bin/mantis.js +8 -4
- package/lib/constants.js +13 -0
- package/lib/container.js +8 -2
- package/lib/impl/codex-skills-service.js +19 -0
- package/lib/impl/http-mantis-client.js +43 -0
- package/lib/impl/mcp-client-service.js +1 -1
- package/lib/interfaces/mantis-client.js +1 -0
- package/lib/services/export-service.js +54 -0
- package/lib/services/setup-service.js +27 -10
- package/lib/services/tool-service.js +73 -4
- package/lib/utils/skills-sync.js +3 -0
- package/package.json +1 -1
- package/skills/mantis/SKILL.md +29 -3
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Full docs (VitePress):
|
|
|
10
10
|
- [Install](https://mantis.csail.mit.edu/docs/mantis-cli/install)
|
|
11
11
|
- [Claude Code](https://mantis.csail.mit.edu/docs/mantis-cli/claude-code)
|
|
12
12
|
- [OpenCode](https://mantis.csail.mit.edu/docs/mantis-cli/opencode)
|
|
13
|
+
- [Codex](https://mantis.csail.mit.edu/docs/mantis-cli/codex)
|
|
13
14
|
|
|
14
15
|
Source: `Mantis/docs/mantis-cli/`
|
|
15
16
|
|
|
@@ -33,7 +34,7 @@ Config: `~/.mantis/config.json`
|
|
|
33
34
|
|
|
34
35
|
| Command | Description |
|
|
35
36
|
| --- | --- |
|
|
36
|
-
| `mantis setup [claude\|opencode]` | API + space/thread, or sync editor skills |
|
|
37
|
+
| `mantis setup [claude\|opencode\|codex]` | API + space/thread, or sync editor skills |
|
|
37
38
|
| `mantis status` | Current config |
|
|
38
39
|
| `mantis select [space\|thread\|both]` | Switch space/thread |
|
|
39
40
|
| `mantis spaces list\|resolve\|set` | Scriptable space ops (JSON) |
|
package/bin/mantis.js
CHANGED
|
@@ -104,7 +104,7 @@ program
|
|
|
104
104
|
|
|
105
105
|
.description('Mantis CLI — spaces, maps, and MCP tools for AI agents')
|
|
106
106
|
|
|
107
|
-
.version('3.
|
|
107
|
+
.version('3.1.0');
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
|
|
@@ -112,15 +112,17 @@ program
|
|
|
112
112
|
|
|
113
113
|
.command('setup [provider]')
|
|
114
114
|
|
|
115
|
-
.description('Configure Mantis, or install skills for claude/opencode')
|
|
115
|
+
.description('Configure Mantis, or install skills for claude/opencode/codex')
|
|
116
116
|
|
|
117
|
-
.
|
|
117
|
+
.option('--project', 'Also sync repo-scoped skills (Codex: ./.agents/skills/)')
|
|
118
|
+
|
|
119
|
+
.action(async (provider, opts) => {
|
|
118
120
|
|
|
119
121
|
try {
|
|
120
122
|
|
|
121
123
|
if (!provider) return setup.run();
|
|
122
124
|
|
|
123
|
-
setup.runProvider(provider);
|
|
125
|
+
setup.runProvider(provider, { project: !!opts.project });
|
|
124
126
|
|
|
125
127
|
} catch (e) {
|
|
126
128
|
|
|
@@ -368,6 +370,8 @@ program
|
|
|
368
370
|
|
|
369
371
|
.allowUnknownOption()
|
|
370
372
|
|
|
373
|
+
.allowExcessArguments()
|
|
374
|
+
|
|
371
375
|
.action(async () => {
|
|
372
376
|
|
|
373
377
|
try {
|
package/lib/constants.js
CHANGED
|
@@ -10,4 +10,17 @@ export const DISABLED_MCP_TOOLS = new Set([
|
|
|
10
10
|
'create_space',
|
|
11
11
|
'create_map_from_url',
|
|
12
12
|
'modify_map_from_url',
|
|
13
|
+
// sandbox-bound: require an X-Chat-ID + live AgentSession container that the
|
|
14
|
+
// CLI has no way to provide. only usable by in-sandbox cc/oc agents.
|
|
15
|
+
'export',
|
|
16
|
+
'cite_file',
|
|
17
|
+
// notebook cell tools: hidden from cc/oc on the API side and not intended
|
|
18
|
+
// for agent/CLI use — cells round-trip through the user's notebook UI.
|
|
19
|
+
'add_cell',
|
|
20
|
+
'edit_cell',
|
|
21
|
+
'delete_cell',
|
|
22
|
+
'execute_cell',
|
|
23
|
+
'get_cell',
|
|
24
|
+
'get_cell_count',
|
|
25
|
+
'check_task_status',
|
|
13
26
|
]);
|
package/lib/container.js
CHANGED
|
@@ -7,12 +7,14 @@ import { FastGlobCodebaseIndexer } from './impl/fast-glob-codebase-indexer.js';
|
|
|
7
7
|
import { FsCsvReader } from './impl/fs-csv-reader.js';
|
|
8
8
|
import { ClaudeSkillsService } from './impl/claude-skills-service.js';
|
|
9
9
|
import { OpencodeSkillsService } from './impl/opencode-skills-service.js';
|
|
10
|
+
import { CodexSkillsService } from './impl/codex-skills-service.js';
|
|
10
11
|
import { SelectionService } from './services/selection-service.js';
|
|
11
12
|
import { SetupService } from './services/setup-service.js';
|
|
12
13
|
import { MapService } from './services/map-service.js';
|
|
13
14
|
import { QueryService } from './services/query-service.js';
|
|
14
15
|
import { ContextService } from './services/context-service.js';
|
|
15
16
|
import { ToolService } from './services/tool-service.js';
|
|
17
|
+
import { ExportService } from './services/export-service.js';
|
|
16
18
|
|
|
17
19
|
export function createContainer(overrides = {}) {
|
|
18
20
|
const configStore = overrides.configStore ?? new FileConfigStore();
|
|
@@ -24,13 +26,15 @@ export function createContainer(overrides = {}) {
|
|
|
24
26
|
const csvReader = overrides.csvReader ?? new FsCsvReader();
|
|
25
27
|
const claudeSkills = overrides.claudeSkills ?? new ClaudeSkillsService();
|
|
26
28
|
const opencodeSkills = overrides.opencodeSkills ?? new OpencodeSkillsService();
|
|
29
|
+
const codexSkills = overrides.codexSkills ?? new CodexSkillsService();
|
|
27
30
|
|
|
28
31
|
const selection = new SelectionService({ configStore, spaces, client, ui });
|
|
29
|
-
const setup = new SetupService({ configStore, selection, claudeSkills, opencodeSkills, ui });
|
|
32
|
+
const setup = new SetupService({ configStore, selection, claudeSkills, opencodeSkills, codexSkills, ui });
|
|
30
33
|
const map = new MapService({ configStore, client, spaces, csvReader, ui });
|
|
31
34
|
const query = new QueryService({ configStore, spaces });
|
|
32
35
|
const context = new ContextService({ configStore });
|
|
33
|
-
const
|
|
36
|
+
const exporter = new ExportService({ configStore, client });
|
|
37
|
+
const tools = new ToolService({ mcp, exporter });
|
|
34
38
|
|
|
35
39
|
return {
|
|
36
40
|
configStore,
|
|
@@ -42,12 +46,14 @@ export function createContainer(overrides = {}) {
|
|
|
42
46
|
csvReader,
|
|
43
47
|
claudeSkills,
|
|
44
48
|
opencodeSkills,
|
|
49
|
+
codexSkills,
|
|
45
50
|
selection,
|
|
46
51
|
setup,
|
|
47
52
|
map,
|
|
48
53
|
query,
|
|
49
54
|
context,
|
|
50
55
|
tools,
|
|
56
|
+
exporter,
|
|
51
57
|
};
|
|
52
58
|
}
|
|
53
59
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { SKILLS_DIR } from '../utils/package-root.js';
|
|
5
|
+
import { syncSkills } from '../utils/skills-sync.js';
|
|
6
|
+
|
|
7
|
+
export class CodexSkillsService {
|
|
8
|
+
sync(cwd = process.cwd(), { project = false } = {}) {
|
|
9
|
+
const globalSkillsDir = path.join(os.homedir(), '.agents', 'skills');
|
|
10
|
+
const targets = [globalSkillsDir];
|
|
11
|
+
let projectSkillsDir;
|
|
12
|
+
if (project) {
|
|
13
|
+
projectSkillsDir = path.join(cwd, '.agents', 'skills');
|
|
14
|
+
targets.push(projectSkillsDir);
|
|
15
|
+
}
|
|
16
|
+
const installed = syncSkills({ skillsDir: SKILLS_DIR, targets });
|
|
17
|
+
return { globalSkillsDir, projectSkillsDir, installed };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -75,6 +75,49 @@ export class HttpMantisClient {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Export the rows behind a Mantis URI as parquet bytes.
|
|
80
|
+
* Returns { data: Buffer, rows, fields } on success.
|
|
81
|
+
* Throws on HTTP error, surfacing the server's structured error body.
|
|
82
|
+
*/
|
|
83
|
+
async exportUri({ uri, spaceStateId, fields, includeEmbedding = false }) {
|
|
84
|
+
const { apiBaseUrl, apiKey } = this._credentials();
|
|
85
|
+
if (!spaceStateId) {
|
|
86
|
+
throw new Error('No thread configured. Run: mantis setup or mantis select thread');
|
|
87
|
+
}
|
|
88
|
+
const root = normalizeBaseUrl(apiBaseUrl);
|
|
89
|
+
const url = new URL('/api/v1/me/export/', `${root}/`);
|
|
90
|
+
const body = { uri, space_state_id: String(spaceStateId), include_embedding: Boolean(includeEmbedding) };
|
|
91
|
+
if (fields && fields.length) body.fields = fields;
|
|
92
|
+
|
|
93
|
+
const res = await fetch(url, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
Authorization: `Bearer ${apiKey}`,
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
Accept: 'application/vnd.apache.parquet, application/json',
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify(body),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (!res.ok) {
|
|
104
|
+
// error responses are JSON; reuse the shared formatter.
|
|
105
|
+
const text = await res.text();
|
|
106
|
+
let data;
|
|
107
|
+
try {
|
|
108
|
+
data = text ? JSON.parse(text) : {};
|
|
109
|
+
} catch {
|
|
110
|
+
data = { error: text || res.statusText };
|
|
111
|
+
}
|
|
112
|
+
throw new Error(formatApiError(data, res));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
116
|
+
const rows = Number(res.headers.get('x-mantis-rows')) || 0;
|
|
117
|
+
const fieldsHeader = res.headers.get('x-mantis-fields') || '';
|
|
118
|
+
return { data: buf, rows, fields: fieldsHeader ? fieldsHeader.split(',') : [] };
|
|
119
|
+
}
|
|
120
|
+
|
|
78
121
|
async createMapInSpace(spaceId, { file, mapName, dataTypes, selectedFields, fieldWeights }) {
|
|
79
122
|
const { apiBaseUrl, apiKey } = this._credentials();
|
|
80
123
|
const root = normalizeBaseUrl(apiBaseUrl);
|
|
@@ -24,7 +24,7 @@ export class McpClientService {
|
|
|
24
24
|
const transport = new StreamableHTTPClientTransport(new URL(mcpUrl(cfg)), {
|
|
25
25
|
requestInit: { headers: this._headers(cfg) },
|
|
26
26
|
});
|
|
27
|
-
const client = new Client({ name: 'mantisai-cli', version: '3.
|
|
27
|
+
const client = new Client({ name: 'mantisai-cli', version: '3.1.0' });
|
|
28
28
|
await client.connect(transport);
|
|
29
29
|
try {
|
|
30
30
|
return await fn(client);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @property {(spaceId: string, name?: string) => Promise<Thread>} createSpaceState
|
|
9
9
|
* @property {(opts: { name: string, isPublic?: boolean }) => Promise<Space>} createSpace
|
|
10
10
|
* @property {(spaceId: string, opts: object) => Promise<object>} createMapInSpace
|
|
11
|
+
* @property {(opts: { uri: string, spaceStateId: string, fields?: string[], includeEmbedding?: boolean }) => Promise<{ data: Buffer, rows: number, fields: string[] }>} exportUri
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const EXPORT_SUBDIR = 'mantis_data';
|
|
6
|
+
|
|
7
|
+
/** Local sandbox dir for exported parquet files: ~/.mantis/mantis_data/ */
|
|
8
|
+
export function exportDir() {
|
|
9
|
+
return path.join(os.homedir(), '.mantis', EXPORT_SUBDIR);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function suggestFilename(uri, nRows) {
|
|
13
|
+
const tail = String(uri).replace(/\/+$/, '').split('/').pop() || 'export';
|
|
14
|
+
const safe = tail.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 40);
|
|
15
|
+
return `${safe}_${nRows}rows.parquet`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetches the parquet behind a Mantis URI from the API and writes it under
|
|
20
|
+
* ~/.mantis/mantis_data/. The CLI-local twin of the in-sandbox `export` MCP
|
|
21
|
+
* tool — same parquet, written to the user's own machine instead of a
|
|
22
|
+
* container.
|
|
23
|
+
*/
|
|
24
|
+
export class ExportService {
|
|
25
|
+
constructor({ configStore, client }) {
|
|
26
|
+
this.configStore = configStore;
|
|
27
|
+
this.client = client;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async exportUri(uri, { fields, out, includeEmbedding = false } = {}) {
|
|
31
|
+
if (!uri) throw new Error('A Mantis URI is required (e.g. mantis://map/<id>).');
|
|
32
|
+
const cfg = this.configStore.requireAuth();
|
|
33
|
+
if (!cfg.spaceStateId) {
|
|
34
|
+
throw new Error('No thread configured. Run: mantis setup or mantis select thread');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { data, rows, fields: cols } = await this.client.exportUri({
|
|
38
|
+
uri,
|
|
39
|
+
spaceStateId: cfg.spaceStateId,
|
|
40
|
+
fields,
|
|
41
|
+
includeEmbedding,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
let name = out ? path.basename(out) : suggestFilename(uri, rows);
|
|
45
|
+
if (!name.endsWith('.parquet')) name += '.parquet';
|
|
46
|
+
|
|
47
|
+
const dir = exportDir();
|
|
48
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
49
|
+
const outPath = path.join(dir, name);
|
|
50
|
+
fs.writeFileSync(outPath, data);
|
|
51
|
+
|
|
52
|
+
return { path: outPath, rows, fields: cols, format: 'parquet', size_bytes: data.length };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { DEFAULT_API_BASE, DEVELOPER_PORTAL_URL } from '../constants.js';
|
|
2
2
|
import { normalizeBaseUrl, mcpUrl } from '../utils/url.js';
|
|
3
3
|
|
|
4
|
-
const PROVIDERS = new Set(['claude', 'opencode']);
|
|
4
|
+
const PROVIDERS = new Set(['claude', 'opencode', 'codex']);
|
|
5
5
|
|
|
6
6
|
export class SetupService {
|
|
7
|
-
constructor({ configStore, selection, claudeSkills, opencodeSkills, ui }) {
|
|
7
|
+
constructor({ configStore, selection, claudeSkills, opencodeSkills, codexSkills, ui }) {
|
|
8
8
|
this.configStore = configStore;
|
|
9
9
|
this.selection = selection;
|
|
10
10
|
this.claudeSkills = claudeSkills;
|
|
11
11
|
this.opencodeSkills = opencodeSkills;
|
|
12
|
+
this.codexSkills = codexSkills;
|
|
12
13
|
this.ui = ui;
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -51,14 +52,14 @@ export class SetupService {
|
|
|
51
52
|
this.ui.info(`Thread: ${cfg.spaceStateName || '-'} (${cfg.spaceStateId || '-'})`);
|
|
52
53
|
this.ui.info(`MCP: ${mcpUrl(cfg)}`);
|
|
53
54
|
this.ui.info('Explore with: mantis tools && mantis use get_space_context');
|
|
54
|
-
this.ui.info('Editor skills: mantis setup claude |
|
|
55
|
+
this.ui.info('Editor skills: mantis setup claude | opencode | codex');
|
|
55
56
|
console.log('');
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
runProvider(provider) {
|
|
59
|
+
runProvider(provider, { project = false } = {}) {
|
|
59
60
|
const p = String(provider || '').toLowerCase();
|
|
60
61
|
if (!PROVIDERS.has(p)) {
|
|
61
|
-
throw new Error(`Unknown provider "${provider}". Use: claude, opencode`);
|
|
62
|
+
throw new Error(`Unknown provider "${provider}". Use: claude, opencode, codex`);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
if (p === 'claude') {
|
|
@@ -70,12 +71,28 @@ export class SetupService {
|
|
|
70
71
|
return { provider: p, skillsDir, installed };
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
if (p === 'opencode') {
|
|
75
|
+
const { globalSkillsDir, projectSkillsDir, installed } = this.opencodeSkills.sync();
|
|
76
|
+
this.ui.banner('Mantis skills → OpenCode');
|
|
77
|
+
this.ui.success(`Synced ${installed.length} skill(s)`);
|
|
78
|
+
this.ui.info(`Global: ${globalSkillsDir}`);
|
|
79
|
+
this.ui.info(`Project: ${projectSkillsDir}`);
|
|
80
|
+
for (const s of installed) this.ui.info(`${s.slash} ← skills/${s.source}`);
|
|
81
|
+
return { provider: p, globalSkillsDir, projectSkillsDir, installed };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { globalSkillsDir, projectSkillsDir, installed } = this.codexSkills.sync(undefined, { project });
|
|
85
|
+
this.ui.banner('Mantis skills → Codex');
|
|
75
86
|
this.ui.success(`Synced ${installed.length} skill(s)`);
|
|
76
|
-
this.ui.info(`
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
this.ui.info(`USER: ${globalSkillsDir}`);
|
|
88
|
+
if (projectSkillsDir) {
|
|
89
|
+
this.ui.info(`REPO: ${projectSkillsDir}`);
|
|
90
|
+
this.ui.info('Same skill names in USER + REPO may appear twice in Codex; disable one via /skills if needed.');
|
|
91
|
+
} else {
|
|
92
|
+
this.ui.info('Repo copy skipped. Re-run with --project to also write ./.agents/skills/ for team commit.');
|
|
93
|
+
}
|
|
94
|
+
for (const s of installed) this.ui.info(`$${s.name} ← skills/${s.source}`);
|
|
95
|
+
this.ui.info('Invoke with $mantis or /skills. Restart Codex if new skills do not appear.');
|
|
79
96
|
return { provider: p, globalSkillsDir, projectSkillsDir, installed };
|
|
80
97
|
}
|
|
81
98
|
}
|
|
@@ -1,25 +1,94 @@
|
|
|
1
1
|
import { DISABLED_MCP_TOOLS } from '../constants.js';
|
|
2
2
|
|
|
3
|
+
const NOTEBOOK_TOOLS = new Set([
|
|
4
|
+
'add_cell',
|
|
5
|
+
'edit_cell',
|
|
6
|
+
'delete_cell',
|
|
7
|
+
'execute_cell',
|
|
8
|
+
'get_cell',
|
|
9
|
+
'get_cell_count',
|
|
10
|
+
'check_task_status',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
// Synthetic tool entry for `export`. The server-side MCP `export` needs an
|
|
14
|
+
// agent sandbox the CLI can't provide, so it's filtered from the raw list;
|
|
15
|
+
// this local variant resolves the URI via REST and writes parquet under
|
|
16
|
+
// ~/.mantis/mantis_data/ on the user's machine instead.
|
|
17
|
+
const LOCAL_EXPORT_TOOL = {
|
|
18
|
+
name: 'export',
|
|
19
|
+
description:
|
|
20
|
+
'Export every row behind a Mantis URI to a local parquet file under '
|
|
21
|
+
+ '~/.mantis/mantis_data/. Use for bulk-row analysis (correlations, '
|
|
22
|
+
+ 'distributions, top-K, outliers) instead of paging inspect(). Read it '
|
|
23
|
+
+ "back with pandas: pd.read_parquet(<path>). Always adds _point_id, "
|
|
24
|
+
+ '_cluster_id, _cluster_label. Capped at 200,000 rows.',
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
uri: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Mantis URI resolving to a point set (map, cluster, bag, point, selection).',
|
|
31
|
+
},
|
|
32
|
+
fields: {
|
|
33
|
+
type: 'array',
|
|
34
|
+
items: { type: 'string' },
|
|
35
|
+
description: 'Column names to project. Omit to export all fields.',
|
|
36
|
+
},
|
|
37
|
+
filename: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Output filename (basename only). Defaults to <uri-tail>_<n>rows.parquet.',
|
|
40
|
+
},
|
|
41
|
+
include_embedding: {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
description: 'Add _embedding (1536-d) and _embedding_2d columns.',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
required: ['uri'],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function normalizeFields(fields) {
|
|
51
|
+
if (fields == null) return undefined;
|
|
52
|
+
if (Array.isArray(fields)) return fields;
|
|
53
|
+
// `--fields title,rating` arrives as a comma string via parseToolArgs.
|
|
54
|
+
return String(fields).split(',').map((s) => s.trim()).filter(Boolean);
|
|
55
|
+
}
|
|
56
|
+
|
|
3
57
|
function disabledToolError(name) {
|
|
4
58
|
if (name === 'create_space') {
|
|
5
59
|
return 'create_space is disabled in the CLI. Use: mantis setup or mantis select space';
|
|
6
60
|
}
|
|
61
|
+
if (name === 'cite_file') {
|
|
62
|
+
return `${name} is unavailable in the CLI — it needs an agent sandbox (X-Chat-ID + live container) that the CLI cannot provide. It only works for in-sandbox claude_code/opencode agents.`;
|
|
63
|
+
}
|
|
64
|
+
if (NOTEBOOK_TOOLS.has(name)) {
|
|
65
|
+
return `${name} is unavailable in the CLI — notebook cell tools round-trip through the user's notebook UI and are not intended for agent/CLI use.`;
|
|
66
|
+
}
|
|
7
67
|
return `${name} is disabled in the CLI. Use: mantis create map or mantis create codebase`;
|
|
8
68
|
}
|
|
9
69
|
|
|
10
70
|
export class ToolService {
|
|
11
|
-
constructor({ mcp }) {
|
|
71
|
+
constructor({ mcp, exporter }) {
|
|
12
72
|
this.mcp = mcp;
|
|
73
|
+
this.exporter = exporter;
|
|
13
74
|
}
|
|
14
75
|
|
|
15
76
|
async listTools() {
|
|
16
77
|
const data = await this.mcp.listTools();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
78
|
+
const tools = (data.tools || []).filter((t) => !DISABLED_MCP_TOOLS.has(t.name));
|
|
79
|
+
// inject the local export variant so it's discoverable via `mantis tools`.
|
|
80
|
+
tools.push(LOCAL_EXPORT_TOOL);
|
|
81
|
+
return { tools };
|
|
20
82
|
}
|
|
21
83
|
|
|
22
84
|
useTool(name, args = {}) {
|
|
85
|
+
if (name === 'export') {
|
|
86
|
+
return this.exporter.exportUri(args.uri, {
|
|
87
|
+
fields: normalizeFields(args.fields),
|
|
88
|
+
out: args.filename ?? args.out,
|
|
89
|
+
includeEmbedding: args.include_embedding ?? args.includeEmbedding ?? false,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
23
92
|
if (DISABLED_MCP_TOOLS.has(name)) throw new Error(disabledToolError(name));
|
|
24
93
|
return this.mcp.callTool(name, args);
|
|
25
94
|
}
|
package/lib/utils/skills-sync.js
CHANGED
|
@@ -23,6 +23,9 @@ export function prepareSkillContent(content, installName) {
|
|
|
23
23
|
yaml = /^name:\s/m.test(yaml)
|
|
24
24
|
? yaml.replace(/^name:\s.*$/m, `name: ${installName}`)
|
|
25
25
|
: `name: ${installName}\n${yaml}`;
|
|
26
|
+
if (!/^description:\s/m.test(yaml)) {
|
|
27
|
+
yaml = `${yaml}\ndescription: Mantis CLI skill (${installName}).`;
|
|
28
|
+
}
|
|
26
29
|
return `---\n${yaml}\n---${body}`;
|
|
27
30
|
}
|
|
28
31
|
|
package/package.json
CHANGED
package/skills/mantis/SKILL.md
CHANGED
|
@@ -33,6 +33,7 @@ Install editor skills (CLI-only workflow, no MCP plugin):
|
|
|
33
33
|
```bash
|
|
34
34
|
mantis setup claude # ~/.claude/skills/
|
|
35
35
|
mantis setup opencode # ~/.config/opencode/skills/ + .opencode/skills/
|
|
36
|
+
mantis setup codex # ~/.agents/skills/ (add --project for ./.agents/skills/)
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
## MCP tools via CLI
|
|
@@ -42,13 +43,38 @@ Every Mantis MCP tool is available through the CLI — no editor MCP plugin requ
|
|
|
42
43
|
```bash
|
|
43
44
|
mantis tools
|
|
44
45
|
mantis use get_space_context
|
|
45
|
-
mantis use
|
|
46
|
-
mantis use
|
|
46
|
+
mantis use search --query "your search"
|
|
47
|
+
mantis use inspect --uri "<mantis-uri>"
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
Use `--args '{"key":"value"}'` for complex arguments. Kebab flags map to snake_case (`--map-id` → `map_id`).
|
|
50
51
|
|
|
51
|
-
Use map IDs, field types, and
|
|
52
|
+
Use map IDs, field types, and URIs from the `get_space_context` output — do not guess them.
|
|
53
|
+
|
|
54
|
+
Common tools: `get_space_context`, `search`, `inspect`, `compare`, `union`/`intersect`/`diff`, the bag mutators (`create_bag`, `add_to_bag`, `filter_to_bag`, …), `set_plot_variables`, `legend_command`, `create_page`.
|
|
55
|
+
|
|
56
|
+
Not available via `mantis use`:
|
|
57
|
+
- `create_space`, `create_map_from_url`, `modify_map_from_url` — use `mantis setup` / `mantis create map` instead.
|
|
58
|
+
- `cite_file` — needs an agent sandbox the CLI can't provide; in-sandbox agents only.
|
|
59
|
+
- the notebook cell tools (`add_cell`, `execute_cell`, …) — not intended for agent use.
|
|
60
|
+
|
|
61
|
+
## Bulk export (local parquet)
|
|
62
|
+
|
|
63
|
+
`mantis use export` resolves a URI to its rows and writes a local parquet file under `~/.mantis/mantis_data/`. Use it when a question needs MANY rows (correlations, distributions, top-K, outliers) instead of paging `inspect`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
mantis use export --uri "mantis://map/<id>" # all rows
|
|
67
|
+
mantis use export --uri "mantis://map/<id>/cluster/<cid>" --fields title,rating
|
|
68
|
+
mantis use export --uri "mantis://map/<id>" --include-embedding # + _embedding (1536-d), _embedding_2d
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Prints `{path, rows, fields, size_bytes}`. Then read it locally:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python3 -c "import pandas as pd; df = pd.read_parquet('<path>'); print(df.head())"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Always-present columns: `_point_id`, `_cluster_id`, `_cluster_label` (so you can `df.groupby('_cluster_label')`). Capped at 200,000 rows — narrow the URI if you hit `too_many_rows`.
|
|
52
78
|
|
|
53
79
|
## REST via CLI (setup & resources)
|
|
54
80
|
|