nubase_cli 0.1.6 → 0.1.8
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 +13 -4
- package/dist/src/index.js +7 -1
- package/dist/src/install-skills.d.ts +3 -0
- package/dist/src/install-skills.js +66 -14
- package/dist/src/mcp-stdio.js +10 -17
- package/package.json +1 -1
- package/skills/nubase/SKILL.md +3 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Installing skills starts a one-time browser authorization session and prints an
|
|
|
16
16
|
npx -y nubase_cli@latest install-skills
|
|
17
17
|
```
|
|
18
18
|
|
|
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.
|
|
19
|
+
This installs the bundled Claude/Codex skills into your user skill directories, writes a local project MCP bridge under `.nubase/mcp-bridge`, 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
20
|
|
|
21
21
|
Restart Claude Code in the project after installing, then run `/mcp` and confirm `nubase` is connected.
|
|
22
22
|
|
|
@@ -69,17 +69,21 @@ node packages/mcp-bridge/dist/src/index.js
|
|
|
69
69
|
{
|
|
70
70
|
"mcpServers": {
|
|
71
71
|
"nubase": {
|
|
72
|
+
"type": "stdio",
|
|
72
73
|
"command": "npx",
|
|
73
74
|
"args": ["-y", "nubase_cli@latest"],
|
|
74
75
|
"env": {
|
|
75
76
|
"NUBASE_AGENT_ID": "claude-code",
|
|
76
|
-
"NUBASE_CONFIG": "
|
|
77
|
+
"NUBASE_CONFIG": "/absolute/project/path/.nubase/config.json"
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
```
|
|
82
83
|
|
|
84
|
+
The bridge code is shared across projects via the npm cache; only `.nubase/config.json`
|
|
85
|
+
(holding this project's `projectKey`) is project-specific, pointed to by `NUBASE_CONFIG`.
|
|
86
|
+
|
|
83
87
|
You may still set `NUBASE_URL` and `NUBASE_PROJECT_KEY` explicitly. Environment variables take precedence over the saved authorization config.
|
|
84
88
|
|
|
85
89
|
## Install Agent Skills
|
|
@@ -98,14 +102,15 @@ Targets:
|
|
|
98
102
|
|
|
99
103
|
Use `--skills-scope project` to write `.claude/skills/nubase/**` and `.codex/skills/nubase/**` in the current project instead.
|
|
100
104
|
|
|
101
|
-
By default, when the target includes `claude`, the command
|
|
105
|
+
By default, when the target includes `claude`, the command creates or merges a project `.mcp.json` that runs the bridge via `npx` (shared across projects through the npm cache):
|
|
102
106
|
|
|
103
107
|
```json
|
|
104
108
|
{
|
|
105
109
|
"mcpServers": {
|
|
106
110
|
"nubase": {
|
|
111
|
+
"type": "stdio",
|
|
107
112
|
"command": "npx",
|
|
108
|
-
"args": ["-y", "nubase_cli
|
|
113
|
+
"args": ["-y", "nubase_cli@<version>"],
|
|
109
114
|
"env": {
|
|
110
115
|
"NUBASE_AGENT_ID": "claude-code",
|
|
111
116
|
"NUBASE_CONFIG": "/absolute/project/path/.nubase/config.json"
|
|
@@ -115,6 +120,10 @@ By default, when the target includes `claude`, the command also creates or merge
|
|
|
115
120
|
}
|
|
116
121
|
```
|
|
117
122
|
|
|
123
|
+
The `npx` spec is pinned to the installed CLI version for reproducibility. Pass
|
|
124
|
+
`--mcp-delivery local` instead to copy a hermetic, version-pinned bridge into
|
|
125
|
+
`.nubase/mcp-bridge` and reference it with `command: "node"` (no npm dependency at runtime).
|
|
126
|
+
|
|
118
127
|
Use `--mcp both` to also write project `.codex/config.toml` for Codex. Use `--no-mcp` to skip MCP config.
|
|
119
128
|
|
|
120
129
|
Installed structure:
|
package/dist/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ 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.8';
|
|
10
10
|
if (process.argv[2] === 'install-skills') {
|
|
11
11
|
const options = parseInstallArgs(process.argv.slice(3));
|
|
12
12
|
const installed = await installSkills(options);
|
|
@@ -20,6 +20,9 @@ if (process.argv[2] === 'install-skills') {
|
|
|
20
20
|
else if (file.endsWith('.gitignore')) {
|
|
21
21
|
console.error(`Ensured Nubase local config is ignored by git: ${file}`);
|
|
22
22
|
}
|
|
23
|
+
else if (file.includes(`${defaultPathSep()}.nubase${defaultPathSep()}mcp-bridge${defaultPathSep()}`)) {
|
|
24
|
+
console.error(`Installed Nubase local MCP bridge: ${file}`);
|
|
25
|
+
}
|
|
23
26
|
else {
|
|
24
27
|
console.error(`Installed Nubase skill: ${file}`);
|
|
25
28
|
}
|
|
@@ -80,3 +83,6 @@ const server = new McpStdioServer(async (request) => {
|
|
|
80
83
|
}
|
|
81
84
|
});
|
|
82
85
|
server.start();
|
|
86
|
+
function defaultPathSep() {
|
|
87
|
+
return process.platform === 'win32' ? '\\' : '/';
|
|
88
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type SkillTarget = 'claude' | 'codex' | 'both';
|
|
2
2
|
export type SkillInstallScope = 'user' | 'project';
|
|
3
3
|
export type McpInstallTarget = 'none' | 'claude' | 'codex' | 'both';
|
|
4
|
+
export type McpDelivery = 'npx' | 'local';
|
|
4
5
|
export interface InstallSkillsOptions {
|
|
5
6
|
target: SkillTarget;
|
|
6
7
|
projectDir: string;
|
|
@@ -9,6 +10,7 @@ export interface InstallSkillsOptions {
|
|
|
9
10
|
skills?: boolean;
|
|
10
11
|
skillsScope?: SkillInstallScope;
|
|
11
12
|
mcp?: McpInstallTarget;
|
|
13
|
+
mcpDelivery?: McpDelivery;
|
|
12
14
|
configPath?: string;
|
|
13
15
|
homeDir?: string;
|
|
14
16
|
}
|
|
@@ -21,5 +23,6 @@ export declare function parseInstallArgs(argv: string[]): {
|
|
|
21
23
|
skills: boolean;
|
|
22
24
|
skillsScope: SkillInstallScope;
|
|
23
25
|
mcp: McpInstallTarget;
|
|
26
|
+
mcpDelivery: McpDelivery;
|
|
24
27
|
configPath: string;
|
|
25
28
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -19,11 +19,22 @@ export async function installSkills(options) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
const mcpTargets = resolveMcpTargets(options.mcp ?? 'claude', targets);
|
|
22
|
+
const mcpDelivery = options.mcpDelivery ?? 'npx';
|
|
23
|
+
let mcpCommand = null;
|
|
24
|
+
if (mcpTargets.length > 0) {
|
|
25
|
+
if (mcpDelivery === 'local') {
|
|
26
|
+
mcpCommand = await installProjectMcpBridge(options.projectDir);
|
|
27
|
+
installed.push(mcpCommand.entrypoint);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
mcpCommand = await npxMcpCommand();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
22
33
|
if (mcpTargets.includes('claude')) {
|
|
23
|
-
installed.push(await installClaudeMcpConfig(options.projectDir, configPath));
|
|
34
|
+
installed.push(await installClaudeMcpConfig(options.projectDir, configPath, mcpCommand));
|
|
24
35
|
}
|
|
25
36
|
if (mcpTargets.includes('codex')) {
|
|
26
|
-
installed.push(await installCodexMcpConfig(options.projectDir, configPath));
|
|
37
|
+
installed.push(await installCodexMcpConfig(options.projectDir, configPath, mcpCommand));
|
|
27
38
|
}
|
|
28
39
|
installed.push(await ensureProjectGitignore(options.projectDir));
|
|
29
40
|
return installed;
|
|
@@ -35,6 +46,7 @@ export function parseInstallArgs(argv) {
|
|
|
35
46
|
let skills = true;
|
|
36
47
|
let skillsScope = 'user';
|
|
37
48
|
let mcp = 'claude';
|
|
49
|
+
let mcpDelivery = 'npx';
|
|
38
50
|
let configPath;
|
|
39
51
|
const authArgs = ['--prompt-only'];
|
|
40
52
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -78,6 +90,13 @@ export function parseInstallArgs(argv) {
|
|
|
78
90
|
}
|
|
79
91
|
skillsScope = value;
|
|
80
92
|
}
|
|
93
|
+
else if (arg === '--mcp-delivery') {
|
|
94
|
+
const value = argv[++i];
|
|
95
|
+
if (value !== 'npx' && value !== 'local') {
|
|
96
|
+
throw new Error('--mcp-delivery must be npx or local');
|
|
97
|
+
}
|
|
98
|
+
mcpDelivery = value;
|
|
99
|
+
}
|
|
81
100
|
else if (arg === '--config') {
|
|
82
101
|
const value = argv[++i];
|
|
83
102
|
if (!value)
|
|
@@ -96,12 +115,14 @@ export function parseInstallArgs(argv) {
|
|
|
96
115
|
}
|
|
97
116
|
configPath = configPath ?? projectConfigPath(projectDir);
|
|
98
117
|
authArgs.push('--config', configPath);
|
|
99
|
-
return { target, projectDir, authorize, authArgs, skills, skillsScope, mcp, configPath };
|
|
118
|
+
return { target, projectDir, authorize, authArgs, skills, skillsScope, mcp, mcpDelivery, configPath };
|
|
100
119
|
}
|
|
101
120
|
function bundledSkillDir() {
|
|
121
|
+
return path.join(bundledPackageRoot(), 'skills', 'nubase');
|
|
122
|
+
}
|
|
123
|
+
function bundledPackageRoot() {
|
|
102
124
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
103
|
-
|
|
104
|
-
return path.join(packageRoot, 'skills', 'nubase');
|
|
125
|
+
return path.resolve(here, '..', '..');
|
|
105
126
|
}
|
|
106
127
|
function skillDestDir(target, scope, projectDir, homeDir) {
|
|
107
128
|
if (scope === 'project') {
|
|
@@ -119,15 +140,44 @@ function resolveMcpTargets(mcp, skillTargets) {
|
|
|
119
140
|
const requested = mcp === 'both' ? ['claude', 'codex'] : [mcp];
|
|
120
141
|
return requested.filter((target) => skillTargets.includes(target));
|
|
121
142
|
}
|
|
122
|
-
async function
|
|
143
|
+
async function npxMcpCommand() {
|
|
144
|
+
const spec = `nubase_cli@${await bundledPackageVersion()}`;
|
|
145
|
+
return { command: 'npx', args: ['-y', spec], entrypoint: spec };
|
|
146
|
+
}
|
|
147
|
+
async function bundledPackageVersion() {
|
|
148
|
+
try {
|
|
149
|
+
const raw = await readFile(path.join(bundledPackageRoot(), 'package.json'), 'utf8');
|
|
150
|
+
const version = JSON.parse(raw).version;
|
|
151
|
+
return typeof version === 'string' && version.trim() ? version.trim() : 'latest';
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return 'latest';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function installProjectMcpBridge(projectDir) {
|
|
158
|
+
const packageRoot = bundledPackageRoot();
|
|
159
|
+
const destRoot = path.join(projectDir, '.nubase', 'mcp-bridge');
|
|
160
|
+
await rm(destRoot, { recursive: true, force: true });
|
|
161
|
+
await mkdir(destRoot, { recursive: true, mode: 0o700 });
|
|
162
|
+
await cp(path.join(packageRoot, 'dist', 'src'), path.join(destRoot, 'dist', 'src'), { recursive: true, force: true });
|
|
163
|
+
await cp(path.join(packageRoot, 'skills'), path.join(destRoot, 'skills'), { recursive: true, force: true });
|
|
164
|
+
await cp(path.join(packageRoot, 'package.json'), path.join(destRoot, 'package.json'), { force: true });
|
|
165
|
+
const entrypoint = path.join(destRoot, 'dist', 'src', 'index.js');
|
|
166
|
+
return {
|
|
167
|
+
command: 'node',
|
|
168
|
+
args: [entrypoint],
|
|
169
|
+
entrypoint,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async function installClaudeMcpConfig(projectDir, nubaseConfigPath, mcpCommand) {
|
|
123
173
|
const mcpConfigPath = path.join(projectDir, '.mcp.json');
|
|
124
174
|
const config = await readProjectMcpConfig(mcpConfigPath);
|
|
125
175
|
config.mcpServers = {
|
|
126
176
|
...(config.mcpServers ?? {}),
|
|
127
177
|
nubase: {
|
|
128
178
|
type: 'stdio',
|
|
129
|
-
command: 'npx',
|
|
130
|
-
args: ['-y', 'nubase_cli@latest'],
|
|
179
|
+
command: mcpCommand?.command ?? 'npx',
|
|
180
|
+
args: mcpCommand?.args ?? ['-y', 'nubase_cli@latest'],
|
|
131
181
|
env: {
|
|
132
182
|
NUBASE_AGENT_ID: 'claude-code',
|
|
133
183
|
NUBASE_CONFIG: nubaseConfigPath,
|
|
@@ -137,11 +187,11 @@ async function installClaudeMcpConfig(projectDir, nubaseConfigPath) {
|
|
|
137
187
|
await writeFile(mcpConfigPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
138
188
|
return mcpConfigPath;
|
|
139
189
|
}
|
|
140
|
-
async function installCodexMcpConfig(projectDir, nubaseConfigPath) {
|
|
190
|
+
async function installCodexMcpConfig(projectDir, nubaseConfigPath, mcpCommand) {
|
|
141
191
|
const configPath = path.join(projectDir, '.codex', 'config.toml');
|
|
142
192
|
await mkdir(path.dirname(configPath), { recursive: true });
|
|
143
193
|
const existing = await readTextIfExists(configPath);
|
|
144
|
-
const block = codexMcpBlock(nubaseConfigPath);
|
|
194
|
+
const block = codexMcpBlock(nubaseConfigPath, mcpCommand);
|
|
145
195
|
const next = upsertCodexMcpBlock(existing, block);
|
|
146
196
|
await writeFile(configPath, next, 'utf8');
|
|
147
197
|
return configPath;
|
|
@@ -166,12 +216,14 @@ async function readTextIfExists(filePath) {
|
|
|
166
216
|
throw err;
|
|
167
217
|
}
|
|
168
218
|
}
|
|
169
|
-
function codexMcpBlock(configPath) {
|
|
219
|
+
function codexMcpBlock(configPath, mcpCommand) {
|
|
220
|
+
const command = mcpCommand?.command ?? 'npx';
|
|
221
|
+
const args = mcpCommand?.args ?? ['-y', 'nubase_cli@latest'];
|
|
170
222
|
return [
|
|
171
223
|
'[mcp_servers.nubase]',
|
|
172
224
|
'type = "stdio"',
|
|
173
|
-
|
|
174
|
-
|
|
225
|
+
`command = "${escapeTomlString(command)}"`,
|
|
226
|
+
`args = [${args.map((arg) => `"${escapeTomlString(arg)}"`).join(', ')}]`,
|
|
175
227
|
'startup_timeout_sec = 30',
|
|
176
228
|
'',
|
|
177
229
|
'[mcp_servers.nubase.env]',
|
package/dist/src/mcp-stdio.js
CHANGED
|
@@ -47,25 +47,18 @@ export class McpStdioServer {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
readMessage() {
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
// MCP stdio transport frames messages as newline-delimited JSON
|
|
51
|
+
// (one JSON-RPC object per line, no embedded newlines).
|
|
52
|
+
const newlineIndex = this.buffer.indexOf('\n');
|
|
53
|
+
if (newlineIndex === -1)
|
|
52
54
|
return null;
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
if (!
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const length = Number(match[1]);
|
|
59
|
-
const bodyStart = headerEnd + 4;
|
|
60
|
-
const bodyEnd = bodyStart + length;
|
|
61
|
-
if (this.buffer.length < bodyEnd)
|
|
62
|
-
return null;
|
|
63
|
-
const body = this.buffer.subarray(bodyStart, bodyEnd).toString('utf8');
|
|
64
|
-
this.buffer = this.buffer.subarray(bodyEnd);
|
|
65
|
-
return JSON.parse(body);
|
|
55
|
+
const line = this.buffer.subarray(0, newlineIndex).toString('utf8').trim();
|
|
56
|
+
this.buffer = this.buffer.subarray(newlineIndex + 1);
|
|
57
|
+
if (!line)
|
|
58
|
+
return this.readMessage();
|
|
59
|
+
return JSON.parse(line);
|
|
66
60
|
}
|
|
67
61
|
write(message) {
|
|
68
|
-
|
|
69
|
-
stdout.write(`Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n${body}`);
|
|
62
|
+
stdout.write(`${JSON.stringify(message)}\n`);
|
|
70
63
|
}
|
|
71
64
|
}
|
package/package.json
CHANGED
package/skills/nubase/SKILL.md
CHANGED
|
@@ -67,6 +67,7 @@ By default this writes:
|
|
|
67
67
|
- `~/.claude/skills/nubase/**`
|
|
68
68
|
- `~/.codex/skills/nubase/**`
|
|
69
69
|
- project `.mcp.json` with a `nubase` stdio MCP server for Claude Code
|
|
70
|
+
- project `.nubase/mcp-bridge/**` local MCP bridge runtime, so agent startup does not depend on `npx @latest`
|
|
70
71
|
- project `.nubase/config.json` after browser authorization
|
|
71
72
|
|
|
72
73
|
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.
|
|
@@ -77,11 +78,12 @@ Expected `.mcp.json` shape:
|
|
|
77
78
|
{
|
|
78
79
|
"mcpServers": {
|
|
79
80
|
"nubase": {
|
|
81
|
+
"type": "stdio",
|
|
80
82
|
"command": "npx",
|
|
81
83
|
"args": ["-y", "nubase_cli@latest"],
|
|
82
84
|
"env": {
|
|
83
85
|
"NUBASE_AGENT_ID": "claude-code",
|
|
84
|
-
"NUBASE_CONFIG": "
|
|
86
|
+
"NUBASE_CONFIG": "/absolute/project/path/.nubase/config.json"
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
}
|