agentxchain 2.155.70 → 2.155.71

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.155.70",
3
+ "version": "2.155.71",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,386 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DOGFOOD-100 Claude credential smoke helper.
4
+ *
5
+ * This script is intentionally state-free: it does not run AgentXchain,
6
+ * mutate `.agentxchain`, or touch staging JSON. Its job is to prove whether
7
+ * the local Claude process can authenticate under a compatible Node runtime
8
+ * before anyone resumes the paused Tusq DOGFOOD-100 session.
9
+ */
10
+
11
+ import { spawn, spawnSync } from 'node:child_process';
12
+ import { accessSync, constants, existsSync, readFileSync } from 'node:fs';
13
+ import { delimiter, dirname, join, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ import {
17
+ getClaudeEnvAuthPresence,
18
+ hasClaudeAuthenticationFailureText,
19
+ hasClaudeNodeIncompatibilityText,
20
+ isClaudeCompatibleNodeVersion,
21
+ resolveClaudeCompatibleNodeBinary,
22
+ } from '../src/lib/claude-local-auth.js';
23
+
24
+ const DEFAULT_TIMEOUT_MS = 15_000;
25
+ const DEFAULT_PROMPT = 'Say only READY.';
26
+ const STDIO_SNIPPET_LIMIT = 1200;
27
+
28
+ const EXIT = {
29
+ success: 0,
30
+ usage_error: 64,
31
+ env_file_missing: 66,
32
+ anthropic_auth_failed: 3,
33
+ node_runtime_incompatible: 4,
34
+ timeout: 5,
35
+ spawn_error: 6,
36
+ exit_nonzero: 2,
37
+ };
38
+
39
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
40
+ const CLI_ROOT = resolve(SCRIPT_DIR, '..');
41
+ const REPO_ROOT = resolve(CLI_ROOT, '..');
42
+
43
+ function parseArgs(argv) {
44
+ const opts = {
45
+ credentialEnvFile: null,
46
+ cwd: process.cwd(),
47
+ claude: 'claude',
48
+ node: null,
49
+ timeoutMs: DEFAULT_TIMEOUT_MS,
50
+ prompt: DEFAULT_PROMPT,
51
+ json: false,
52
+ };
53
+
54
+ for (let i = 0; i < argv.length;) {
55
+ const arg = argv[i];
56
+ const next = () => {
57
+ if (i + 1 >= argv.length) {
58
+ throw new Error(`Missing value for ${arg}`);
59
+ }
60
+ i += 1;
61
+ return argv[i];
62
+ };
63
+ if (arg === '--credential-env-file') opts.credentialEnvFile = next();
64
+ else if (arg === '--env-file') {
65
+ throw new Error('Use --credential-env-file; --env-file collides with Node.js process flags.');
66
+ }
67
+ else if (arg === '--cwd') opts.cwd = next();
68
+ else if (arg === '--claude') opts.claude = next();
69
+ else if (arg === '--node') opts.node = next();
70
+ else if (arg === '--timeout-ms') opts.timeoutMs = Number.parseInt(next(), 10);
71
+ else if (arg === '--prompt') opts.prompt = next();
72
+ else if (arg === '--json') opts.json = true;
73
+ else if (arg === '-h' || arg === '--help') {
74
+ printHelp();
75
+ process.exit(0);
76
+ } else {
77
+ throw new Error(`Unknown argument: ${arg}`);
78
+ }
79
+ i += 1;
80
+ }
81
+
82
+ if (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs <= 0) {
83
+ throw new Error('--timeout-ms must be a positive integer');
84
+ }
85
+ return opts;
86
+ }
87
+
88
+ function printHelp() {
89
+ console.log(`Usage: node cli/scripts/dogfood-claude-smoke.mjs [options]
90
+
91
+ Options:
92
+ --credential-env-file <path>
93
+ Load dotenv-style credential env before running Claude.
94
+ --cwd <path> Working directory for Claude. Defaults to current dir.
95
+ --claude <path> Claude executable or name. Defaults to "claude".
96
+ --node <path> Compatible Node binary override.
97
+ --timeout-ms <n> Watchdog timeout. Defaults to ${DEFAULT_TIMEOUT_MS}.
98
+ --prompt <text> Non-secret prompt. Defaults to "${DEFAULT_PROMPT}".
99
+ --json Emit compact JSON only.
100
+ `);
101
+ }
102
+
103
+ function parseDotenv(raw) {
104
+ const env = {};
105
+ for (const line of String(raw || '').split(/\r?\n/)) {
106
+ const trimmed = line.trim();
107
+ if (!trimmed || trimmed.startsWith('#')) continue;
108
+ const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
109
+ if (!match) continue;
110
+ const [, key, rawValue] = match;
111
+ let value = rawValue.trim();
112
+ if (
113
+ (value.startsWith('"') && value.endsWith('"')) ||
114
+ (value.startsWith("'") && value.endsWith("'"))
115
+ ) {
116
+ value = value.slice(1, -1);
117
+ }
118
+ env[key] = value;
119
+ }
120
+ return env;
121
+ }
122
+
123
+ function loadEnvFile(env, envFile) {
124
+ if (!envFile) return env;
125
+ const resolved = resolve(envFile);
126
+ if (!existsSync(resolved)) {
127
+ return {
128
+ error: {
129
+ ok: false,
130
+ classification: 'env_file_missing',
131
+ message: `Env file not found: ${resolved}`,
132
+ },
133
+ };
134
+ }
135
+ return { ...env, ...parseDotenv(readFileSync(resolved, 'utf8')) };
136
+ }
137
+
138
+ function isExecutable(filePath) {
139
+ if (!filePath) return false;
140
+ try {
141
+ accessSync(filePath, constants.X_OK);
142
+ return true;
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ function resolveCommandPath(command, envPath) {
149
+ if (!command) return null;
150
+ if (command.includes('/')) {
151
+ const resolved = resolve(command);
152
+ return existsSync(resolved) ? resolved : null;
153
+ }
154
+ for (const dir of String(envPath || '').split(delimiter).filter(Boolean)) {
155
+ const candidate = join(dir, command);
156
+ if (existsSync(candidate)) return candidate;
157
+ }
158
+ return null;
159
+ }
160
+
161
+ function readNodeVersion(nodePath) {
162
+ if (!isExecutable(nodePath)) return null;
163
+ const result = spawnSync(nodePath, ['--version'], {
164
+ encoding: 'utf8',
165
+ stdio: ['ignore', 'pipe', 'ignore'],
166
+ timeout: 2000,
167
+ });
168
+ if (result.status !== 0) return null;
169
+ return String(result.stdout || '').trim();
170
+ }
171
+
172
+ function isNodeEntrypoint(filePath) {
173
+ try {
174
+ const firstLine = readFileSync(filePath, 'utf8').split(/\r?\n/, 1)[0] || '';
175
+ return /^#!.*(?:^|[\/\s])(?:env\s+)?node(?:\s|$)/.test(firstLine);
176
+ } catch {
177
+ return false;
178
+ }
179
+ }
180
+
181
+ function resolveNode(opts, env) {
182
+ const candidate = opts.node ? resolve(opts.node) : resolveClaudeCompatibleNodeBinary(env);
183
+ const version = readNodeVersion(candidate);
184
+ return {
185
+ command: candidate,
186
+ version,
187
+ compatible: isClaudeCompatibleNodeVersion(version),
188
+ source: opts.node ? 'argv' : 'agentxchain_resolution',
189
+ };
190
+ }
191
+
192
+ function buildSpawnSpec(opts, env) {
193
+ const claudeEntry = resolveCommandPath(opts.claude, env.PATH);
194
+ const node = resolveNode(opts, env);
195
+ const claude = {
196
+ configured: opts.claude,
197
+ resolved: claudeEntry,
198
+ is_node_entrypoint: claudeEntry ? isNodeEntrypoint(claudeEntry) : false,
199
+ spawn_wrapper: null,
200
+ };
201
+
202
+ if (!claudeEntry) {
203
+ return {
204
+ command: opts.claude,
205
+ args: ['--print'],
206
+ node,
207
+ claude,
208
+ preflightClassification: 'spawn_error',
209
+ preflightMessage: `Claude executable not found: ${opts.claude}`,
210
+ };
211
+ }
212
+
213
+ if (claude.is_node_entrypoint) {
214
+ if (!node.compatible || !node.command) {
215
+ return {
216
+ command: claudeEntry,
217
+ args: ['--print'],
218
+ node,
219
+ claude,
220
+ preflightClassification: 'node_runtime_incompatible',
221
+ preflightMessage: 'Claude is a Node entrypoint but no compatible Node binary is available.',
222
+ };
223
+ }
224
+ claude.spawn_wrapper = 'claude_compatible_node';
225
+ return {
226
+ command: node.command,
227
+ args: [claudeEntry, '--print'],
228
+ node,
229
+ claude,
230
+ preflightClassification: null,
231
+ preflightMessage: null,
232
+ };
233
+ }
234
+
235
+ return {
236
+ command: claudeEntry,
237
+ args: ['--print'],
238
+ node,
239
+ claude,
240
+ preflightClassification: null,
241
+ preflightMessage: null,
242
+ };
243
+ }
244
+
245
+ function snippet(text) {
246
+ const raw = String(text || '');
247
+ if (raw.length <= STDIO_SNIPPET_LIMIT) return raw;
248
+ return raw.slice(raw.length - STDIO_SNIPPET_LIMIT);
249
+ }
250
+
251
+ function classifyProcess({ code, signal, timedOut, stdout, stderr, spawnError }) {
252
+ const combined = `${stdout || ''}\n${stderr || ''}`;
253
+ if (spawnError) return 'spawn_error';
254
+ if (timedOut) return 'timeout';
255
+ if (hasClaudeNodeIncompatibilityText(combined)) return 'node_runtime_incompatible';
256
+ if (hasClaudeAuthenticationFailureText(combined)) return 'anthropic_auth_failed';
257
+ if (code === 0) return 'success';
258
+ return 'exit_nonzero';
259
+ }
260
+
261
+ function runClaude(spawnSpec, opts, env) {
262
+ return new Promise((resolveResult) => {
263
+ let child;
264
+ try {
265
+ child = spawn(spawnSpec.command, spawnSpec.args, {
266
+ cwd: resolve(opts.cwd),
267
+ env,
268
+ stdio: ['pipe', 'pipe', 'pipe'],
269
+ });
270
+ } catch (error) {
271
+ resolveResult({ spawnError: normalizeError(error), stdout: '', stderr: '' });
272
+ return;
273
+ }
274
+
275
+ let stdout = '';
276
+ let stderr = '';
277
+ let settled = false;
278
+ let timedOut = false;
279
+
280
+ const finish = (result) => {
281
+ if (settled) return;
282
+ settled = true;
283
+ clearTimeout(timer);
284
+ resolveResult({ ...result, timedOut, stdout, stderr });
285
+ };
286
+
287
+ const timer = setTimeout(() => {
288
+ timedOut = true;
289
+ try { child.kill('SIGTERM'); } catch {}
290
+ finish({ code: null, signal: 'SIGTERM' });
291
+ }, opts.timeoutMs);
292
+ if (typeof timer.unref === 'function') timer.unref();
293
+
294
+ child.stdout?.on('data', (chunk) => { stdout += chunk.toString('utf8'); });
295
+ child.stderr?.on('data', (chunk) => { stderr += chunk.toString('utf8'); });
296
+ child.on('error', (error) => finish({ spawnError: normalizeError(error), code: null, signal: null }));
297
+ child.on('exit', (code, signal) => finish({ code, signal }));
298
+
299
+ try {
300
+ child.stdin?.end(`${opts.prompt}\n`);
301
+ } catch {}
302
+ });
303
+ }
304
+
305
+ function normalizeError(error) {
306
+ return {
307
+ code: error?.code ?? null,
308
+ errno: error?.errno ?? null,
309
+ syscall: error?.syscall ?? null,
310
+ message: error?.message ?? String(error),
311
+ };
312
+ }
313
+
314
+ function buildPayload({ classification, message = null, opts, env, spawnSpec = null, processResult = null }) {
315
+ return {
316
+ ok: classification === 'success',
317
+ classification,
318
+ message,
319
+ cwd: resolve(opts.cwd),
320
+ auth_env_present: getClaudeEnvAuthPresence(env),
321
+ node: spawnSpec?.node ?? null,
322
+ claude: spawnSpec?.claude ?? { configured: opts.claude, resolved: null, is_node_entrypoint: null, spawn_wrapper: null },
323
+ exit_code: processResult?.code ?? null,
324
+ exit_signal: processResult?.signal ?? null,
325
+ stdout_snippet: snippet(processResult?.stdout),
326
+ stderr_snippet: snippet(processResult?.stderr),
327
+ spawn_error: processResult?.spawnError ?? null,
328
+ };
329
+ }
330
+
331
+ function emit(payload, jsonOnly) {
332
+ if (jsonOnly) {
333
+ console.log(JSON.stringify(payload));
334
+ return;
335
+ }
336
+ console.log(JSON.stringify(payload, null, 2));
337
+ }
338
+
339
+ async function main() {
340
+ let opts;
341
+ try {
342
+ opts = parseArgs(process.argv.slice(2));
343
+ } catch (error) {
344
+ const payload = {
345
+ ok: false,
346
+ classification: 'usage_error',
347
+ message: error?.message || String(error),
348
+ };
349
+ emit(payload, false);
350
+ process.exit(EXIT.usage_error);
351
+ }
352
+
353
+ const envResult = loadEnvFile(process.env, opts.credentialEnvFile);
354
+ if (envResult?.error) {
355
+ const payload = { ...envResult.error, auth_env_present: getClaudeEnvAuthPresence(process.env) };
356
+ emit(payload, opts.json);
357
+ process.exit(EXIT.env_file_missing);
358
+ }
359
+ const env = envResult;
360
+ const spawnSpec = buildSpawnSpec(opts, env);
361
+ if (spawnSpec.preflightClassification) {
362
+ const payload = buildPayload({
363
+ classification: spawnSpec.preflightClassification,
364
+ message: spawnSpec.preflightMessage,
365
+ opts,
366
+ env,
367
+ spawnSpec,
368
+ });
369
+ emit(payload, opts.json);
370
+ process.exit(EXIT[spawnSpec.preflightClassification] ?? EXIT.exit_nonzero);
371
+ }
372
+
373
+ const processResult = await runClaude(spawnSpec, opts, env);
374
+ const classification = classifyProcess(processResult);
375
+ const payload = buildPayload({ classification, opts, env, spawnSpec, processResult });
376
+ emit(payload, opts.json);
377
+ process.exit(EXIT[classification] ?? EXIT.exit_nonzero);
378
+ }
379
+
380
+ await main();
381
+
382
+ export {
383
+ buildSpawnSpec,
384
+ classifyProcess,
385
+ parseDotenv,
386
+ };