mantisai-cli 2.0.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,14 @@
1
+ {
2
+ "name": "mantis-plugins",
3
+ "owner": {
4
+ "name": "Kellis Lab"
5
+ },
6
+ "plugins": [
7
+ {
8
+ "name": "mantis",
9
+ "description": "Mantis MCP: spaces, threads, and map context for Claude Code",
10
+ "version": "1.0.15",
11
+ "source": "./"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "mantis",
3
+ "description": "Connect Claude Code to Mantis MCP — explore maps, clusters, and notebooks in your spaces",
4
+ "version": "1.0.15",
5
+ "author": {
6
+ "name": "MIT Mantis"
7
+ },
8
+ "homepage": "https://mantis.csail.mit.edu",
9
+ "userConfig": {
10
+ "api_base_url": {
11
+ "type": "string",
12
+ "title": "Mantis API URL",
13
+ "description": "Base URL only (no path). Default is the CSAIL H200 API; use http://localhost:8000 for local Docker.",
14
+ "default": "https://kellis-h200-1.csail.mit.edu",
15
+ "required": true
16
+ }
17
+ }
18
+ }
package/.mcp.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "mantis": {
4
+ "type": "http",
5
+ "url": "http://localhost:8000/mcp_integrated/",
6
+ "headers": {
7
+ "X-Space-State-ID": "51e4a4c2-1510-4456-afe9-ccbd422c7646"
8
+ }
9
+ }
10
+ }
11
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Kellis Lab, MIT
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # mantisai-cli
2
+
3
+ Mantis developer CLI — manage spaces, create maps from local data, and configure [Claude Code](https://code.claude.com) as a Mantis client. Distributed alongside the **mantis@mantis-plugins** Claude Code plugin.
4
+
5
+ > **Naming note:** the repo is `KellisLab/mantis-cli` but the npm package is published as **`mantisai-cli`** because `mantis-cli` was already taken on npm. The binary you run is just `mantis`.
6
+
7
+ ## Requirements
8
+
9
+ - [Node.js](https://nodejs.org/) 18+
10
+ - A running Mantis API (default `https://kellis-h200-1.csail.mit.edu`; local dev: `http://localhost:8000`)
11
+ - A Mantis **Developer API key** (`live_…`) from https://mantis.csail.mit.edu/developer/#keys
12
+ - (Optional, for plugin features) [Claude Code](https://code.claude.com) (`claude` on your PATH)
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install -g mantisai-cli
18
+ mantis setup
19
+ ```
20
+
21
+ `mantis setup` walks through API URL, API key, space, and thread selection. If Claude Code is installed, it also registers the **mantis@mantis-plugins** plugin and writes the MCP config.
22
+
23
+ ## Commands
24
+
25
+ | Command | Description |
26
+ |---------|-------------|
27
+ | `mantis setup` | Connect to Mantis, pick space & thread, configure Claude Code |
28
+ | `mantis status` | Show current API, space, thread, and MCP URL |
29
+ | `mantis select [space\|thread\|both]` | Switch active space and/or thread |
30
+ | `mantis create map <file>` | Create a Mantis map from a local CSV/XLSX |
31
+ | `mantis create codebase [root]` | Index a local codebase to CSV; optionally create a map |
32
+
33
+ Run any command with `--help` for full options.
34
+
35
+ ### Examples
36
+
37
+ ```bash
38
+ # Create a new private space and load a CSV as a map
39
+ mantis create map ./data.csv \
40
+ --space-mode new --space-name "Sales" --private \
41
+ --map-name "Q4 Pipeline" \
42
+ --title-column name --semantic-column description --numeric-column revenue
43
+
44
+ # Add a map to an existing space
45
+ mantis create map ./more-data.csv \
46
+ --space-mode existing --space-id <uuid> \
47
+ --map-name "Cohort 2"
48
+
49
+ # Index your repo into Mantis
50
+ mantis create codebase . --create-map --space-mode new --space-name "Repo Index" --private
51
+ ```
52
+
53
+ ## Claude Code integration
54
+
55
+ After `mantis setup`:
56
+
57
+ 1. Open Claude Code (restart if it was already running).
58
+ 2. `/plugin` → enable **mantis @ mantis-plugins** (user scope) if needed.
59
+ 3. `/reload-plugins`
60
+ 4. Confirm with `/mantis:status`.
61
+
62
+ ### Slash commands
63
+
64
+ | Command | Description |
65
+ |---------|-------------|
66
+ | `/mantis:connect` | Run setup / reconnect |
67
+ | `/mantis:space` | Change space (search or paste a `/space/{uuid}` link) |
68
+ | `/mantis:thread` | Change thread (space state) |
69
+ | `/mantis:status` | Show current space, thread, and MCP URL |
70
+ | `/mantis:select` | Quick space/thread selection |
71
+
72
+ After changing **thread**, run `/reload-plugins` so MCP picks up the new `X-Space-State-ID` header.
73
+
74
+ ### Paste a space link
75
+
76
+ In `mantis setup` or `/mantis:space`, paste a URL such as:
77
+
78
+ `https://mantis.csail.mit.edu/space/1e1ed055-c869-4b78-b41f-4216a44049d4/`
79
+
80
+ Use the same Mantis API base URL as the space you are targeting (local vs production).
81
+
82
+ ## Update
83
+
84
+ **CLI**:
85
+
86
+ ```bash
87
+ npm install -g mantisai-cli@latest
88
+ ```
89
+
90
+ **Plugin (slash commands, MCP, skills)** — in Claude Code:
91
+
92
+ ```text
93
+ /plugin marketplace update mantis-plugins
94
+ ```
95
+
96
+ Then update **mantis@mantis-plugins** and `/reload-plugins`.
97
+
98
+ ## Manual plugin install
99
+
100
+ ```bash
101
+ claude plugin marketplace add KellisLab/mantis-cli
102
+ claude plugin install mantis@mantis-plugins --scope user
103
+ ```
104
+
105
+ Enable in `/plugin`, then `/reload-plugins`.
106
+
107
+ ## Migrating from `mantis-claude-code`
108
+
109
+ If you previously installed the package as `mantis-claude-code`, migrate with:
110
+
111
+ ```bash
112
+ npm uninstall -g mantis-claude-code
113
+ npm install -g mantisai-cli
114
+ mantis setup
115
+ ```
116
+
117
+ Your local config at `~/.mantis/claude-code/config.json` is preserved.
118
+
119
+ ## npm
120
+
121
+ https://www.npmjs.com/package/mantisai-cli
122
+
123
+ ## License
124
+
125
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { spacesForPrompt } from '../lib/list-cli.js';
3
+
4
+ const args = process.argv.slice(2);
5
+ let filter = '';
6
+ let offset = 0;
7
+ let limit = 4;
8
+ for (let i = 0; i < args.length; i++) {
9
+ const a = args[i];
10
+ if (a === '--offset') {
11
+ offset = parseInt(args[++i], 10) || 0;
12
+ continue;
13
+ }
14
+ if (a === '--limit') {
15
+ limit = parseInt(args[++i], 10) || 4;
16
+ continue;
17
+ }
18
+ if (a === '--filter') {
19
+ filter = args[++i] ?? '';
20
+ continue;
21
+ }
22
+ filter = args.slice(i).join(' ').trim();
23
+ break;
24
+ }
25
+
26
+ try {
27
+ const data = await spacesForPrompt(filter, { limit, offset });
28
+ process.stdout.write(JSON.stringify(data));
29
+ } catch (e) {
30
+ console.error(JSON.stringify({ error: e.message }));
31
+ process.exit(1);
32
+ }
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { threadsForPrompt } from '../lib/list-cli.js';
3
+
4
+ const args = process.argv.slice(2);
5
+ let filter = '';
6
+ let offset = 0;
7
+ let limit = 4;
8
+ for (let i = 0; i < args.length; i++) {
9
+ const a = args[i];
10
+ if (a === '--offset') {
11
+ offset = parseInt(args[++i], 10) || 0;
12
+ continue;
13
+ }
14
+ if (a === '--limit') {
15
+ limit = parseInt(args[++i], 10) || 4;
16
+ continue;
17
+ }
18
+ if (a === '--filter') {
19
+ filter = args[++i] ?? '';
20
+ continue;
21
+ }
22
+ filter = args.slice(i).join(' ').trim();
23
+ break;
24
+ }
25
+
26
+ try {
27
+ const data = await threadsForPrompt(filter, { limit, offset });
28
+ process.stdout.write(JSON.stringify(data));
29
+ } catch (e) {
30
+ console.error(JSON.stringify({ error: e.message }));
31
+ process.exit(1);
32
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig } from '../lib/config.js';
3
+
4
+ const cfg = loadConfig();
5
+ const headers = {};
6
+ if (cfg.spaceStateId) {
7
+ headers['X-Space-State-ID'] = String(cfg.spaceStateId);
8
+ }
9
+ process.stdout.write(JSON.stringify(headers));
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { pickSpace } from '../lib/picker.js';
3
+
4
+ const filter = process.argv.slice(2).join(' ').trim();
5
+ await pickSpace(filter);
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { pickThread } from '../lib/picker.js';
3
+
4
+ const filter = process.argv.slice(2).join(' ').trim();
5
+ await pickThread(filter);
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig } from '../lib/config.js';
3
+ import { resolveSpaceFromInput } from '../lib/spaces.js';
4
+
5
+ const input = process.argv.slice(2).join(' ').trim();
6
+ if (!input) {
7
+ console.error(JSON.stringify({ error: 'Provide a space link or UUID' }));
8
+ process.exit(1);
9
+ }
10
+
11
+ try {
12
+ const cfg = loadConfig();
13
+ if (!cfg.apiKey || !cfg.apiBaseUrl) {
14
+ throw new Error('Run mantis setup first (API key + URL).');
15
+ }
16
+ const space = await resolveSpaceFromInput(cfg.apiBaseUrl, cfg.apiKey, input);
17
+ if (!space) {
18
+ console.error(JSON.stringify({ error: 'Space not found or no access' }));
19
+ process.exit(1);
20
+ }
21
+ process.stdout.write(JSON.stringify({ space, threadCleared: true }));
22
+ } catch (e) {
23
+ console.error(JSON.stringify({ error: e.message }));
24
+ process.exit(1);
25
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { pickSpace, pickThread } from '../lib/picker.js';
3
+ import { banner } from '../lib/ui.js';
4
+
5
+ banner('Mantis — switch space / thread');
6
+ await pickSpace();
7
+ await pickThread();
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig, saveConfig } from '../lib/config.js';
3
+ import { syncMcpConfigs } from '../lib/mcp-config.js';
4
+
5
+ const id = process.argv[2];
6
+ const name = process.argv[3] || '';
7
+ if (!id) {
8
+ console.error('Usage: mantis-set-space <space-uuid> [name]');
9
+ process.exit(1);
10
+ }
11
+
12
+ const cfg = loadConfig();
13
+ const changed = cfg.spaceId !== id;
14
+ const next = {
15
+ ...cfg,
16
+ spaceId: id,
17
+ spaceName: name || cfg.spaceName,
18
+ ...(changed ? { spaceStateId: undefined, spaceStateName: undefined } : {}),
19
+ };
20
+ saveConfig(next);
21
+ syncMcpConfigs(next);
22
+ console.log(
23
+ JSON.stringify({
24
+ ok: true,
25
+ spaceId: id,
26
+ spaceName: name,
27
+ threadCleared: changed,
28
+ needThread: changed,
29
+ needReloadPlugins: true,
30
+ }),
31
+ );
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import { createSpaceState } from '../lib/api.js';
3
+ import { loadConfig, saveConfig } from '../lib/config.js';
4
+ import { syncMcpConfigs } from '../lib/mcp-config.js';
5
+
6
+ const arg = process.argv[2];
7
+ const arg2 = process.argv[3];
8
+ if (!arg) {
9
+ console.error('Usage: mantis-set-thread <thread-uuid> [name] | mantis-set-thread --new [name]');
10
+ process.exit(1);
11
+ }
12
+
13
+ const cfg = loadConfig();
14
+ if (!cfg.apiKey || !cfg.apiBaseUrl || !cfg.spaceId) {
15
+ console.error('Run mantis setup and pick a space first.');
16
+ process.exit(1);
17
+ }
18
+
19
+ let thread;
20
+ if (arg === '--new') {
21
+ const name = arg2 || 'Claude Code';
22
+ thread = await createSpaceState(cfg.apiBaseUrl, cfg.apiKey, cfg.spaceId, name);
23
+ } else {
24
+ thread = { id: arg, name: arg2 || arg };
25
+ }
26
+
27
+ const next = {
28
+ ...cfg,
29
+ spaceStateId: thread.id,
30
+ spaceStateName: thread.name,
31
+ };
32
+ saveConfig(next);
33
+ syncMcpConfigs(next);
34
+ console.log(JSON.stringify({ ok: true, spaceStateId: thread.id, name: thread.name }));
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_API_BASE,
4
+ DEVELOPER_PORTAL_URL,
5
+ loadConfig,
6
+ normalizeBaseUrl,
7
+ saveConfig,
8
+ } from '../lib/config.js';
9
+ import { pickSpace, pickThread } from '../lib/picker.js';
10
+ import { installClaudePlugin } from '../lib/claude-plugin.js';
11
+ import { syncMcpConfigs } from '../lib/mcp-config.js';
12
+ import { banner, die, info, promptInput, promptSecret, success } from '../lib/ui.js';
13
+
14
+ async function main() {
15
+ const prev = loadConfig();
16
+ banner('Mantis ↔ Claude Code', 'Links your Claude Code session to a Mantis space and thread');
17
+
18
+ const apiBaseUrl = normalizeBaseUrl(
19
+ await promptInput('Mantis API URL', {
20
+ default: prev.apiBaseUrl || process.env.MANTIS_API_URL || DEFAULT_API_BASE,
21
+ }),
22
+ );
23
+ info(`API keys: ${DEVELOPER_PORTAL_URL}`);
24
+ const apiKey = (await promptSecret('API key (Ctrl+click link above to open portal)', {
25
+ default: prev.apiKey,
26
+ }))?.trim();
27
+ if (!apiKey) die('API key is required.');
28
+
29
+ saveConfig({ apiBaseUrl, apiKey });
30
+
31
+ const afterSpace = await pickSpace();
32
+ const final = await pickThread();
33
+ saveConfig({ ...final, apiBaseUrl, apiKey });
34
+ syncMcpConfigs({ ...final, apiBaseUrl, apiKey });
35
+
36
+ console.log('');
37
+ success('Setup complete');
38
+ info(`Space: ${final.spaceName} (${final.spaceId})`);
39
+ info(`Thread: ${final.spaceStateName} (${final.spaceStateId})`);
40
+ info(`MCP: ${apiBaseUrl}/mcp_integrated/`);
41
+ info('In Claude Code run /reload-plugins so MCP uses this space and thread.');
42
+ try {
43
+ const cc = installClaudePlugin();
44
+ if (cc.method === 'cli') {
45
+ success(`Claude Code plugin installed (mantis@mantis-plugins, ${cc.scope || 'user'} scope)`);
46
+ info('Run /reload-plugins in Claude Code if it is already open');
47
+ } else {
48
+ success('Claude Code settings updated');
49
+ info(cc.hint);
50
+ }
51
+ } catch (e) {
52
+ info(`Claude plugin install failed: ${e.message}`);
53
+ info('Fix: claude plugin install mantis@mantis-plugins');
54
+ info('Then in Claude Code: Enable plugin → /reload-plugins');
55
+ }
56
+ console.log('');
57
+ }
58
+
59
+ main().catch((e) => die(e.message || String(e)));
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig, configPath, mcpUrl } from '../lib/config.js';
3
+
4
+ const cfg = loadConfig();
5
+ console.log('Mantis Claude Code — status\n');
6
+ console.log(`Config file: ${configPath()}`);
7
+ console.log(`API base: ${cfg.apiBaseUrl || '(not set — use mantis setup)'}`);
8
+ console.log(`MCP URL: ${cfg.apiBaseUrl ? mcpUrl(cfg) : '(run setup)'}`);
9
+ console.log(`API key: ${cfg.apiKey ? '***' + cfg.apiKey.slice(-6) : '(not set)'}`);
10
+ console.log(`Space: ${cfg.spaceName || '-'} (${cfg.spaceId || '-'})`);
11
+ console.log(`Thread: ${cfg.spaceStateName || '-'} (${cfg.spaceStateId || '-'})`);
12
+ if (!cfg.spaceStateId) {
13
+ console.log('\nNo thread selected. Run: mantis setup');
14
+ process.exit(1);
15
+ }
package/bin/mantis.js ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { Command } from 'commander';
4
+
5
+ import { createCodebaseCsv } from '../lib/codebase-csv.js';
6
+ import { createMapFlow } from '../lib/map-create.js';
7
+ import { die, info, success } from '../lib/ui.js';
8
+
9
+ const program = new Command();
10
+
11
+ function defaultCodebaseOut(root) {
12
+ const base = path.basename(path.resolve(root || process.cwd())) || 'codebase';
13
+ return path.resolve(process.cwd(), `${base}-codebase.csv`);
14
+ }
15
+
16
+ function addMapOptions(cmd) {
17
+ return cmd
18
+ .option('--space-mode <mode>', 'new or existing')
19
+ .option('--space-id <uuid>', 'existing Mantis space id')
20
+ .option('--space-name <name>', 'new space name or display name for existing space')
21
+ .option('--space-search <query>', 'search text for choosing an existing space')
22
+ .option('--public', 'make a new space public')
23
+ .option('--private', 'make a new space private')
24
+ .option('--map-name <name>', 'map name')
25
+ .option('--title-column <columns>', 'comma-separated title column names')
26
+ .option('--semantic-column <columns>', 'comma-separated semantic column names')
27
+ .option('--numeric-column <columns>', 'comma-separated numeric column names')
28
+ .option('--categoric-column <columns>', 'comma-separated categorical column names')
29
+ .option('--date-column <columns>', 'comma-separated date column names')
30
+ .option('--links-column <columns>', 'comma-separated link column names')
31
+ .option('--delete-column <columns>', 'comma-separated ignored column names')
32
+ .option('--data-types <json>', 'raw data_types JSON array')
33
+ .option('--activate', 'set created space/thread as active in Claude Code')
34
+ .option('--no-activate', 'do not set active Claude Code context')
35
+ .option('--thread-name <name>', 'thread name when activating');
36
+ }
37
+
38
+ program
39
+ .name('mantis')
40
+ .description('Mantis developer CLI — manage spaces, create maps, and configure Claude Code integration')
41
+ .version('2.0.0');
42
+
43
+ program
44
+ .command('setup')
45
+ .description('Connect Claude Code to Mantis')
46
+ .action(async () => import('./mantis-setup.js'));
47
+
48
+ program
49
+ .command('status')
50
+ .description('Show current Mantis Claude Code config')
51
+ .action(async () => import('./mantis-status.js'));
52
+
53
+ program
54
+ .command('select')
55
+ .description('Select active Mantis space and thread')
56
+ .argument('[target]', 'space, thread, or both', 'both')
57
+ .action(async (target) => {
58
+ if (target === 'space') return import('./mantis-pick-space.js');
59
+ if (target === 'thread') return import('./mantis-pick-thread.js');
60
+ return import('./mantis-select.js');
61
+ });
62
+
63
+ const create = program.command('create').description('Create Mantis resources from local inputs');
64
+
65
+ addMapOptions(create
66
+ .command('map')
67
+ .description('Create a Mantis map from a local CSV/XLSX file')
68
+ .argument('<file>', 'CSV/XLSX file path'))
69
+ .action(async (file, opts) => {
70
+ try {
71
+ if (opts.private) opts.public = false;
72
+ const result = await createMapFlow(path.resolve(file), opts);
73
+ console.log(JSON.stringify(result, null, 2));
74
+ } catch (e) {
75
+ die(e.message || String(e));
76
+ }
77
+ });
78
+
79
+ addMapOptions(create
80
+ .command('codebase')
81
+ .description('Index a local codebase into CSV, optionally creating a Mantis map')
82
+ .argument('[root]', 'codebase root', '.')
83
+ .option('--out <file>', 'output CSV path')
84
+ .option('--max-chars <n>', 'max characters stored per file', (v) => Number(v), 12000)
85
+ .option('--create-map', 'create a Mantis map after writing the CSV'))
86
+ .action(async (root, opts) => {
87
+ try {
88
+ if (opts.private) opts.public = false;
89
+ const out = path.resolve(opts.out || defaultCodebaseOut(root));
90
+ const result = await createCodebaseCsv(root, out, { maxChars: opts.maxChars });
91
+ success(`Indexed ${result.count} files`);
92
+ info(`CSV: ${result.outFile}`);
93
+ if (opts.createMap) {
94
+ const mapResult = await createMapFlow(result.outFile, {
95
+ ...opts,
96
+ mapName: opts.mapName || `${path.basename(path.resolve(root))} Code Index`,
97
+ titleColumn: opts.titleColumn || 'path',
98
+ semanticColumn: opts.semanticColumn || 'summary,content,imports',
99
+ categoricColumn: opts.categoricColumn || 'language,kind,extension',
100
+ numericColumn: opts.numericColumn || 'loc,bytes',
101
+ });
102
+ console.log(JSON.stringify({ csv: result, map: mapResult }, null, 2));
103
+ } else {
104
+ console.log(JSON.stringify(result, null, 2));
105
+ }
106
+ } catch (e) {
107
+ die(e.message || String(e));
108
+ }
109
+ });
110
+
111
+ program.parseAsync(process.argv);
package/lib/api.js ADDED
@@ -0,0 +1,100 @@
1
+ import { normalizeBaseUrl } from './config.js';
2
+
3
+ async function request(baseUrl, apiKey, method, pathname, { params, body } = {}) {
4
+ const root = normalizeBaseUrl(baseUrl);
5
+ const url = new URL(pathname.startsWith('/') ? pathname : `/${pathname}`, `${root}/`);
6
+ if (params) {
7
+ for (const [k, v] of Object.entries(params)) {
8
+ if (v != null && v !== '') url.searchParams.set(k, String(v));
9
+ }
10
+ }
11
+ const res = await fetch(url, {
12
+ method,
13
+ headers: {
14
+ Authorization: `Bearer ${apiKey}`,
15
+ Accept: 'application/json',
16
+ ...(body ? { 'Content-Type': 'application/json' } : {}),
17
+ },
18
+ body: body ? JSON.stringify(body) : undefined,
19
+ });
20
+ const text = await res.text();
21
+ let data;
22
+ try {
23
+ data = text ? JSON.parse(text) : {};
24
+ } catch {
25
+ data = { error: text || res.statusText };
26
+ }
27
+ if (!res.ok) {
28
+ const msg = data.error || data.detail || res.statusText || `HTTP ${res.status}`;
29
+ throw new Error(msg);
30
+ }
31
+ return data;
32
+ }
33
+
34
+ export function listSpaces(
35
+ baseUrl,
36
+ apiKey,
37
+ { scope = 'accessible', limit = 20, offset = 0, q, space_id } = {},
38
+ ) {
39
+ return request(baseUrl, apiKey, 'GET', '/api/v1/me/spaces/', {
40
+ params: { scope, limit, offset, q, space_id },
41
+ });
42
+ }
43
+
44
+ export function listSpaceStates(baseUrl, apiKey, spaceId, { limit = 20, offset = 0 } = {}) {
45
+ return request(baseUrl, apiKey, 'GET', '/api/v1/me/space-states/', {
46
+ params: { space_id: spaceId, limit, offset },
47
+ });
48
+ }
49
+
50
+ export function createSpaceState(baseUrl, apiKey, spaceId, name = 'Claude Code') {
51
+ return request(baseUrl, apiKey, 'POST', '/api/v1/me/space-states/', {
52
+ body: { space_id: spaceId, name },
53
+ });
54
+ }
55
+
56
+ export function createSpace(baseUrl, apiKey, { name, isPublic = false }) {
57
+ return request(baseUrl, apiKey, 'POST', '/api/v1/spaces/', {
58
+ body: { name, public: Boolean(isPublic) },
59
+ });
60
+ }
61
+
62
+ export async function createMapInSpace(baseUrl, apiKey, spaceId, {
63
+ file,
64
+ mapName,
65
+ dataTypes,
66
+ selectedFields,
67
+ fieldWeights,
68
+ }) {
69
+ const root = normalizeBaseUrl(baseUrl);
70
+ const url = new URL(`/api/v1/spaces/${encodeURIComponent(spaceId)}/maps/`, `${root}/`);
71
+ const form = new FormData();
72
+ const bytes = await import('node:fs/promises').then((fs) => fs.readFile(file));
73
+ const name = await import('node:path').then((path) => path.basename(file));
74
+ form.set('file', new Blob([bytes]), name);
75
+ if (mapName) form.set('map_name', mapName);
76
+ if (dataTypes) form.set('data_types', JSON.stringify(dataTypes));
77
+ if (selectedFields) form.set('selected_fields', JSON.stringify(selectedFields));
78
+ if (fieldWeights) form.set('field_weights', JSON.stringify(fieldWeights));
79
+
80
+ const res = await fetch(url, {
81
+ method: 'POST',
82
+ headers: {
83
+ Authorization: `Bearer ${apiKey}`,
84
+ Accept: 'application/json',
85
+ },
86
+ body: form,
87
+ });
88
+ const text = await res.text();
89
+ let data;
90
+ try {
91
+ data = text ? JSON.parse(text) : {};
92
+ } catch {
93
+ data = { error: text || res.statusText };
94
+ }
95
+ if (!res.ok) {
96
+ const msg = data.error || data.detail || res.statusText || `HTTP ${res.status}`;
97
+ throw new Error(msg);
98
+ }
99
+ return data;
100
+ }