reins-method 0.1.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,231 @@
1
+ #!/usr/bin/env node
2
+
3
+ // REINS Method installer — `npx reins-method install`
4
+ //
5
+ // Clones/updates ~/.reins, runs an interactive setup wizard (arrow-key
6
+ // selectable menus via @clack/prompts), then hands the collected answers to
7
+ // `bin/reins install --non-interactive` to do the actual file work. Day-to-day
8
+ // commands (`reins update`, `reins sync`, ...) remain a plain bash CLI with no
9
+ // runtime dependencies — Node is only needed for this installer.
10
+
11
+ const path = require('node:path');
12
+ const os = require('node:os');
13
+ const fs = require('node:fs');
14
+ const { execFileSync } = require('node:child_process');
15
+ const { Command } = require('commander');
16
+
17
+ const REINS_HOME = process.env.REINS_HOME || path.join(os.homedir(), '.reins');
18
+ const REPO_URL =
19
+ process.env.REINS_REPO_URL || 'https://github.com/gustavodiasp/reins-method.git';
20
+
21
+ async function getClack() {
22
+ return import('@clack/prompts');
23
+ }
24
+
25
+ async function getColors() {
26
+ return (await import('picocolors')).default;
27
+ }
28
+
29
+ async function printBanner() {
30
+ const c = await getColors();
31
+ const lines = [
32
+ '█████ █████ ███ █ █ ████',
33
+ '█ █ █ █ ██ █ █',
34
+ '█ █ █ █ █ █ █ █',
35
+ '█████ ████ █ █ ██ ███',
36
+ '█ █ █ █ █ █ █',
37
+ '█ █ █ █ █ █ █',
38
+ '█ █ █████ ███ █ █ ████',
39
+ ];
40
+ const colors = [c.red, c.red, c.yellow, c.yellow, c.yellow, c.yellow, c.yellow];
41
+ for (let i = 0; i < lines.length; i++) {
42
+ console.log(colors[i] ? colors[i](lines[i]) : lines[i]);
43
+ }
44
+ console.log('');
45
+ console.log(
46
+ c.bold(
47
+ 'AI is like a horse — strong, but it goes where it wants without a rider.',
48
+ ),
49
+ );
50
+ console.log(c.bold('REINS is the bridle.'));
51
+ console.log('');
52
+ }
53
+
54
+ function ensureGit() {
55
+ try {
56
+ execFileSync('git', ['--version'], { stdio: 'ignore' });
57
+ } catch {
58
+ console.error('Error: git is required.');
59
+ process.exit(1);
60
+ }
61
+ }
62
+
63
+ function cloneOrPull() {
64
+ if (fs.existsSync(path.join(REINS_HOME, '.git'))) {
65
+ console.log(`REINS Method already installed at ${REINS_HOME} — pulling latest core...`);
66
+ execFileSync('git', ['-C', REINS_HOME, 'pull', '--ff-only'], { stdio: 'inherit' });
67
+ } else if (fs.existsSync(REINS_HOME)) {
68
+ console.error(`Error: ${REINS_HOME} exists and is not a REINS git checkout. Move it aside first.`);
69
+ process.exit(1);
70
+ } else {
71
+ console.log(`Cloning REINS Method to ${REINS_HOME}...`);
72
+ execFileSync('git', ['clone', '--depth', '1', REPO_URL, REINS_HOME], { stdio: 'inherit' });
73
+ }
74
+ fs.chmodSync(path.join(REINS_HOME, 'bin', 'reins'), 0o755);
75
+ }
76
+
77
+ const AGENT_OPTIONS = [
78
+ { value: 'claude-code', label: 'Claude Code' },
79
+ { value: 'copilot', label: 'GitHub Copilot CLI' },
80
+ { value: 'codex', label: 'Codex CLI' },
81
+ { value: 'gemini', label: 'Gemini CLI' },
82
+ { value: 'aider', label: 'Aider' },
83
+ { value: 'other', label: 'OpenCode / Cursor / Other' },
84
+ ];
85
+
86
+ async function runInstall() {
87
+ const clack = await getClack();
88
+
89
+ await printBanner();
90
+ ensureGit();
91
+ cloneOrPull();
92
+
93
+ const configPath = path.join(REINS_HOME, 'user', 'config.yaml');
94
+
95
+ clack.intro('REINS Method setup');
96
+
97
+ if (fs.existsSync(configPath)) {
98
+ const again = await clack.confirm({
99
+ message: `REINS is already configured at ${REINS_HOME}. Re-run the wizard?`,
100
+ initialValue: false,
101
+ });
102
+ if (clack.isCancel(again) || !again) {
103
+ clack.outro('Nothing to do.');
104
+ return;
105
+ }
106
+ }
107
+
108
+ const agent = await clack.select({
109
+ message: "What's your primary AI Coding Agent?",
110
+ options: AGENT_OPTIONS,
111
+ });
112
+ if (clack.isCancel(agent)) {
113
+ clack.cancel('Install cancelled.');
114
+ process.exit(0);
115
+ }
116
+
117
+ const wantsStandards = await clack.confirm({
118
+ message: 'Do you have company code standards to configure?',
119
+ initialValue: false,
120
+ });
121
+ if (clack.isCancel(wantsStandards)) {
122
+ clack.cancel('Install cancelled.');
123
+ process.exit(0);
124
+ }
125
+
126
+ const wantsHistoric = await clack.confirm({
127
+ message: 'Enable Historic Mode (performance tracking)?',
128
+ initialValue: false,
129
+ });
130
+ if (clack.isCancel(wantsHistoric)) {
131
+ clack.cancel('Install cancelled.');
132
+ process.exit(0);
133
+ }
134
+
135
+ const adapterPath = await clack.text({
136
+ message: 'Install an adapter pack now? Path to local pack (leave empty to skip):',
137
+ placeholder: '(skip)',
138
+ defaultValue: '',
139
+ });
140
+ if (clack.isCancel(adapterPath)) {
141
+ clack.cancel('Install cancelled.');
142
+ process.exit(0);
143
+ }
144
+
145
+ const args = [
146
+ 'install',
147
+ '--non-interactive',
148
+ `--agent=${agent}`,
149
+ `--standards=${wantsStandards ? 'yes' : 'no'}`,
150
+ `--historic=${wantsHistoric ? 'on' : 'off'}`,
151
+ ];
152
+ if (adapterPath) args.push(`--adapter=${adapterPath}`);
153
+
154
+ const spinner = clack.spinner();
155
+ spinner.start('Configuring REINS Method');
156
+ try {
157
+ execFileSync(path.join(REINS_HOME, 'bin', 'reins'), args, { stdio: 'pipe' });
158
+ spinner.stop('Configured REINS Method');
159
+ } catch (e) {
160
+ spinner.stop('Configuration failed');
161
+ process.stderr.write(e.stdout || '');
162
+ process.stderr.write(e.stderr || '');
163
+ process.exit(1);
164
+ }
165
+
166
+ if (wantsStandards) {
167
+ const companyMd = path.join(REINS_HOME, 'user', 'standards', 'company.md');
168
+ const editNow = await clack.confirm({
169
+ message: `Open ${companyMd} in your editor now?`,
170
+ initialValue: true,
171
+ });
172
+ if (!clack.isCancel(editNow) && editNow) {
173
+ const editor = process.env.EDITOR || 'vi';
174
+ try {
175
+ execFileSync(editor, [companyMd], { stdio: 'inherit' });
176
+ } catch {
177
+ // non-fatal — user can edit it later
178
+ }
179
+ }
180
+ }
181
+
182
+ clack.note(
183
+ [
184
+ `REINS Method installed to ${REINS_HOME}`,
185
+ 'Restart your terminal/agent to activate the `reins` command, then run:',
186
+ '',
187
+ ' reins status',
188
+ '',
189
+ 'Optional companion tools (see README.md "Companion tools"):',
190
+ ' - headroom: pip install "headroom-ai[all]"',
191
+ ' - graphify: pip install graphifyy',
192
+ ].join('\n'),
193
+ 'Done',
194
+ );
195
+ clack.outro('Getting started: reins status');
196
+ }
197
+
198
+ function runDelegated(cmd, args) {
199
+ if (!fs.existsSync(path.join(REINS_HOME, 'bin', 'reins'))) {
200
+ console.error(`Error: REINS Method is not installed at ${REINS_HOME}. Run "npx reins-method install" first.`);
201
+ process.exit(1);
202
+ }
203
+ try {
204
+ execFileSync(path.join(REINS_HOME, 'bin', 'reins'), [cmd, ...args], { stdio: 'inherit' });
205
+ } catch (e) {
206
+ process.exit(e.status || 1);
207
+ }
208
+ }
209
+
210
+ const program = new Command();
211
+ program
212
+ .name('reins-method')
213
+ .description('REINS Method installer — interactive setup for the agent-agnostic AI workflow')
214
+ .version(require('../../package.json').version);
215
+
216
+ program
217
+ .command('install', { isDefault: true })
218
+ .description('Install or reconfigure REINS Method (interactive wizard)')
219
+ .action(runInstall);
220
+
221
+ program
222
+ .command('update')
223
+ .description('Pull latest core and regenerate agent bridge files')
224
+ .action(() => runDelegated('update', []));
225
+
226
+ program
227
+ .command('uninstall')
228
+ .description('Unhook REINS from your agent/shell, optionally delete ~/.reins')
229
+ .action(() => runDelegated('uninstall', []));
230
+
231
+ program.parseAsync(process.argv);