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/lib/map-create.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
import { createMapInSpace, createSpace, createSpaceState } from './api.js';
|
|
4
|
-
import { loadConfig, normalizeBaseUrl, saveConfig } from './config.js';
|
|
5
|
-
import { readCsvHeaders } from './csv.js';
|
|
6
|
-
import { fieldSummary, inferFieldTypes } from './fields.js';
|
|
7
|
-
import { fetchSpaceById, searchSpaces } from './spaces.js';
|
|
8
|
-
import { syncMcpConfigs } from './mcp-config.js';
|
|
9
|
-
import { info, promptConfirm, promptInput, promptSelect, success } from './ui.js';
|
|
10
|
-
|
|
11
|
-
function requireAuth() {
|
|
12
|
-
const cfg = loadConfig();
|
|
13
|
-
if (!cfg.apiKey || !cfg.apiBaseUrl) {
|
|
14
|
-
throw new Error('Run mantis setup first (API key + URL).');
|
|
15
|
-
}
|
|
16
|
-
return cfg;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function boolOption(value, fallback = false) {
|
|
20
|
-
if (value == null) return fallback;
|
|
21
|
-
if (typeof value === 'boolean') return value;
|
|
22
|
-
return ['1', 'true', 'yes', 'public'].includes(String(value).toLowerCase());
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function frontendBaseUrl(apiBaseUrl) {
|
|
26
|
-
const root = normalizeBaseUrl(apiBaseUrl).replace(/\/api\/?$/, '');
|
|
27
|
-
let url;
|
|
28
|
-
try {
|
|
29
|
-
url = new URL(root);
|
|
30
|
-
} catch {
|
|
31
|
-
return root;
|
|
32
|
-
}
|
|
33
|
-
// Local dev: API on :8000, frontend on :3000.
|
|
34
|
-
if (/^(localhost|127\.0\.0\.1)$/i.test(url.hostname)) {
|
|
35
|
-
url.port = '3000';
|
|
36
|
-
return url.origin;
|
|
37
|
-
}
|
|
38
|
-
// Mantis production: API on kellis-h200-1.csail.mit.edu, frontend on mantis.csail.mit.edu.
|
|
39
|
-
if (/(^|\.)kellis-h200-1\.csail\.mit\.edu$/i.test(url.hostname)) {
|
|
40
|
-
url.hostname = 'mantis.csail.mit.edu';
|
|
41
|
-
return url.origin;
|
|
42
|
-
}
|
|
43
|
-
return root;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function spaceUrl(baseUrl, spaceId) {
|
|
47
|
-
return `${frontendBaseUrl(baseUrl)}/space/${spaceId}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function chooseExistingSpace(cfg, filter = '') {
|
|
51
|
-
const page = await searchSpaces(cfg.apiBaseUrl, cfg.apiKey, { q: filter, limit: 12 });
|
|
52
|
-
if (!page.spaces?.length) throw new Error('No spaces found.');
|
|
53
|
-
return promptSelect('Which space should receive this map?', page.spaces.map((s) => ({
|
|
54
|
-
name: `${s.name} · ${s.map_count ?? 0} map(s) · ${s.role || 'space'}`,
|
|
55
|
-
value: s,
|
|
56
|
-
description: s.id,
|
|
57
|
-
})));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function resolveSpaceTarget(cfg, opts) {
|
|
61
|
-
let mode = opts.spaceMode;
|
|
62
|
-
if (!mode) {
|
|
63
|
-
mode = await promptSelect('Where should this map go?', [
|
|
64
|
-
{ name: 'Create a new space', value: 'new' },
|
|
65
|
-
{ name: 'Add to an existing space', value: 'existing' },
|
|
66
|
-
]);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (mode === 'existing') {
|
|
70
|
-
if (opts.spaceId) {
|
|
71
|
-
const space = opts.spaceName ? null : await fetchSpaceById(cfg.apiBaseUrl, cfg.apiKey, opts.spaceId);
|
|
72
|
-
return { spaceId: opts.spaceId, spaceName: opts.spaceName || space?.name };
|
|
73
|
-
}
|
|
74
|
-
const picked = await chooseExistingSpace(cfg, opts.spaceSearch || '');
|
|
75
|
-
return { spaceId: picked.id, spaceName: picked.name };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const spaceName = opts.spaceName || await promptInput('New space name', {
|
|
79
|
-
default: path.basename(opts.file, path.extname(opts.file)) || 'Claude Code Map',
|
|
80
|
-
});
|
|
81
|
-
const isPublic = opts.public != null ? boolOption(opts.public) : await promptConfirm('Make this space public?', { default: false });
|
|
82
|
-
return { spaceName, isPublic };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function createMapFlow(file, opts = {}) {
|
|
86
|
-
const cfg = requireAuth();
|
|
87
|
-
const mapName = opts.mapName || await promptInput('Map name', {
|
|
88
|
-
default: path.basename(file, path.extname(file)),
|
|
89
|
-
});
|
|
90
|
-
const headers = readCsvHeaders(file);
|
|
91
|
-
const dataTypes = opts.dataTypes ? JSON.parse(opts.dataTypes) : inferFieldTypes(headers, opts);
|
|
92
|
-
info(`Fields: ${fieldSummary(headers, dataTypes).join(' | ')}`);
|
|
93
|
-
|
|
94
|
-
const target = await resolveSpaceTarget(cfg, { ...opts, file });
|
|
95
|
-
|
|
96
|
-
let spaceId = target.spaceId;
|
|
97
|
-
let spaceName = target.spaceName;
|
|
98
|
-
if (!spaceId) {
|
|
99
|
-
const space = await createSpace(cfg.apiBaseUrl, cfg.apiKey, {
|
|
100
|
-
name: target.spaceName,
|
|
101
|
-
isPublic: target.isPublic,
|
|
102
|
-
});
|
|
103
|
-
spaceId = space.id;
|
|
104
|
-
spaceName = space.name;
|
|
105
|
-
info(`Created space: ${spaceName} (${spaceId})`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const result = await createMapInSpace(cfg.apiBaseUrl, cfg.apiKey, spaceId, {
|
|
109
|
-
file,
|
|
110
|
-
mapName,
|
|
111
|
-
dataTypes,
|
|
112
|
-
selectedFields: opts.selectedFields,
|
|
113
|
-
fieldWeights: opts.fieldWeights,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const primaryMapId = result.map_id || result.base_map_id || result.map_ids?.[0];
|
|
117
|
-
target.spaceName = spaceName;
|
|
118
|
-
success('Map creation started');
|
|
119
|
-
info(`Space: ${target.spaceName || spaceId} (${spaceId})`);
|
|
120
|
-
info(`Map: ${mapName}${primaryMapId ? ` (${primaryMapId})` : ''}`);
|
|
121
|
-
if (spaceId) info(`Link: ${spaceUrl(cfg.apiBaseUrl, spaceId)}`);
|
|
122
|
-
|
|
123
|
-
const activate = opts.activate != null ? boolOption(opts.activate) : await promptConfirm('Set this as the active Claude Code Mantis space?', { default: true });
|
|
124
|
-
let thread = null;
|
|
125
|
-
if (activate) {
|
|
126
|
-
const threadName = opts.threadName || await promptInput('Thread name', { default: `${mapName} Exploration` });
|
|
127
|
-
thread = await createSpaceState(cfg.apiBaseUrl, cfg.apiKey, spaceId, threadName);
|
|
128
|
-
const next = {
|
|
129
|
-
...cfg,
|
|
130
|
-
spaceId,
|
|
131
|
-
spaceName: target.spaceName || mapName,
|
|
132
|
-
spaceStateId: thread.id,
|
|
133
|
-
spaceStateName: thread.name,
|
|
134
|
-
};
|
|
135
|
-
saveConfig(next);
|
|
136
|
-
syncMcpConfigs(next);
|
|
137
|
-
success(`Active thread: ${thread.name}`);
|
|
138
|
-
info('Run /reload-plugins in Claude Code before using Mantis MCP tools.');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
...result,
|
|
143
|
-
space_id: spaceId,
|
|
144
|
-
map_id: primaryMapId,
|
|
145
|
-
space_url: spaceId ? spaceUrl(cfg.apiBaseUrl, spaceId) : undefined,
|
|
146
|
-
thread,
|
|
147
|
-
};
|
|
148
|
-
}
|
package/lib/mcp-config.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
|
|
6
|
-
import { mcpUrl } from './config.js';
|
|
7
|
-
|
|
8
|
-
const MARKETPLACE = 'mantis-plugins';
|
|
9
|
-
const PACKAGE = 'mantisai-cli';
|
|
10
|
-
|
|
11
|
-
function existingPackageRoot(root) {
|
|
12
|
-
return root && fs.existsSync(path.join(root, 'package.json')) ? path.normalize(root) : null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function isInstallRoot(root) {
|
|
16
|
-
const p = root.toLowerCase();
|
|
17
|
-
return p.includes(`${path.sep}.claude${path.sep}plugins${path.sep}`) ||
|
|
18
|
-
p.endsWith(`${path.sep}node_modules${path.sep}${PACKAGE}`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function cacheRoots() {
|
|
22
|
-
const base = path.join(os.homedir(), '.claude', 'plugins', 'cache', MARKETPLACE, 'mantis');
|
|
23
|
-
if (!fs.existsSync(base)) return [];
|
|
24
|
-
return fs.readdirSync(base).map((v) => path.join(base, v));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function pluginRoots() {
|
|
28
|
-
const here = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
|
-
const home = os.homedir();
|
|
30
|
-
return [
|
|
31
|
-
here,
|
|
32
|
-
path.join(home, '.claude', 'plugins', 'marketplaces', MARKETPLACE),
|
|
33
|
-
path.join(home, '.claude', 'plugins', 'npm-cache', 'node_modules', PACKAGE),
|
|
34
|
-
path.join(home, 'AppData', 'Roaming', 'npm', 'node_modules', PACKAGE),
|
|
35
|
-
...cacheRoots(),
|
|
36
|
-
].map(existingPackageRoot).filter(Boolean).filter(isInstallRoot).filter((v, i, a) => a.indexOf(v) === i);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function mcpConfig(cfg) {
|
|
40
|
-
const server = { type: 'http', url: mcpUrl(cfg) };
|
|
41
|
-
if (cfg.spaceStateId) server.headers = { 'X-Space-State-ID': String(cfg.spaceStateId) };
|
|
42
|
-
return { mcpServers: { mantis: server } };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function syncMcpConfigs(cfg) {
|
|
46
|
-
const body = `${JSON.stringify(mcpConfig(cfg), null, 2)}\n`;
|
|
47
|
-
for (const root of pluginRoots()) {
|
|
48
|
-
fs.writeFileSync(path.join(root, '.mcp.json'), body);
|
|
49
|
-
}
|
|
50
|
-
}
|
package/lib/picker.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { createSpaceState, fetchThreads } from './fetch.js';
|
|
2
|
-
import { loadConfig, saveConfig } from './config.js';
|
|
3
|
-
import { syncMcpConfigs } from './mcp-config.js';
|
|
4
|
-
import { searchSpaces, resolveSpaceFromInput } from './spaces.js';
|
|
5
|
-
import { parseSpaceIdFromInput } from './space-id.js';
|
|
6
|
-
import { die, info, promptInput, promptSearch, success } from './ui.js';
|
|
7
|
-
|
|
8
|
-
const BROWSE_PAGE = 20;
|
|
9
|
-
|
|
10
|
-
function spaceLabel(s) {
|
|
11
|
-
const name = s.name || '(unnamed)';
|
|
12
|
-
const maps = s.map_count != null ? ` · ${s.map_count} map(s)` : '';
|
|
13
|
-
const role = s.role ? ` · ${s.role}` : '';
|
|
14
|
-
return name + maps + role;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function spaceChoices(spaces) {
|
|
18
|
-
return spaces.map((s) => ({
|
|
19
|
-
name: spaceLabel(s),
|
|
20
|
-
value: s,
|
|
21
|
-
description: s.id,
|
|
22
|
-
}));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function requireConfig() {
|
|
26
|
-
const cfg = loadConfig();
|
|
27
|
-
if (!cfg.apiKey || !cfg.apiBaseUrl) {
|
|
28
|
-
die('Run mantis setup or /mantis:connect first (API key + URL).');
|
|
29
|
-
}
|
|
30
|
-
return cfg;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function threadChoices(threads, query) {
|
|
34
|
-
const terms = (query || '').trim().toLowerCase();
|
|
35
|
-
const list = terms
|
|
36
|
-
? threads.filter((t) => {
|
|
37
|
-
const hay = `${t.name} ${t.id}`.toLowerCase();
|
|
38
|
-
return hay.includes(terms);
|
|
39
|
-
})
|
|
40
|
-
: threads;
|
|
41
|
-
const out = [
|
|
42
|
-
{ name: '➕ Create new thread', value: '__new__', description: 'New space state' },
|
|
43
|
-
];
|
|
44
|
-
for (const t of list) {
|
|
45
|
-
const when = t.updated_at ? String(t.updated_at).slice(0, 10) : '';
|
|
46
|
-
out.push({
|
|
47
|
-
name: t.name || '(unnamed)',
|
|
48
|
-
value: t,
|
|
49
|
-
description: when || t.id,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function pickSpaceByLink(cfg) {
|
|
56
|
-
const raw = await promptInput('Paste Mantis space link or UUID (Enter to browse)', {
|
|
57
|
-
default: '',
|
|
58
|
-
});
|
|
59
|
-
if (!raw?.trim()) return null;
|
|
60
|
-
const space = await resolveSpaceFromInput(cfg.apiBaseUrl, cfg.apiKey, raw);
|
|
61
|
-
if (!space) die('Space not found or you do not have access.');
|
|
62
|
-
return saveSpace(cfg, space);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function pickSpace() {
|
|
66
|
-
const cfg = await requireConfig();
|
|
67
|
-
|
|
68
|
-
if (!process.stdin.isTTY) {
|
|
69
|
-
const page = await searchSpaces(cfg.apiBaseUrl, cfg.apiKey, { limit: 1 });
|
|
70
|
-
if (!page.spaces?.length) die('No spaces found.');
|
|
71
|
-
return saveSpace(cfg, page.spaces[0]);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const fromLink = await pickSpaceByLink(cfg);
|
|
75
|
-
if (fromLink) return fromLink;
|
|
76
|
-
|
|
77
|
-
const picked = await promptSearch(
|
|
78
|
-
'Select space (↑↓ type to search, paste link in previous step)',
|
|
79
|
-
async (input) => {
|
|
80
|
-
const id = parseSpaceIdFromInput(input);
|
|
81
|
-
if (id) {
|
|
82
|
-
const one = await resolveSpaceFromInput(cfg.apiBaseUrl, cfg.apiKey, input);
|
|
83
|
-
return one ? spaceChoices([one]) : [{ name: 'No match for that link/id', value: null, disabled: true }];
|
|
84
|
-
}
|
|
85
|
-
const page = await searchSpaces(cfg.apiBaseUrl, cfg.apiKey, {
|
|
86
|
-
q: input || '',
|
|
87
|
-
limit: BROWSE_PAGE,
|
|
88
|
-
offset: 0,
|
|
89
|
-
});
|
|
90
|
-
return spaceChoices(page.spaces || []);
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
if (!picked) die('No space selected.');
|
|
94
|
-
return saveSpace(cfg, picked);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function saveSpace(cfg, space) {
|
|
98
|
-
const changed = cfg.spaceId !== space.id;
|
|
99
|
-
const next = { ...cfg, spaceId: space.id, spaceName: space.name };
|
|
100
|
-
if (changed) {
|
|
101
|
-
delete next.spaceStateId;
|
|
102
|
-
delete next.spaceStateName;
|
|
103
|
-
}
|
|
104
|
-
saveConfig(next);
|
|
105
|
-
syncMcpConfigs(next);
|
|
106
|
-
success(`Space: ${space.name}`);
|
|
107
|
-
if (changed) {
|
|
108
|
-
info('Thread cleared — pick a thread, then /reload-plugins in Claude Code.');
|
|
109
|
-
}
|
|
110
|
-
return next;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export async function pickThread(initialFilter = '') {
|
|
114
|
-
const cfg = await requireConfig();
|
|
115
|
-
if (!cfg.spaceId) die('Pick a space first: /mantis:space');
|
|
116
|
-
|
|
117
|
-
const threads = await fetchThreads(cfg.apiBaseUrl, cfg.apiKey, cfg.spaceId);
|
|
118
|
-
|
|
119
|
-
if (!process.stdin.isTTY) {
|
|
120
|
-
if (!threads.length) die('No threads. Run interactively to create one.');
|
|
121
|
-
return saveThread(cfg, threads[0]);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const picked = await promptSearch(
|
|
125
|
-
`Thread in "${cfg.spaceName || 'space'}" (↑↓, type to filter)`,
|
|
126
|
-
async (input) => threadChoices(threads, input || initialFilter),
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
if (picked === '__new__') {
|
|
130
|
-
const name = await promptInput('Thread name', { default: 'Claude Code' });
|
|
131
|
-
const created = await createSpaceState(
|
|
132
|
-
cfg.apiBaseUrl, cfg.apiKey, cfg.spaceId, name || 'Claude Code',
|
|
133
|
-
);
|
|
134
|
-
return saveThread(cfg, created);
|
|
135
|
-
}
|
|
136
|
-
return saveThread(cfg, picked);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function saveThread(cfg, thread) {
|
|
140
|
-
const next = {
|
|
141
|
-
...cfg,
|
|
142
|
-
spaceStateId: thread.id,
|
|
143
|
-
spaceStateName: thread.name,
|
|
144
|
-
};
|
|
145
|
-
saveConfig(next);
|
|
146
|
-
syncMcpConfigs(next);
|
|
147
|
-
success(`Thread: ${thread.name}`);
|
|
148
|
-
info('MCP headers update on reconnect — run /reload-plugins if tools lack context.');
|
|
149
|
-
return next;
|
|
150
|
-
}
|
package/lib/spaces.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { listSpaces } from './api.js';
|
|
2
|
-
import { parseSpaceIdFromInput } from './space-id.js';
|
|
3
|
-
|
|
4
|
-
export async function fetchSpaceById(baseUrl, apiKey, spaceId) {
|
|
5
|
-
const page = await listSpaces(baseUrl, apiKey, {
|
|
6
|
-
scope: 'accessible',
|
|
7
|
-
space_id: spaceId,
|
|
8
|
-
limit: 1,
|
|
9
|
-
offset: 0,
|
|
10
|
-
});
|
|
11
|
-
return page.spaces?.[0] ?? null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function resolveSpaceFromInput(baseUrl, apiKey, text) {
|
|
15
|
-
const id = parseSpaceIdFromInput(text);
|
|
16
|
-
if (!id) return null;
|
|
17
|
-
return fetchSpaceById(baseUrl, apiKey, id);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** One page of spaces; optional name/id search — does not load the full catalog. */
|
|
21
|
-
export async function searchSpaces(
|
|
22
|
-
baseUrl,
|
|
23
|
-
apiKey,
|
|
24
|
-
{ q = '', limit = 20, offset = 0, scope = 'accessible' } = {},
|
|
25
|
-
) {
|
|
26
|
-
const id = parseSpaceIdFromInput(q);
|
|
27
|
-
if (id) {
|
|
28
|
-
const one = await fetchSpaceById(baseUrl, apiKey, id);
|
|
29
|
-
return {
|
|
30
|
-
spaces: one ? [one] : [],
|
|
31
|
-
total: one ? 1 : 0,
|
|
32
|
-
limit: 1,
|
|
33
|
-
offset: 0,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
const page = await listSpaces(baseUrl, apiKey, {
|
|
37
|
-
scope,
|
|
38
|
-
limit,
|
|
39
|
-
offset,
|
|
40
|
-
q: q.trim() || undefined,
|
|
41
|
-
});
|
|
42
|
-
return {
|
|
43
|
-
spaces: page.spaces || [],
|
|
44
|
-
total: page.total ?? (page.spaces || []).length,
|
|
45
|
-
limit: page.limit ?? limit,
|
|
46
|
-
offset: page.offset ?? offset,
|
|
47
|
-
};
|
|
48
|
-
}
|
package/lib/ui.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { confirm, input, password, search, select } from '@inquirer/prompts';
|
|
2
|
-
|
|
3
|
-
export function isCancel(err) {
|
|
4
|
-
return err?.name === 'ExitPromptError';
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function die(msg) {
|
|
8
|
-
console.error(`\n ✖ ${msg}\n`);
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function banner(title, subtitle) {
|
|
13
|
-
console.log('');
|
|
14
|
-
console.log(` \x1b[36m${title}\x1b[0m`);
|
|
15
|
-
if (subtitle) console.log(` \x1b[2m${subtitle}\x1b[0m`);
|
|
16
|
-
console.log('');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function success(msg) {
|
|
20
|
-
console.log(` \x1b[32m✔\x1b[0m ${msg}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function info(msg) {
|
|
24
|
-
console.log(` \x1b[2m→\x1b[0m ${msg}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function promptInput(message, { default: def } = {}) {
|
|
28
|
-
try {
|
|
29
|
-
return await input({ message, default: def });
|
|
30
|
-
} catch (e) {
|
|
31
|
-
if (isCancel(e)) die('Cancelled.');
|
|
32
|
-
throw e;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function promptSecret(message, { default: def } = {}) {
|
|
37
|
-
if (def) {
|
|
38
|
-
return promptInput(message, { default: def });
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
return await password({ message, mask: '•' });
|
|
42
|
-
} catch (e) {
|
|
43
|
-
if (isCancel(e)) die('Cancelled.');
|
|
44
|
-
throw e;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function promptSearch(message, source, { pageSize = 12 } = {}) {
|
|
49
|
-
try {
|
|
50
|
-
return await search({ message, pageSize, source });
|
|
51
|
-
} catch (e) {
|
|
52
|
-
if (isCancel(e)) die('Cancelled.');
|
|
53
|
-
throw e;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function promptSelect(message, choices) {
|
|
58
|
-
try {
|
|
59
|
-
return await select({ message, choices });
|
|
60
|
-
} catch (e) {
|
|
61
|
-
if (isCancel(e)) die('Cancelled.');
|
|
62
|
-
throw e;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function promptConfirm(message, { default: def = false } = {}) {
|
|
67
|
-
try {
|
|
68
|
-
return await confirm({ message, default: def });
|
|
69
|
-
} catch (e) {
|
|
70
|
-
if (isCancel(e)) die('Cancelled.');
|
|
71
|
-
throw e;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
File without changes
|