smart-terminal-mcp 1.2.31 → 1.2.34

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.
@@ -3,7 +3,10 @@
3
3
  "allow": [
4
4
  "WebSearch",
5
5
  "Bash(dir:*)",
6
- "Bash(npm test:*)"
6
+ "Bash(npm test:*)",
7
+ "Bash(\"C:\\\\Users\\\\Alessandro\\\\AppData\\\\Local\\\\mcp-publisher\\\\mcp-publisher.exe\" login:*)",
8
+ "Bash(node -e \"console.log\\(require\\('./package.json'\\).version\\)\")",
9
+ "Bash(npm view *)"
7
10
  ]
8
11
  }
9
12
  }
@@ -1 +1 @@
1
- ghu_mi7nqUh7yokw3ywHX7jTSENKl3oizw1yvyfE
1
+ ghu_oOdnT8lgxyaN964Yo3rMrjHyBj8zcd2uGDnB
@@ -1 +1 @@
1
- {"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzMwMDM0NDQsIm5iZiI6MTc3MzAwMzE0NCwiaWF0IjoxNzczMDAzMTQ0LCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6InB1bmdnZ2kiLCJwZXJtaXNzaW9ucyI6W3siYWN0aW9uIjoicHVibGlzaCIsInJlc291cmNlIjoiaW8uZ2l0aHViLnB1bmdnZ2kvKiJ9XX0.WdnInC_87a16F4MtyjLr4XCG-DDGAGMUOxKY2Y9SWnxiy8QBY25fBY4dbR53gQwveB2-L86e6MjtQ4Yj7kXtCA","expires_at":1773003444}
1
+ {"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzUyMDMwODcsIm5iZiI6MTc3NTIwMjc4NywiaWF0IjoxNzc1MjAyNzg3LCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6InB1bmdnZ2kiLCJwZXJtaXNzaW9ucyI6W3siYWN0aW9uIjoicHVibGlzaCIsInJlc291cmNlIjoiaW8uZ2l0aHViLnB1bmdnZ2kvKiJ9XX0.m5_YrGmyIMZ7vVMZBU5G31PBgEfVfWI4WmGZvkNhmk4wSL_cxEF-M2ARhgoymUInngh6qgdql2FLX_SJszlqAw","expires_at":1775203087}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-terminal-mcp",
3
- "version": "1.2.31",
3
+ "version": "1.2.34",
4
4
  "description": "MCP PTY server providing AI agents with real interactive terminal access",
5
5
  "mcpName": "io.github.pungggi/smart-terminal",
6
6
  "repository": {
@@ -15,7 +15,7 @@
15
15
  "scripts": {
16
16
  "start": "node src/index.js",
17
17
  "test": "node --test",
18
- "stable": "npm dist-tag add smart-terminal-mcp@1.2.29 stable",
18
+ "stable": "npm dist-tag add smart-terminal-mcp@1.2.30 stable",
19
19
  "release": "node scripts/publish.js"
20
20
  },
21
21
  "keywords": [
@@ -48,6 +48,16 @@ function runCommand(command, errorMessage, extraEnv = {}) {
48
48
  }
49
49
  }
50
50
 
51
+ function parseFlags(args) {
52
+ const flags = new Set();
53
+ const positional = [];
54
+ for (const arg of args) {
55
+ if (arg.startsWith('--')) flags.add(arg);
56
+ else positional.push(arg);
57
+ }
58
+ return { flags, positional };
59
+ }
60
+
51
61
  function createServerCardReaderInit() {
52
62
  return parseExpression(
53
63
  `JSON.parse(readFileSync(new URL('${SERVER_CARD_FILE_URL}', import.meta.url), 'utf8'))`,
@@ -510,10 +520,16 @@ function updateVersionFiles(nextVersion) {
510
520
  }
511
521
 
512
522
  export async function main(args = process.argv.slice(2)) {
523
+ const { flags, positional } = parseFlags(args);
524
+ const skipNpm = flags.has('--skip-npm');
525
+ const skipRegistry = flags.has('--skip-registry');
526
+ const skipSmithery = flags.has('--skip-smithery');
527
+
513
528
  let fileSnapshots = null;
529
+ let npmPublished = false;
514
530
 
515
531
  try {
516
- const versionArg = args[0] || 'patch';
532
+ const versionArg = positional[0] || 'patch';
517
533
  const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
518
534
  const currentVersion = pkg.version;
519
535
  const nextVersion = bumpVersion(currentVersion, versionArg);
@@ -529,23 +545,55 @@ export async function main(args = process.argv.slice(2)) {
529
545
  updateServerCard(tools, nextVersion);
530
546
 
531
547
  // ── Step 3: npm ──
532
- console.log('\n📦 Phase 1: Publishing to npm...');
533
- runCommand('npm publish --access public', 'npm publishing failed.');
548
+ if (skipNpm) {
549
+ console.log('\n📦 Phase 1: Publishing to npm... SKIPPED (--skip-npm)');
550
+ } else {
551
+ console.log('\n📦 Phase 1: Publishing to npm...');
552
+ runCommand('npm publish --access public', 'npm publishing failed.');
553
+ npmPublished = true;
554
+ }
534
555
 
535
556
  // ── Step 4: Official MCP Registry ──
536
- console.log('\n🌍 Phase 2: Publishing to Official MCP Registry...');
537
- runCommand(`${getMcpPublisher()} publish`, 'Official MCP Registry publishing failed.');
557
+ if (skipRegistry) {
558
+ console.log('\n🌍 Phase 2: Publishing to Official MCP Registry... SKIPPED (--skip-registry)');
559
+ } else {
560
+ console.log('\n🌍 Phase 2: Publishing to Official MCP Registry...');
561
+ try {
562
+ runCommand(`${getMcpPublisher()} publish`, 'Official MCP Registry publishing failed.');
563
+ } catch (error) {
564
+ const causeStderr = error.cause?.stderr?.toString?.() ?? '';
565
+ const causeMessage = error.message ?? '';
566
+ if (causeStderr.includes('401') || causeStderr.includes('Unauthorized') || causeStderr.includes('expired')
567
+ || causeMessage.includes('401') || causeMessage.includes('Unauthorized') || causeMessage.includes('expired')) {
568
+ const pub = getMcpPublisher();
569
+ console.error(`\n💡 Auth token expired. Run: ${pub} login github`);
570
+ console.error(` Then retry with: npm run release -- ${versionArg} --skip-npm`);
571
+ }
572
+ throw error;
573
+ }
574
+ }
538
575
 
539
576
  // ── Step 5: Smithery.ai (HTTP server + tunnel + publish) ──
540
- await publishToSmithery();
577
+ if (skipSmithery) {
578
+ console.log('\n💎 Phase 3: Publishing to Smithery.ai... SKIPPED (--skip-smithery)');
579
+ } else {
580
+ await publishToSmithery();
581
+ }
541
582
 
542
- console.log(`\n✅ SUCCESSFULLY PUBLISHED version ${nextVersion} to all registries!`);
583
+ const skipped = [skipNpm && 'npm', skipRegistry && 'registry', skipSmithery && 'smithery'].filter(Boolean);
584
+ const suffix = skipped.length ? ` (skipped: ${skipped.join(', ')})` : ' to all registries';
585
+ console.log(`\n✅ SUCCESSFULLY PUBLISHED version ${nextVersion}${suffix}!`);
543
586
  } catch (error) {
544
587
  console.error('\n❌ Publish failed.');
545
588
  console.error(error);
546
589
  if (fileSnapshots) {
547
- // Restore only files this script manages so unrelated local edits stay intact.
548
- revertVersionChanges(fileSnapshots);
590
+ if (npmPublished) {
591
+ // npm already has this version — don't revert local files or we'll get version drift.
592
+ console.error('\n⚠️ npm was already published. Local version files kept at the new version.');
593
+ console.error(' Fix the issue and re-run with --skip-npm to complete remaining phases.');
594
+ } else {
595
+ revertVersionChanges(fileSnapshots);
596
+ }
549
597
  }
550
598
  throw error;
551
599
  }
package/server-card.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "serverInfo": {
3
3
  "name": "smart-terminal-mcp",
4
- "version": "1.2.31"
4
+ "version": "1.2.34"
5
5
  },
6
6
  "tools": [
7
7
  {
@@ -77,7 +77,7 @@
77
77
  },
78
78
  {
79
79
  "name": "terminal_run",
80
- "description": "Run a binary directly; no shell quoting needed.",
80
+ "description": "Run a binary directly. shell=true for built-ins/pipes/redirects.",
81
81
  "inputSchema": {
82
82
  "type": "object",
83
83
  "properties": {
@@ -138,6 +138,11 @@
138
138
  "successFilePattern": {
139
139
  "type": "string",
140
140
  "description": "Regex"
141
+ },
142
+ "shell": {
143
+ "type": "boolean",
144
+ "default": false,
145
+ "description": "Run via system shell"
141
146
  }
142
147
  },
143
148
  "required": [
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/pungggi/smart-terminal-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.2.31",
9
+ "version": "1.2.34",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "smart-terminal-mcp",
14
- "version": "1.2.31",
14
+ "version": "1.2.34",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }
package/smithery.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pungggi/smart-terminal",
3
3
  "description": "MCP PTY server providing AI agents with real interactive terminal access",
4
- "version": "1.2.31",
4
+ "version": "1.2.34",
5
5
  "repository": "https://github.com/pungggi/smart-terminal-mcp",
6
6
  "tags": [
7
7
  "terminal",
@@ -26,11 +26,12 @@ export async function runCommand({
26
26
  successExitCode = 0,
27
27
  successFile,
28
28
  successFilePattern,
29
+ shell = false,
29
30
  }) {
30
31
  assertSuccessChecksAreValid({ successFile, successFilePattern });
31
32
  const resolvedCwd = resolvePath(cwd ?? process.cwd());
32
33
  const startedAt = Date.now();
33
- const spawnPlan = buildSpawnPlan({ cmd, args, cwd: resolvedCwd });
34
+ const spawnPlan = buildSpawnPlan({ cmd, args, cwd: resolvedCwd, useShell: shell });
34
35
 
35
36
  return new Promise((resolve, reject) => {
36
37
  const stdoutChunks = [];
@@ -42,7 +43,7 @@ export async function runCommand({
42
43
 
43
44
  const child = spawn(spawnPlan.command, spawnPlan.args, {
44
45
  cwd: resolvedCwd,
45
- shell: false,
46
+ shell: spawnPlan.shell ?? false,
46
47
  windowsHide: true,
47
48
  windowsVerbatimArguments: spawnPlan.windowsVerbatimArguments,
48
49
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -222,7 +223,16 @@ function formatFileCheckError(error) {
222
223
  return error?.message ?? String(error);
223
224
  }
224
225
 
225
- function buildSpawnPlan({ cmd, args, cwd }) {
226
+ function buildSpawnPlan({ cmd, args, cwd, useShell = false }) {
227
+ if (useShell) {
228
+ return {
229
+ command: cmd,
230
+ args,
231
+ shell: true,
232
+ windowsVerbatimArguments: false,
233
+ };
234
+ }
235
+
226
236
  if (process.platform !== 'win32') {
227
237
  return {
228
238
  command: cmd,
@@ -372,11 +382,11 @@ function quoteWindowsBatchArgument(value) {
372
382
 
373
383
  function formatStartError({ cmd, err }) {
374
384
  const baseMessage = `Failed to start command "${cmd}": ${err.message}`;
375
- if (process.platform !== 'win32' || err?.code !== 'ENOENT' || looksLikePath(cmd)) {
385
+ if (err?.code !== 'ENOENT' || looksLikePath(cmd)) {
376
386
  return baseMessage;
377
387
  }
378
388
 
379
- return `${baseMessage}. If this command should come from PATH, verify it is installed and visible to the server process. Shell built-ins such as dir or cd still require terminal_exec.`;
389
+ return `${baseMessage}. Verify it is installed and on PATH for the server process. For shell built-ins, pipes, or redirections, pass shell:true or use terminal_start + terminal_exec.`;
380
390
  }
381
391
 
382
392
  export function getStructuredParserHint({ cmd, args, ok, parseRequested, parsed, stdout }) {
package/src/tools.js CHANGED
@@ -152,7 +152,7 @@ export function registerTools(server, manager) {
152
152
  // --- terminal_run ---
153
153
  tool(
154
154
  'terminal_run',
155
- 'Run a binary directly; no shell quoting needed.',
155
+ 'Run a binary directly. shell=true for built-ins/pipes/redirects.',
156
156
  {
157
157
  cmd: z.string(),
158
158
  args: z.array(z.string()).default([]),
@@ -165,8 +165,9 @@ export function registerTools(server, manager) {
165
165
  successExitCode: z.number().int().nullable().default(0).describe('null=any'),
166
166
  successFile: z.string().optional(),
167
167
  successFilePattern: z.string().optional().describe('Regex'),
168
+ shell: z.boolean().default(false).describe('Run via system shell'),
168
169
  },
169
- async ({ cmd, args, cwd, timeout, maxOutputBytes, parse, parseOnly, summary, successExitCode, successFile, successFilePattern }) => {
170
+ async ({ cmd, args, cwd, timeout, maxOutputBytes, parse, parseOnly, summary, successExitCode, successFile, successFilePattern, shell }) => {
170
171
  const result = await runCommand({
171
172
  cmd,
172
173
  args,
@@ -179,6 +180,7 @@ export function registerTools(server, manager) {
179
180
  successExitCode,
180
181
  successFile,
181
182
  successFilePattern,
183
+ shell,
182
184
  });
183
185
  return jsonContent(result);
184
186
  }
@@ -288,18 +288,13 @@ test('runCommand handles explicit command paths with spaces', async () => {
288
288
  }
289
289
  });
290
290
 
291
- test('runCommand adds a helpful ENOENT hint on Windows for PATH commands', async (t) => {
292
- if (process.platform !== 'win32') {
293
- t.skip('Windows-only behavior');
294
- return;
295
- }
296
-
291
+ test('runCommand adds a helpful ENOENT hint for PATH commands', async () => {
297
292
  await assert.rejects(
298
293
  runCommand({
299
294
  cmd: '__smart_terminal_missing_command__',
300
295
  parse: false,
301
296
  }),
302
- /verify it is installed and visible to the server process/
297
+ /pass shell:true or use terminal_start \+ terminal_exec/
303
298
  );
304
299
  });
305
300
 
@@ -288,6 +288,36 @@ test('terminal_run can re-evaluate success from a file pattern', async () => {
288
288
  }
289
289
  });
290
290
 
291
+ test('terminal_run shell=true executes commands via the system shell', async () => {
292
+ const server = createFakeServer();
293
+ registerTools(server, {});
294
+
295
+ const result = await server.tools.get('terminal_run').handler({
296
+ cmd: 'echo shell-ok',
297
+ args: [],
298
+ shell: true,
299
+ parse: false,
300
+ });
301
+
302
+ const payload = JSON.parse(result.content[0].text);
303
+ assert.equal(payload.ok, true);
304
+ assert.match(payload.stdout.raw, /shell-ok/);
305
+ });
306
+
307
+ test('terminal_run ENOENT error hints at shell=true and terminal_exec fallback', async () => {
308
+ const server = createFakeServer();
309
+ registerTools(server, {});
310
+
311
+ await assert.rejects(
312
+ () => server.tools.get('terminal_run').handler({
313
+ cmd: 'smart-terminal-missing-binary-xyz',
314
+ args: [],
315
+ parse: false,
316
+ }),
317
+ /shell:true or use terminal_start \+ terminal_exec/
318
+ );
319
+ });
320
+
291
321
  test('terminal_read rejects idleTimeout values that are not less than timeout', async () => {
292
322
  const server = createFakeServer();
293
323
  let getCalls = 0;