mantisai-cli 2.0.1 → 3.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.
- package/README.md +26 -99
- package/bin/mantis.js +406 -23
- package/lib/constants.js +13 -0
- package/lib/container.js +63 -0
- package/lib/impl/claude-skills-service.js +13 -0
- package/lib/impl/fast-glob-codebase-indexer.js +114 -0
- package/lib/impl/file-config-store.js +38 -0
- package/lib/impl/fs-csv-reader.js +12 -0
- package/lib/impl/http-mantis-client.js +108 -0
- package/lib/impl/inquirer-ui-service.js +73 -0
- package/lib/impl/mcp-client-service.js +64 -0
- package/lib/impl/opencode-skills-service.js +17 -0
- package/lib/impl/space-repository.js +72 -0
- package/lib/interfaces/codebase-indexer.js +5 -0
- package/lib/interfaces/config-store.js +11 -0
- package/lib/interfaces/csv-reader.js +5 -0
- package/lib/interfaces/index.js +7 -0
- package/lib/interfaces/mantis-client.js +13 -0
- package/lib/interfaces/mcp-client.js +6 -0
- package/lib/interfaces/space-repository.js +13 -0
- package/lib/interfaces/ui.js +13 -0
- package/lib/services/context-service.js +22 -0
- package/lib/services/map-service.js +114 -0
- package/lib/services/query-service.js +50 -0
- package/lib/services/selection-service.js +174 -0
- package/lib/services/setup-service.js +81 -0
- package/lib/services/tool-service.js +26 -0
- package/lib/types.js +30 -0
- package/lib/utils/cli-args.js +29 -0
- package/lib/utils/package-root.js +7 -0
- package/lib/utils/skills-sync.js +43 -0
- package/lib/{space-id.js → utils/space-id.js} +0 -1
- package/lib/utils/threads.js +12 -0
- package/lib/utils/tool-args.js +38 -0
- package/lib/utils/url.js +33 -0
- package/package.json +4 -7
- package/skills/codebase/SKILL.md +8 -6
- package/skills/connect/SKILL.md +10 -26
- package/skills/createmap/SKILL.md +5 -3
- package/skills/mantis/SKILL.md +48 -32
- package/skills/select/SKILL.md +12 -9
- package/skills/space/SKILL.md +10 -47
- package/skills/status/SKILL.md +4 -9
- package/skills/thread/SKILL.md +15 -27
- package/.claude-plugin/marketplace.json +0 -14
- package/.claude-plugin/plugin.json +0 -18
- package/.mcp.json +0 -11
- package/bin/mantis-list-spaces.js +0 -32
- package/bin/mantis-list-threads.js +0 -32
- package/bin/mantis-mcp-headers.js +0 -9
- package/bin/mantis-pick-space.js +0 -5
- package/bin/mantis-pick-thread.js +0 -5
- package/bin/mantis-resolve-space.js +0 -25
- package/bin/mantis-select.js +0 -7
- package/bin/mantis-set-space.js +0 -31
- package/bin/mantis-set-thread.js +0 -34
- package/bin/mantis-setup.js +0 -59
- package/bin/mantis-status.js +0 -15
- package/lib/api.js +0 -100
- package/lib/claude-plugin.js +0 -150
- package/lib/codebase-csv.js +0 -115
- package/lib/config.js +0 -65
- package/lib/csv.js +0 -10
- package/lib/fetch.js +0 -36
- package/lib/list-cli.js +0 -55
- package/lib/map-create.js +0 -148
- package/lib/mcp-config.js +0 -50
- package/lib/picker.js +0 -150
- package/lib/spaces.js +0 -48
- package/lib/ui.js +0 -73
- /package/lib/{fields.js → utils/fields.js} +0 -0
package/bin/mantis-set-thread.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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 }));
|
package/bin/mantis-setup.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
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)));
|
package/bin/mantis-status.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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/lib/api.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
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
|
-
}
|
package/lib/claude-plugin.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
import { loadConfig } from './config.js';
|
|
8
|
-
import { syncMcpConfigs } from './mcp-config.js';
|
|
9
|
-
|
|
10
|
-
const MARKETPLACE = 'mantis-plugins';
|
|
11
|
-
const PLUGIN_ID = `mantis@${MARKETPLACE}`;
|
|
12
|
-
const MARKETPLACE_REPO = 'KellisLab/mantis-cli';
|
|
13
|
-
const NPM_PACKAGE = 'mantisai-cli';
|
|
14
|
-
const LEGACY_NPM_PACKAGES = ['mantis-cli', 'mantis-claude-code'];
|
|
15
|
-
|
|
16
|
-
export function resolvePluginRoot() {
|
|
17
|
-
try {
|
|
18
|
-
const root = execSync('npm root -g', { encoding: 'utf8', windowsHide: true }).trim();
|
|
19
|
-
for (const candidate of [NPM_PACKAGE, ...LEGACY_NPM_PACKAGES]) {
|
|
20
|
-
const globalPkg = path.join(root, candidate);
|
|
21
|
-
if (fs.existsSync(path.join(globalPkg, '.claude-plugin', 'plugin.json'))) {
|
|
22
|
-
return path.normalize(globalPkg);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
} catch {
|
|
26
|
-
/* not installed globally */
|
|
27
|
-
}
|
|
28
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
29
|
-
return path.normalize(path.resolve(here, '..'));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function settingsPath() {
|
|
33
|
-
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function installedPluginsPath() {
|
|
37
|
-
return path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function readSettings() {
|
|
41
|
-
const file = settingsPath();
|
|
42
|
-
if (!fs.existsSync(file)) return {};
|
|
43
|
-
try {
|
|
44
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
45
|
-
} catch {
|
|
46
|
-
return {};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function writeSettings(settings) {
|
|
51
|
-
const file = settingsPath();
|
|
52
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
53
|
-
fs.writeFileSync(file, `${JSON.stringify(settings, null, 2)}\n`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function stripBrokenInlinePlugins(settings) {
|
|
57
|
-
const bad = (v) =>
|
|
58
|
-
typeof v === 'string' &&
|
|
59
|
-
(v.includes('$(npm') || v.includes('mantis-claude-code-plugin') || v.includes('mantis-cli-plugin'));
|
|
60
|
-
if (Array.isArray(settings.plugins)) {
|
|
61
|
-
settings.plugins = settings.plugins.filter((p) => {
|
|
62
|
-
const paths = [p?.path, p?.commands, p?.source?.path].filter(Boolean);
|
|
63
|
-
return !paths.some(bad);
|
|
64
|
-
});
|
|
65
|
-
if (!settings.plugins.length) delete settings.plugins;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function isPluginInstalled() {
|
|
70
|
-
try {
|
|
71
|
-
const data = JSON.parse(fs.readFileSync(installedPluginsPath(), 'utf8'));
|
|
72
|
-
const entries = data.plugins?.[PLUGIN_ID];
|
|
73
|
-
if (!Array.isArray(entries) || !entries.length) return false;
|
|
74
|
-
const installPath = entries[0]?.installPath;
|
|
75
|
-
if (!installPath || !fs.existsSync(installPath)) return false;
|
|
76
|
-
return !fs.existsSync(path.join(installPath, '.orphaned_at'));
|
|
77
|
-
} catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function mergeMarketplaceIntoSettings() {
|
|
83
|
-
const settings = readSettings();
|
|
84
|
-
stripBrokenInlinePlugins(settings);
|
|
85
|
-
settings.extraKnownMarketplaces = settings.extraKnownMarketplaces || {};
|
|
86
|
-
settings.extraKnownMarketplaces[MARKETPLACE] = {
|
|
87
|
-
source: { source: 'github', repo: MARKETPLACE_REPO },
|
|
88
|
-
};
|
|
89
|
-
writeSettings(settings);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const BENIGN = [
|
|
93
|
-
/already enabled/i,
|
|
94
|
-
/already installed/i,
|
|
95
|
-
/already on disk/i,
|
|
96
|
-
/no updates/i,
|
|
97
|
-
/is already enabled/i,
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
function quoteCmdArg(arg) {
|
|
101
|
-
return `"${String(arg).replace(/"/g, '\\"')}"`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function runClaude(argv, { optional = false } = {}) {
|
|
105
|
-
const opts = { encoding: 'utf8', windowsHide: true };
|
|
106
|
-
const r = process.platform === 'win32'
|
|
107
|
-
? spawnSync(['claude', ...argv.map(quoteCmdArg)].join(' '), { ...opts, shell: true })
|
|
108
|
-
: spawnSync('claude', argv, opts);
|
|
109
|
-
if (r.error) {
|
|
110
|
-
const msg =
|
|
111
|
-
r.error.code === 'ENOENT'
|
|
112
|
-
? 'claude not found on PATH — install Claude Code CLI first'
|
|
113
|
-
: r.error.message;
|
|
114
|
-
if (optional) return null;
|
|
115
|
-
throw new Error(msg);
|
|
116
|
-
}
|
|
117
|
-
const out = `${r.stdout || ''}${r.stderr || ''}`.trim();
|
|
118
|
-
if (r.status === 0 || BENIGN.some((re) => re.test(out))) return out;
|
|
119
|
-
if (optional) return null;
|
|
120
|
-
throw new Error(out || `claude ${argv.join(' ')} failed (exit ${r.status})`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function installPluginScopes() {
|
|
124
|
-
for (const scope of ['user', 'local']) {
|
|
125
|
-
if (isPluginInstalled()) return scope;
|
|
126
|
-
runClaude(['plugin', 'install', PLUGIN_ID, '--scope', scope], { optional: true });
|
|
127
|
-
}
|
|
128
|
-
return isPluginInstalled() ? 'user' : null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function installClaudePlugin() {
|
|
132
|
-
const pluginRoot = resolvePluginRoot();
|
|
133
|
-
mergeMarketplaceIntoSettings();
|
|
134
|
-
|
|
135
|
-
runClaude(['plugin', 'marketplace', 'add', MARKETPLACE_REPO, '--scope', 'user'], { optional: true });
|
|
136
|
-
const scope = installPluginScopes();
|
|
137
|
-
if (!scope) {
|
|
138
|
-
throw new Error(
|
|
139
|
-
'Plugin not registered after install. Run: claude plugin install mantis@mantis-plugins',
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
runClaude(['plugin', 'update', PLUGIN_ID, '--scope', scope], { optional: true });
|
|
143
|
-
runClaude(['plugin', 'enable', PLUGIN_ID, '--scope', scope], { optional: true });
|
|
144
|
-
const settings = readSettings();
|
|
145
|
-
settings.enabledPlugins = settings.enabledPlugins || {};
|
|
146
|
-
settings.enabledPlugins[PLUGIN_ID] = true;
|
|
147
|
-
writeSettings(settings);
|
|
148
|
-
syncMcpConfigs(loadConfig());
|
|
149
|
-
return { ok: true, method: 'cli', pluginRoot, scope };
|
|
150
|
-
}
|
package/lib/codebase-csv.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import fg from 'fast-glob';
|
|
4
|
-
import { stringify } from 'csv-stringify/sync';
|
|
5
|
-
|
|
6
|
-
const DEFAULT_IGNORES = [
|
|
7
|
-
'**/.git/**',
|
|
8
|
-
'**/.next/**',
|
|
9
|
-
'**/.turbo/**',
|
|
10
|
-
'**/.venv/**',
|
|
11
|
-
'**/__pycache__/**',
|
|
12
|
-
'**/build/**',
|
|
13
|
-
'**/coverage/**',
|
|
14
|
-
'**/dist/**',
|
|
15
|
-
'**/node_modules/**',
|
|
16
|
-
'**/vendor/**',
|
|
17
|
-
'**/*.lock',
|
|
18
|
-
'**/package-lock.json',
|
|
19
|
-
'**/pnpm-lock.yaml',
|
|
20
|
-
'**/yarn.lock',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
const EXT_LANGUAGE = {
|
|
24
|
-
'.css': 'css',
|
|
25
|
-
'.go': 'go',
|
|
26
|
-
'.html': 'html',
|
|
27
|
-
'.java': 'java',
|
|
28
|
-
'.js': 'javascript',
|
|
29
|
-
'.jsx': 'javascript',
|
|
30
|
-
'.json': 'json',
|
|
31
|
-
'.md': 'markdown',
|
|
32
|
-
'.py': 'python',
|
|
33
|
-
'.rs': 'rust',
|
|
34
|
-
'.scss': 'scss',
|
|
35
|
-
'.sql': 'sql',
|
|
36
|
-
'.ts': 'typescript',
|
|
37
|
-
'.tsx': 'typescript',
|
|
38
|
-
'.vue': 'vue',
|
|
39
|
-
'.yaml': 'yaml',
|
|
40
|
-
'.yml': 'yaml',
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const SOURCE_EXTENSIONS = Object.keys(EXT_LANGUAGE);
|
|
44
|
-
|
|
45
|
-
function inferKind(rel) {
|
|
46
|
-
const base = path.basename(rel).toLowerCase();
|
|
47
|
-
if (base.includes('test') || base.includes('spec')) return 'test';
|
|
48
|
-
if (rel.includes('/components/') || rel.includes('\\components\\')) return 'component';
|
|
49
|
-
if (rel.includes('/pages/') || rel.includes('\\pages\\') || rel.includes('/app/')) return 'route';
|
|
50
|
-
if (rel.includes('/api/') || rel.includes('\\api\\')) return 'api';
|
|
51
|
-
if (rel.includes('/hooks/') || rel.includes('\\hooks\\')) return 'hook';
|
|
52
|
-
if (rel.includes('/utils/') || rel.includes('\\utils\\') || rel.includes('/lib/')) return 'utility';
|
|
53
|
-
return 'source';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function importsFrom(content) {
|
|
57
|
-
const imports = new Set();
|
|
58
|
-
const patterns = [
|
|
59
|
-
/^\s*import\s+.*?\s+from\s+['"]([^'"]+)['"]/gm,
|
|
60
|
-
/^\s*import\s+['"]([^'"]+)['"]/gm,
|
|
61
|
-
/^\s*const\s+.*?=\s+require\(['"]([^'"]+)['"]\)/gm,
|
|
62
|
-
/^\s*from\s+([\w.]+)\s+import\s+/gm,
|
|
63
|
-
];
|
|
64
|
-
for (const re of patterns) {
|
|
65
|
-
for (const match of content.matchAll(re)) imports.add(match[1]);
|
|
66
|
-
}
|
|
67
|
-
return [...imports].slice(0, 40).join(', ');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function summarize(content) {
|
|
71
|
-
const lines = content.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
72
|
-
const comment = lines.find((l) => /^\/\/|^#|^\/\*|^\*|^<!--/.test(l));
|
|
73
|
-
return (comment || lines[0] || '').replace(/^\/\/|^#|^\/\*+|^\*|<!--|-->/g, '').trim().slice(0, 240);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function readText(file, maxChars) {
|
|
77
|
-
const raw = fs.readFileSync(file, 'utf8');
|
|
78
|
-
return raw.length > maxChars ? `${raw.slice(0, maxChars)}\n\n[truncated]` : raw;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function createCodebaseCsv(rootDir, outFile, { maxChars = 12000, include } = {}) {
|
|
82
|
-
const root = path.resolve(rootDir || process.cwd());
|
|
83
|
-
const patterns = include?.length ? include : SOURCE_EXTENSIONS.map((ext) => `**/*${ext}`);
|
|
84
|
-
const files = await fg(patterns, {
|
|
85
|
-
cwd: root,
|
|
86
|
-
absolute: true,
|
|
87
|
-
dot: false,
|
|
88
|
-
ignore: DEFAULT_IGNORES,
|
|
89
|
-
onlyFiles: true,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const rows = files.sort().map((file) => {
|
|
93
|
-
const rel = path.relative(root, file).replace(/\\/g, '/');
|
|
94
|
-
const ext = path.extname(file).toLowerCase();
|
|
95
|
-
const content = readText(file, maxChars);
|
|
96
|
-
const stat = fs.statSync(file);
|
|
97
|
-
return {
|
|
98
|
-
path: rel,
|
|
99
|
-
file_name: path.basename(file),
|
|
100
|
-
extension: ext.replace(/^\./, ''),
|
|
101
|
-
language: EXT_LANGUAGE[ext] || ext.replace(/^\./, ''),
|
|
102
|
-
kind: inferKind(rel),
|
|
103
|
-
loc: content.split(/\r?\n/).length,
|
|
104
|
-
bytes: stat.size,
|
|
105
|
-
imports: importsFrom(content),
|
|
106
|
-
summary: summarize(content),
|
|
107
|
-
content,
|
|
108
|
-
};
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (!rows.length) throw new Error(`No source files found in ${root}`);
|
|
112
|
-
fs.mkdirSync(path.dirname(path.resolve(outFile)), { recursive: true });
|
|
113
|
-
fs.writeFileSync(outFile, stringify(rows, { header: true }));
|
|
114
|
-
return { root, outFile: path.resolve(outFile), count: rows.length };
|
|
115
|
-
}
|
package/lib/config.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
|
|
5
|
-
const CONFIG_NAME = 'config.json';
|
|
6
|
-
export const DEFAULT_API_BASE = 'https://kellis-h200-1.csail.mit.edu';
|
|
7
|
-
export const DEVELOPER_PORTAL_URL = 'https://mantis.csail.mit.edu/developer/#keys';
|
|
8
|
-
|
|
9
|
-
export function canonicalConfigPath() {
|
|
10
|
-
return path.join(os.homedir(), '.mantis', 'claude-code', CONFIG_NAME);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function pluginDataConfigPath() {
|
|
14
|
-
if (!process.env.CLAUDE_PLUGIN_DATA) return null;
|
|
15
|
-
return path.join(process.env.CLAUDE_PLUGIN_DATA, CONFIG_NAME);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Paths that may hold config (canonical first). */
|
|
19
|
-
export function allConfigPaths() {
|
|
20
|
-
const paths = [canonicalConfigPath()];
|
|
21
|
-
const plugin = pluginDataConfigPath();
|
|
22
|
-
if (plugin) paths.push(plugin);
|
|
23
|
-
return [...new Set(paths)];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function configPath() {
|
|
27
|
-
return canonicalConfigPath();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function loadConfig() {
|
|
31
|
-
const canonical = canonicalConfigPath();
|
|
32
|
-
if (fs.existsSync(canonical)) {
|
|
33
|
-
try {
|
|
34
|
-
return JSON.parse(fs.readFileSync(canonical, 'utf8'));
|
|
35
|
-
} catch {
|
|
36
|
-
/* fall through */
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const plugin = pluginDataConfigPath();
|
|
40
|
-
if (plugin && fs.existsSync(plugin)) {
|
|
41
|
-
try {
|
|
42
|
-
return JSON.parse(fs.readFileSync(plugin, 'utf8'));
|
|
43
|
-
} catch {
|
|
44
|
-
/* empty */
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return {};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function saveConfig(cfg) {
|
|
51
|
-
const body = JSON.stringify(cfg, null, 2);
|
|
52
|
-
for (const file of allConfigPaths()) {
|
|
53
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
54
|
-
fs.writeFileSync(file, body);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function normalizeBaseUrl(url) {
|
|
59
|
-
return String(url || '').trim().replace(/\/+$/, '');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function mcpUrl(cfg) {
|
|
63
|
-
const base = normalizeBaseUrl(cfg.apiBaseUrl || process.env.MANTIS_API_URL || DEFAULT_API_BASE);
|
|
64
|
-
return `${base}/mcp_integrated/`;
|
|
65
|
-
}
|
package/lib/csv.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { parse } from 'csv-parse/sync';
|
|
3
|
-
|
|
4
|
-
export function readCsvHeaders(file) {
|
|
5
|
-
const input = fs.readFileSync(file, 'utf8');
|
|
6
|
-
const rows = parse(input, { to_line: 1, relax_quotes: true });
|
|
7
|
-
const headers = rows[0] || [];
|
|
8
|
-
if (!headers.length) throw new Error('CSV has no header row.');
|
|
9
|
-
return headers.map((h) => String(h).trim()).filter(Boolean);
|
|
10
|
-
}
|
package/lib/fetch.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createSpaceState, listSpaceStates, listSpaces } from './api.js';
|
|
2
|
-
|
|
3
|
-
const PAGE = 100;
|
|
4
|
-
|
|
5
|
-
async function fetchPaginated(fetchPage) {
|
|
6
|
-
const all = [];
|
|
7
|
-
let offset = 0;
|
|
8
|
-
for (;;) {
|
|
9
|
-
const { items, total } = await fetchPage(PAGE, offset);
|
|
10
|
-
const batch = items || [];
|
|
11
|
-
all.push(...batch);
|
|
12
|
-
if (batch.length < PAGE) break;
|
|
13
|
-
if (Number.isFinite(total) && all.length >= total) break;
|
|
14
|
-
offset += PAGE;
|
|
15
|
-
}
|
|
16
|
-
return all;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function fetchAccessibleSpaces(baseUrl, apiKey) {
|
|
20
|
-
return fetchPaginated(async (limit, offset) => {
|
|
21
|
-
const page = await listSpaces(baseUrl, apiKey, { scope: 'accessible', limit, offset });
|
|
22
|
-
return { items: page.spaces, total: page.total };
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** @deprecated use fetchAccessibleSpaces */
|
|
27
|
-
export const fetchOwnedSpaces = fetchAccessibleSpaces;
|
|
28
|
-
|
|
29
|
-
export async function fetchThreads(baseUrl, apiKey, spaceId) {
|
|
30
|
-
return fetchPaginated(async (limit, offset) => {
|
|
31
|
-
const page = await listSpaceStates(baseUrl, apiKey, spaceId, { limit, offset });
|
|
32
|
-
return { items: page.space_states, total: page.total };
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export { createSpaceState };
|
package/lib/list-cli.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { loadConfig } from './config.js';
|
|
2
|
-
import { searchSpaces } from './spaces.js';
|
|
3
|
-
import { fetchThreads } from './fetch.js';
|
|
4
|
-
|
|
5
|
-
export function filterItems(items, query, keys) {
|
|
6
|
-
const terms = (query || '').trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
7
|
-
if (!terms.length) return items;
|
|
8
|
-
return items.filter((item) => {
|
|
9
|
-
const hay = keys.map((k) => String(item[k] ?? '')).join(' ').toLowerCase();
|
|
10
|
-
return terms.every((t) => hay.includes(t));
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function spacesForPrompt(filter = '', { limit = 4, offset = 0 } = {}) {
|
|
15
|
-
const cfg = loadConfig();
|
|
16
|
-
if (!cfg.apiKey || !cfg.apiBaseUrl) {
|
|
17
|
-
throw new Error('Run mantis setup first (API key + URL).');
|
|
18
|
-
}
|
|
19
|
-
const page = await searchSpaces(cfg.apiBaseUrl, cfg.apiKey, {
|
|
20
|
-
q: filter,
|
|
21
|
-
limit,
|
|
22
|
-
offset,
|
|
23
|
-
});
|
|
24
|
-
return {
|
|
25
|
-
spaces: page.spaces || [],
|
|
26
|
-
total: page.total,
|
|
27
|
-
offset,
|
|
28
|
-
limit,
|
|
29
|
-
hasMore: offset + limit < page.total,
|
|
30
|
-
filter,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function threadsForPrompt(filter = '', { limit = 4, offset = 0 } = {}) {
|
|
35
|
-
const cfg = loadConfig();
|
|
36
|
-
if (!cfg.apiKey || !cfg.apiBaseUrl) throw new Error('Run mantis setup first.');
|
|
37
|
-
if (!cfg.spaceId) throw new Error('Pick a space first (/mantis:space).');
|
|
38
|
-
|
|
39
|
-
const all = filterItems(
|
|
40
|
-
await fetchThreads(cfg.apiBaseUrl, cfg.apiKey, cfg.spaceId),
|
|
41
|
-
filter,
|
|
42
|
-
['name', 'id'],
|
|
43
|
-
);
|
|
44
|
-
const page = all.slice(offset, offset + limit);
|
|
45
|
-
return {
|
|
46
|
-
threads: page,
|
|
47
|
-
total: all.length,
|
|
48
|
-
offset,
|
|
49
|
-
limit,
|
|
50
|
-
hasMore: offset + limit < all.length,
|
|
51
|
-
spaceId: cfg.spaceId,
|
|
52
|
-
spaceName: cfg.spaceName,
|
|
53
|
-
filter,
|
|
54
|
-
};
|
|
55
|
-
}
|