nubase_cli 0.1.7 → 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 CHANGED
@@ -70,8 +70,8 @@ node packages/mcp-bridge/dist/src/index.js
70
70
  "mcpServers": {
71
71
  "nubase": {
72
72
  "type": "stdio",
73
- "command": "node",
74
- "args": ["/absolute/project/path/.nubase/mcp-bridge/dist/src/index.js"],
73
+ "command": "npx",
74
+ "args": ["-y", "nubase_cli@latest"],
75
75
  "env": {
76
76
  "NUBASE_AGENT_ID": "claude-code",
77
77
  "NUBASE_CONFIG": "/absolute/project/path/.nubase/config.json"
@@ -81,6 +81,9 @@ node packages/mcp-bridge/dist/src/index.js
81
81
  }
82
82
  ```
83
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
+
84
87
  You may still set `NUBASE_URL` and `NUBASE_PROJECT_KEY` explicitly. Environment variables take precedence over the saved authorization config.
85
88
 
86
89
  ## Install Agent Skills
@@ -99,15 +102,15 @@ Targets:
99
102
 
100
103
  Use `--skills-scope project` to write `.claude/skills/nubase/**` and `.codex/skills/nubase/**` in the current project instead.
101
104
 
102
- By default, when the target includes `claude`, the command also copies the local MCP bridge to `.nubase/mcp-bridge` and creates or merges project `.mcp.json`:
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):
103
106
 
104
107
  ```json
105
108
  {
106
109
  "mcpServers": {
107
110
  "nubase": {
108
111
  "type": "stdio",
109
- "command": "node",
110
- "args": ["/absolute/project/path/.nubase/mcp-bridge/dist/src/index.js"],
112
+ "command": "npx",
113
+ "args": ["-y", "nubase_cli@<version>"],
111
114
  "env": {
112
115
  "NUBASE_AGENT_ID": "claude-code",
113
116
  "NUBASE_CONFIG": "/absolute/project/path/.nubase/config.json"
@@ -117,6 +120,10 @@ By default, when the target includes `claude`, the command also copies the local
117
120
  }
118
121
  ```
119
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
+
120
127
  Use `--mcp both` to also write project `.codex/config.toml` for Codex. Use `--no-mcp` to skip MCP config.
121
128
 
122
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.7';
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);
@@ -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
  };
@@ -19,10 +19,16 @@ export async function installSkills(options) {
19
19
  }
20
20
  }
21
21
  const mcpTargets = resolveMcpTargets(options.mcp ?? 'claude', targets);
22
+ const mcpDelivery = options.mcpDelivery ?? 'npx';
22
23
  let mcpCommand = null;
23
24
  if (mcpTargets.length > 0) {
24
- mcpCommand = await installProjectMcpBridge(options.projectDir);
25
- installed.push(mcpCommand.entrypoint);
25
+ if (mcpDelivery === 'local') {
26
+ mcpCommand = await installProjectMcpBridge(options.projectDir);
27
+ installed.push(mcpCommand.entrypoint);
28
+ }
29
+ else {
30
+ mcpCommand = await npxMcpCommand();
31
+ }
26
32
  }
27
33
  if (mcpTargets.includes('claude')) {
28
34
  installed.push(await installClaudeMcpConfig(options.projectDir, configPath, mcpCommand));
@@ -40,6 +46,7 @@ export function parseInstallArgs(argv) {
40
46
  let skills = true;
41
47
  let skillsScope = 'user';
42
48
  let mcp = 'claude';
49
+ let mcpDelivery = 'npx';
43
50
  let configPath;
44
51
  const authArgs = ['--prompt-only'];
45
52
  for (let i = 0; i < argv.length; i += 1) {
@@ -83,6 +90,13 @@ export function parseInstallArgs(argv) {
83
90
  }
84
91
  skillsScope = value;
85
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
+ }
86
100
  else if (arg === '--config') {
87
101
  const value = argv[++i];
88
102
  if (!value)
@@ -101,7 +115,7 @@ export function parseInstallArgs(argv) {
101
115
  }
102
116
  configPath = configPath ?? projectConfigPath(projectDir);
103
117
  authArgs.push('--config', configPath);
104
- return { target, projectDir, authorize, authArgs, skills, skillsScope, mcp, configPath };
118
+ return { target, projectDir, authorize, authArgs, skills, skillsScope, mcp, mcpDelivery, configPath };
105
119
  }
106
120
  function bundledSkillDir() {
107
121
  return path.join(bundledPackageRoot(), 'skills', 'nubase');
@@ -126,6 +140,20 @@ function resolveMcpTargets(mcp, skillTargets) {
126
140
  const requested = mcp === 'both' ? ['claude', 'codex'] : [mcp];
127
141
  return requested.filter((target) => skillTargets.includes(target));
128
142
  }
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
+ }
129
157
  async function installProjectMcpBridge(projectDir) {
130
158
  const packageRoot = bundledPackageRoot();
131
159
  const destRoot = path.join(projectDir, '.nubase', 'mcp-bridge');
@@ -47,25 +47,18 @@ export class McpStdioServer {
47
47
  }
48
48
  }
49
49
  readMessage() {
50
- const headerEnd = this.buffer.indexOf('\r\n\r\n');
51
- if (headerEnd === -1)
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 header = this.buffer.subarray(0, headerEnd).toString('utf8');
54
- const match = /Content-Length:\s*(\d+)/i.exec(header);
55
- if (!match) {
56
- throw new Error('Missing Content-Length header');
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
- const body = JSON.stringify(message);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nubase_cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -79,8 +79,8 @@ Expected `.mcp.json` shape:
79
79
  "mcpServers": {
80
80
  "nubase": {
81
81
  "type": "stdio",
82
- "command": "node",
83
- "args": ["/absolute/project/path/.nubase/mcp-bridge/dist/src/index.js"],
82
+ "command": "npx",
83
+ "args": ["-y", "nubase_cli@latest"],
84
84
  "env": {
85
85
  "NUBASE_AGENT_ID": "claude-code",
86
86
  "NUBASE_CONFIG": "/absolute/project/path/.nubase/config.json"