nubase_cli 0.1.3 → 0.1.6
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 +50 -13
- package/dist/src/auth-config.d.ts +2 -0
- package/dist/src/auth-config.js +6 -0
- package/dist/src/authorize.d.ts +1 -0
- package/dist/src/authorize.js +25 -4
- package/dist/src/config.js +2 -2
- package/dist/src/index.js +13 -2
- package/dist/src/install-skills.d.ts +11 -0
- package/dist/src/install-skills.js +170 -9
- package/package.json +1 -1
- package/skills/nubase/SKILL.md +23 -7
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ stdio MCP bridge for Nubase. Use it when Codex, Claude Code, Cursor, IDEA, or an
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx nubase_cli
|
|
8
|
+
npx -y nubase_cli@latest
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Browser Authorization
|
|
@@ -13,21 +13,35 @@ npx nubase_cli
|
|
|
13
13
|
Installing skills starts a one-time browser authorization session and prints an authorization URL:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx -y nubase_cli install-skills
|
|
16
|
+
npx -y nubase_cli@latest install-skills
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Open the printed URL, sign in to Studio, choose a project, and approve. The URL includes a per-session UUID and points back to the temporary localhost callback started by the install command. After approval, the CLI writes
|
|
19
|
+
This installs the bundled Claude/Codex skills into your user skill directories, writes project MCP config for Claude Code, and starts browser authorization. Open the printed URL, sign in to Studio, choose a project, and approve. The URL includes a per-session UUID and points back to the temporary localhost callback started by the install command. After approval, the CLI writes project-local `.nubase/config.json` and closes the localhost callback server.
|
|
20
|
+
|
|
21
|
+
Restart Claude Code in the project after installing, then run `/mcp` and confirm `nubase` is connected.
|
|
20
22
|
|
|
21
23
|
For automation, skip the prompt:
|
|
22
24
|
|
|
23
25
|
```bash
|
|
24
|
-
npx -y nubase_cli install-skills --no-authorize
|
|
26
|
+
npx -y nubase_cli@latest install-skills --no-authorize
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To install project-local skill files instead of user-level skill files:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx -y nubase_cli@latest install-skills --skills-scope project
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To skip MCP config registration:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx -y nubase_cli@latest install-skills --no-mcp
|
|
25
39
|
```
|
|
26
40
|
|
|
27
41
|
You can also start a standalone authorization session:
|
|
28
42
|
|
|
29
43
|
```bash
|
|
30
|
-
npx -y nubase_cli authorize
|
|
44
|
+
npx -y nubase_cli@latest authorize
|
|
31
45
|
```
|
|
32
46
|
|
|
33
47
|
Future `nubase_cli` runs read this file when `NUBASE_PROJECT_KEY` is not set.
|
|
@@ -35,7 +49,7 @@ Future `nubase_cli` runs read this file when `NUBASE_PROJECT_KEY` is not set.
|
|
|
35
49
|
Options:
|
|
36
50
|
|
|
37
51
|
```bash
|
|
38
|
-
npx nubase_cli authorize \
|
|
52
|
+
npx -y nubase_cli@latest authorize \
|
|
39
53
|
--studio-url http://localhost:3000 \
|
|
40
54
|
--nubase-url http://localhost:9999 \
|
|
41
55
|
--agent-id codex
|
|
@@ -56,9 +70,10 @@ node packages/mcp-bridge/dist/src/index.js
|
|
|
56
70
|
"mcpServers": {
|
|
57
71
|
"nubase": {
|
|
58
72
|
"command": "npx",
|
|
59
|
-
"args": ["nubase_cli"],
|
|
73
|
+
"args": ["-y", "nubase_cli@latest"],
|
|
60
74
|
"env": {
|
|
61
|
-
"NUBASE_AGENT_ID": "claude-code"
|
|
75
|
+
"NUBASE_AGENT_ID": "claude-code",
|
|
76
|
+
"NUBASE_CONFIG": ".nubase/config.json"
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
79
|
}
|
|
@@ -69,17 +84,38 @@ You may still set `NUBASE_URL` and `NUBASE_PROJECT_KEY` explicitly. Environment
|
|
|
69
84
|
|
|
70
85
|
## Install Agent Skills
|
|
71
86
|
|
|
72
|
-
Install the bundled Nubase skills
|
|
87
|
+
Install the bundled Nubase skills and project MCP config:
|
|
73
88
|
|
|
74
89
|
```bash
|
|
75
|
-
npx -y nubase_cli install-skills
|
|
90
|
+
npx -y nubase_cli@latest install-skills
|
|
76
91
|
```
|
|
77
92
|
|
|
78
93
|
Targets:
|
|
79
94
|
|
|
80
|
-
- `claude`:
|
|
81
|
-
- `codex`:
|
|
82
|
-
- `both`:
|
|
95
|
+
- `claude`: installs `~/.claude/skills/nubase/**`
|
|
96
|
+
- `codex`: installs `~/.codex/skills/nubase/**`
|
|
97
|
+
- `both`: installs both
|
|
98
|
+
|
|
99
|
+
Use `--skills-scope project` to write `.claude/skills/nubase/**` and `.codex/skills/nubase/**` in the current project instead.
|
|
100
|
+
|
|
101
|
+
By default, when the target includes `claude`, the command also creates or merges project `.mcp.json`:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"nubase": {
|
|
107
|
+
"command": "npx",
|
|
108
|
+
"args": ["-y", "nubase_cli@latest"],
|
|
109
|
+
"env": {
|
|
110
|
+
"NUBASE_AGENT_ID": "claude-code",
|
|
111
|
+
"NUBASE_CONFIG": "/absolute/project/path/.nubase/config.json"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Use `--mcp both` to also write project `.codex/config.toml` for Codex. Use `--no-mcp` to skip MCP config.
|
|
83
119
|
|
|
84
120
|
Installed structure:
|
|
85
121
|
|
|
@@ -99,6 +135,7 @@ The bridge injects user and session context from environment variables:
|
|
|
99
135
|
```bash
|
|
100
136
|
NUBASE_URL=https://nubase.ai
|
|
101
137
|
NUBASE_PROJECT_KEY=YOUR_NUBASE_PROJECT_KEY
|
|
138
|
+
NUBASE_CONFIG=.nubase/config.json
|
|
102
139
|
NUBASE_USER_JWT=USER_ACCESS_TOKEN
|
|
103
140
|
NUBASE_USER_ID=USER_UUID
|
|
104
141
|
NUBASE_AGENT_ID=codex
|
|
@@ -8,6 +8,8 @@ export interface StoredAuthConfig {
|
|
|
8
8
|
savedAt: string;
|
|
9
9
|
}
|
|
10
10
|
export declare function defaultConfigPath(env?: NodeJS.ProcessEnv): string;
|
|
11
|
+
export declare function projectConfigPath(projectDir?: string): string;
|
|
12
|
+
export declare function legacyConfigPath(): string;
|
|
11
13
|
export declare function loadStoredAuthConfig(configPath?: string): Promise<{
|
|
12
14
|
nubaseUrl: string;
|
|
13
15
|
projectKey: string;
|
package/dist/src/auth-config.js
CHANGED
|
@@ -4,6 +4,12 @@ import path from 'node:path';
|
|
|
4
4
|
export function defaultConfigPath(env = process.env) {
|
|
5
5
|
if (env.NUBASE_CONFIG)
|
|
6
6
|
return env.NUBASE_CONFIG;
|
|
7
|
+
return projectConfigPath();
|
|
8
|
+
}
|
|
9
|
+
export function projectConfigPath(projectDir = process.cwd()) {
|
|
10
|
+
return path.join(projectDir, '.nubase', 'config.json');
|
|
11
|
+
}
|
|
12
|
+
export function legacyConfigPath() {
|
|
7
13
|
return path.join(os.homedir(), '.nubase', 'config.json');
|
|
8
14
|
}
|
|
9
15
|
export async function loadStoredAuthConfig(configPath = defaultConfigPath()) {
|
package/dist/src/authorize.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface AuthorizeOptions {
|
|
|
6
6
|
openBrowser: boolean;
|
|
7
7
|
timeoutMs: number;
|
|
8
8
|
promptOnly: boolean;
|
|
9
|
+
configPath: string;
|
|
9
10
|
}
|
|
10
11
|
export declare function parseAuthorizeArgs(argv: string[], env?: NodeJS.ProcessEnv): AuthorizeOptions;
|
|
11
12
|
export declare function authorize(options: AuthorizeOptions): Promise<StoredAuthConfig>;
|
package/dist/src/authorize.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
3
|
import http from 'node:http';
|
|
4
|
-
import { saveStoredAuthConfig } from './auth-config.js';
|
|
4
|
+
import { defaultConfigPath, saveStoredAuthConfig } from './auth-config.js';
|
|
5
5
|
import { DEFAULT_NUBASE_URL } from './config.js';
|
|
6
6
|
const DEFAULT_STUDIO_URL = 'https://nubase.ai/studio';
|
|
7
7
|
export function parseAuthorizeArgs(argv, env = process.env) {
|
|
@@ -12,6 +12,7 @@ export function parseAuthorizeArgs(argv, env = process.env) {
|
|
|
12
12
|
openBrowser: true,
|
|
13
13
|
timeoutMs: 5 * 60 * 1000,
|
|
14
14
|
promptOnly: false,
|
|
15
|
+
configPath: defaultConfigPath(env),
|
|
15
16
|
};
|
|
16
17
|
for (let i = 0; i < argv.length; i += 1) {
|
|
17
18
|
const arg = argv[i];
|
|
@@ -24,6 +25,9 @@ export function parseAuthorizeArgs(argv, env = process.env) {
|
|
|
24
25
|
else if (arg === '--agent-id') {
|
|
25
26
|
options.agentId = requiredValue(argv, ++i, arg);
|
|
26
27
|
}
|
|
28
|
+
else if (arg === '--config') {
|
|
29
|
+
options.configPath = requiredValue(argv, ++i, arg);
|
|
30
|
+
}
|
|
27
31
|
else if (arg === '--timeout-seconds') {
|
|
28
32
|
const seconds = Number(requiredValue(argv, ++i, arg));
|
|
29
33
|
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
@@ -77,6 +81,10 @@ async function startAuthorization(options) {
|
|
|
77
81
|
return;
|
|
78
82
|
}
|
|
79
83
|
try {
|
|
84
|
+
if (req.method === 'OPTIONS' && req.url === '/callback') {
|
|
85
|
+
sendCorsNoContent(res);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
80
88
|
if (req.method === 'GET' && req.url?.startsWith('/callback')) {
|
|
81
89
|
const url = new URL(req.url, origin);
|
|
82
90
|
if (url.searchParams.get('state') !== state) {
|
|
@@ -89,7 +97,7 @@ async function startAuthorization(options) {
|
|
|
89
97
|
if (req.method === 'POST' && req.url === '/callback') {
|
|
90
98
|
const payload = await readJson(req);
|
|
91
99
|
const config = validateCallbackPayload(payload, state, options.nubaseUrl);
|
|
92
|
-
const saved = await saveStoredAuthConfig(config);
|
|
100
|
+
const saved = await saveStoredAuthConfig(config, options.configPath);
|
|
93
101
|
sendJson(res, 200, { ok: true });
|
|
94
102
|
cleanup();
|
|
95
103
|
resolve(saved);
|
|
@@ -158,12 +166,25 @@ async function readJson(req) {
|
|
|
158
166
|
function sendJson(res, status, body) {
|
|
159
167
|
res.writeHead(status, {
|
|
160
168
|
'Content-Type': 'application/json',
|
|
161
|
-
|
|
169
|
+
...corsHeaders(),
|
|
162
170
|
});
|
|
163
171
|
res.end(JSON.stringify(body));
|
|
164
172
|
}
|
|
173
|
+
function sendCorsNoContent(res) {
|
|
174
|
+
res.writeHead(204, corsHeaders());
|
|
175
|
+
res.end();
|
|
176
|
+
}
|
|
177
|
+
function corsHeaders() {
|
|
178
|
+
return {
|
|
179
|
+
'Access-Control-Allow-Origin': '*',
|
|
180
|
+
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
|
|
181
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
182
|
+
'Access-Control-Allow-Private-Network': 'true',
|
|
183
|
+
'Access-Control-Max-Age': '600',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
165
186
|
function sendHtml(res, status, title, message) {
|
|
166
|
-
res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
187
|
+
res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders() });
|
|
167
188
|
res.end(`<!doctype html><meta charset="utf-8"><title>${escapeHtml(title)}</title><body style="font-family: system-ui, sans-serif; padding: 32px;"><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p></body>`);
|
|
168
189
|
}
|
|
169
190
|
function openBrowser(url) {
|
package/dist/src/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defaultConfigPath, loadStoredAuthConfig } from './auth-config.js';
|
|
1
|
+
import { defaultConfigPath, legacyConfigPath, loadStoredAuthConfig } from './auth-config.js';
|
|
2
2
|
export const DEFAULT_NUBASE_URL = 'https://nubase.ai';
|
|
3
3
|
export function loadConfig(env = process.env) {
|
|
4
4
|
const nubaseUrl = stripTrailingSlash(env.NUBASE_URL || DEFAULT_NUBASE_URL);
|
|
@@ -21,7 +21,7 @@ export async function loadConfigAsync(env = process.env) {
|
|
|
21
21
|
const config = loadConfig(env);
|
|
22
22
|
if (config.projectKey)
|
|
23
23
|
return config;
|
|
24
|
-
const stored = await loadStoredAuthConfig(defaultConfigPath(env));
|
|
24
|
+
const stored = await loadStoredAuthConfig(defaultConfigPath(env)) ?? (env.NUBASE_CONFIG ? null : await loadStoredAuthConfig(legacyConfigPath()));
|
|
25
25
|
if (!stored)
|
|
26
26
|
return config;
|
|
27
27
|
return {
|
package/dist/src/index.js
CHANGED
|
@@ -6,12 +6,23 @@ import { installSkills, parseInstallArgs } from './install-skills.js';
|
|
|
6
6
|
import { McpStdioServer } from './mcp-stdio.js';
|
|
7
7
|
import { NubaseClient } from './nubase-client.js';
|
|
8
8
|
import { callTool, TOOLS } from './tools.js';
|
|
9
|
-
const CLI_VERSION = '0.1.
|
|
9
|
+
const CLI_VERSION = '0.1.6';
|
|
10
10
|
if (process.argv[2] === 'install-skills') {
|
|
11
11
|
const options = parseInstallArgs(process.argv.slice(3));
|
|
12
12
|
const installed = await installSkills(options);
|
|
13
13
|
for (const file of installed) {
|
|
14
|
-
|
|
14
|
+
if (file.endsWith('.mcp.json')) {
|
|
15
|
+
console.error(`Registered Nubase MCP server config: ${file}`);
|
|
16
|
+
}
|
|
17
|
+
else if (file.endsWith('.codex/config.toml')) {
|
|
18
|
+
console.error(`Registered Nubase Codex MCP config: ${file}`);
|
|
19
|
+
}
|
|
20
|
+
else if (file.endsWith('.gitignore')) {
|
|
21
|
+
console.error(`Ensured Nubase local config is ignored by git: ${file}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.error(`Installed Nubase skill: ${file}`);
|
|
25
|
+
}
|
|
15
26
|
}
|
|
16
27
|
if (options.authorize) {
|
|
17
28
|
console.error('');
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
export type SkillTarget = 'claude' | 'codex' | 'both';
|
|
2
|
+
export type SkillInstallScope = 'user' | 'project';
|
|
3
|
+
export type McpInstallTarget = 'none' | 'claude' | 'codex' | 'both';
|
|
2
4
|
export interface InstallSkillsOptions {
|
|
3
5
|
target: SkillTarget;
|
|
4
6
|
projectDir: string;
|
|
5
7
|
authorize?: boolean;
|
|
6
8
|
authArgs?: string[];
|
|
9
|
+
skills?: boolean;
|
|
10
|
+
skillsScope?: SkillInstallScope;
|
|
11
|
+
mcp?: McpInstallTarget;
|
|
12
|
+
configPath?: string;
|
|
13
|
+
homeDir?: string;
|
|
7
14
|
}
|
|
8
15
|
export declare function installSkills(options: InstallSkillsOptions): Promise<string[]>;
|
|
9
16
|
export declare function parseInstallArgs(argv: string[]): {
|
|
@@ -11,4 +18,8 @@ export declare function parseInstallArgs(argv: string[]): {
|
|
|
11
18
|
projectDir: string;
|
|
12
19
|
authorize: boolean;
|
|
13
20
|
authArgs: string[];
|
|
21
|
+
skills: boolean;
|
|
22
|
+
skillsScope: SkillInstallScope;
|
|
23
|
+
mcp: McpInstallTarget;
|
|
24
|
+
configPath: string;
|
|
14
25
|
};
|
|
@@ -1,24 +1,41 @@
|
|
|
1
|
-
import { cp, mkdir } from 'node:fs/promises';
|
|
1
|
+
import { cp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { projectConfigPath } from './auth-config.js';
|
|
4
6
|
export async function installSkills(options) {
|
|
5
7
|
const skillDir = bundledSkillDir();
|
|
6
8
|
const targets = options.target === 'both' ? ['claude', 'codex'] : [options.target];
|
|
7
9
|
const installed = [];
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const skillsScope = options.skillsScope ?? 'user';
|
|
11
|
+
const configPath = path.resolve(options.configPath ?? projectConfigPath(options.projectDir));
|
|
12
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
13
|
+
if (options.skills !== false) {
|
|
14
|
+
for (const target of targets) {
|
|
15
|
+
const destDir = skillDestDir(target, skillsScope, options.projectDir, homeDir);
|
|
16
|
+
await mkdir(path.dirname(destDir), { recursive: true });
|
|
17
|
+
await cp(skillDir, destDir, { recursive: true, force: true });
|
|
18
|
+
installed.push(path.join(destDir, 'SKILL.md'));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const mcpTargets = resolveMcpTargets(options.mcp ?? 'claude', targets);
|
|
22
|
+
if (mcpTargets.includes('claude')) {
|
|
23
|
+
installed.push(await installClaudeMcpConfig(options.projectDir, configPath));
|
|
24
|
+
}
|
|
25
|
+
if (mcpTargets.includes('codex')) {
|
|
26
|
+
installed.push(await installCodexMcpConfig(options.projectDir, configPath));
|
|
15
27
|
}
|
|
28
|
+
installed.push(await ensureProjectGitignore(options.projectDir));
|
|
16
29
|
return installed;
|
|
17
30
|
}
|
|
18
31
|
export function parseInstallArgs(argv) {
|
|
19
32
|
let target = 'both';
|
|
20
33
|
let projectDir = process.cwd();
|
|
21
34
|
let authorize = true;
|
|
35
|
+
let skills = true;
|
|
36
|
+
let skillsScope = 'user';
|
|
37
|
+
let mcp = 'claude';
|
|
38
|
+
let configPath;
|
|
22
39
|
const authArgs = ['--prompt-only'];
|
|
23
40
|
for (let i = 0; i < argv.length; i += 1) {
|
|
24
41
|
const arg = argv[i];
|
|
@@ -38,17 +55,161 @@ export function parseInstallArgs(argv) {
|
|
|
38
55
|
else if (arg === '--no-authorize') {
|
|
39
56
|
authorize = false;
|
|
40
57
|
}
|
|
58
|
+
else if (arg === '--no-skills') {
|
|
59
|
+
skills = false;
|
|
60
|
+
}
|
|
61
|
+
else if (arg === '--no-mcp-config') {
|
|
62
|
+
mcp = 'none';
|
|
63
|
+
}
|
|
64
|
+
else if (arg === '--no-mcp') {
|
|
65
|
+
mcp = 'none';
|
|
66
|
+
}
|
|
67
|
+
else if (arg === '--mcp') {
|
|
68
|
+
const value = argv[++i];
|
|
69
|
+
if (value !== 'none' && value !== 'claude' && value !== 'codex' && value !== 'both') {
|
|
70
|
+
throw new Error('--mcp must be none, claude, codex, or both');
|
|
71
|
+
}
|
|
72
|
+
mcp = value;
|
|
73
|
+
}
|
|
74
|
+
else if (arg === '--skills-scope') {
|
|
75
|
+
const value = argv[++i];
|
|
76
|
+
if (value !== 'user' && value !== 'project') {
|
|
77
|
+
throw new Error('--skills-scope must be user or project');
|
|
78
|
+
}
|
|
79
|
+
skillsScope = value;
|
|
80
|
+
}
|
|
81
|
+
else if (arg === '--config') {
|
|
82
|
+
const value = argv[++i];
|
|
83
|
+
if (!value)
|
|
84
|
+
throw new Error('--config requires a value');
|
|
85
|
+
configPath = path.resolve(projectDir, value);
|
|
86
|
+
}
|
|
41
87
|
else if (arg === '--studio-url' || arg === '--nubase-url' || arg === '--agent-id' || arg === '--timeout-seconds') {
|
|
42
88
|
const value = argv[++i];
|
|
43
89
|
if (!value)
|
|
44
90
|
throw new Error(`${arg} requires a value`);
|
|
45
91
|
authArgs.push(arg, value);
|
|
46
92
|
}
|
|
93
|
+
else {
|
|
94
|
+
throw new Error(`Unknown install-skills option: ${arg}`);
|
|
95
|
+
}
|
|
47
96
|
}
|
|
48
|
-
|
|
97
|
+
configPath = configPath ?? projectConfigPath(projectDir);
|
|
98
|
+
authArgs.push('--config', configPath);
|
|
99
|
+
return { target, projectDir, authorize, authArgs, skills, skillsScope, mcp, configPath };
|
|
49
100
|
}
|
|
50
101
|
function bundledSkillDir() {
|
|
51
102
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
52
103
|
const packageRoot = path.resolve(here, '..', '..');
|
|
53
104
|
return path.join(packageRoot, 'skills', 'nubase');
|
|
54
105
|
}
|
|
106
|
+
function skillDestDir(target, scope, projectDir, homeDir) {
|
|
107
|
+
if (scope === 'project') {
|
|
108
|
+
return target === 'claude'
|
|
109
|
+
? path.join(projectDir, '.claude', 'skills', 'nubase')
|
|
110
|
+
: path.join(projectDir, '.codex', 'skills', 'nubase');
|
|
111
|
+
}
|
|
112
|
+
return target === 'claude'
|
|
113
|
+
? path.join(homeDir, '.claude', 'skills', 'nubase')
|
|
114
|
+
: path.join(homeDir, '.codex', 'skills', 'nubase');
|
|
115
|
+
}
|
|
116
|
+
function resolveMcpTargets(mcp, skillTargets) {
|
|
117
|
+
if (mcp === 'none')
|
|
118
|
+
return [];
|
|
119
|
+
const requested = mcp === 'both' ? ['claude', 'codex'] : [mcp];
|
|
120
|
+
return requested.filter((target) => skillTargets.includes(target));
|
|
121
|
+
}
|
|
122
|
+
async function installClaudeMcpConfig(projectDir, nubaseConfigPath) {
|
|
123
|
+
const mcpConfigPath = path.join(projectDir, '.mcp.json');
|
|
124
|
+
const config = await readProjectMcpConfig(mcpConfigPath);
|
|
125
|
+
config.mcpServers = {
|
|
126
|
+
...(config.mcpServers ?? {}),
|
|
127
|
+
nubase: {
|
|
128
|
+
type: 'stdio',
|
|
129
|
+
command: 'npx',
|
|
130
|
+
args: ['-y', 'nubase_cli@latest'],
|
|
131
|
+
env: {
|
|
132
|
+
NUBASE_AGENT_ID: 'claude-code',
|
|
133
|
+
NUBASE_CONFIG: nubaseConfigPath,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
await writeFile(mcpConfigPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
138
|
+
return mcpConfigPath;
|
|
139
|
+
}
|
|
140
|
+
async function installCodexMcpConfig(projectDir, nubaseConfigPath) {
|
|
141
|
+
const configPath = path.join(projectDir, '.codex', 'config.toml');
|
|
142
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
143
|
+
const existing = await readTextIfExists(configPath);
|
|
144
|
+
const block = codexMcpBlock(nubaseConfigPath);
|
|
145
|
+
const next = upsertCodexMcpBlock(existing, block);
|
|
146
|
+
await writeFile(configPath, next, 'utf8');
|
|
147
|
+
return configPath;
|
|
148
|
+
}
|
|
149
|
+
async function ensureProjectGitignore(projectDir) {
|
|
150
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
151
|
+
const existing = await readTextIfExists(gitignorePath);
|
|
152
|
+
const lines = existing.split(/\r?\n/);
|
|
153
|
+
if (!lines.includes('.nubase/')) {
|
|
154
|
+
const next = `${existing.trimEnd()}${existing.trimEnd() ? '\n' : ''}.nubase/\n`;
|
|
155
|
+
await writeFile(gitignorePath, next, 'utf8');
|
|
156
|
+
}
|
|
157
|
+
return gitignorePath;
|
|
158
|
+
}
|
|
159
|
+
async function readTextIfExists(filePath) {
|
|
160
|
+
try {
|
|
161
|
+
return await readFile(filePath, 'utf8');
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
if (err.code === 'ENOENT')
|
|
165
|
+
return '';
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function codexMcpBlock(configPath) {
|
|
170
|
+
return [
|
|
171
|
+
'[mcp_servers.nubase]',
|
|
172
|
+
'type = "stdio"',
|
|
173
|
+
'command = "npx"',
|
|
174
|
+
'args = ["-y", "nubase_cli@latest"]',
|
|
175
|
+
'startup_timeout_sec = 30',
|
|
176
|
+
'',
|
|
177
|
+
'[mcp_servers.nubase.env]',
|
|
178
|
+
'NUBASE_AGENT_ID = "codex"',
|
|
179
|
+
`NUBASE_CONFIG = "${escapeTomlString(configPath)}"`,
|
|
180
|
+
'',
|
|
181
|
+
].join('\n');
|
|
182
|
+
}
|
|
183
|
+
function upsertCodexMcpBlock(existing, block) {
|
|
184
|
+
const pattern = /(?:^|\n)\[mcp_servers\.nubase\][\s\S]*?(?=\n\[mcp_servers\.(?!nubase(?:\.env)?\b)|\n\[[^\]]+\]|\s*$)/;
|
|
185
|
+
const trimmedBlock = `\n${block.trimEnd()}\n`;
|
|
186
|
+
if (pattern.test(existing)) {
|
|
187
|
+
return existing.replace(pattern, trimmedBlock).replace(/^\n/, '');
|
|
188
|
+
}
|
|
189
|
+
const prefix = existing.trimEnd();
|
|
190
|
+
return `${prefix}${prefix ? '\n\n' : ''}${block}`;
|
|
191
|
+
}
|
|
192
|
+
function escapeTomlString(value) {
|
|
193
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
194
|
+
}
|
|
195
|
+
async function readProjectMcpConfig(configPath) {
|
|
196
|
+
try {
|
|
197
|
+
const raw = await readFile(configPath, 'utf8');
|
|
198
|
+
const parsed = JSON.parse(raw);
|
|
199
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
200
|
+
throw new Error('.mcp.json must contain a JSON object');
|
|
201
|
+
}
|
|
202
|
+
if (parsed.mcpServers !== undefined && (!parsed.mcpServers || typeof parsed.mcpServers !== 'object' || Array.isArray(parsed.mcpServers))) {
|
|
203
|
+
throw new Error('.mcp.json mcpServers must be a JSON object');
|
|
204
|
+
}
|
|
205
|
+
return parsed;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
if (err.code === 'ENOENT')
|
|
209
|
+
return {};
|
|
210
|
+
if (err instanceof SyntaxError) {
|
|
211
|
+
throw new Error(`Could not parse ${configPath}: ${err.message}`);
|
|
212
|
+
}
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
}
|
package/package.json
CHANGED
package/skills/nubase/SKILL.md
CHANGED
|
@@ -56,28 +56,44 @@ If a tool is unavailable, continue with REST/API guidance and tell the user what
|
|
|
56
56
|
|
|
57
57
|
## Setup
|
|
58
58
|
|
|
59
|
-
Install MCP
|
|
59
|
+
Install the Nubase skills and project MCP config:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx -y nubase_cli@latest install-skills
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
By default this writes:
|
|
66
|
+
|
|
67
|
+
- `~/.claude/skills/nubase/**`
|
|
68
|
+
- `~/.codex/skills/nubase/**`
|
|
69
|
+
- project `.mcp.json` with a `nubase` stdio MCP server for Claude Code
|
|
70
|
+
- project `.nubase/config.json` after browser authorization
|
|
71
|
+
|
|
72
|
+
After installing, restart Claude Code in the project and run `/mcp`. The `nubase` server must be connected before this skill can call `nubase_overview`, `memory_context`, or other MCP tools.
|
|
73
|
+
|
|
74
|
+
Expected `.mcp.json` shape:
|
|
60
75
|
|
|
61
76
|
```json
|
|
62
77
|
{
|
|
63
78
|
"mcpServers": {
|
|
64
79
|
"nubase": {
|
|
65
80
|
"command": "npx",
|
|
66
|
-
"args": ["nubase_cli"],
|
|
81
|
+
"args": ["-y", "nubase_cli@latest"],
|
|
67
82
|
"env": {
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"NUBASE_AGENT_ID": "codex"
|
|
83
|
+
"NUBASE_AGENT_ID": "claude-code",
|
|
84
|
+
"NUBASE_CONFIG": ".nubase/config.json"
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
}
|
|
75
89
|
```
|
|
76
90
|
|
|
77
|
-
|
|
91
|
+
If `NUBASE_PROJECT_KEY` is not set, `nubase_cli` reads the browser authorization saved at the project `NUBASE_CONFIG` path.
|
|
92
|
+
|
|
93
|
+
To install project-local skill files instead of user-level skill files:
|
|
78
94
|
|
|
79
95
|
```bash
|
|
80
|
-
npx nubase_cli install-skills --
|
|
96
|
+
npx -y nubase_cli@latest install-skills --skills-scope project
|
|
81
97
|
```
|
|
82
98
|
|
|
83
99
|
This installs one Nubase skill directory containing:
|