@injectivelabs/ainj 0.0.1
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/LICENSE +201 -0
- package/README.md +309 -0
- package/ainj.config.json +3 -0
- package/dist/cli/index.js +53 -0
- package/dist/cli/injectived-cli.js +11 -0
- package/dist/cli/install-cli.js +13 -0
- package/dist/cli/mcp-cli.js +44 -0
- package/dist/cli/router.js +18 -0
- package/dist/cli/skills-cli.js +28 -0
- package/dist/cli/status-cli.js +43 -0
- package/dist/cli/update-cli.js +16 -0
- package/dist/env.js +22 -0
- package/dist/install/harnesses/claude-code.js +31 -0
- package/dist/install/harnesses/codex.js +51 -0
- package/dist/install/harnesses/cursor.js +23 -0
- package/dist/install/harnesses/index.js +13 -0
- package/dist/install/harnesses/windsurf.js +23 -0
- package/dist/install/index.js +17 -0
- package/dist/install/prompts.js +109 -0
- package/dist/install/skills-install.js +38 -0
- package/dist/install/state.js +23 -0
- package/dist/install/which.js +7 -0
- package/dist/lib/ainj.js +6 -0
- package/dist/lib/cli.js +44 -0
- package/dist/lib/exec.js +4 -0
- package/dist/lib/mcp/client.js +68 -0
- package/dist/lib/mcp/connect.js +17 -0
- package/dist/lib/mcp/docs.js +4 -0
- package/dist/lib/mcp/main.js +4 -0
- package/dist/lib/mcp/spawn.js +12 -0
- package/dist/lib/skills.js +85 -0
- package/dist/mcp/docs/client.js +11 -0
- package/dist/mcp/docs/index.js +125 -0
- package/dist/mcp/main/index.js +144 -0
- package/package.json +71 -0
- package/scripts/sync-skills.js +66 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { readState } from '../install/state.js';
|
|
6
|
+
|
|
7
|
+
const { version } = createRequire(import.meta.url)('../../package.json');
|
|
8
|
+
const thisDir = import.meta.dirname;
|
|
9
|
+
|
|
10
|
+
export async function run(
|
|
11
|
+
_args,
|
|
12
|
+
{ _readState = readState, _readdir = readdir, _log = console.log } = {},
|
|
13
|
+
) {
|
|
14
|
+
const state = _readState('global');
|
|
15
|
+
|
|
16
|
+
const scope = state.scope ?? '(none)';
|
|
17
|
+
const mainPort = state.ports?.main ?? '(none)';
|
|
18
|
+
const docsPort = state.ports?.docs ?? '(none)';
|
|
19
|
+
const harnesses = state.harnesses?.length ? state.harnesses.join(', ') : '(none)';
|
|
20
|
+
const defaultHarness = state.defaultHarness ?? '(none)';
|
|
21
|
+
|
|
22
|
+
const agentsSkillsDir = path.join(thisDir, '../../.agents/skills');
|
|
23
|
+
let skillNames = [];
|
|
24
|
+
try {
|
|
25
|
+
const entries = await _readdir(agentsSkillsDir, { withFileTypes: true });
|
|
26
|
+
skillNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (err.code !== 'ENOENT') throw err;
|
|
29
|
+
}
|
|
30
|
+
let skillsStr;
|
|
31
|
+
if (skillNames.length > 0) {
|
|
32
|
+
skillsStr = `${skillNames.length} available.`;
|
|
33
|
+
} else {
|
|
34
|
+
skillsStr = '(none)';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_log(`version: ${version}`);
|
|
38
|
+
_log(`scope: ${scope}`);
|
|
39
|
+
_log(`ports: main=${mainPort}, docs=${docsPort}`);
|
|
40
|
+
_log(`harnesses: ${harnesses}`);
|
|
41
|
+
_log(`default harness: ${defaultHarness}`);
|
|
42
|
+
_log(`skills: ${skillsStr}`);
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
|
|
4
|
+
import { readState } from '../install/state.js';
|
|
5
|
+
|
|
6
|
+
const { name: packageName } = createRequire(import.meta.url)('../../package.json');
|
|
7
|
+
|
|
8
|
+
export async function run(_args, { _readState = readState, _spawnSync = spawnSync } = {}) {
|
|
9
|
+
const state = _readState('global');
|
|
10
|
+
|
|
11
|
+
if (state.scope === 'local') {
|
|
12
|
+
_spawnSync('npm', ['install', `${packageName}@latest`], { stdio: 'inherit' });
|
|
13
|
+
} else {
|
|
14
|
+
_spawnSync('npm', ['install', '-g', `${packageName}@latest`], { stdio: 'inherit' });
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function loadEnv(cwd = process.cwd()) {
|
|
5
|
+
let content;
|
|
6
|
+
try {
|
|
7
|
+
content = readFileSync(join(cwd, '.env'), 'utf8');
|
|
8
|
+
} catch {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
for (const line of content.split('\n')) {
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
14
|
+
const eqIdx = trimmed.indexOf('=');
|
|
15
|
+
if (eqIdx === -1) continue;
|
|
16
|
+
const key = trimmed.slice(0, eqIdx);
|
|
17
|
+
const value = trimmed.slice(eqIdx + 1);
|
|
18
|
+
if (key && !(key in process.env)) {
|
|
19
|
+
process.env[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export function write(
|
|
6
|
+
scope,
|
|
7
|
+
mainPort,
|
|
8
|
+
docsPort,
|
|
9
|
+
{ homeDir = os.homedir(), cwd = process.cwd() } = {},
|
|
10
|
+
) {
|
|
11
|
+
const filePath =
|
|
12
|
+
scope === 'global'
|
|
13
|
+
? path.join(homeDir, '.claude.json')
|
|
14
|
+
: path.join(cwd, '.claude', 'settings.json');
|
|
15
|
+
|
|
16
|
+
let data = {};
|
|
17
|
+
try {
|
|
18
|
+
data = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
19
|
+
} catch {
|
|
20
|
+
// File absent or unreadable: start fresh.
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!data.mcpServers) data.mcpServers = {};
|
|
24
|
+
data.mcpServers['ainj-main'] = { command: 'ainj', args: ['mcp', 'main', 'stdio'] };
|
|
25
|
+
data.mcpServers['ainj-docs'] = { command: 'ainj', args: ['mcp', 'docs', 'stdio'] };
|
|
26
|
+
data.mcpServers['ainj-main-http'] = { url: `http://localhost:${mainPort}/mcp` };
|
|
27
|
+
data.mcpServers['ainj-docs-http'] = { url: `http://localhost:${docsPort}/mcp` };
|
|
28
|
+
|
|
29
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
30
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { parse as parseToml, stringify as stringifyToml } from 'smol-toml';
|
|
5
|
+
|
|
6
|
+
export function write(
|
|
7
|
+
scope,
|
|
8
|
+
mainPort,
|
|
9
|
+
docsPort,
|
|
10
|
+
{ homeDir = os.homedir(), cwd = process.cwd() } = {},
|
|
11
|
+
) {
|
|
12
|
+
if (scope === 'local') {
|
|
13
|
+
_writeLocal(cwd, mainPort, docsPort);
|
|
14
|
+
} else {
|
|
15
|
+
_writeGlobal(homeDir, mainPort, docsPort);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _writeGlobal(homeDir, mainPort, docsPort) {
|
|
20
|
+
const filePath = path.join(homeDir, '.codex', 'config.json');
|
|
21
|
+
let data = {};
|
|
22
|
+
try {
|
|
23
|
+
data = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
24
|
+
} catch {
|
|
25
|
+
// File absent or unreadable: start fresh.
|
|
26
|
+
}
|
|
27
|
+
_mergeEntries(data, mainPort, docsPort);
|
|
28
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
29
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function _writeLocal(cwd, mainPort, docsPort) {
|
|
33
|
+
const filePath = path.join(cwd, '.codex', 'config.toml');
|
|
34
|
+
let data = {};
|
|
35
|
+
try {
|
|
36
|
+
data = parseToml(readFileSync(filePath, 'utf8'));
|
|
37
|
+
} catch {
|
|
38
|
+
// File absent or unreadable: start fresh.
|
|
39
|
+
}
|
|
40
|
+
_mergeEntries(data, mainPort, docsPort);
|
|
41
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
42
|
+
writeFileSync(filePath, stringifyToml(data));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function _mergeEntries(data, mainPort, docsPort) {
|
|
46
|
+
if (!data.mcpServers) data.mcpServers = {};
|
|
47
|
+
data.mcpServers['ainj-main'] = { command: 'ainj', args: ['mcp', 'main', 'stdio'] };
|
|
48
|
+
data.mcpServers['ainj-docs'] = { command: 'ainj', args: ['mcp', 'docs', 'stdio'] };
|
|
49
|
+
data.mcpServers['ainj-main-http'] = { url: `http://localhost:${mainPort}/mcp` };
|
|
50
|
+
data.mcpServers['ainj-docs-http'] = { url: `http://localhost:${docsPort}/mcp` };
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export function write(_scope, mainPort, docsPort, { homeDir = os.homedir() } = {}) {
|
|
6
|
+
const filePath = path.join(homeDir, '.cursor', 'mcp.json');
|
|
7
|
+
|
|
8
|
+
let data = {};
|
|
9
|
+
try {
|
|
10
|
+
data = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
11
|
+
} catch {
|
|
12
|
+
// File absent or unreadable: start fresh.
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!data.mcpServers) data.mcpServers = {};
|
|
16
|
+
data.mcpServers['ainj-main'] = { command: 'ainj', args: ['mcp', 'main', 'stdio'] };
|
|
17
|
+
data.mcpServers['ainj-docs'] = { command: 'ainj', args: ['mcp', 'docs', 'stdio'] };
|
|
18
|
+
data.mcpServers['ainj-main-http'] = { url: `http://localhost:${mainPort}/mcp` };
|
|
19
|
+
data.mcpServers['ainj-docs-http'] = { url: `http://localhost:${docsPort}/mcp` };
|
|
20
|
+
|
|
21
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
22
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { write as claudeCode } from './claude-code.js';
|
|
2
|
+
import { write as codex } from './codex.js';
|
|
3
|
+
import { write as cursor } from './cursor.js';
|
|
4
|
+
import { write as windsurf } from './windsurf.js';
|
|
5
|
+
|
|
6
|
+
const writers = { claude: claudeCode, codex, cursor, windsurf };
|
|
7
|
+
|
|
8
|
+
export function writeHarnessConfigs(harnesses, scope, mainPort, docsPort, opts = {}) {
|
|
9
|
+
for (const harness of harnesses) {
|
|
10
|
+
const writer = writers[harness];
|
|
11
|
+
if (writer) writer(scope, mainPort, docsPort, opts);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export function write(_scope, mainPort, docsPort, { homeDir = os.homedir() } = {}) {
|
|
6
|
+
const filePath = path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json');
|
|
7
|
+
|
|
8
|
+
let data = {};
|
|
9
|
+
try {
|
|
10
|
+
data = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
11
|
+
} catch {
|
|
12
|
+
// File absent or unreadable: start fresh.
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!data.mcpServers) data.mcpServers = {};
|
|
16
|
+
data.mcpServers['ainj-main'] = { command: 'ainj', args: ['mcp', 'main', 'stdio'] };
|
|
17
|
+
data.mcpServers['ainj-docs'] = { command: 'ainj', args: ['mcp', 'docs', 'stdio'] };
|
|
18
|
+
data.mcpServers['ainj-main-http'] = { url: `http://localhost:${mainPort}/mcp` };
|
|
19
|
+
data.mcpServers['ainj-docs-http'] = { url: `http://localhost:${docsPort}/mcp` };
|
|
20
|
+
|
|
21
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
22
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { runDefaults, runWizard } from './prompts.js';
|
|
3
|
+
|
|
4
|
+
export async function main(
|
|
5
|
+
isTTY = process.stdout.isTTY,
|
|
6
|
+
{ _runDefaults = runDefaults, _runWizard = runWizard } = {},
|
|
7
|
+
) {
|
|
8
|
+
if (isTTY) {
|
|
9
|
+
await _runWizard();
|
|
10
|
+
} else {
|
|
11
|
+
await _runDefaults();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
16
|
+
await main();
|
|
17
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { writeHarnessConfigs as writeHarnessConfigsDefault } from './harnesses/index.js';
|
|
3
|
+
import { installSkills as installSkillsDefault } from './skills-install.js';
|
|
4
|
+
import { readState, writeState } from './state.js';
|
|
5
|
+
import { which as whichDefault } from './which.js';
|
|
6
|
+
|
|
7
|
+
export async function runDefaults({
|
|
8
|
+
_which = whichDefault,
|
|
9
|
+
_writeState = writeState,
|
|
10
|
+
...stateOpts
|
|
11
|
+
} = {}) {
|
|
12
|
+
const defaultHarness = _which('claude') ? 'claude' : _which('codex') ? 'codex' : null;
|
|
13
|
+
_writeState(
|
|
14
|
+
'global',
|
|
15
|
+
{
|
|
16
|
+
scope: 'global',
|
|
17
|
+
ports: { main: 3001, docs: 3002 },
|
|
18
|
+
harnesses: [],
|
|
19
|
+
defaultHarness,
|
|
20
|
+
nonInteractive: true,
|
|
21
|
+
},
|
|
22
|
+
stateOpts,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function runWizard({
|
|
27
|
+
_which = whichDefault,
|
|
28
|
+
_readState = readState,
|
|
29
|
+
_writeState = writeState,
|
|
30
|
+
_writeHarnessConfigs = writeHarnessConfigsDefault,
|
|
31
|
+
_installSkills = installSkillsDefault,
|
|
32
|
+
_p = p,
|
|
33
|
+
...stateOpts
|
|
34
|
+
} = {}) {
|
|
35
|
+
const localState = _readState('local', stateOpts);
|
|
36
|
+
const existing =
|
|
37
|
+
Object.keys(localState).length > 0 ? localState : _readState('global', stateOpts);
|
|
38
|
+
|
|
39
|
+
_p.intro('AInj setup');
|
|
40
|
+
|
|
41
|
+
const scope = await _p.select({
|
|
42
|
+
message: 'Install scope',
|
|
43
|
+
options: [
|
|
44
|
+
{ value: 'global', label: 'global (recommended)' },
|
|
45
|
+
{ value: 'local', label: 'local (this project only)' },
|
|
46
|
+
],
|
|
47
|
+
initialValue: existing.scope ?? 'global',
|
|
48
|
+
});
|
|
49
|
+
if (_p.isCancel(scope)) {
|
|
50
|
+
_p.cancel('Setup cancelled.');
|
|
51
|
+
process.exit(0);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const mainPort = await _p.text({
|
|
56
|
+
message: 'Main MCP port',
|
|
57
|
+
initialValue: String(existing.ports?.main ?? 3001),
|
|
58
|
+
});
|
|
59
|
+
if (_p.isCancel(mainPort)) {
|
|
60
|
+
_p.cancel('Setup cancelled.');
|
|
61
|
+
process.exit(0);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const docsPort = await _p.text({
|
|
66
|
+
message: 'Docs MCP port',
|
|
67
|
+
initialValue: String(existing.ports?.docs ?? 3002),
|
|
68
|
+
});
|
|
69
|
+
if (_p.isCancel(docsPort)) {
|
|
70
|
+
_p.cancel('Setup cancelled.');
|
|
71
|
+
process.exit(0);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const harnesses = await _p.multiselect({
|
|
76
|
+
message: 'Select harnesses to configure',
|
|
77
|
+
options: [
|
|
78
|
+
{ value: 'claude', label: 'Claude Code' },
|
|
79
|
+
{ value: 'codex', label: 'Codex' },
|
|
80
|
+
{ value: 'cursor', label: 'Cursor' },
|
|
81
|
+
{ value: 'windsurf', label: 'Windsurf' },
|
|
82
|
+
],
|
|
83
|
+
required: false,
|
|
84
|
+
initialValues: existing.harnesses ?? [],
|
|
85
|
+
});
|
|
86
|
+
if (_p.isCancel(harnesses)) {
|
|
87
|
+
_p.cancel('Setup cancelled.');
|
|
88
|
+
process.exit(0);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_writeHarnessConfigs(harnesses, scope, Number(mainPort), Number(docsPort));
|
|
93
|
+
await _installSkills();
|
|
94
|
+
|
|
95
|
+
const defaultHarness = _which('claude') ? 'claude' : _which('codex') ? 'codex' : null;
|
|
96
|
+
|
|
97
|
+
_writeState(
|
|
98
|
+
scope,
|
|
99
|
+
{
|
|
100
|
+
scope,
|
|
101
|
+
ports: { main: Number(mainPort), docs: Number(docsPort) },
|
|
102
|
+
harnesses,
|
|
103
|
+
defaultHarness,
|
|
104
|
+
},
|
|
105
|
+
stateOpts,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
_p.outro('AInj configured.');
|
|
109
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawnSync as _spawnSyncDefault } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { syncSkills } from '../../scripts/sync-skills.js';
|
|
6
|
+
|
|
7
|
+
const thisDir = import.meta.dirname;
|
|
8
|
+
const SKILLS_PACKAGE = 'skills@1.5.13';
|
|
9
|
+
|
|
10
|
+
export async function installSkills({
|
|
11
|
+
_spawnSync = _spawnSyncDefault,
|
|
12
|
+
_warn = console.warn,
|
|
13
|
+
_fs = fs,
|
|
14
|
+
_syncSkills = syncSkills,
|
|
15
|
+
} = {}) {
|
|
16
|
+
const skillsDir = path.join(thisDir, '../../.agents/skills/');
|
|
17
|
+
|
|
18
|
+
// Check if skills directory already exists, otherwise sync from upstream.
|
|
19
|
+
let skillsDirExists = false;
|
|
20
|
+
try {
|
|
21
|
+
const skillsDirStat = await _fs.stat(skillsDir);
|
|
22
|
+
skillsDirExists = skillsDirStat.isDirectory();
|
|
23
|
+
} catch (ex) {
|
|
24
|
+
if (ex.code !== 'ENOENT') throw ex;
|
|
25
|
+
}
|
|
26
|
+
if (!skillsDirExists) {
|
|
27
|
+
await _syncSkills();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log('skills directory:', skillsDir);
|
|
31
|
+
const result = _spawnSync('npx', ['-y', SKILLS_PACKAGE, 'add', '--global', skillsDir], {
|
|
32
|
+
stdio: 'inherit',
|
|
33
|
+
cwd: skillsDir,
|
|
34
|
+
});
|
|
35
|
+
if (result.status !== 0) {
|
|
36
|
+
_warn(`skills install failed with status ${result.status}, skipping`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export function stateFilePath(scope, { homeDir = os.homedir(), cwd = process.cwd() } = {}) {
|
|
6
|
+
const base = scope === 'global' ? homeDir : cwd;
|
|
7
|
+
return path.join(base, '.ainj', 'config.json');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function readState(scope, opts = {}) {
|
|
11
|
+
try {
|
|
12
|
+
const content = readFileSync(stateFilePath(scope, opts), 'utf8');
|
|
13
|
+
return JSON.parse(content);
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function writeState(scope, data, opts = {}) {
|
|
20
|
+
const filePath = stateFilePath(scope, opts);
|
|
21
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
22
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
23
|
+
}
|
package/dist/lib/ainj.js
ADDED
package/dist/lib/cli.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { execFile as execFileCb, spawnSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
|
|
7
|
+
const execFile = promisify(execFileCb);
|
|
8
|
+
const _require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
export function resolveBinary() {
|
|
11
|
+
// Strategy 1: resolve via injective-core package bin field
|
|
12
|
+
try {
|
|
13
|
+
const pkgPath = _require.resolve('injective-core/package.json');
|
|
14
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
15
|
+
if (pkg.bin?.injectived) {
|
|
16
|
+
return resolve(dirname(pkgPath), pkg.bin.injectived);
|
|
17
|
+
}
|
|
18
|
+
} catch {
|
|
19
|
+
// fall through to strategy 2
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Strategy 2: which injectived
|
|
23
|
+
const result = spawnSync('which', ['injectived'], { encoding: 'utf8' });
|
|
24
|
+
if (result.status === 0) {
|
|
25
|
+
return result.stdout.trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
throw new Error('injectived not found: injective-core not installed and injectived not on $PATH');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function cli(...args) {
|
|
32
|
+
const bin = resolveBinary();
|
|
33
|
+
const { stdout, stderr } = await execFile(bin, args);
|
|
34
|
+
return { stdout, stderr };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Object.defineProperty(cli, 'path', {
|
|
38
|
+
get: resolveBinary,
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export { cli };
|
|
44
|
+
export default cli;
|
package/dist/lib/exec.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { connectHttp, connectStdio } from './connect.js';
|
|
2
|
+
import { resolveServer } from './spawn.js';
|
|
3
|
+
|
|
4
|
+
const INITIAL = 'INITIAL';
|
|
5
|
+
const MANAGED = 'MANAGED';
|
|
6
|
+
const CONNECTED = 'CONNECTED';
|
|
7
|
+
|
|
8
|
+
export class McpClient {
|
|
9
|
+
#state = INITIAL;
|
|
10
|
+
#client = null;
|
|
11
|
+
#bin;
|
|
12
|
+
|
|
13
|
+
constructor(bin) {
|
|
14
|
+
this.#bin = bin;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get state() {
|
|
18
|
+
return this.#state;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async start() {
|
|
22
|
+
if (this.#state !== INITIAL) throw new Error(`Cannot start(): current state is ${this.#state}`);
|
|
23
|
+
const { command, args } = resolveServer(this.#bin);
|
|
24
|
+
this.#client = await connectStdio(command, args);
|
|
25
|
+
this.#state = MANAGED;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async stop() {
|
|
29
|
+
if (this.#state !== MANAGED) throw new Error(`Cannot stop(): current state is ${this.#state}`);
|
|
30
|
+
await this.#client.close();
|
|
31
|
+
this.#client = null;
|
|
32
|
+
this.#state = INITIAL;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async connect(url) {
|
|
36
|
+
if (this.#state !== INITIAL)
|
|
37
|
+
throw new Error(`Cannot connect(): current state is ${this.#state}`);
|
|
38
|
+
this.#client = await connectHttp(url);
|
|
39
|
+
this.#state = CONNECTED;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async disconnect() {
|
|
43
|
+
if (this.#state !== CONNECTED)
|
|
44
|
+
throw new Error(`Cannot disconnect(): current state is ${this.#state}`);
|
|
45
|
+
await this.#client.close();
|
|
46
|
+
this.#client = null;
|
|
47
|
+
this.#state = INITIAL;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async toolCall(name, params = {}) {
|
|
51
|
+
if (this.#state === INITIAL) {
|
|
52
|
+
throw new Error('toolCall() requires MANAGED or CONNECTED state; current state is INITIAL');
|
|
53
|
+
}
|
|
54
|
+
if (params === null) {
|
|
55
|
+
throw new TypeError('toolCall params must be a plain JSON object; got null');
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(params)) {
|
|
58
|
+
throw new TypeError('toolCall params must be a plain JSON object; got array');
|
|
59
|
+
}
|
|
60
|
+
if (typeof params !== 'object') {
|
|
61
|
+
throw new TypeError(`toolCall params must be a plain JSON object; got ${typeof params}`);
|
|
62
|
+
}
|
|
63
|
+
if (name === 'tools/list') {
|
|
64
|
+
return this.#client.listTools(params);
|
|
65
|
+
}
|
|
66
|
+
return this.#client.callTool({ name, arguments: params });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
|
|
5
|
+
export async function connectStdio(command, args) {
|
|
6
|
+
const transport = new StdioClientTransport({ command, args });
|
|
7
|
+
const client = new Client({ name: 'ainj', version: '0.1.0' });
|
|
8
|
+
await client.connect(transport);
|
|
9
|
+
return client;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function connectHttp(url) {
|
|
13
|
+
const transport = new StreamableHTTPClientTransport(new URL(url));
|
|
14
|
+
const client = new Client({ name: 'ainj', version: '0.1.0' });
|
|
15
|
+
await client.connect(transport);
|
|
16
|
+
return client;
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
function resolveAinj() {
|
|
5
|
+
const local = fileURLToPath(new URL('../../../node_modules/.bin/ainj', import.meta.url));
|
|
6
|
+
if (existsSync(local)) return local;
|
|
7
|
+
return 'ainj';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function resolveServer(bin) {
|
|
11
|
+
return { command: resolveAinj(), args: ['mcp', bin, 'stdio'] };
|
|
12
|
+
}
|