@indy2kro/agentenv 0.2.2 → 0.3.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.
Files changed (92) hide show
  1. package/README.md +46 -39
  2. package/dist/adapters/cursor.d.ts +1 -1
  3. package/dist/adapters/cursor.d.ts.map +1 -1
  4. package/dist/adapters/cursor.js +5 -1
  5. package/dist/adapters/cursor.js.map +1 -1
  6. package/dist/adapters/gemini.d.ts +1 -1
  7. package/dist/adapters/gemini.js +1 -1
  8. package/dist/adapters/rtk-delegation.d.ts +7 -0
  9. package/dist/adapters/rtk-delegation.d.ts.map +1 -1
  10. package/dist/adapters/rtk-delegation.js +5 -3
  11. package/dist/adapters/rtk-delegation.js.map +1 -1
  12. package/dist/adapters/vibe.d.ts +1 -1
  13. package/dist/adapters/vibe.js +1 -1
  14. package/dist/adapters/windsurf.d.ts +1 -1
  15. package/dist/adapters/windsurf.js +1 -1
  16. package/dist/cli/exit.d.ts +19 -0
  17. package/dist/cli/exit.d.ts.map +1 -0
  18. package/dist/cli/exit.js +36 -0
  19. package/dist/cli/exit.js.map +1 -0
  20. package/dist/commands/apply.d.ts +2 -2
  21. package/dist/commands/apply.d.ts.map +1 -1
  22. package/dist/commands/apply.js +25 -1
  23. package/dist/commands/apply.js.map +1 -1
  24. package/dist/commands/completion.d.ts +30 -0
  25. package/dist/commands/completion.d.ts.map +1 -0
  26. package/dist/commands/completion.js +181 -0
  27. package/dist/commands/completion.js.map +1 -0
  28. package/dist/commands/doctor.d.ts +15 -1
  29. package/dist/commands/doctor.d.ts.map +1 -1
  30. package/dist/commands/doctor.js +79 -8
  31. package/dist/commands/doctor.js.map +1 -1
  32. package/dist/commands/setup.d.ts +4 -1
  33. package/dist/commands/setup.d.ts.map +1 -1
  34. package/dist/commands/setup.js +20 -63
  35. package/dist/commands/setup.js.map +1 -1
  36. package/dist/commands/status.d.ts +124 -1
  37. package/dist/commands/status.d.ts.map +1 -1
  38. package/dist/commands/status.js +440 -135
  39. package/dist/commands/status.js.map +1 -1
  40. package/dist/commands/uninstall.d.ts +54 -0
  41. package/dist/commands/uninstall.d.ts.map +1 -0
  42. package/dist/commands/uninstall.js +246 -0
  43. package/dist/commands/uninstall.js.map +1 -0
  44. package/dist/commands/update.d.ts.map +1 -1
  45. package/dist/commands/update.js +78 -34
  46. package/dist/commands/update.js.map +1 -1
  47. package/dist/commands/wizard.d.ts +8 -0
  48. package/dist/commands/wizard.d.ts.map +1 -0
  49. package/dist/commands/{configure.js → wizard.js} +81 -69
  50. package/dist/commands/wizard.js.map +1 -0
  51. package/dist/config/schema.d.ts +2 -3
  52. package/dist/config/schema.d.ts.map +1 -1
  53. package/dist/config/schema.js +2 -11
  54. package/dist/config/schema.js.map +1 -1
  55. package/dist/config/scopes.d.ts.map +1 -1
  56. package/dist/config/scopes.js +4 -0
  57. package/dist/config/scopes.js.map +1 -1
  58. package/dist/generate/agentsmd.d.ts.map +1 -1
  59. package/dist/generate/agentsmd.js +17 -1
  60. package/dist/generate/agentsmd.js.map +1 -1
  61. package/dist/index.js +35 -8
  62. package/dist/index.js.map +1 -1
  63. package/dist/shell/detector.js +1 -1
  64. package/dist/shell/detector.js.map +1 -1
  65. package/dist/toolchain/fallbacks.d.ts +1 -2
  66. package/dist/toolchain/fallbacks.d.ts.map +1 -1
  67. package/dist/toolchain/fallbacks.js +2 -22
  68. package/dist/toolchain/fallbacks.js.map +1 -1
  69. package/dist/toolchain/mise.d.ts +33 -2
  70. package/dist/toolchain/mise.d.ts.map +1 -1
  71. package/dist/toolchain/mise.js +65 -12
  72. package/dist/toolchain/mise.js.map +1 -1
  73. package/dist/toolchain/rtk.d.ts +1 -1
  74. package/dist/toolchain/rtk.d.ts.map +1 -1
  75. package/dist/toolchain/rtk.js +9 -5
  76. package/dist/toolchain/rtk.js.map +1 -1
  77. package/dist/ui/output.d.ts +29 -0
  78. package/dist/ui/output.d.ts.map +1 -0
  79. package/dist/ui/output.js +72 -0
  80. package/dist/ui/output.js.map +1 -0
  81. package/dist/ui/spinner.d.ts +5 -1
  82. package/dist/ui/spinner.d.ts.map +1 -1
  83. package/dist/ui/spinner.js +13 -2
  84. package/dist/ui/spinner.js.map +1 -1
  85. package/dist/wizard/build.d.ts +17 -1
  86. package/dist/wizard/build.d.ts.map +1 -1
  87. package/dist/wizard/build.js +46 -1
  88. package/dist/wizard/build.js.map +1 -1
  89. package/package.json +1 -1
  90. package/dist/commands/configure.d.ts +0 -14
  91. package/dist/commands/configure.d.ts.map +0 -1
  92. package/dist/commands/configure.js.map +0 -1
@@ -3,11 +3,13 @@ import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { isAgentInstalled, resolveBinary } from '../adapters/detect.js';
5
5
  import { BINARY_MAP, TOOL_DESCRIPTIONS, TOOL_KEYS, TOOL_TIERS, getEnabledAgents, loadConfig, resolveIntegrationScope, validateConfig, } from '../config/schema.js';
6
- import { findConfigPath, resolveScopeDir } from '../config/scopes.js';
6
+ import { findConfigPath, resolveScopeDir, userConfigDir } from '../config/scopes.js';
7
7
  import { checkAgentShellConfiguration, detectShell } from '../shell/detector.js';
8
8
  import { SuperpowersAdapter, ghAuthLine } from '../integrations/index.js';
9
9
  import { resolveGhAuthProbe } from '../toolchain/gh.js';
10
+ import { MISE_TOOL_NAMES, PINNED_TOOL_VERSIONS, getInstalledToolState, getMiseVersion, } from '../toolchain/mise.js';
10
11
  import { colorizeLine, theme } from '../ui/theme.js';
12
+ import { renderLogo, resolveResultLine, setQuietEnabled } from '../ui/output.js';
11
13
  const AGENT_CONFIG_FILES = {
12
14
  claude_code: {
13
15
  label: 'Claude Code',
@@ -30,10 +32,32 @@ const AGENT_CONFIG_FILES = {
30
32
  vibe: { label: 'Mistral Vibe', check: (baseDir) => path.join(baseDir, 'RTK.md') },
31
33
  };
32
34
  export { AGENT_CONFIG_FILES };
35
+ export function computeStatusExitCode(report) {
36
+ const drifted = report.config === null ||
37
+ report.validation.errors.length > 0 ||
38
+ report.tools.some((tool) => tool.drift) ||
39
+ report.agents.some((agent) => agent.drift) ||
40
+ report.generated.some((entry) => !entry.exists || !entry.managed) ||
41
+ report.integrations.some((integration) => integration.drift) ||
42
+ report.customTools.some((tool) => tool.drift);
43
+ return drifted ? 1 : 0;
44
+ }
45
+ const defaultSuperpowersStatus = async (baseDir, config) => {
46
+ const adapter = new SuperpowersAdapter();
47
+ return adapter.status(baseDir, config);
48
+ };
33
49
  function homeDir() {
34
50
  return process.env.HOME || process.env.USERPROFILE || '';
35
51
  }
36
- function containsManagedMarker(file, config) {
52
+ function norm(p) {
53
+ return path.resolve(p).replace(/\\/g, '/').toLowerCase();
54
+ }
55
+ function scopeFromConfigPath(configPath) {
56
+ return norm(configPath) === norm(path.join(userConfigDir(), 'agentenv.toml'))
57
+ ? 'user'
58
+ : 'project';
59
+ }
60
+ function hasManagedMarker(file, config) {
37
61
  try {
38
62
  const content = fs.readFileSync(file, 'utf-8');
39
63
  const marker = config.generate?.marker_start ?? '<!-- agentenv-managed-start -->';
@@ -43,172 +67,453 @@ function containsManagedMarker(file, config) {
43
67
  return false;
44
68
  }
45
69
  }
46
- function customToolStatus(tool) {
47
- if (tool.already_installed) {
48
- const candidate = pathForPlatform(tool);
49
- if (candidate && fs.existsSync(candidate))
50
- return `present (${candidate})`;
51
- return 'MISSING on disk';
52
- }
53
- return `mise source: ${tool.mise_source}${tool.version ? ` (${tool.version})` : ''}`;
54
- }
55
- function pathForPlatform(tool) {
70
+ function customToolPath(tool) {
56
71
  if (process.platform === 'win32')
57
72
  return tool.path_windows;
58
73
  if (process.platform === 'darwin')
59
74
  return tool.path_macos;
60
75
  return tool.path_linux;
61
76
  }
62
- export const statusCommand = new Command()
63
- .name('status')
64
- .description('Show current configuration and environment status')
65
- .action(async () => {
66
- console.log(theme.heading('\n=== agentenv status ===\n'));
67
- const configPath = findConfigPath();
77
+ function customToolStatus(tool, exists) {
78
+ if (tool.already_installed) {
79
+ const candidate = customToolPath(tool);
80
+ const pathExists = candidate !== undefined && exists(candidate);
81
+ return {
82
+ status: candidate && pathExists ? `present (${candidate})` : 'MISSING on disk',
83
+ drift: !pathExists,
84
+ };
85
+ }
86
+ return {
87
+ status: `mise source: ${tool.mise_source}${tool.version ? ` (${tool.version})` : ''}`,
88
+ drift: false,
89
+ };
90
+ }
91
+ function tier0Report(shell, enabledAgents, deps) {
92
+ const isWindows = shell.isWindows;
93
+ const shellHome = homeDir();
94
+ const agentChecks = isWindows === true
95
+ ? enabledAgents.map((agent) => ({
96
+ agent,
97
+ label: AGENT_CONFIG_FILES[agent]?.label ?? agent,
98
+ needsFix: (deps.checkAgentShellConfiguration ?? checkAgentShellConfiguration)(agent, shellHome).needsFix,
99
+ }))
100
+ : [];
101
+ return {
102
+ isWindows,
103
+ currentShell: shell.currentShell,
104
+ gitBashPath: shell.gitBashPath ?? null,
105
+ posixCompatible: shell.isPosixCompatible,
106
+ missingUtilities: shell.missingUtilities,
107
+ agentChecks,
108
+ needsFix: isWindows && (!shell.gitBashPath || agentChecks.some((check) => check.needsFix)),
109
+ };
110
+ }
111
+ /** Gather the full status report (structured, no printing). */
112
+ export async function gatherStatus(deps = {}) {
113
+ const getMiseVersionFn = deps.getMiseVersion ?? getMiseVersion;
114
+ const configPath = (deps.findConfigPath ?? findConfigPath)();
68
115
  if (!configPath) {
69
- console.log('No agentenv.toml found (project or user scope).');
70
- console.log(' Run `agentenv setup` or `agentenv configure` to create one.\n');
71
- process.exitCode = 1;
72
- return;
116
+ const report = {
117
+ command: 'status',
118
+ config: null,
119
+ scope: null,
120
+ baseDir: null,
121
+ exitCode: 0,
122
+ validation: { errors: [], warnings: [] },
123
+ agents: [],
124
+ tools: [],
125
+ customTools: [],
126
+ generated: [],
127
+ integrations: [],
128
+ rtkEnabled: false,
129
+ tier0: null,
130
+ ghAuth: null,
131
+ miseVersion: getMiseVersionFn(),
132
+ integrationDetails: [],
133
+ };
134
+ return { ...report, exitCode: computeStatusExitCode(report) };
73
135
  }
74
136
  let config;
75
137
  try {
76
- config = loadConfig();
138
+ config = (deps.loadConfig ?? loadConfig)(configPath);
77
139
  }
78
140
  catch (error) {
79
- console.error(theme.fail(error instanceof Error ? error.message : String(error)));
80
- process.exitCode = 1;
81
- return;
82
- }
83
- const baseDir = resolveScopeDir(config.scope);
84
- console.log(`Config: ${configPath}`);
85
- console.log(`Scope: ${config.scope ?? 'project'}`);
86
- console.log(`Base directory: ${baseDir}`);
87
- const report = validateConfig(config);
88
- const validationSummary = `Validation: ${report.errors.length === 0 ? 'valid' : `${report.errors.length} error(s)`}${report.warnings.length > 0 ? `, ${report.warnings.length} warning(s)` : ''}`;
89
- console.log(report.errors.length === 0 ? theme.ok(validationSummary) : theme.fail(validationSummary));
90
- for (const warning of report.warnings)
91
- console.log(theme.warn(` warning: ${warning}`));
92
- console.log(`RTK command rewriting: ${config.rtk?.enabled ? 'enabled' : 'disabled'}`);
141
+ const scope = scopeFromConfigPath(configPath);
142
+ const report = {
143
+ command: 'status',
144
+ config: configPath,
145
+ scope,
146
+ baseDir: (deps.resolveScopeDir ?? resolveScopeDir)(scope),
147
+ exitCode: 0,
148
+ validation: {
149
+ errors: [error instanceof Error ? error.message : String(error)],
150
+ warnings: [],
151
+ },
152
+ agents: [],
153
+ tools: [],
154
+ customTools: [],
155
+ generated: [],
156
+ integrations: [],
157
+ rtkEnabled: false,
158
+ tier0: null,
159
+ ghAuth: null,
160
+ miseVersion: getMiseVersionFn(),
161
+ integrationDetails: [],
162
+ };
163
+ return { ...report, exitCode: computeStatusExitCode(report) };
164
+ }
165
+ const resolveDir = deps.resolveScopeDir ?? resolveScopeDir;
166
+ const baseDir = resolveDir(config.scope);
167
+ const validation = (deps.validateConfig ?? validateConfig)(config);
168
+ const shell = (deps.detectShell ?? detectShell)();
169
+ const enabledAgents = (deps.getEnabledAgents ?? getEnabledAgents)(config);
170
+ const exists = deps.fileExists ?? fs.existsSync;
171
+ const rtkEnabled = config.rtk?.enabled === true;
172
+ const agents = enabledAgents.map((agent) => {
173
+ const meta = AGENT_CONFIG_FILES[agent];
174
+ const installed = (deps.isAgentInstalled ?? isAgentInstalled)(agent);
175
+ const configured = exists(meta.check(baseDir));
176
+ return {
177
+ key: agent,
178
+ label: meta.label,
179
+ installed,
180
+ configured,
181
+ drift: installed && !configured,
182
+ };
183
+ });
184
+ const enabledTools = TOOL_KEYS.filter((key) => config.tools?.[key] === true);
185
+ let ghAuth = null;
186
+ const installedState = (deps.getInstalledToolState ?? getInstalledToolState)();
187
+ const hasVersionData = Object.keys(installedState).length > 0;
188
+ const tools = enabledTools.map((key) => {
189
+ const binary = BINARY_MAP[key];
190
+ const found = (deps.resolveBinary ?? resolveBinary)(binary) !== null;
191
+ if (key === 'gh' && found) {
192
+ ghAuth = ghAuthLine((deps.resolveGhAuthProbe ?? resolveGhAuthProbe)()().status);
193
+ }
194
+ const miseName = MISE_TOOL_NAMES[key] || key;
195
+ const state = installedState[miseName];
196
+ const pinned = config.tool_versions?.[key] ?? PINNED_TOOL_VERSIONS[miseName] ?? null;
197
+ const installedVersions = state?.versions ?? [];
198
+ const installed = hasVersionData && installedVersions.length > 0
199
+ ? installedVersions[installedVersions.length - 1]
200
+ : null;
201
+ const versionDrift = pinned !== null &&
202
+ installed !== null &&
203
+ installedVersions.length > 0 &&
204
+ !installedVersions.includes(pinned);
205
+ return {
206
+ key,
207
+ binary,
208
+ tier: TOOL_TIERS[key] ?? 0,
209
+ found,
210
+ drift: !found || versionDrift,
211
+ pinned,
212
+ installed,
213
+ };
214
+ });
215
+ const customTools = (config.custom_tools ?? []).map((tool) => {
216
+ const { status, drift } = customToolStatus(tool, exists);
217
+ return { name: tool.name, status, drift };
218
+ });
219
+ const generatedEntries = [
220
+ ['mise.toml', path.join(baseDir, 'mise.toml'), false],
221
+ ['AGENTS.md', path.join(baseDir, 'AGENTS.md'), true],
222
+ ['CLAUDE.md', path.join(baseDir, 'CLAUDE.md'), true],
223
+ ];
224
+ const generated = generatedEntries.map(([label, file, markerCheck]) => {
225
+ const generatedExists = exists(file);
226
+ const managed = generatedExists &&
227
+ (markerCheck ? (deps.hasManagedMarker ?? hasManagedMarker)(file, config) : true);
228
+ return { label, exists: generatedExists, managed };
229
+ });
230
+ const integrations = [];
231
+ const integrationDetails = [];
232
+ const integrationKey = 'superpowers';
233
+ const superpowersConfig = config.integrations?.superpowers;
234
+ if (superpowersConfig?.enabled === true) {
235
+ // Resolve against the top-level scope before calling the adapter — the
236
+ // adapter only ever sees an IntegrationConfig, not the full
237
+ // AgentenvConfig, so it can't fall back to the top-level scope itself
238
+ // (see the matching resolution in apply.ts).
239
+ const scope = resolveIntegrationScope(config, superpowersConfig);
240
+ const result = await (deps.superpowersStatus ?? defaultSuperpowersStatus)(resolveDir(scope), {
241
+ ...superpowersConfig,
242
+ scope,
243
+ });
244
+ integrations.push({
245
+ key: integrationKey,
246
+ enabled: true,
247
+ source: result.source ?? null,
248
+ ref: result.ref ?? null,
249
+ scope: result.scope,
250
+ agents: superpowersConfig.agents ?? [],
251
+ states: result.agents.map(({ agent, state, detail }) => ({
252
+ agent,
253
+ state,
254
+ detail: detail ?? null,
255
+ })),
256
+ drift: result.agents.some((entry) => entry.state === 'missing' || entry.state === 'drifted'),
257
+ });
258
+ integrationDetails.push({
259
+ key: integrationKey,
260
+ hooksAllowed: superpowersConfig.allow_hooks ?? false,
261
+ externalRequestsAllowed: superpowersConfig.allow_external_requests ?? false,
262
+ warnings: result.warnings,
263
+ });
264
+ }
265
+ else {
266
+ integrations.push({
267
+ key: integrationKey,
268
+ enabled: false,
269
+ source: null,
270
+ ref: null,
271
+ scope: null,
272
+ agents: [],
273
+ states: [],
274
+ drift: false,
275
+ });
276
+ integrationDetails.push({
277
+ key: integrationKey,
278
+ hooksAllowed: null,
279
+ externalRequestsAllowed: null,
280
+ warnings: [],
281
+ });
282
+ }
283
+ const report = {
284
+ command: 'status',
285
+ config: configPath,
286
+ scope: config.scope ?? 'project',
287
+ baseDir,
288
+ exitCode: 0,
289
+ validation,
290
+ agents,
291
+ tools,
292
+ customTools,
293
+ generated,
294
+ integrations,
295
+ rtkEnabled,
296
+ tier0: tier0Report(shell, enabledAgents, deps),
297
+ ghAuth,
298
+ miseVersion: getMiseVersionFn(),
299
+ integrationDetails,
300
+ };
301
+ return { ...report, exitCode: computeStatusExitCode(report) };
302
+ }
303
+ function statusSummary(report) {
304
+ if (report.config === null) {
305
+ return {
306
+ severity: 'fail',
307
+ headline: 'No configuration found',
308
+ summary: 'run `agentenv setup` or `agentenv apply` to get started',
309
+ };
310
+ }
311
+ const issues = report.validation.errors.length +
312
+ report.tools.filter((tool) => tool.drift).length +
313
+ report.agents.filter((agent) => agent.drift).length +
314
+ report.customTools.filter((tool) => tool.drift).length +
315
+ report.generated.filter((entry) => !entry.exists || !entry.managed).length +
316
+ report.integrations.filter((integration) => integration.drift).length;
317
+ if (issues === 0) {
318
+ return { severity: 'ok', headline: 'Environment is clean' };
319
+ }
320
+ return {
321
+ severity: 'fail',
322
+ headline: `Status: ${issues} issue${issues === 1 ? '' : 's'} found`,
323
+ summary: 'run `agentenv apply` to fix, or `agentenv update` for new versions',
324
+ };
325
+ }
326
+ export function statusToJson(report) {
327
+ return {
328
+ command: report.command,
329
+ config: report.config,
330
+ scope: report.scope,
331
+ baseDir: report.baseDir,
332
+ exitCode: report.exitCode,
333
+ validation: report.validation,
334
+ agents: report.agents,
335
+ tools: report.tools,
336
+ customTools: report.customTools,
337
+ generated: report.generated,
338
+ integrations: report.integrations,
339
+ };
340
+ }
341
+ function displayName(key) {
342
+ return key.charAt(0).toUpperCase() + key.slice(1);
343
+ }
344
+ /** Collapsed one-line-per-area view for `status --short`. */
345
+ export function renderStatusShort(report) {
346
+ const chunks = [];
347
+ chunks.push(`Config: ${report.config ?? '(none)'}`);
348
+ if (report.config !== null) {
349
+ chunks.push(`Scope: ${report.scope ?? 'project'}`);
350
+ chunks.push(`Base directory: ${report.baseDir}`);
351
+ }
352
+ const toolDrift = report.tools.filter((tool) => tool.drift).length;
353
+ chunks.push(`Tools: ${report.tools.length} configured${toolDrift > 0 ? `, ${toolDrift} drifted` : ''}`);
354
+ const agentDrift = report.agents.filter((agent) => agent.drift).length;
355
+ chunks.push(`Agents: ${report.agents.length} enabled${agentDrift > 0 ? `, ${agentDrift} drifted` : ''}`);
356
+ if (report.customTools.length > 0) {
357
+ const customDrift = report.customTools.filter((tool) => tool.drift).length;
358
+ chunks.push(`Custom tools: ${report.customTools.length}${customDrift > 0 ? `, ${customDrift} drifted` : ''}`);
359
+ }
360
+ const generatedMissing = report.generated.filter((entry) => !entry.exists || !entry.managed).length;
361
+ chunks.push(`Generated files: ${report.generated.length}${generatedMissing > 0 ? `, ${generatedMissing} missing/unmanaged` : ''}`);
362
+ if (report.integrations.length > 0) {
363
+ const integrationDrift = report.integrations.filter((integration) => integration.drift).length;
364
+ chunks.push(`Integrations: ${report.integrations.length}${integrationDrift > 0 ? `, ${integrationDrift} drifted` : ''}`);
365
+ }
366
+ if (report.validation.errors.length > 0) {
367
+ chunks.push(`Config errors: ${report.validation.errors.length}`);
368
+ }
369
+ if (report.validation.warnings.length > 0) {
370
+ chunks.push(`Warnings: ${report.validation.warnings.length}`);
371
+ }
372
+ for (const warning of report.validation.warnings)
373
+ chunks.push(theme.warn(` warning: ${warning}`));
374
+ return `${chunks.join('\n')}\n`;
375
+ }
376
+ /** Render the human report body (banners are emitted by the command). */
377
+ export function renderStatus(report) {
378
+ if (report.config === null) {
379
+ return [
380
+ 'No agentenv.toml found (project or user scope).',
381
+ ' Run `agentenv setup` (alias: `configure`) to create one.',
382
+ '',
383
+ ].join('\n');
384
+ }
385
+ const chunks = [];
386
+ chunks.push(`Config: ${report.config}`);
387
+ chunks.push(`Scope: ${report.scope ?? 'project'}`);
388
+ chunks.push(`Base directory: ${report.baseDir}`);
389
+ chunks.push(`Mise: ${report.miseVersion}`);
390
+ const validationSummary = `Validation: ${report.validation.errors.length === 0 ? 'valid' : `${report.validation.errors.length} error(s)`}${report.validation.warnings.length > 0 ? `, ${report.validation.warnings.length} warning(s)` : ''}`;
391
+ chunks.push(report.validation.errors.length === 0
392
+ ? theme.ok(validationSummary)
393
+ : theme.fail(validationSummary));
394
+ for (const warning of report.validation.warnings)
395
+ chunks.push(theme.warn(` warning: ${warning}`));
396
+ chunks.push(`RTK command rewriting: ${report.rtkEnabled ? 'enabled' : 'disabled'}`);
93
397
  // Tier 0 shell status
94
- const shell = detectShell();
95
- console.log(theme.heading('\nTier 0 (shell):'));
96
- if (shell.isWindows) {
97
- // shell.isPosixCompatible reflects THIS interactive shell (PowerShell,
98
- // almost always not bash) — that's expected and isn't what `apply`
99
- // fixes. What apply actually manages is each enabled agent's own
100
- // config pointing at Git Bash, so that's what decides whether a fix
101
- // is actually needed.
102
- console.log(` Current shell: ${shell.currentShell}`);
103
- if (shell.gitBashPath)
104
- console.log(` Git Bash: ${shell.gitBashPath}`);
105
- else
106
- console.log(' Git Bash: NOT FOUND');
107
- const shellHome = process.env.HOME || process.env.USERPROFILE || '';
108
- const agentShellChecks = getEnabledAgents(config).map((agent) => ({
109
- agent,
110
- check: checkAgentShellConfiguration(agent, shellHome),
111
- }));
112
- if (agentShellChecks.length > 0) {
113
- console.log(' Agent shell overrides:');
114
- for (const { agent, check } of agentShellChecks) {
115
- const label = AGENT_CONFIG_FILES[agent]?.label ?? agent;
116
- console.log(colorizeLine(` ${check.needsFix ? '✗' : '✓'} ${label}`));
398
+ if (report.tier0) {
399
+ chunks.push(theme.heading('\nTier 0 (shell):'));
400
+ const tier0 = report.tier0;
401
+ if (tier0.isWindows) {
402
+ chunks.push(` Current shell: ${tier0.currentShell}`);
403
+ if (tier0.gitBashPath)
404
+ chunks.push(` Git Bash: ${tier0.gitBashPath}`);
405
+ else
406
+ chunks.push(' Git Bash: NOT FOUND');
407
+ if (tier0.agentChecks.length > 0) {
408
+ chunks.push(' Agent shell overrides:');
409
+ for (const check of tier0.agentChecks) {
410
+ chunks.push(colorizeLine(` ${check.needsFix ? '✗' : '✓'} ${check.label}`));
411
+ }
412
+ }
413
+ if (tier0.needsFix) {
414
+ chunks.push(theme.warn(' Fix: run `agentenv apply` (Tier 0 shell fix)'));
117
415
  }
118
416
  }
119
- if (!shell.gitBashPath || agentShellChecks.some(({ check }) => check.needsFix)) {
120
- console.log(theme.warn(' Fix: run `agentenv apply` (Tier 0 shell fix)'));
417
+ else {
418
+ chunks.push(` ${tier0.posixCompatible ? 'POSIX-compatible' : 'non-POSIX'} (not Windows; Tier 0 N/A)`);
419
+ if (tier0.missingUtilities.length > 0) {
420
+ chunks.push(` Missing utilities: ${tier0.missingUtilities.join(', ')}`);
421
+ }
121
422
  }
122
423
  }
123
- else {
124
- console.log(` ${shell.isPosixCompatible ? 'POSIX-compatible' : 'non-POSIX'} (not Windows; Tier 0 N/A)`);
125
- const missing = shell.missingUtilities;
126
- if (missing.length > 0)
127
- console.log(` Missing utilities: ${missing.join(', ')}`);
128
- }
129
424
  // Agents
130
- const enabledAgents = getEnabledAgents(config);
131
- console.log(theme.heading('\nAgents:'));
132
- if (enabledAgents.length === 0) {
133
- console.log(' (none enabled)');
425
+ chunks.push(theme.heading('\nAgents:'));
426
+ if (report.agents.length === 0) {
427
+ chunks.push(' (none enabled)');
134
428
  }
135
- for (const agent of enabledAgents) {
136
- const meta = AGENT_CONFIG_FILES[agent];
137
- const installed = isAgentInstalled(agent);
138
- const configured = fs.existsSync(meta.check(baseDir));
139
- const drift = installed && !configured;
140
- const line = ` ${meta.label.padEnd(16)} installed: ${installed ? 'yes' : 'no'} ` +
141
- `configured: ${configured ? 'yes' : 'no'}${drift ? ' <- drift: installed but not configured' : ''}`;
142
- console.log(drift ? theme.warn(line) : line);
429
+ for (const agent of report.agents) {
430
+ const line = ` ${agent.label.padEnd(16)} installed: ${agent.installed ? 'yes' : 'no'} ` +
431
+ `configured: ${agent.configured ? 'yes' : 'no'}${agent.drift ? ' <- drift: installed but not configured' : ''}`;
432
+ chunks.push(agent.drift ? theme.warn(line) : line);
143
433
  }
144
434
  // Tools
145
- const enabledTools = TOOL_KEYS.filter((key) => config.tools?.[key] === true);
146
- console.log(theme.heading('\nTools:'));
147
- for (const key of enabledTools) {
148
- const binary = BINARY_MAP[key];
149
- const found = resolveBinary(binary) !== null;
150
- const tier = TOOL_TIERS[key] ?? '?';
151
- const desc = TOOL_DESCRIPTIONS[key] ?? '';
152
- console.log(colorizeLine(` ${found ? '✓' : '✗'} ${binary.padEnd(12)} ${key} (Tier ${tier})${found ? '' : ' <- drift: enabled in config but not on PATH'} — ${desc}`));
153
- if (key === 'gh' && found) {
154
- console.log(colorizeLine(` ${ghAuthLine(resolveGhAuthProbe()().status)}`));
435
+ chunks.push(theme.heading('\nTools:'));
436
+ for (const tool of report.tools) {
437
+ const desc = TOOL_DESCRIPTIONS[tool.key] ?? '';
438
+ const versionTail = !tool.found
439
+ ? ''
440
+ : tool.pinned !== null
441
+ ? tool.installed !== null && tool.installed !== tool.pinned
442
+ ? ` <- drift: version ${tool.pinned} configured, ${tool.installed} installed`
443
+ : ` (${tool.pinned} installed)`
444
+ : tool.installed !== null
445
+ ? ` (${tool.installed})`
446
+ : '';
447
+ chunks.push(colorizeLine(` ${tool.found ? '✓' : '✗'} ${tool.binary.padEnd(12)} ${tool.key} (Tier ${tool.tier})${tool.found ? `${versionTail}` : ' <- drift: enabled in config but not on PATH'} — ${desc}`));
448
+ if (tool.key === 'gh' && report.ghAuth) {
449
+ chunks.push(colorizeLine(` ${report.ghAuth}`));
155
450
  }
156
451
  }
157
- if (enabledTools.length === 0)
158
- console.log(' (none enabled)');
452
+ if (report.tools.length === 0)
453
+ chunks.push(' (none enabled)');
159
454
  // Custom tools
160
- const customTools = config.custom_tools ?? [];
161
- console.log(theme.heading('\nCustom tools:'));
162
- if (customTools.length === 0) {
163
- console.log(' (none)');
455
+ chunks.push(theme.heading('\nCustom tools:'));
456
+ if (report.customTools.length === 0) {
457
+ chunks.push(' (none)');
164
458
  }
165
- for (const tool of customTools) {
166
- console.log(` ${tool.name}: ${customToolStatus(tool)}`);
459
+ for (const tool of report.customTools) {
460
+ chunks.push(` ${tool.name}: ${tool.status}`);
167
461
  }
168
462
  // Generated files
169
- console.log(theme.heading('\nGenerated files:'));
170
- const generated = [
171
- ['mise.toml', path.join(baseDir, 'mise.toml'), false],
172
- ['AGENTS.md', path.join(baseDir, 'AGENTS.md'), true],
173
- ['CLAUDE.md', path.join(baseDir, 'CLAUDE.md'), true],
174
- ];
175
- for (const [label, file, markerCheck] of generated) {
176
- const exists = fs.existsSync(file);
177
- if (!exists) {
178
- console.log(theme.fail(` ✗ ${label} (missing)`));
463
+ chunks.push(theme.heading('\nGenerated files:'));
464
+ for (const entry of report.generated) {
465
+ if (!entry.exists) {
466
+ chunks.push(theme.fail(` ✗ ${entry.label} (missing)`));
179
467
  continue;
180
468
  }
181
- const managed = markerCheck ? containsManagedMarker(file, config) : true;
182
- console.log(colorizeLine(` ✓ ${label}${managed ? '' : ' (managed marker block missing)'}`));
469
+ chunks.push(colorizeLine(` ✓ ${entry.label}${entry.managed ? '' : ' (managed marker block missing)'}`));
183
470
  }
184
471
  // Integrations
185
- console.log(theme.heading('\nIntegrations:'));
186
- const superpowersConfig = config.integrations?.superpowers;
187
- console.log(' Superpowers');
188
- if (!superpowersConfig || superpowersConfig.enabled !== true) {
189
- console.log(' enabled: no');
190
- }
191
- else {
192
- // Resolve against the top-level scope before calling the adapter — the
193
- // adapter only ever sees an IntegrationConfig, not the full
194
- // AgentenvConfig, so it can't fall back to the top-level scope itself
195
- // (see the matching resolution in apply.ts).
196
- const scope = resolveIntegrationScope(config, superpowersConfig);
197
- const adapter = new SuperpowersAdapter();
198
- const result = await adapter.status(resolveScopeDir(scope), { ...superpowersConfig, scope });
199
- console.log(' enabled: yes');
200
- console.log(` source: ${result.source ?? '(unset)'}`);
201
- console.log(` ref: ${result.ref ?? '(unset)'}`);
202
- console.log(` scope: ${result.scope}`);
203
- for (const agentState of result.agents) {
472
+ chunks.push(theme.heading('\nIntegrations:'));
473
+ for (const integration of report.integrations) {
474
+ chunks.push(` ${displayName(integration.key)}`);
475
+ if (!integration.enabled) {
476
+ chunks.push(' enabled: no');
477
+ continue;
478
+ }
479
+ chunks.push(' enabled: yes');
480
+ chunks.push(` source: ${integration.source ?? '(unset)'}`);
481
+ chunks.push(` ref: ${integration.ref ?? '(unset)'}`);
482
+ chunks.push(` scope: ${integration.scope}`);
483
+ for (const agentState of integration.states) {
204
484
  const label = AGENT_CONFIG_FILES[agentState.agent]?.label ?? agentState.agent;
205
- console.log(` ${label.padEnd(16)} ${agentState.state}${agentState.detail ? ` (${agentState.detail})` : ''}`);
485
+ chunks.push(` ${label.padEnd(16)} ${agentState.state}${agentState.detail ? ` (${agentState.detail})` : ''}`);
486
+ }
487
+ const details = report.integrationDetails.find((detail) => detail.key === integration.key);
488
+ chunks.push(` hooks allowed: ${details?.hooksAllowed ? 'yes' : 'no'}`);
489
+ chunks.push(` external requests allowed: ${details?.externalRequestsAllowed ? 'yes' : 'no'}`);
490
+ for (const warning of details?.warnings ?? []) {
491
+ chunks.push(theme.warn(` warning: ${warning}`));
206
492
  }
207
- console.log(` hooks allowed: ${superpowersConfig.allow_hooks ? 'yes' : 'no'}`);
208
- console.log(` external requests allowed: ${superpowersConfig.allow_external_requests ? 'yes' : 'no'}`);
209
- for (const warning of result.warnings)
210
- console.log(theme.warn(` warning: ${warning}`));
211
493
  }
212
- console.log(theme.heading('\n=== Status complete ===\n'));
494
+ return chunks.join('\n') + '\n';
495
+ }
496
+ export const statusCommand = new Command()
497
+ .name('status')
498
+ .description('Show current configuration and environment status')
499
+ .option('--json', 'emit a machine-readable JSON document on stdout')
500
+ .option('--short', 'collapsed one-line-per-area report (no per-item detail)')
501
+ .action(async (options) => {
502
+ const json = options.json === true;
503
+ const short = options.short === true;
504
+ if (json)
505
+ setQuietEnabled(true);
506
+ renderLogo();
507
+ const report = await gatherStatus();
508
+ if (json) {
509
+ console.log(JSON.stringify(statusToJson(report), null, 2));
510
+ }
511
+ else {
512
+ process.stdout.write(short ? renderStatusShort(report) : renderStatus(report));
513
+ const summary = statusSummary(report);
514
+ console.log(resolveResultLine(summary));
515
+ }
516
+ if (report.exitCode === 1)
517
+ process.exitCode = 1;
213
518
  });
214
519
  //# sourceMappingURL=status.js.map