agentera 0.0.0 → 0.0.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.
package/README.md CHANGED
@@ -12,6 +12,29 @@ npx agentera prime
12
12
  npm install -g agentera
13
13
  ```
14
14
 
15
+ ## v3 hint
16
+
17
+ While the Python CLI remains on `@latest`, every 0.x shim invocation emits a
18
+ three-line deprecation hint to **stderr** telling the user the v3 TypeScript
19
+ CLI is ready on the `@next` tag:
20
+
21
+ ```
22
+ agentera 2.x (Python) is in maintenance; the v3 TypeScript CLI is ready on the @next tag:
23
+ npx -y agentera@next prime
24
+ Set AGENTERA_NO_V3_HINT=1 to suppress this message.
25
+ ```
26
+
27
+ The hint never appears on `--version`/`-V` and never touches stdout, so
28
+ `--json` and pipe consumers stay clean. `--help`/`-h` also lists the `@next`
29
+ pointer, and the install-help failure path (no backend available) leads with
30
+ the `@next` line.
31
+
32
+ Suppress the hint for non-interactive CI or scripted invocations:
33
+
34
+ ```bash
35
+ AGENTERA_NO_V3_HINT=1 npx agentera prime
36
+ ```
37
+
15
38
  ## Delegation order
16
39
 
17
40
  When you run `agentera`, the shim forwards to the existing Python CLI using the first match:
@@ -41,8 +64,8 @@ npx agentera@0.0.0 --version
41
64
  ## Development
42
65
 
43
66
  ```bash
44
- node packages/cli/bin/agentera.mjs --version
45
- node packages/cli/bin/agentera.mjs --help
67
+ node packages/cli/shim/bin/agentera.mjs --version
68
+ node packages/cli/shim/bin/agentera.mjs --help
46
69
  ```
47
70
 
48
71
  Suite version pin lives in `package.json` under `agentera.suiteVersion` / `agentera.gitRef`; npm `version` stays on the `0.0.x` line until 3.0.
package/bin/agentera.mjs CHANGED
@@ -35,6 +35,9 @@ if (userArgs[0] === "--help" || userArgs[0] === "-h") {
35
35
  console.log(" 2. scripts/agentera in a parent repo (uv run)");
36
36
  console.log(" 3. uvx --from git+https://github.com/jgabor/agentera@<tag> agentera");
37
37
  console.log("");
38
+ console.log("v3 (TypeScript) is available now on the @next tag:");
39
+ console.log(" npx -y agentera@next prime");
40
+ console.log("");
38
41
  console.log("https://github.com/jgabor/agentera");
39
42
  process.exit(0);
40
43
  }
package/lib/exec.mjs CHANGED
@@ -1,36 +1,68 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import path from "node:path";
3
2
 
4
3
  import { resolveBackend } from "./resolve.mjs";
5
4
 
5
+ const V3_NEXT_COMMAND = "npx -y agentera@next prime";
6
+
7
+ /**
8
+ * Print the v3 deprecation hint to stderr unless the user opted out via
9
+ * AGENTERA_NO_V3_HINT=1. Called once per dispatch (after the --version
10
+ * early-return) so every v2 invocation surfaces the @next pointer while the
11
+ * Python CLI remains on @latest.
12
+ *
13
+ * @param {{ logStderr?: (message: string) => void }} [options]
14
+ * @returns {void}
15
+ */
16
+ export function printV3Hint(options = {}) {
17
+ if (process.env.AGENTERA_NO_V3_HINT === "1") {
18
+ return;
19
+ }
20
+ const log = options.logStderr ?? ((msg) => console.error(msg));
21
+ log("agentera 2.x (Python) is in maintenance; the v3 TypeScript CLI is ready on the @next tag:");
22
+ log(` ${V3_NEXT_COMMAND}`);
23
+ log("Set AGENTERA_NO_V3_HINT=1 to suppress this message.");
24
+ }
25
+
26
+ /**
27
+ * @typedef {object} RunBackendResult
28
+ * @property {number} exitCode
29
+ * @property {boolean} fallthrough true when the backend is unusable and dispatch should try the next strategy
30
+ */
31
+
6
32
  /**
7
33
  * @param {import('./resolve.mjs').ResolveResult} backend
8
34
  * @param {string[]} args
9
- * @param {{ gitRef?: string; gitRepo?: string }} [meta]
10
- * @returns {number}
35
+ * @param {{ gitRef?: string; gitRepo?: string; cwd?: string }} [meta]
36
+ * @returns {RunBackendResult}
11
37
  */
12
38
  export function runBackend(backend, args, meta = {}) {
13
39
  if (backend.kind === "app-home" && backend.scriptPath) {
14
- const appRoot = path.dirname(path.dirname(backend.scriptPath));
15
- return spawnChecked("uv", ["run", backend.scriptPath, ...args], {
16
- cwd: appRoot,
40
+ const exitCode = spawnChecked("uv", ["run", backend.scriptPath, ...args], {
41
+ cwd: meta.cwd,
17
42
  });
43
+ return { exitCode, fallthrough: exitCode !== 0 };
18
44
  }
19
45
 
20
46
  if (backend.kind === "repo" && backend.repoRoot) {
21
- return spawnChecked("uv", ["run", "scripts/agentera", ...args], {
22
- cwd: backend.repoRoot,
23
- });
47
+ return {
48
+ exitCode: spawnChecked("uv", ["run", "scripts/agentera", ...args], {
49
+ cwd: backend.repoRoot,
50
+ }),
51
+ fallthrough: false,
52
+ };
24
53
  }
25
54
 
26
55
  if (backend.kind === "uvx") {
27
- const gitRef = backend.gitRef ?? meta.gitRef ?? "v2.7.6";
56
+ const gitRef = backend.gitRef ?? meta.gitRef ?? "v2.7.7";
28
57
  const gitRepo = backend.gitRepo ?? meta.gitRepo ?? "https://github.com/jgabor/agentera";
29
58
  const from = `git+${gitRepo}@${gitRef}`;
30
- return spawnChecked("uvx", ["--from", from, "agentera", ...args], {});
59
+ return {
60
+ exitCode: spawnChecked("uvx", ["--from", from, "agentera", ...args], {}),
61
+ fallthrough: false,
62
+ };
31
63
  }
32
64
 
33
- return 1;
65
+ return { exitCode: 1, fallthrough: false };
34
66
  }
35
67
 
36
68
  /**
@@ -63,6 +95,7 @@ function spawnChecked(command, args, options) {
63
95
  * @param {string} [options.gitRef]
64
96
  * @param {string} [options.gitRepo]
65
97
  * @param {(message: string) => void} [options.printInstallHelp]
98
+ * @param {(message: string) => void} [options.logStderr]
66
99
  * @returns {number}
67
100
  */
68
101
  export function dispatch(argv, options = {}) {
@@ -71,13 +104,37 @@ export function dispatch(argv, options = {}) {
71
104
  return 0;
72
105
  }
73
106
 
74
- const backend = resolveBackend({
107
+ printV3Hint({ logStderr: options.logStderr });
108
+
109
+ let backend = resolveBackend({
75
110
  cwd: options.cwd,
76
111
  env: options.env,
77
112
  gitRef: options.gitRef,
78
113
  gitRepo: options.gitRepo,
114
+ logStderr: options.logStderr,
79
115
  });
80
116
 
117
+ if (backend.kind === "app-home") {
118
+ const result = runBackend(backend, userArgs, {
119
+ gitRef: options.gitRef,
120
+ gitRepo: options.gitRepo,
121
+ cwd: options.cwd,
122
+ });
123
+ if (!result.fallthrough) {
124
+ return result.exitCode;
125
+ }
126
+ console.error(
127
+ `agentera: app-home backend crashed (exit ${result.exitCode}); falling through to next resolution strategy`,
128
+ );
129
+ backend = resolveBackend({
130
+ cwd: options.cwd,
131
+ env: options.env,
132
+ gitRef: options.gitRef,
133
+ gitRepo: options.gitRepo,
134
+ excludeAppHome: true,
135
+ });
136
+ }
137
+
81
138
  if (backend.kind === "none") {
82
139
  const print = options.printInstallHelp ?? printInstallHelp;
83
140
  print(backend.reason ?? "no backend available");
@@ -87,7 +144,8 @@ export function dispatch(argv, options = {}) {
87
144
  return runBackend(backend, userArgs, {
88
145
  gitRef: options.gitRef,
89
146
  gitRepo: options.gitRepo,
90
- });
147
+ cwd: options.cwd,
148
+ }).exitCode;
91
149
  }
92
150
 
93
151
  /**
@@ -97,6 +155,11 @@ export function printInstallHelp(reason) {
97
155
  const lines = [
98
156
  "agentera: npm CLI shim (0.x) — native TypeScript CLI ships in Agentera 3.0.",
99
157
  reason ? `agentera: ${reason}` : "",
158
+ ];
159
+ if (process.env.AGENTERA_NO_V3_HINT !== "1") {
160
+ lines.push("", "The v3 TypeScript CLI is ready now on the @next tag:", ` ${V3_NEXT_COMMAND}`);
161
+ }
162
+ lines.push(
100
163
  "",
101
164
  "Install Agentera for your runtime:",
102
165
  " npx skills add jgabor/agentera -g -a claude-code --skill agentera -y",
@@ -109,8 +172,8 @@ export function printInstallHelp(reason) {
109
172
  " uv run scripts/agentera prime",
110
173
  "",
111
174
  "https://github.com/jgabor/agentera#install",
112
- ].filter(Boolean);
113
- for (const line of lines) {
175
+ );
176
+ for (const line of lines.filter(Boolean)) {
114
177
  console.error(line);
115
178
  }
116
179
  }
package/lib/resolve.mjs CHANGED
@@ -5,6 +5,9 @@ const DEFAULT_GIT_REPO = "https://github.com/jgabor/agentera";
5
5
  const APP_SCRIPT_REL = path.join("app", "scripts", "agentera");
6
6
  const REPO_SCRIPT_REL = path.join("scripts", "agentera");
7
7
 
8
+ const HEAD_READ_BYTES = 2048;
9
+ const APP_HOME_MISMATCH_PREFIX = "agentera: app-home backend unavailable: shebang/content mismatch";
10
+
8
11
  /**
9
12
  * @typedef {'app-home' | 'repo' | 'uvx' | 'none'} BackendKind
10
13
  */
@@ -19,6 +22,16 @@ const REPO_SCRIPT_REL = path.join("scripts", "agentera");
19
22
  * @property {string} [reason]
20
23
  */
21
24
 
25
+ /**
26
+ * @typedef {object} ResolveOptions
27
+ * @property {string} [cwd]
28
+ * @property {NodeJS.ProcessEnv} [env]
29
+ * @property {string} [gitRef]
30
+ * @property {string} [gitRepo]
31
+ * @property {boolean} [excludeAppHome]
32
+ * @property {(message: string) => void} [logStderr]
33
+ */
34
+
22
35
  /**
23
36
  * @param {string | undefined} command
24
37
  * @returns {boolean}
@@ -56,6 +69,110 @@ export function isRunnableFile(filePath) {
56
69
  }
57
70
  }
58
71
 
72
+ /**
73
+ * @param {string} filePath
74
+ * @returns {string | null}
75
+ */
76
+ function readHead(filePath) {
77
+ let fd;
78
+ try {
79
+ fd = fs.openSync(filePath, "r");
80
+ const buf = Buffer.alloc(HEAD_READ_BYTES);
81
+ const bytes = fs.readSync(fd, buf, 0, HEAD_READ_BYTES, 0);
82
+ return buf.slice(0, bytes).toString("utf8");
83
+ } catch {
84
+ return null;
85
+ } finally {
86
+ if (fd !== undefined) {
87
+ try {
88
+ fs.closeSync(fd);
89
+ } catch {
90
+ // ignore
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * @param {string} shebang
98
+ * @returns {'js' | 'python' | 'unknown'}
99
+ */
100
+ function shebangRuntime(shebang) {
101
+ if (/\bnode\b/.test(shebang)) {
102
+ return "js";
103
+ }
104
+ if (/\bpython\d*\b/.test(shebang) || /\buv\b[^\n]*\brun\b/.test(shebang)) {
105
+ return "python";
106
+ }
107
+ return "unknown";
108
+ }
109
+
110
+ /**
111
+ * @param {string} body
112
+ * @returns {'js' | 'python' | 'unknown'}
113
+ */
114
+ function contentLanguage(body) {
115
+ if (body.includes("# /// script")) {
116
+ return "python";
117
+ }
118
+ if (/\brequire\s*\(/.test(body) || /\bmodule\.exports\b/.test(body)) {
119
+ return "js";
120
+ }
121
+ if (/^\s*import\s+[^'"\n]+\s+from\s+['"]/m.test(body)) {
122
+ return "js";
123
+ }
124
+ if (/^\s*import\s+['"]/m.test(body)) {
125
+ return "js";
126
+ }
127
+ const lines = body.split("\n").slice(0, 30);
128
+ for (const raw of lines) {
129
+ const t = raw.trimStart();
130
+ if (
131
+ /^import\s+\w+\s*$/.test(t) ||
132
+ t.startsWith("from ") ||
133
+ t.startsWith("def ") ||
134
+ t.startsWith("class ")
135
+ ) {
136
+ return "python";
137
+ }
138
+ if (
139
+ t.startsWith("const ") ||
140
+ t.startsWith("let ") ||
141
+ t.startsWith("var ") ||
142
+ t.startsWith("export ")
143
+ ) {
144
+ return "js";
145
+ }
146
+ }
147
+ return "unknown";
148
+ }
149
+
150
+ /**
151
+ * @param {string} filePath
152
+ * @returns {string | null}
153
+ */
154
+ export function detectShebangContentMismatch(filePath) {
155
+ const head = readHead(filePath);
156
+ if (head === null) {
157
+ return null;
158
+ }
159
+ const nl = head.indexOf("\n");
160
+ const shebang = (nl >= 0 ? head.slice(0, nl) : head).trim();
161
+ if (!shebang.startsWith("#!")) {
162
+ return null;
163
+ }
164
+ const body = nl >= 0 ? head.slice(nl + 1) : "";
165
+ const expected = shebangRuntime(shebang);
166
+ const actual = contentLanguage(body);
167
+ if (expected === "unknown" || actual === "unknown") {
168
+ return null;
169
+ }
170
+ if (expected !== actual) {
171
+ return `shebang=${expected} content=${actual}`;
172
+ }
173
+ return null;
174
+ }
175
+
59
176
  /**
60
177
  * @param {string} startDir
61
178
  * @returns {string | null}
@@ -77,33 +194,43 @@ export function findRepoRoot(startDir) {
77
194
 
78
195
  /**
79
196
  * @param {string | undefined} agenteraHome
197
+ * @param {{ logStderr?: (message: string) => void }} [options]
80
198
  * @returns {string | null}
81
199
  */
82
- export function findAppHomeScript(agenteraHome) {
200
+ export function findAppHomeScript(agenteraHome, options = {}) {
83
201
  if (!agenteraHome) {
84
202
  return null;
85
203
  }
86
204
  const scriptPath = path.join(path.resolve(agenteraHome), APP_SCRIPT_REL);
87
- return isRunnableFile(scriptPath) ? scriptPath : null;
205
+ if (!isRunnableFile(scriptPath)) {
206
+ return null;
207
+ }
208
+ const mismatch = detectShebangContentMismatch(scriptPath);
209
+ if (mismatch) {
210
+ const log = options.logStderr ?? ((msg) => console.error(msg));
211
+ log(`${APP_HOME_MISMATCH_PREFIX} (${mismatch}) at ${scriptPath}`);
212
+ return null;
213
+ }
214
+ return scriptPath;
88
215
  }
89
216
 
90
217
  /**
91
- * @param {object} options
92
- * @param {string} [options.cwd]
93
- * @param {NodeJS.ProcessEnv} [options.env]
94
- * @param {string} [options.gitRef]
95
- * @param {string} [options.gitRepo]
218
+ * @param {ResolveOptions} [options]
96
219
  * @returns {ResolveResult}
97
220
  */
98
221
  export function resolveBackend({
99
222
  cwd = process.cwd(),
100
223
  env = process.env,
101
- gitRef = "v2.7.6",
224
+ gitRef = "v2.7.7",
102
225
  gitRepo = DEFAULT_GIT_REPO,
226
+ excludeAppHome = false,
227
+ logStderr,
103
228
  } = {}) {
104
- const scriptPath = findAppHomeScript(env.AGENTERA_HOME);
105
- if (scriptPath) {
106
- return { kind: "app-home", scriptPath };
229
+ if (!excludeAppHome) {
230
+ const scriptPath = findAppHomeScript(env.AGENTERA_HOME, { logStderr });
231
+ if (scriptPath) {
232
+ return { kind: "app-home", scriptPath };
233
+ }
107
234
  }
108
235
 
109
236
  const repoRoot = findRepoRoot(cwd);
package/package.json CHANGED
@@ -1,24 +1,20 @@
1
1
  {
2
2
  "name": "agentera",
3
- "version": "0.0.0",
3
+ "version": "0.0.2",
4
4
  "description": "npm shim for Agentera — delegates to the Python CLI until the 3.0 TypeScript release",
5
- "type": "module",
6
- "license": "Apache-2.0",
7
- "author": "Jonathan Gabor",
8
- "homepage": "https://github.com/jgabor/agentera",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/jgabor/agentera.git",
12
- "directory": "packages/cli"
13
- },
14
5
  "keywords": [
15
6
  "agentera",
16
7
  "agents",
17
8
  "cli",
18
9
  "orchestration"
19
10
  ],
20
- "engines": {
21
- "node": ">=22"
11
+ "homepage": "https://github.com/jgabor/agentera",
12
+ "license": "Apache-2.0",
13
+ "author": "Jonathan Gabor",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/jgabor/agentera.git",
17
+ "directory": "packages/cli"
22
18
  },
23
19
  "bin": {
24
20
  "agentera": "./bin/agentera.mjs"
@@ -29,8 +25,16 @@
29
25
  "README.md",
30
26
  "LICENSE"
31
27
  ],
28
+ "type": "module",
29
+ "scripts": {
30
+ "test": "pnpm -C .. test test/shim/ && node --test test/*.test.mjs",
31
+ "publish:stable": "node ./scripts/publish-stable.mjs"
32
+ },
33
+ "engines": {
34
+ "node": ">=22"
35
+ },
32
36
  "agentera": {
33
- "suiteVersion": "2.7.6",
34
- "gitRef": "v2.7.6"
37
+ "suiteVersion": "2.7.7",
38
+ "gitRef": "ce4a7054a8438b8b0aac013bac4b86ba1a9de3dd"
35
39
  }
36
40
  }