agentsmd-hierarchy 1.0.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.
@@ -0,0 +1,105 @@
1
+ import { input, select } from '@inquirer/prompts';
2
+
3
+ function canPrompt() {
4
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
5
+ }
6
+
7
+ export async function resolveOptionalScope({
8
+ allSelected,
9
+ allSelectionLabel,
10
+ normalizePath,
11
+ optionPath,
12
+ positionalPath,
13
+ promptMessage,
14
+ singleSelectionLabel,
15
+ }) {
16
+ const explicitValues = [
17
+ allSelected ? '__ALL__' : null,
18
+ optionPath,
19
+ positionalPath,
20
+ ].filter(Boolean);
21
+
22
+ if (explicitValues.length > 1) {
23
+ throw new Error('Pass only one of a positional path, --path, or --all.');
24
+ }
25
+
26
+ if (allSelected) {
27
+ return normalizePath('.');
28
+ }
29
+
30
+ if (optionPath) {
31
+ return normalizePath(optionPath);
32
+ }
33
+
34
+ if (positionalPath) {
35
+ return normalizePath(positionalPath);
36
+ }
37
+
38
+ if (!canPrompt()) {
39
+ return normalizePath('.');
40
+ }
41
+
42
+ const selectionMode = await select({
43
+ choices: [
44
+ {
45
+ name: allSelectionLabel,
46
+ value: 'all',
47
+ },
48
+ {
49
+ name: singleSelectionLabel,
50
+ value: 'single',
51
+ },
52
+ ],
53
+ message: promptMessage,
54
+ });
55
+
56
+ if (selectionMode === 'all') {
57
+ return normalizePath('.');
58
+ }
59
+
60
+ const selectedPath = await input({
61
+ message:
62
+ 'Enter a repo-relative directory or AGENTS.md file path (for example: packages/front or packages/front/AGENTS.md):',
63
+ validate(value) {
64
+ return value.trim() ? true : 'Enter a repo-relative path.';
65
+ },
66
+ });
67
+
68
+ return normalizePath(selectedPath.trim());
69
+ }
70
+
71
+ export async function resolveRequiredDirectory({
72
+ normalizeDirectory,
73
+ optionDirectory,
74
+ positionalDirectory,
75
+ }) {
76
+ const explicitValues = [optionDirectory, positionalDirectory].filter(Boolean);
77
+
78
+ if (explicitValues.length > 1) {
79
+ throw new Error('Pass either a positional directory or --path, not both.');
80
+ }
81
+
82
+ if (optionDirectory) {
83
+ return normalizeDirectory(optionDirectory);
84
+ }
85
+
86
+ if (positionalDirectory) {
87
+ return normalizeDirectory(positionalDirectory);
88
+ }
89
+
90
+ if (!canPrompt()) {
91
+ throw new Error(
92
+ 'Usage: node .codex/skills/agentsmd-hierarchy/scripts/scaffold-agents.mjs <repo-relative-directory>',
93
+ );
94
+ }
95
+
96
+ const selectedDirectory = await input({
97
+ message:
98
+ 'Enter the repo-relative directory to scaffold (for example: packages/front/src):',
99
+ validate(value) {
100
+ return value.trim() ? true : 'Enter a repo-relative directory.';
101
+ },
102
+ });
103
+
104
+ return normalizeDirectory(selectedDirectory.trim());
105
+ }
@@ -0,0 +1,21 @@
1
+ # agentsmd-hierarchy/scripts/lib
2
+
3
+ Shared implementation modules for the bundled CLI, including structured logging, install planning, command registration, and AGENTS validation.
4
+
5
+ ## Directories
6
+
7
+ - None.
8
+
9
+ ## Files
10
+
11
+ - `cli-logger.mjs`: Color-aware structured logger used by commands and tests.
12
+ - `errors.mjs`: Shared command error types and guards for consistent CLI failure handling.
13
+ - `install-core.mjs`: Install planning and execution logic for Codex, Claude, Cursor, and Codex plugin bundle exports.
14
+ - `program.mjs`: Commander program definition that wires subcommands, shared flags, and error handling together.
15
+ - `validate-agents-core.mjs`: Core inventory, sync, and validation engine for hierarchical `AGENTS.md` files.
16
+
17
+ ## Writing Rules
18
+
19
+ - Keep modules reusable from both CLI entrypoints and tests by accepting injected runtime state where practical.
20
+ - Preserve stable option handling, debug log structure, and user-facing error messages because tests assert them.
21
+ - Prefer adding shared behavior here rather than expanding the published bin wrappers.
@@ -0,0 +1,149 @@
1
+ const ANSI = {
2
+ blue: '\x1b[34m',
3
+ bold: '\x1b[1m',
4
+ cyan: '\x1b[36m',
5
+ dim: '\x1b[2m',
6
+ green: '\x1b[32m',
7
+ red: '\x1b[31m',
8
+ reset: '\x1b[0m',
9
+ yellow: '\x1b[33m',
10
+ };
11
+
12
+ function shouldUseColor(stdout, stderr) {
13
+ if ('NO_COLOR' in process.env) {
14
+ return false;
15
+ }
16
+
17
+ if (process.env.FORCE_COLOR === '0') {
18
+ return false;
19
+ }
20
+
21
+ if (
22
+ process.env.FORCE_COLOR &&
23
+ process.env.FORCE_COLOR !== '0' &&
24
+ process.env.FORCE_COLOR !== ''
25
+ ) {
26
+ return true;
27
+ }
28
+
29
+ return Boolean(stdout.isTTY || stderr.isTTY);
30
+ }
31
+
32
+ function applyAnsi(text, stdout, stderr, ...codes) {
33
+ if (!shouldUseColor(stdout, stderr) || codes.length === 0) {
34
+ return text;
35
+ }
36
+
37
+ return `${codes.join('')}${text}${ANSI.reset}`;
38
+ }
39
+
40
+ function getLevelStyle(level) {
41
+ switch (level) {
42
+ case 'DEBUG':
43
+ return [ANSI.dim];
44
+ case 'INFO':
45
+ return [ANSI.blue, ANSI.bold];
46
+ case 'STEP':
47
+ return [ANSI.cyan, ANSI.bold];
48
+ case 'DONE':
49
+ return [ANSI.green, ANSI.bold];
50
+ case 'WARN':
51
+ return [ANSI.yellow, ANSI.bold];
52
+ case 'FAIL':
53
+ return [ANSI.red, ANSI.bold];
54
+ case 'NOTE':
55
+ return [ANSI.dim];
56
+ default:
57
+ return [];
58
+ }
59
+ }
60
+
61
+ function sortDebugDetails(value) {
62
+ if (Array.isArray(value)) {
63
+ return value.map(sortDebugDetails);
64
+ }
65
+
66
+ if (value && typeof value === 'object') {
67
+ return Object.keys(value)
68
+ .sort()
69
+ .reduce((result, key) => {
70
+ result[key] = sortDebugDetails(value[key]);
71
+ return result;
72
+ }, {});
73
+ }
74
+
75
+ return value;
76
+ }
77
+
78
+ function formatDebugMessage(eventOrMessage, details) {
79
+ if (details === undefined) {
80
+ return String(eventOrMessage);
81
+ }
82
+
83
+ return `${eventOrMessage} ${JSON.stringify(sortDebugDetails(details))}`;
84
+ }
85
+
86
+ export function createLogger(name, options = {}) {
87
+ const {
88
+ debugEnabled = false,
89
+ stderr = process.stderr,
90
+ stdout = process.stdout,
91
+ } = options;
92
+ const prefix = applyAnsi(`[${name}]`, stdout, stderr, ANSI.cyan, ANSI.bold);
93
+
94
+ function write(stream, level, message) {
95
+ const styledLevel = applyAnsi(
96
+ level,
97
+ stdout,
98
+ stderr,
99
+ ...getLevelStyle(level),
100
+ );
101
+ stream.write(`${prefix} ${styledLevel} ${message}\n`);
102
+ }
103
+
104
+ return {
105
+ style(text, tone) {
106
+ switch (tone) {
107
+ case 'path':
108
+ return applyAnsi(text, stdout, stderr, ANSI.cyan);
109
+ case 'success':
110
+ return applyAnsi(text, stdout, stderr, ANSI.green);
111
+ case 'warning':
112
+ return applyAnsi(text, stdout, stderr, ANSI.yellow);
113
+ case 'error':
114
+ return applyAnsi(text, stdout, stderr, ANSI.red);
115
+ case 'muted':
116
+ return applyAnsi(text, stdout, stderr, ANSI.dim);
117
+ case 'strong':
118
+ return applyAnsi(text, stdout, stderr, ANSI.bold);
119
+ default:
120
+ return text;
121
+ }
122
+ },
123
+ info(message) {
124
+ write(stdout, 'INFO', message);
125
+ },
126
+ debug(eventOrMessage, details) {
127
+ if (!debugEnabled) {
128
+ return;
129
+ }
130
+
131
+ write(stdout, 'DEBUG', formatDebugMessage(eventOrMessage, details));
132
+ },
133
+ step(message) {
134
+ write(stdout, 'STEP', message);
135
+ },
136
+ success(message) {
137
+ write(stdout, 'DONE', message);
138
+ },
139
+ warn(message) {
140
+ write(stderr, 'WARN', message);
141
+ },
142
+ error(message) {
143
+ write(stderr, 'FAIL', message);
144
+ },
145
+ note(message) {
146
+ write(stdout, 'NOTE', message);
147
+ },
148
+ };
149
+ }
@@ -0,0 +1,12 @@
1
+ export class CommandError extends Error {
2
+ constructor(message, options = {}) {
3
+ super(message);
4
+ this.name = 'CommandError';
5
+ this.exitCode = options.exitCode ?? 1;
6
+ this.exposeHelp = options.exposeHelp ?? false;
7
+ }
8
+ }
9
+
10
+ export function isCommandError(error) {
11
+ return error instanceof CommandError;
12
+ }