axinstall 1.0.0 → 1.2.0

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/dist/cli.js CHANGED
@@ -35,7 +35,8 @@ const program = new Command()
35
35
  .option("-v, --verbose", "Enable verbose output")
36
36
  .addHelpText("after", `
37
37
  Examples:
38
- $ axinstall claude Install Claude Code
38
+ $ axinstall claude Install Claude Code globally
39
+ $ axinstall claude --local Install to node_modules/.bin
39
40
  $ axinstall claude --with npm Use specific package manager
40
41
  $ axinstall claude --dry-run Preview installation command
41
42
  $ axinstall --list-agents | tail -n +2 | cut -f1 List agent CLI names
@@ -53,8 +54,8 @@ Requirements:
53
54
  program
54
55
  .argument("[agent]", `Agent to install (${AGENT_CLIS.join(", ")})`)
55
56
  .option("--with <installer>", `Package manager to use (${INSTALLER_IDS.join(", ")})`)
57
+ .option("--local", "Install to node_modules/.bin (run via package runner)", false)
56
58
  .option("--dry-run", "Show command without executing", false)
57
- .option("--local", "Install locally instead of globally", false)
58
59
  .action((agentCli, cliOptions) => {
59
60
  runCommand(agentCli, cliOptions);
60
61
  });
@@ -19,7 +19,7 @@ declare function getAvailableInstallers(): Installer[];
19
19
  /**
20
20
  * Auto-detect the best available installer based on priority.
21
21
  *
22
- * Priority order: pnpm > bun > yarn > npm > brew
22
+ * Priority order: npm > pnpm > bun > yarn > brew
23
23
  */
24
24
  declare function detectBestInstaller(): Installer | undefined;
25
25
  /**
@@ -48,7 +48,7 @@ function getAvailableInstallers() {
48
48
  /**
49
49
  * Auto-detect the best available installer based on priority.
50
50
  *
51
- * Priority order: pnpm > bun > yarn > npm > brew
51
+ * Priority order: npm > pnpm > bun > yarn > brew
52
52
  */
53
53
  function detectBestInstaller() {
54
54
  const available = new Set(getAvailableInstallers().map((installer) => installer.id));
@@ -6,7 +6,8 @@ import type { Installer } from "./types.js";
6
6
  interface InstallOptions {
7
7
  agent: Agent;
8
8
  installer: Installer;
9
- global: boolean;
9
+ /** Install locally instead of globally. Defaults to false. */
10
+ local?: boolean;
10
11
  dryRun: boolean;
11
12
  }
12
13
  interface InstallResult {
@@ -5,15 +5,22 @@ import { execFileSync } from "node:child_process";
5
5
  import { resolveInstallerCommand } from "./resolve-installer-command.js";
6
6
  /**
7
7
  * Build the installation arguments for an agent.
8
+ *
9
+ * @throws {Error} If local install is requested but the installer doesn't support it.
8
10
  */
9
11
  function buildInstallArguments(options) {
10
- const { agent, installer, global: isGlobal } = options;
12
+ const { agent, installer, local } = options;
11
13
  // For brew, use the CLI name as the package name (formula name)
12
14
  // For npm-based installers, use the npm package name
13
15
  const packageName = installer.id === "brew" ? agent.cli : agent.package;
14
- const installArguments = isGlobal
15
- ? installer.globalInstallArgs
16
- : installer.localInstallArgs;
16
+ // Reject local installs when the installer doesn't support them
17
+ if (local) {
18
+ if (!installer.localInstallArgs) {
19
+ throw new Error(`Installer '${installer.id}' does not support local installation`);
20
+ }
21
+ return installer.localInstallArgs.map((argument) => argument.replace("{package}", packageName));
22
+ }
23
+ const installArguments = installer.installArgs;
17
24
  return installArguments.map((argument) => argument.replace("{package}", packageName));
18
25
  }
19
26
  function formatCommand(command, arguments_) {
@@ -32,7 +39,19 @@ function buildInstallCommand(options) {
32
39
  */
33
40
  function executeInstall(options) {
34
41
  const command = resolveInstallerCommand(options.installer);
35
- const arguments_ = buildInstallArguments(options);
42
+ // Wrap argument building to catch validation errors and return InstallOutcome
43
+ let arguments_;
44
+ try {
45
+ arguments_ = buildInstallArguments(options);
46
+ }
47
+ catch (error) {
48
+ const localFlag = options.local ? " (local)" : "";
49
+ return {
50
+ ok: false,
51
+ command: `${options.installer.id}${localFlag}: ${command} ...`,
52
+ error: error instanceof Error ? error : new Error(String(error)),
53
+ };
54
+ }
36
55
  const displayCommand = formatCommand(command, arguments_);
37
56
  if (options.dryRun) {
38
57
  return { ok: true, command: displayCommand };
@@ -3,8 +3,8 @@
3
3
  */
4
4
  interface InstallOptions {
5
5
  with?: string;
6
- dryRun: boolean;
7
6
  local: boolean;
7
+ dryRun: boolean;
8
8
  verbose?: boolean;
9
9
  }
10
10
  export declare function handleInstall(agentCli: string, options: InstallOptions): void;
@@ -4,7 +4,7 @@
4
4
  import { AGENT_CLIS, getAgent, isValidAgentCli } from "axshared";
5
5
  import { detectBestInstaller, isInstallerAvailable, } from "./detect-installer.js";
6
6
  import { executeInstall } from "./execute-install.js";
7
- import { getInstaller } from "./installer-data.js";
7
+ import { getAllInstallers, getInstaller } from "./installer-data.js";
8
8
  import { resolveInstallerCommand } from "./resolve-installer-command.js";
9
9
  import { INSTALLER_IDS } from "./types.js";
10
10
  const INSTALLER_ID_SET = new Set(INSTALLER_IDS);
@@ -58,11 +58,21 @@ export function handleInstall(agentCli, options) {
58
58
  return;
59
59
  }
60
60
  }
61
- const isGlobal = !options.local;
61
+ // Validate local installation support
62
+ if (options.local && !installer.localInstallArgs) {
63
+ const localInstallers = getAllInstallers()
64
+ .filter((installer_) => installer_.localInstallArgs)
65
+ .map((installer_) => installer_.id)
66
+ .join(", ");
67
+ console.error(`Error: Installer '${installer.id}' does not support local installation`);
68
+ console.error(`Use a different installer: ${localInstallers}`);
69
+ process.exitCode = 1;
70
+ return;
71
+ }
62
72
  const result = executeInstall({
63
73
  agent,
64
74
  installer,
65
- global: isGlobal,
75
+ local: options.local,
66
76
  dryRun: options.dryRun,
67
77
  });
68
78
  if (options.dryRun) {
@@ -72,11 +82,27 @@ export function handleInstall(agentCli, options) {
72
82
  if (result.ok) {
73
83
  if (options.verbose) {
74
84
  console.error(`Successfully installed ${agent.name}.`);
75
- console.error(`Run '${agent.cli}' to get started.`);
85
+ if (options.local) {
86
+ // Suggest the appropriate runner for the installer used
87
+ const runnerCommands = {
88
+ npm: `npx ${agent.cli}`,
89
+ pnpm: `pnpm exec ${agent.cli}`,
90
+ bun: `bunx ${agent.cli}`,
91
+ yarn: `yarn run ${agent.cli}`,
92
+ brew: agent.cli, // brew installs globally
93
+ };
94
+ console.error(`Run '${runnerCommands[installer.id]}' to get started.`);
95
+ }
96
+ else {
97
+ console.error(`Run '${agent.cli}' to get started.`);
98
+ }
76
99
  }
77
100
  return;
78
101
  }
79
102
  console.error(`Error: Failed to install ${agent.name}`);
80
103
  console.error(`Command: ${result.command}`);
104
+ if (result.error.message) {
105
+ console.error(`Details: ${result.error.message}`);
106
+ }
81
107
  process.exitCode = 1;
82
108
  }
@@ -7,7 +7,7 @@ const npm = {
7
7
  command: "npm",
8
8
  envVar: "AXINSTALL_NPM_PATH",
9
9
  checkArgs: ["--version"],
10
- globalInstallArgs: ["install", "-g", "{package}"],
10
+ installArgs: ["install", "-g", "{package}"],
11
11
  localInstallArgs: ["install", "{package}"],
12
12
  };
13
13
  const pnpm = {
@@ -16,7 +16,7 @@ const pnpm = {
16
16
  command: "pnpm",
17
17
  envVar: "AXINSTALL_PNPM_PATH",
18
18
  checkArgs: ["--version"],
19
- globalInstallArgs: ["add", "-g", "{package}"],
19
+ installArgs: ["add", "-g", "{package}"],
20
20
  localInstallArgs: ["add", "{package}"],
21
21
  };
22
22
  const bun = {
@@ -25,7 +25,7 @@ const bun = {
25
25
  command: "bun",
26
26
  envVar: "AXINSTALL_BUN_PATH",
27
27
  checkArgs: ["--version"],
28
- globalInstallArgs: ["add", "-g", "{package}"],
28
+ installArgs: ["add", "-g", "{package}"],
29
29
  localInstallArgs: ["add", "{package}"],
30
30
  };
31
31
  const yarn = {
@@ -34,7 +34,7 @@ const yarn = {
34
34
  command: "yarn",
35
35
  envVar: "AXINSTALL_YARN_PATH",
36
36
  checkArgs: ["--version"],
37
- globalInstallArgs: ["global", "add", "{package}"],
37
+ installArgs: ["global", "add", "{package}"],
38
38
  localInstallArgs: ["add", "{package}"],
39
39
  };
40
40
  const brew = {
@@ -43,8 +43,8 @@ const brew = {
43
43
  command: "brew",
44
44
  envVar: "AXINSTALL_BREW_PATH",
45
45
  checkArgs: ["--version"],
46
- globalInstallArgs: ["install", "{package}"],
47
- localInstallArgs: ["install", "{package}"],
46
+ installArgs: ["install", "{package}"],
47
+ // No localInstallArgs - brew only installs globally
48
48
  };
49
49
  /** All installers indexed by ID */
50
50
  const INSTALLERS = {
@@ -56,10 +56,10 @@ const INSTALLERS = {
56
56
  };
57
57
  /** Default priority order for auto-detection */
58
58
  const INSTALLER_PRIORITY = [
59
+ "npm",
59
60
  "pnpm",
60
61
  "bun",
61
62
  "yarn",
62
- "npm",
63
63
  "brew",
64
64
  ];
65
65
  function getInstaller(id) {
@@ -7,7 +7,7 @@ export interface CliOptions {
7
7
  only?: string;
8
8
  verbose?: boolean;
9
9
  with?: string;
10
- dryRun: boolean;
11
10
  local: boolean;
11
+ dryRun: boolean;
12
12
  }
13
13
  export declare function runCommand(agentCli: string | undefined, options: CliOptions): void;
package/dist/types.d.ts CHANGED
@@ -15,9 +15,9 @@ interface Installer {
15
15
  /** Args for a version check. */
16
16
  checkArgs: readonly string[];
17
17
  /** Args for global install. Use {package} as placeholder. */
18
- globalInstallArgs: readonly string[];
19
- /** Args for local install. Use {package} as placeholder. */
20
- localInstallArgs: readonly string[];
18
+ installArgs: readonly string[];
19
+ /** Args for local install. Use {package} as placeholder. Undefined if not supported. */
20
+ localInstallArgs?: readonly string[];
21
21
  }
22
22
  /** Result of checking installer availability */
23
23
  interface InstallerCheckResult {
@@ -20,16 +20,16 @@ export function validateOptions(agentCli, options) {
20
20
  message: "--with is only valid when installing an agent.",
21
21
  };
22
22
  }
23
- if ((options.listAgents || options.status) && options.local) {
23
+ if ((options.listAgents || options.status) && options.dryRun) {
24
24
  return {
25
25
  valid: false,
26
- message: "--local is only valid when installing an agent.",
26
+ message: "--dry-run is only valid when installing an agent.",
27
27
  };
28
28
  }
29
- if ((options.listAgents || options.status) && options.dryRun) {
29
+ if ((options.listAgents || options.status) && options.local) {
30
30
  return {
31
31
  valid: false,
32
- message: "--dry-run is only valid when installing an agent.",
32
+ message: "--local is only valid when installing an agent.",
33
33
  };
34
34
  }
35
35
  if (options.only && !options.status) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "axinstall",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "1.0.0",
5
+ "version": "1.2.0",
6
6
  "description": "Universal installer for AI CLI agents with automatic package manager detection",
7
7
  "repository": {
8
8
  "type": "git",
@@ -43,9 +43,9 @@
43
43
  "rebuild": "pnpm run clean && pnpm run build",
44
44
  "start": "pnpm -s run rebuild && node bin/axinstall",
45
45
  "test": "vitest run --exclude '**/*.integration.test.ts'",
46
+ "test:all": "vitest run --testTimeout=300000",
46
47
  "test:coverage": "vitest run --coverage --exclude '**/*.integration.test.ts'",
47
48
  "test:integration": "vitest run --testTimeout=300000 src/docker-install.integration.test.ts",
48
- "test:all": "vitest run --testTimeout=300000",
49
49
  "test:watch": "vitest --exclude '**/*.integration.test.ts'",
50
50
  "typecheck": "tsc -b --noEmit"
51
51
  },
@@ -63,33 +63,27 @@
63
63
  "automation",
64
64
  "coding-assistant"
65
65
  ],
66
- "packageManager": "pnpm@10.26.1",
66
+ "packageManager": "pnpm@10.27.0",
67
67
  "engines": {
68
68
  "node": ">=22.14.0"
69
69
  },
70
70
  "dependencies": {
71
71
  "@commander-js/extra-typings": "^14.0.0",
72
- "axshared": "^1.1.0",
72
+ "axshared": "^1.8.0",
73
73
  "commander": "^14.0.2"
74
74
  },
75
75
  "devDependencies": {
76
- "@eslint/compat": "^2.0.0",
77
- "@eslint/js": "^9.39.2",
78
76
  "@total-typescript/ts-reset": "^0.6.1",
79
77
  "@types/node": "^25.0.3",
80
78
  "@vitest/coverage-v8": "^4.0.16",
81
- "@vitest/eslint-plugin": "^1.5.4",
82
79
  "eslint": "^9.39.2",
83
- "eslint-config-prettier": "^10.1.8",
84
- "eslint-plugin-unicorn": "^62.0.0",
80
+ "eslint-config-axkit": "^1.0.0",
85
81
  "fta-check": "^1.5.1",
86
82
  "fta-cli": "^3.0.0",
87
- "globals": "^16.5.0",
88
- "knip": "^5.76.1",
83
+ "knip": "^5.80.0",
89
84
  "prettier": "3.7.4",
90
85
  "semantic-release": "^25.0.2",
91
86
  "typescript": "^5.9.3",
92
- "typescript-eslint": "^8.50.0",
93
87
  "vitest": "^4.0.16"
94
88
  }
95
89
  }