cli4ai 1.2.1 → 1.2.2

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.
@@ -4,8 +4,12 @@
4
4
  import { existsSync, symlinkSync, mkdirSync, cpSync, rmSync, readdirSync, unlinkSync, lstatSync } from 'fs';
5
5
  import { resolve, dirname, join, normalize } from 'path';
6
6
  import { createInterface } from 'readline';
7
- import { tmpdir } from 'os';
7
+ import { tmpdir, platform } from 'os';
8
8
  import { spawnSync } from 'child_process';
9
+ // Windows-safe command helpers
10
+ const isWindows = platform() === 'win32';
11
+ const npmCmd = isWindows ? 'npm.cmd' : 'npm';
12
+ const tarCmd = 'tar'; // Windows 10+ has tar built-in
9
13
  import { output, outputError, log } from '../lib/cli.js';
10
14
  import { loadManifest, tryLoadManifest } from '../core/manifest.js';
11
15
  import { ensureCli4aiHome, ensureLocalDir, PACKAGES_DIR, LOCAL_PACKAGES_DIR, loadConfig } from '../core/config.js';
@@ -98,7 +102,7 @@ async function downloadFromNpm(packageName, targetDir) {
98
102
  try {
99
103
  // Use npm pack to download the tarball - using spawnSync to prevent command injection
100
104
  log(`Downloading ${packageName} from npm...`);
101
- const packResult = spawnSync('npm', ['pack', packageName, `--pack-destination=${tmpDir}`], {
105
+ const packResult = spawnSync(npmCmd, ['pack', packageName, `--pack-destination=${tmpDir}`], {
102
106
  stdio: 'pipe',
103
107
  cwd: tmpDir,
104
108
  encoding: 'utf-8'
@@ -118,7 +122,7 @@ async function downloadFromNpm(packageName, targetDir) {
118
122
  // Extract the tarball using spawnSync to prevent command injection
119
123
  // Use --strip-components=1 equivalent by extracting to a subdir
120
124
  const tarPath = join(tmpDir, tarball);
121
- const extractResult = spawnSync('tar', ['-xzf', tarPath, '-C', tmpDir], {
125
+ const extractResult = spawnSync(tarCmd, ['-xzf', tarPath, '-C', tmpDir], {
122
126
  stdio: 'pipe',
123
127
  encoding: 'utf-8'
124
128
  });
@@ -161,7 +165,8 @@ async function downloadFromNpm(packageName, targetDir) {
161
165
  */
162
166
  function checkCliTool(name) {
163
167
  try {
164
- const result = spawnSync('which', [name], { stdio: 'pipe' });
168
+ const cmd = isWindows ? 'where' : 'which';
169
+ const result = spawnSync(cmd, [name], { stdio: 'pipe' });
165
170
  return result.status === 0;
166
171
  }
167
172
  catch {
@@ -176,7 +181,7 @@ async function installNpmDependencies(pkgPath, dependencies) {
176
181
  if (deps.length === 0)
177
182
  return;
178
183
  log(`Installing npm dependencies: ${deps.join(', ')}`);
179
- const result = spawnSync('npm', ['install', ...deps], {
184
+ const result = spawnSync(npmCmd, ['install', ...deps], {
180
185
  cwd: pkgPath,
181
186
  stdio: 'inherit'
182
187
  });
@@ -2,8 +2,11 @@
2
2
  * cli4ai browse - Interactive package browser
3
3
  */
4
4
  import { spawnSync } from 'child_process';
5
+ import { platform } from 'os';
5
6
  import { log, outputError } from '../lib/cli.js';
6
7
  import { getNpmGlobalPackages, getGlobalPackages, getLocalPackages } from '../core/config.js';
8
+ // Windows-safe cli4ai command (npm creates .cmd wrappers on Windows)
9
+ const cli4aiCmd = platform() === 'win32' ? 'cli4ai.cmd' : 'cli4ai';
7
10
  // ANSI codes
8
11
  const RESET = '\x1B[0m';
9
12
  const BOLD = '\x1B[1m';
@@ -328,7 +331,7 @@ async function installPackages(packages, scope) {
328
331
  if (scopeFlag)
329
332
  addArgs.push(scopeFlag);
330
333
  addArgs.push('-y');
331
- const result = spawnSync('cli4ai', addArgs, { stdio: 'pipe' });
334
+ const result = spawnSync(cli4aiCmd, addArgs, { stdio: 'pipe' });
332
335
  clearInterval(spinner);
333
336
  if (result.status === 0) {
334
337
  process.stderr.write(`\r ${GREEN}✓${RESET} ${BOLD}${shortName}${RESET} installed \n`);
@@ -3,7 +3,10 @@
3
3
  */
4
4
  import { resolve } from 'path';
5
5
  import { spawnSync } from 'child_process';
6
+ import { platform } from 'os';
6
7
  import { output, outputError, log } from '../lib/cli.js';
8
+ // Windows-safe npm command
9
+ const npmCmd = platform() === 'win32' ? 'npm.cmd' : 'npm';
7
10
  import { findPackage, loadConfig } from '../core/config.js';
8
11
  import { loadManifest, tryLoadManifest } from '../core/manifest.js';
9
12
  import { remotePackageInfo, RemoteConnectionError, RemoteApiError } from '../core/remote-client.js';
@@ -62,7 +65,7 @@ export async function infoCommand(packageName, options) {
62
65
  try {
63
66
  log(`Fetching ${scopedName} from npm...`);
64
67
  // Use spawnSync with argument array to prevent command injection
65
- const result = spawnSync('npm', ['view', scopedName, '--json'], {
68
+ const result = spawnSync(npmCmd, ['view', scopedName, '--json'], {
66
69
  encoding: 'utf-8',
67
70
  timeout: 10000,
68
71
  stdio: ['pipe', 'pipe', 'pipe']
@@ -4,7 +4,10 @@
4
4
  import { readdirSync, existsSync } from 'fs';
5
5
  import { resolve } from 'path';
6
6
  import { spawnSync } from 'child_process';
7
+ import { platform } from 'os';
7
8
  import { output, outputError, log } from '../lib/cli.js';
9
+ // Windows-safe npm command
10
+ const npmCmd = platform() === 'win32' ? 'npm.cmd' : 'npm';
8
11
  import { loadConfig, getGlobalPackages, getLocalPackages } from '../core/config.js';
9
12
  import { tryLoadManifest } from '../core/manifest.js';
10
13
  import { remoteListPackages, RemoteConnectionError, RemoteApiError } from '../core/remote-client.js';
@@ -94,7 +97,7 @@ export async function searchCommand(query, options) {
94
97
  try {
95
98
  log(`Searching npm for @cli4ai packages...`);
96
99
  // Use spawnSync with argument array to prevent command injection
97
- const searchResult = spawnSync('npm', ['search', `@cli4ai/${query}`, '--json'], {
100
+ const searchResult = spawnSync(npmCmd, ['search', `@cli4ai/${query}`, '--json'], {
98
101
  encoding: 'utf-8',
99
102
  timeout: 10000,
100
103
  stdio: ['pipe', 'pipe', 'pipe']
@@ -102,7 +105,7 @@ export async function searchCommand(query, options) {
102
105
  let npmResults = searchResult.stdout || '';
103
106
  // Fallback to searching @cli4ai if specific query fails
104
107
  if (!npmResults || npmResults === '[]') {
105
- const fallbackResult = spawnSync('npm', ['search', '@cli4ai', '--json'], {
108
+ const fallbackResult = spawnSync(npmCmd, ['search', '@cli4ai', '--json'], {
106
109
  encoding: 'utf-8',
107
110
  timeout: 10000,
108
111
  stdio: ['pipe', 'pipe', 'pipe']
@@ -2,7 +2,7 @@
2
2
  * Global cli4ai configuration (~/.cli4ai/)
3
3
  */
4
4
  import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, lstatSync, realpathSync, openSync, closeSync, unlinkSync, renameSync } from 'fs';
5
- import { resolve, join, normalize } from 'path';
5
+ import { resolve, join, normalize, sep } from 'path';
6
6
  import { homedir } from 'os';
7
7
  import { outputError, log } from '../lib/cli.js';
8
8
  // ═══════════════════════════════════════════════════════════════════════════
@@ -36,7 +36,7 @@ function validateSymlinkTarget(symlinkPath) {
36
36
  // Check if the real path is within a safe prefix
37
37
  const isSafe = SAFE_SYMLINK_PREFIXES.some(prefix => {
38
38
  const normalizedPrefix = normalize(prefix);
39
- return normalizedRealPath.startsWith(normalizedPrefix + '/') ||
39
+ return normalizedRealPath.startsWith(normalizedPrefix + sep) ||
40
40
  normalizedRealPath === normalizedPrefix;
41
41
  });
42
42
  if (!isSafe) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli4ai",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "The package manager for AI CLI tools - cli4ai.com",
5
5
  "type": "module",
6
6
  "bin": {