@introspection-ai/cli 0.18.1 → 0.20.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/README.md CHANGED
@@ -6,7 +6,8 @@ operating agents across local development and the Introspection platform.
6
6
  ```bash
7
7
  npm install -g @introspection-ai/cli
8
8
 
9
- # authenticate and inspect the available commands
9
+ # install Pi when missing, then align Recipes and coding-agent plugins
10
+ introspection setup
10
11
  introspection login
11
12
  introspection --help
12
13
  ```
@@ -19,6 +20,7 @@ npx @introspection-ai/cli --help
19
20
 
20
21
  The platform-specific CLI and its Rust judge engine are delivered via an
21
22
  optional dependency (`@introspection-ai/cli-<os>-<cpu>`) selected automatically
22
- by npm.
23
+ by npm. The CLI requires Node.js 24 or newer. On Node 18–23 the npm launcher
24
+ installs but every command exits with upgrade instructions.
23
25
 
24
26
  Full documentation: https://github.com/introspection-org/introspection-cli
@@ -6,6 +6,10 @@
6
6
  "use strict";
7
7
 
8
8
  const { spawnSync } = require("node:child_process");
9
+ const fs = require("node:fs");
10
+ const path = require("node:path");
11
+
12
+ const MIN_NODE_MAJOR = 24;
9
13
 
10
14
  const PACKAGES = {
11
15
  "darwin-arm64": "@introspection-ai/cli-darwin-arm64",
@@ -15,6 +19,137 @@ const PACKAGES = {
15
19
  "win32-x64": "@introspection-ai/cli-win32-x64",
16
20
  };
17
21
 
22
+ const PACKAGE_MANAGER_ENV = "INTROSPECTION_CLI_PACKAGE_MANAGER";
23
+ const PACKAGE_MANAGER_PATH_ENV = "INTROSPECTION_CLI_PACKAGE_MANAGER_PATH";
24
+ const LOCKFILES = [
25
+ ["pnpm-lock.yaml", "pnpm"],
26
+ ["yarn.lock", "yarn"],
27
+ ["bun.lock", "bun"],
28
+ ["bun.lockb", "bun"],
29
+ ["package-lock.json", "npm"],
30
+ ];
31
+
32
+ /**
33
+ * Identify the package manager that owns this launcher. This mirrors Vercel's
34
+ * update-command approach: use install metadata while it is available, then
35
+ * walk the installed package tree for a lockfile or a distinctive global-store
36
+ * path. An uncertain answer stays uncertain; the native CLI will show generic
37
+ * reinstall guidance rather than silently falling back to npm.
38
+ */
39
+ function detectPackageManager({
40
+ entrypoint = __filename,
41
+ existsSync = fs.existsSync,
42
+ realpathSync = fs.realpathSync,
43
+ } = {}) {
44
+ const logical = path.resolve(entrypoint);
45
+ let physical;
46
+ try {
47
+ physical = realpathSync(entrypoint);
48
+ } catch {
49
+ physical = logical;
50
+ }
51
+ const locations = [...new Set([logical, physical])];
52
+ const normalized = locations.map(location =>
53
+ location.replaceAll("\\", "/").toLowerCase()
54
+ );
55
+ const globalLocations = normalized.filter(location =>
56
+ location.includes("/pnpm/global/") ||
57
+ location.includes("/yarn/global/") ||
58
+ location.includes("/.bun/install/global/") ||
59
+ location.includes("/lib/node_modules/") ||
60
+ location.includes("/appdata/roaming/npm/node_modules/")
61
+ );
62
+ if (globalLocations.length === 0) {
63
+ return null;
64
+ }
65
+
66
+ if (globalLocations.some(location =>
67
+ location.includes("/.pnpm/") || location.includes("/pnpm/global/")
68
+ )) {
69
+ return "pnpm";
70
+ }
71
+ if (globalLocations.some(location => location.includes("/yarn/global/"))) {
72
+ return "yarn";
73
+ }
74
+ if (globalLocations.some(location => location.includes("/.bun/install/global/"))) {
75
+ return "bun";
76
+ }
77
+ if (
78
+ globalLocations.some(location =>
79
+ location.includes("/lib/node_modules/") ||
80
+ location.includes("/appdata/roaming/npm/node_modules/")
81
+ )
82
+ ) {
83
+ return "npm";
84
+ }
85
+
86
+ for (const location of locations) {
87
+ for (let dir = path.dirname(location); ; dir = path.dirname(dir)) {
88
+ for (const [lockfile, manager] of LOCKFILES) {
89
+ if (existsSync(path.join(dir, lockfile))) {
90
+ return manager;
91
+ }
92
+ }
93
+ const parent = path.dirname(dir);
94
+ if (parent === dir) break;
95
+ }
96
+ }
97
+
98
+ return null;
99
+ }
100
+
101
+ /**
102
+ * Resolve the package-manager executable from the launcher's own installation
103
+ * prefix. Never consult PATH: it may now point at another NVM/Corepack prefix.
104
+ */
105
+ function findOwningPackageManager(
106
+ manager,
107
+ {
108
+ entrypoint = __filename,
109
+ invokedAs = process.argv[1],
110
+ platform = process.platform,
111
+ existsSync = fs.existsSync,
112
+ realpathSync = fs.realpathSync,
113
+ } = {}
114
+ ) {
115
+ if (!manager) return null;
116
+ const executable = platform === "win32" ? `${manager}.cmd` : manager;
117
+ const candidates = [];
118
+ const invokedSibling = invokedAs
119
+ ? path.join(path.dirname(path.resolve(invokedAs)), executable)
120
+ : null;
121
+
122
+ const logical = path.resolve(entrypoint);
123
+ let physical;
124
+ try {
125
+ physical = realpathSync(entrypoint);
126
+ } catch {
127
+ physical = logical;
128
+ }
129
+ for (const location of [...new Set([logical, physical])]) {
130
+ const normalized = location.replaceAll("\\", "/");
131
+ const lower = normalized.toLowerCase();
132
+ const addFromMarker = (marker, suffix) => {
133
+ const offset = lower.indexOf(marker);
134
+ if (offset !== -1) {
135
+ candidates.push(path.join(normalized.slice(0, offset), ...suffix));
136
+ }
137
+ };
138
+
139
+ if (manager === "npm") {
140
+ addFromMarker("/lib/node_modules/", ["bin", executable]);
141
+ addFromMarker("/node_modules/", [executable]);
142
+ } else if (manager === "pnpm") {
143
+ addFromMarker("/global/", [executable]);
144
+ } else if (manager === "bun") {
145
+ addFromMarker("/install/global/", ["bin", executable]);
146
+ }
147
+ }
148
+ if (invokedSibling) candidates.push(invokedSibling);
149
+
150
+ return candidates.find(candidate => existsSync(candidate)) || null;
151
+ }
152
+
18
153
  function resolveBinary() {
19
154
  const key = `${process.platform}-${process.arch}`;
20
155
  const pkg = PACKAGES[key];
@@ -36,21 +171,61 @@ function resolveBinary() {
36
171
  }
37
172
  }
38
173
 
39
- let binary;
40
- try {
41
- binary = resolveBinary();
42
- } catch (err) {
43
- console.error(err.message);
44
- process.exit(1);
174
+ function requireSupportedNode(version = process.versions.node) {
175
+ const major = Number.parseInt(version.split(".")[0], 10);
176
+ if (!Number.isInteger(major) || major < MIN_NODE_MAJOR) {
177
+ throw new Error(
178
+ `Introspection CLI requires Node.js ${MIN_NODE_MAJOR} or newer; ` +
179
+ `this command is running on Node.js ${version}.\n\n` +
180
+ `Upgrade the Node runtime that launches \`introspection\`, then reinstall:\n` +
181
+ ` npm install -g @introspection-ai/cli@latest`
182
+ );
183
+ }
45
184
  }
46
185
 
47
- const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
48
- if (result.error) {
49
- console.error(result.error.message);
50
- process.exit(1);
186
+ function main() {
187
+ let binary;
188
+ try {
189
+ requireSupportedNode();
190
+ binary = resolveBinary();
191
+ } catch (err) {
192
+ console.error(err.message);
193
+ process.exit(1);
194
+ }
195
+
196
+ const manager = detectPackageManager();
197
+ const managerPath = findOwningPackageManager(manager);
198
+ const env = manager && managerPath
199
+ ? {
200
+ ...process.env,
201
+ [PACKAGE_MANAGER_ENV]: manager,
202
+ [PACKAGE_MANAGER_PATH_ENV]: managerPath,
203
+ }
204
+ : process.env;
205
+ const result = spawnSync(binary, process.argv.slice(2), {
206
+ stdio: "inherit",
207
+ env,
208
+ });
209
+ if (result.error) {
210
+ console.error(result.error.message);
211
+ process.exit(1);
212
+ }
213
+ // Re-raise a terminating signal as our own exit; otherwise pass the code.
214
+ if (result.signal) {
215
+ process.kill(process.pid, result.signal);
216
+ }
217
+ process.exit(result.status ?? 0);
51
218
  }
52
- // Re-raise a terminating signal as our own exit; otherwise pass the code.
53
- if (result.signal) {
54
- process.kill(process.pid, result.signal);
219
+
220
+ if (require.main === module) {
221
+ main();
55
222
  }
56
- process.exit(result.status ?? 0);
223
+
224
+ module.exports = {
225
+ MIN_NODE_MAJOR,
226
+ PACKAGE_MANAGER_ENV,
227
+ PACKAGE_MANAGER_PATH_ENV,
228
+ detectPackageManager,
229
+ findOwningPackageManager,
230
+ requireSupportedNode,
231
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@introspection-ai/cli",
3
- "version": "0.18.1",
3
+ "version": "0.20.0",
4
4
  "description": "CLI for operating agents on the Introspection platform",
5
5
  "keywords": [
6
6
  "introspection",
@@ -18,18 +18,23 @@
18
18
  "bin": {
19
19
  "introspection": "bin/introspection.js"
20
20
  },
21
+ "scripts": {
22
+ "postinstall": "node scripts/postinstall.js",
23
+ "test": "node --test test/*.test.js"
24
+ },
21
25
  "files": [
22
26
  "bin/introspection.js",
27
+ "scripts/postinstall.js",
23
28
  "README.md"
24
29
  ],
25
30
  "engines": {
26
- "node": ">=24"
31
+ "node": ">=18"
27
32
  },
28
33
  "optionalDependencies": {
29
- "@introspection-ai/cli-darwin-arm64": "0.18.1",
30
- "@introspection-ai/cli-darwin-x64": "0.18.1",
31
- "@introspection-ai/cli-linux-x64": "0.18.1",
32
- "@introspection-ai/cli-linux-arm64": "0.18.1",
33
- "@introspection-ai/cli-win32-x64": "0.18.1"
34
+ "@introspection-ai/cli-darwin-arm64": "0.20.0",
35
+ "@introspection-ai/cli-darwin-x64": "0.20.0",
36
+ "@introspection-ai/cli-linux-x64": "0.20.0",
37
+ "@introspection-ai/cli-linux-arm64": "0.20.0",
38
+ "@introspection-ai/cli-win32-x64": "0.20.0"
34
39
  }
35
40
  }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // Installation hooks are advisory only: package managers may suppress or skip
5
+ // them, and installing this package does not authorize changing Pi or coding
6
+ // agent hosts. The idempotent setup command owns those machine-level changes.
7
+ // Human-only output follows the CLI's stderr + TTY contract.
8
+ if (process.stderr.isTTY) {
9
+ process.stderr.write(`
10
+ Introspection CLI installed. To install or align Pi, Recipes, and plugins, run:
11
+
12
+ introspection setup
13
+ `);
14
+ }