instar 0.1.1 → 0.1.3
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/dist/cli.js +1 -1
- package/dist/commands/init.js +7 -7
- package/dist/commands/setup.js +58 -18
- package/dist/core/Config.js +15 -2
- package/dist/core/Prerequisites.d.ts +11 -2
- package/dist/core/Prerequisites.js +118 -14
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/init.ts +7 -7
- package/src/commands/setup.ts +60 -17
- package/src/core/Config.ts +16 -2
- package/src/core/Prerequisites.ts +129 -14
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ const program = new Command();
|
|
|
25
25
|
program
|
|
26
26
|
.name('instar')
|
|
27
27
|
.description('Persistent autonomy infrastructure for AI agents')
|
|
28
|
-
.version('0.1.
|
|
28
|
+
.version('0.1.3')
|
|
29
29
|
.option('--classic', 'Use the classic inquirer-based setup wizard instead of Claude')
|
|
30
30
|
.action((opts) => runSetup(opts)); // Default: run interactive setup when no subcommand given
|
|
31
31
|
// ── Setup (explicit alias) ────────────────────────────────────────
|
package/dist/commands/init.js
CHANGED
|
@@ -31,7 +31,7 @@ import path from 'node:path';
|
|
|
31
31
|
import pc from 'picocolors';
|
|
32
32
|
import { randomUUID } from 'node:crypto';
|
|
33
33
|
import { ensureStateDir } from '../core/Config.js';
|
|
34
|
-
import {
|
|
34
|
+
import { ensurePrerequisites } from '../core/Prerequisites.js';
|
|
35
35
|
import { defaultIdentity } from '../scaffold/bootstrap.js';
|
|
36
36
|
import { generateAgentMd, generateUserMd, generateMemoryMd, generateClaudeMd, } from '../scaffold/templates.js';
|
|
37
37
|
/**
|
|
@@ -57,9 +57,9 @@ async function initFreshProject(projectName, options) {
|
|
|
57
57
|
console.log(pc.bold(` Creating new agent project: ${pc.cyan(projectName)}`));
|
|
58
58
|
console.log(pc.dim(` Directory: ${projectDir}`));
|
|
59
59
|
console.log();
|
|
60
|
-
// Check prerequisites
|
|
61
|
-
const prereqs =
|
|
62
|
-
if (!
|
|
60
|
+
// Check and install prerequisites
|
|
61
|
+
const prereqs = await ensurePrerequisites();
|
|
62
|
+
if (!prereqs.allMet) {
|
|
63
63
|
process.exit(1);
|
|
64
64
|
}
|
|
65
65
|
// Check if directory already exists
|
|
@@ -198,9 +198,9 @@ async function initExistingProject(options) {
|
|
|
198
198
|
const port = options.port || 4040;
|
|
199
199
|
console.log(pc.bold(`\nInitializing instar in: ${pc.cyan(projectDir)}`));
|
|
200
200
|
console.log();
|
|
201
|
-
// Check prerequisites
|
|
202
|
-
const prereqs =
|
|
203
|
-
if (!
|
|
201
|
+
// Check and install prerequisites
|
|
202
|
+
const prereqs = await ensurePrerequisites();
|
|
203
|
+
if (!prereqs.allMet) {
|
|
204
204
|
process.exit(1);
|
|
205
205
|
}
|
|
206
206
|
const tmuxPath = prereqs.results.find(r => r.name === 'tmux').path;
|
package/dist/commands/setup.js
CHANGED
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* No flags needed. No manual config editing. Just answers.
|
|
16
16
|
*/
|
|
17
|
-
import { spawn } from 'node:child_process';
|
|
17
|
+
import { execSync, spawn } from 'node:child_process';
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import pc from 'picocolors';
|
|
21
21
|
import { input, confirm, select, number } from '@inquirer/prompts';
|
|
22
22
|
import { Cron } from 'croner';
|
|
23
|
-
import {
|
|
23
|
+
import { detectClaudePath, ensureStateDir } from '../core/Config.js';
|
|
24
|
+
import { ensurePrerequisites } from '../core/Prerequisites.js';
|
|
24
25
|
import { UserManager } from '../users/UserManager.js';
|
|
25
26
|
/**
|
|
26
27
|
* Launch the conversational setup wizard via Claude Code.
|
|
@@ -31,13 +32,21 @@ export async function runSetup(opts) {
|
|
|
31
32
|
if (opts?.classic) {
|
|
32
33
|
return runClassicSetup();
|
|
33
34
|
}
|
|
34
|
-
// Check
|
|
35
|
+
// Check and install prerequisites
|
|
36
|
+
console.log();
|
|
37
|
+
const prereqs = await ensurePrerequisites();
|
|
38
|
+
// Check for Claude CLI (may have been just installed)
|
|
35
39
|
const claudePath = detectClaudePath();
|
|
36
40
|
if (!claudePath) {
|
|
37
41
|
console.log();
|
|
38
42
|
console.log(pc.yellow(' Claude CLI not found — falling back to classic setup wizard.'));
|
|
39
43
|
console.log(pc.dim(' Install Claude Code for the conversational experience:'));
|
|
40
|
-
console.log(pc.dim('
|
|
44
|
+
console.log(pc.dim(' npm install -g @anthropic-ai/claude-code'));
|
|
45
|
+
console.log();
|
|
46
|
+
return runClassicSetup();
|
|
47
|
+
}
|
|
48
|
+
if (!prereqs.allMet) {
|
|
49
|
+
console.log(pc.yellow(' Some prerequisites are still missing. Falling back to classic setup.'));
|
|
41
50
|
console.log();
|
|
42
51
|
return runClassicSetup();
|
|
43
52
|
}
|
|
@@ -114,22 +123,14 @@ async function runClassicSetup() {
|
|
|
114
123
|
console.log(pc.bold(' Welcome to Instar'));
|
|
115
124
|
console.log(pc.dim(' Persistent agent infrastructure for any Claude Code project'));
|
|
116
125
|
console.log();
|
|
117
|
-
// ── Step 0: Check prerequisites
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
if (!tmuxPath) {
|
|
121
|
-
console.log(pc.red(' tmux is required but not installed.'));
|
|
122
|
-
console.log(' Install: brew install tmux (macOS) or apt install tmux (Linux)');
|
|
126
|
+
// ── Step 0: Check and install prerequisites ─────────────────────
|
|
127
|
+
const prereqs = await ensurePrerequisites();
|
|
128
|
+
if (!prereqs.allMet) {
|
|
123
129
|
process.exit(1);
|
|
124
130
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
console.log(' Install: https://docs.anthropic.com/en/docs/claude-code');
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
console.log(` ${pc.green('✓')} Claude CLI found: ${pc.dim(claudePath)}`);
|
|
132
|
-
console.log();
|
|
131
|
+
const tmuxPath = prereqs.results.find(r => r.name === 'tmux').path;
|
|
132
|
+
// Use a scoped name to avoid shadowing the outer runSetup's claudePath
|
|
133
|
+
const claudePath = prereqs.results.find(r => r.name === 'Claude CLI').path;
|
|
133
134
|
// ── Step 1: Project ──────────────────────────────────────────────
|
|
134
135
|
const detectedDir = process.cwd();
|
|
135
136
|
const detectedName = path.basename(detectedDir);
|
|
@@ -302,6 +303,29 @@ async function runClassicSetup() {
|
|
|
302
303
|
console.log(` ${pc.cyan('.instar/jobs.json')} — job definitions`);
|
|
303
304
|
console.log(` ${pc.cyan('.instar/users.json')} — user profiles`);
|
|
304
305
|
console.log();
|
|
306
|
+
// Check if instar is globally installed (needed for server commands)
|
|
307
|
+
const isGloballyInstalled = isInstarGlobal();
|
|
308
|
+
if (!isGloballyInstalled) {
|
|
309
|
+
console.log(pc.dim(' Tip: instar is not installed globally. For persistent server'));
|
|
310
|
+
console.log(pc.dim(' commands (start, stop, status), install it globally:'));
|
|
311
|
+
console.log();
|
|
312
|
+
const installGlobal = await confirm({
|
|
313
|
+
message: 'Install instar globally? (npm install -g instar)',
|
|
314
|
+
default: true,
|
|
315
|
+
});
|
|
316
|
+
if (installGlobal) {
|
|
317
|
+
try {
|
|
318
|
+
console.log(pc.dim(' Running: npm install -g instar'));
|
|
319
|
+
execSync('npm install -g instar', { encoding: 'utf-8', stdio: 'inherit' });
|
|
320
|
+
console.log(` ${pc.green('✓')} instar installed globally`);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
console.log(pc.yellow(' Could not install globally. You can run it later:'));
|
|
324
|
+
console.log(` ${pc.cyan('npm install -g instar')}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
console.log();
|
|
328
|
+
}
|
|
305
329
|
// Offer to start server
|
|
306
330
|
const startNow = await confirm({
|
|
307
331
|
message: 'Start the agent server now?',
|
|
@@ -324,6 +348,22 @@ async function runClassicSetup() {
|
|
|
324
348
|
}
|
|
325
349
|
console.log();
|
|
326
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Check if instar is installed globally (vs running via npx).
|
|
353
|
+
*/
|
|
354
|
+
function isInstarGlobal() {
|
|
355
|
+
try {
|
|
356
|
+
const result = execSync('which instar 2>/dev/null || where instar 2>/dev/null', {
|
|
357
|
+
encoding: 'utf-8',
|
|
358
|
+
stdio: 'pipe',
|
|
359
|
+
}).trim();
|
|
360
|
+
// npx creates a temp binary — check if it's a real global install
|
|
361
|
+
return !!result && !result.includes('.npm/_npx');
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
327
367
|
// ── Prompt Helpers ───────────────────────────────────────────────
|
|
328
368
|
/**
|
|
329
369
|
* Full Telegram walkthrough. Returns config or null if skipped.
|
package/dist/core/Config.js
CHANGED
|
@@ -32,18 +32,31 @@ export function detectTmuxPath() {
|
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
export function detectClaudePath() {
|
|
35
|
+
const home = process.env.HOME || '';
|
|
35
36
|
const candidates = [
|
|
36
|
-
path.join(
|
|
37
|
+
path.join(home, '.claude', 'local', 'claude'),
|
|
37
38
|
'/usr/local/bin/claude',
|
|
38
39
|
'/opt/homebrew/bin/claude',
|
|
39
40
|
];
|
|
41
|
+
// Also check npm global bin directory (where `npm install -g` puts things)
|
|
42
|
+
try {
|
|
43
|
+
const npmPrefix = execSync('npm config get prefix', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
44
|
+
if (npmPrefix) {
|
|
45
|
+
candidates.push(path.join(npmPrefix, 'bin', 'claude'));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch { /* ignore */ }
|
|
49
|
+
// Check nvm/fnm managed paths
|
|
50
|
+
if (process.env.NVM_BIN) {
|
|
51
|
+
candidates.push(path.join(process.env.NVM_BIN, 'claude'));
|
|
52
|
+
}
|
|
40
53
|
for (const candidate of candidates) {
|
|
41
54
|
if (fs.existsSync(candidate))
|
|
42
55
|
return candidate;
|
|
43
56
|
}
|
|
44
57
|
// Fallback: check PATH
|
|
45
58
|
try {
|
|
46
|
-
const result = execSync('which claude', { encoding: 'utf-8' }).trim();
|
|
59
|
+
const result = execSync('which claude', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
47
60
|
if (result && fs.existsSync(result))
|
|
48
61
|
return result;
|
|
49
62
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prerequisite detection and installation
|
|
2
|
+
* Prerequisite detection and auto-installation.
|
|
3
3
|
*
|
|
4
4
|
* Checks for required software (tmux, Claude CLI, Node.js)
|
|
5
|
-
* and
|
|
5
|
+
* and offers to install missing dependencies automatically.
|
|
6
6
|
*/
|
|
7
7
|
export interface PrerequisiteResult {
|
|
8
8
|
name: string;
|
|
@@ -10,6 +10,10 @@ export interface PrerequisiteResult {
|
|
|
10
10
|
path?: string;
|
|
11
11
|
version?: string;
|
|
12
12
|
installHint: string;
|
|
13
|
+
/** Whether this prerequisite can be auto-installed. */
|
|
14
|
+
canAutoInstall: boolean;
|
|
15
|
+
/** The command to run to auto-install this prerequisite. */
|
|
16
|
+
installCommand?: string;
|
|
13
17
|
}
|
|
14
18
|
export interface PrerequisiteCheck {
|
|
15
19
|
allMet: boolean;
|
|
@@ -25,4 +29,9 @@ export declare function checkPrerequisites(): PrerequisiteCheck;
|
|
|
25
29
|
* Returns true if all prerequisites are met.
|
|
26
30
|
*/
|
|
27
31
|
export declare function printPrerequisiteCheck(check: PrerequisiteCheck): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Interactive prerequisite check that offers to install missing dependencies.
|
|
34
|
+
* Returns a fresh PrerequisiteCheck after any installations.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ensurePrerequisites(): Promise<PrerequisiteCheck>;
|
|
28
37
|
//# sourceMappingURL=Prerequisites.d.ts.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prerequisite detection and installation
|
|
2
|
+
* Prerequisite detection and auto-installation.
|
|
3
3
|
*
|
|
4
4
|
* Checks for required software (tmux, Claude CLI, Node.js)
|
|
5
|
-
* and
|
|
5
|
+
* and offers to install missing dependencies automatically.
|
|
6
6
|
*/
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
8
|
import pc from 'picocolors';
|
|
9
|
+
import { confirm } from '@inquirer/prompts';
|
|
9
10
|
import { detectTmuxPath, detectClaudePath } from './Config.js';
|
|
10
11
|
/**
|
|
11
12
|
* Detect the current platform for install guidance.
|
|
@@ -69,27 +70,46 @@ function getNodeVersion() {
|
|
|
69
70
|
return { version, major };
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
72
|
-
* Build install hint for tmux based on platform.
|
|
73
|
+
* Build install hint and command for tmux based on platform.
|
|
73
74
|
*/
|
|
74
|
-
function
|
|
75
|
+
function tmuxInstallInfo() {
|
|
75
76
|
const platform = detectPlatform();
|
|
76
77
|
switch (platform) {
|
|
77
78
|
case 'macos-arm':
|
|
78
79
|
case 'macos-intel':
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
if (hasHomebrew()) {
|
|
81
|
+
return {
|
|
82
|
+
hint: 'Install with: brew install tmux',
|
|
83
|
+
canAutoInstall: true,
|
|
84
|
+
command: 'brew install tmux',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
hint: 'Install Homebrew first (https://brew.sh), then: brew install tmux',
|
|
89
|
+
canAutoInstall: false,
|
|
90
|
+
};
|
|
82
91
|
case 'linux':
|
|
83
|
-
return
|
|
92
|
+
return {
|
|
93
|
+
hint: 'Install with: sudo apt install tmux (Debian/Ubuntu) or sudo yum install tmux (RHEL/CentOS)',
|
|
94
|
+
canAutoInstall: true,
|
|
95
|
+
command: 'sudo apt install -y tmux',
|
|
96
|
+
};
|
|
84
97
|
default:
|
|
85
|
-
return
|
|
98
|
+
return {
|
|
99
|
+
hint: 'Install tmux: https://github.com/tmux/tmux/wiki/Installing',
|
|
100
|
+
canAutoInstall: false,
|
|
101
|
+
};
|
|
86
102
|
}
|
|
87
103
|
}
|
|
88
104
|
/**
|
|
89
|
-
* Build install hint for Claude CLI
|
|
105
|
+
* Build install hint and command for Claude CLI.
|
|
90
106
|
*/
|
|
91
|
-
function
|
|
92
|
-
return
|
|
107
|
+
function claudeInstallInfo() {
|
|
108
|
+
return {
|
|
109
|
+
hint: 'Install Claude Code: npm install -g @anthropic-ai/claude-code',
|
|
110
|
+
canAutoInstall: true,
|
|
111
|
+
command: 'npm install -g @anthropic-ai/claude-code',
|
|
112
|
+
};
|
|
93
113
|
}
|
|
94
114
|
/**
|
|
95
115
|
* Check all prerequisites and return a structured result.
|
|
@@ -105,24 +125,31 @@ export function checkPrerequisites() {
|
|
|
105
125
|
installHint: node.major < 18
|
|
106
126
|
? `Node.js 18+ required (found ${node.version}). Update: https://nodejs.org`
|
|
107
127
|
: '',
|
|
128
|
+
canAutoInstall: false,
|
|
108
129
|
});
|
|
109
130
|
// 2. tmux
|
|
110
131
|
const tmuxPath = detectTmuxPath();
|
|
132
|
+
const tmuxInfo = tmuxInstallInfo();
|
|
111
133
|
results.push({
|
|
112
134
|
name: 'tmux',
|
|
113
135
|
found: !!tmuxPath,
|
|
114
136
|
path: tmuxPath || undefined,
|
|
115
137
|
version: tmuxPath ? getTmuxVersion(tmuxPath) : undefined,
|
|
116
|
-
installHint:
|
|
138
|
+
installHint: tmuxInfo.hint,
|
|
139
|
+
canAutoInstall: tmuxInfo.canAutoInstall,
|
|
140
|
+
installCommand: tmuxInfo.command,
|
|
117
141
|
});
|
|
118
142
|
// 3. Claude CLI
|
|
119
143
|
const claudePath = detectClaudePath();
|
|
144
|
+
const claudeInfo = claudeInstallInfo();
|
|
120
145
|
results.push({
|
|
121
146
|
name: 'Claude CLI',
|
|
122
147
|
found: !!claudePath,
|
|
123
148
|
path: claudePath || undefined,
|
|
124
149
|
version: claudePath ? getClaudeVersion(claudePath) : undefined,
|
|
125
|
-
installHint:
|
|
150
|
+
installHint: claudeInfo.hint,
|
|
151
|
+
canAutoInstall: claudeInfo.canAutoInstall,
|
|
152
|
+
installCommand: claudeInfo.command,
|
|
126
153
|
});
|
|
127
154
|
const missing = results.filter(r => !r.found);
|
|
128
155
|
return {
|
|
@@ -131,6 +158,26 @@ export function checkPrerequisites() {
|
|
|
131
158
|
missing,
|
|
132
159
|
};
|
|
133
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Attempt to install a missing prerequisite.
|
|
163
|
+
* Returns true if installation succeeded.
|
|
164
|
+
*/
|
|
165
|
+
function installPrerequisite(result) {
|
|
166
|
+
if (!result.installCommand)
|
|
167
|
+
return false;
|
|
168
|
+
try {
|
|
169
|
+
console.log(pc.dim(` Running: ${result.installCommand}`));
|
|
170
|
+
execSync(result.installCommand, {
|
|
171
|
+
encoding: 'utf-8',
|
|
172
|
+
stdio: 'inherit',
|
|
173
|
+
timeout: 120000, // 2 min timeout
|
|
174
|
+
});
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
134
181
|
/**
|
|
135
182
|
* Print prerequisite check results to console.
|
|
136
183
|
* Returns true if all prerequisites are met.
|
|
@@ -156,4 +203,61 @@ export function printPrerequisiteCheck(check) {
|
|
|
156
203
|
}
|
|
157
204
|
return check.allMet;
|
|
158
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Interactive prerequisite check that offers to install missing dependencies.
|
|
208
|
+
* Returns a fresh PrerequisiteCheck after any installations.
|
|
209
|
+
*/
|
|
210
|
+
export async function ensurePrerequisites() {
|
|
211
|
+
let check = checkPrerequisites();
|
|
212
|
+
console.log(pc.bold(' Checking prerequisites...'));
|
|
213
|
+
console.log();
|
|
214
|
+
for (const result of check.results) {
|
|
215
|
+
if (result.found) {
|
|
216
|
+
const versionStr = result.version ? ` (${result.version})` : '';
|
|
217
|
+
const pathStr = result.path ? pc.dim(` ${result.path}`) : '';
|
|
218
|
+
console.log(` ${pc.green('✓')} ${result.name}${versionStr}${pathStr}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (check.allMet) {
|
|
222
|
+
console.log();
|
|
223
|
+
return check;
|
|
224
|
+
}
|
|
225
|
+
// Handle missing prerequisites
|
|
226
|
+
for (const missing of check.missing) {
|
|
227
|
+
console.log();
|
|
228
|
+
console.log(` ${pc.red('✗')} ${missing.name} — not found`);
|
|
229
|
+
if (missing.canAutoInstall && missing.installCommand) {
|
|
230
|
+
const install = await confirm({
|
|
231
|
+
message: `Install ${missing.name}? (${pc.dim(missing.installCommand)})`,
|
|
232
|
+
default: true,
|
|
233
|
+
});
|
|
234
|
+
if (install) {
|
|
235
|
+
const success = installPrerequisite(missing);
|
|
236
|
+
if (success) {
|
|
237
|
+
console.log(` ${pc.green('✓')} ${missing.name} installed successfully`);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
console.log(` ${pc.red('✗')} Failed to install ${missing.name}`);
|
|
241
|
+
console.log(` Try manually: ${missing.installHint}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
console.log(` ${missing.installHint}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.log(` ${missing.installHint}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Re-check after installations
|
|
253
|
+
check = checkPrerequisites();
|
|
254
|
+
console.log();
|
|
255
|
+
if (!check.allMet) {
|
|
256
|
+
const stillMissing = check.missing.map(r => r.name).join(', ');
|
|
257
|
+
console.log(pc.red(` Still missing: ${stillMissing}`));
|
|
258
|
+
console.log(pc.dim(' Install the missing prerequisites and run instar again.'));
|
|
259
|
+
console.log();
|
|
260
|
+
}
|
|
261
|
+
return check;
|
|
262
|
+
}
|
|
159
263
|
//# sourceMappingURL=Prerequisites.js.map
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -29,7 +29,7 @@ const program = new Command();
|
|
|
29
29
|
program
|
|
30
30
|
.name('instar')
|
|
31
31
|
.description('Persistent autonomy infrastructure for AI agents')
|
|
32
|
-
.version('0.1.
|
|
32
|
+
.version('0.1.3')
|
|
33
33
|
.option('--classic', 'Use the classic inquirer-based setup wizard instead of Claude')
|
|
34
34
|
.action((opts) => runSetup(opts)); // Default: run interactive setup when no subcommand given
|
|
35
35
|
|
package/src/commands/init.ts
CHANGED
|
@@ -32,7 +32,7 @@ import path from 'node:path';
|
|
|
32
32
|
import pc from 'picocolors';
|
|
33
33
|
import { randomUUID } from 'node:crypto';
|
|
34
34
|
import { detectTmuxPath, detectClaudePath, ensureStateDir } from '../core/Config.js';
|
|
35
|
-
import {
|
|
35
|
+
import { ensurePrerequisites } from '../core/Prerequisites.js';
|
|
36
36
|
import { defaultIdentity } from '../scaffold/bootstrap.js';
|
|
37
37
|
import {
|
|
38
38
|
generateAgentMd,
|
|
@@ -75,9 +75,9 @@ async function initFreshProject(projectName: string, options: InitOptions): Prom
|
|
|
75
75
|
console.log(pc.dim(` Directory: ${projectDir}`));
|
|
76
76
|
console.log();
|
|
77
77
|
|
|
78
|
-
// Check prerequisites
|
|
79
|
-
const prereqs =
|
|
80
|
-
if (!
|
|
78
|
+
// Check and install prerequisites
|
|
79
|
+
const prereqs = await ensurePrerequisites();
|
|
80
|
+
if (!prereqs.allMet) {
|
|
81
81
|
process.exit(1);
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -246,9 +246,9 @@ async function initExistingProject(options: InitOptions): Promise<void> {
|
|
|
246
246
|
console.log(pc.bold(`\nInitializing instar in: ${pc.cyan(projectDir)}`));
|
|
247
247
|
console.log();
|
|
248
248
|
|
|
249
|
-
// Check prerequisites
|
|
250
|
-
const prereqs =
|
|
251
|
-
if (!
|
|
249
|
+
// Check and install prerequisites
|
|
250
|
+
const prereqs = await ensurePrerequisites();
|
|
251
|
+
if (!prereqs.allMet) {
|
|
252
252
|
process.exit(1);
|
|
253
253
|
}
|
|
254
254
|
|
package/src/commands/setup.ts
CHANGED
|
@@ -22,6 +22,7 @@ import pc from 'picocolors';
|
|
|
22
22
|
import { input, confirm, select, number } from '@inquirer/prompts';
|
|
23
23
|
import { Cron } from 'croner';
|
|
24
24
|
import { detectTmuxPath, detectClaudePath, ensureStateDir } from '../core/Config.js';
|
|
25
|
+
import { ensurePrerequisites } from '../core/Prerequisites.js';
|
|
25
26
|
import { UserManager } from '../users/UserManager.js';
|
|
26
27
|
import { validateJob } from '../scheduler/JobLoader.js';
|
|
27
28
|
import type { AgentKitConfig, JobDefinition, UserProfile, UserChannel } from '../core/types.js';
|
|
@@ -36,13 +37,23 @@ export async function runSetup(opts?: { classic?: boolean }): Promise<void> {
|
|
|
36
37
|
return runClassicSetup();
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
// Check
|
|
40
|
+
// Check and install prerequisites
|
|
41
|
+
console.log();
|
|
42
|
+
const prereqs = await ensurePrerequisites();
|
|
43
|
+
|
|
44
|
+
// Check for Claude CLI (may have been just installed)
|
|
40
45
|
const claudePath = detectClaudePath();
|
|
41
46
|
if (!claudePath) {
|
|
42
47
|
console.log();
|
|
43
48
|
console.log(pc.yellow(' Claude CLI not found — falling back to classic setup wizard.'));
|
|
44
49
|
console.log(pc.dim(' Install Claude Code for the conversational experience:'));
|
|
45
|
-
console.log(pc.dim('
|
|
50
|
+
console.log(pc.dim(' npm install -g @anthropic-ai/claude-code'));
|
|
51
|
+
console.log();
|
|
52
|
+
return runClassicSetup();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!prereqs.allMet) {
|
|
56
|
+
console.log(pc.yellow(' Some prerequisites are still missing. Falling back to classic setup.'));
|
|
46
57
|
console.log();
|
|
47
58
|
return runClassicSetup();
|
|
48
59
|
}
|
|
@@ -123,25 +134,16 @@ async function runClassicSetup(): Promise<void> {
|
|
|
123
134
|
console.log(pc.dim(' Persistent agent infrastructure for any Claude Code project'));
|
|
124
135
|
console.log();
|
|
125
136
|
|
|
126
|
-
// ── Step 0: Check prerequisites
|
|
127
|
-
|
|
128
|
-
const tmuxPath = detectTmuxPath();
|
|
129
|
-
const claudePath = detectClaudePath();
|
|
137
|
+
// ── Step 0: Check and install prerequisites ─────────────────────
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.log(' Install: brew install tmux (macOS) or apt install tmux (Linux)');
|
|
139
|
+
const prereqs = await ensurePrerequisites();
|
|
140
|
+
if (!prereqs.allMet) {
|
|
134
141
|
process.exit(1);
|
|
135
142
|
}
|
|
136
|
-
console.log(` ${pc.green('✓')} tmux found: ${pc.dim(tmuxPath)}`);
|
|
137
143
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
console.log(` ${pc.green('✓')} Claude CLI found: ${pc.dim(claudePath)}`);
|
|
144
|
-
console.log();
|
|
144
|
+
const tmuxPath = prereqs.results.find(r => r.name === 'tmux')!.path!;
|
|
145
|
+
// Use a scoped name to avoid shadowing the outer runSetup's claudePath
|
|
146
|
+
const claudePath = prereqs.results.find(r => r.name === 'Claude CLI')!.path!;
|
|
145
147
|
|
|
146
148
|
// ── Step 1: Project ──────────────────────────────────────────────
|
|
147
149
|
|
|
@@ -349,6 +351,31 @@ async function runClassicSetup(): Promise<void> {
|
|
|
349
351
|
console.log(` ${pc.cyan('.instar/users.json')} — user profiles`);
|
|
350
352
|
console.log();
|
|
351
353
|
|
|
354
|
+
// Check if instar is globally installed (needed for server commands)
|
|
355
|
+
const isGloballyInstalled = isInstarGlobal();
|
|
356
|
+
if (!isGloballyInstalled) {
|
|
357
|
+
console.log(pc.dim(' Tip: instar is not installed globally. For persistent server'));
|
|
358
|
+
console.log(pc.dim(' commands (start, stop, status), install it globally:'));
|
|
359
|
+
console.log();
|
|
360
|
+
|
|
361
|
+
const installGlobal = await confirm({
|
|
362
|
+
message: 'Install instar globally? (npm install -g instar)',
|
|
363
|
+
default: true,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (installGlobal) {
|
|
367
|
+
try {
|
|
368
|
+
console.log(pc.dim(' Running: npm install -g instar'));
|
|
369
|
+
execSync('npm install -g instar', { encoding: 'utf-8', stdio: 'inherit' });
|
|
370
|
+
console.log(` ${pc.green('✓')} instar installed globally`);
|
|
371
|
+
} catch {
|
|
372
|
+
console.log(pc.yellow(' Could not install globally. You can run it later:'));
|
|
373
|
+
console.log(` ${pc.cyan('npm install -g instar')}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
console.log();
|
|
377
|
+
}
|
|
378
|
+
|
|
352
379
|
// Offer to start server
|
|
353
380
|
const startNow = await confirm({
|
|
354
381
|
message: 'Start the agent server now?',
|
|
@@ -373,6 +400,22 @@ async function runClassicSetup(): Promise<void> {
|
|
|
373
400
|
console.log();
|
|
374
401
|
}
|
|
375
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Check if instar is installed globally (vs running via npx).
|
|
405
|
+
*/
|
|
406
|
+
function isInstarGlobal(): boolean {
|
|
407
|
+
try {
|
|
408
|
+
const result = execSync('which instar 2>/dev/null || where instar 2>/dev/null', {
|
|
409
|
+
encoding: 'utf-8',
|
|
410
|
+
stdio: 'pipe',
|
|
411
|
+
}).trim();
|
|
412
|
+
// npx creates a temp binary — check if it's a real global install
|
|
413
|
+
return !!result && !result.includes('.npm/_npx');
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
376
419
|
// ── Prompt Helpers ───────────────────────────────────────────────
|
|
377
420
|
|
|
378
421
|
/**
|
package/src/core/Config.ts
CHANGED
|
@@ -37,19 +37,33 @@ export function detectTmuxPath(): string | null {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export function detectClaudePath(): string | null {
|
|
40
|
+
const home = process.env.HOME || '';
|
|
40
41
|
const candidates = [
|
|
41
|
-
path.join(
|
|
42
|
+
path.join(home, '.claude', 'local', 'claude'),
|
|
42
43
|
'/usr/local/bin/claude',
|
|
43
44
|
'/opt/homebrew/bin/claude',
|
|
44
45
|
];
|
|
45
46
|
|
|
47
|
+
// Also check npm global bin directory (where `npm install -g` puts things)
|
|
48
|
+
try {
|
|
49
|
+
const npmPrefix = execSync('npm config get prefix', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
50
|
+
if (npmPrefix) {
|
|
51
|
+
candidates.push(path.join(npmPrefix, 'bin', 'claude'));
|
|
52
|
+
}
|
|
53
|
+
} catch { /* ignore */ }
|
|
54
|
+
|
|
55
|
+
// Check nvm/fnm managed paths
|
|
56
|
+
if (process.env.NVM_BIN) {
|
|
57
|
+
candidates.push(path.join(process.env.NVM_BIN, 'claude'));
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
for (const candidate of candidates) {
|
|
47
61
|
if (fs.existsSync(candidate)) return candidate;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
// Fallback: check PATH
|
|
51
65
|
try {
|
|
52
|
-
const result = execSync('which claude', { encoding: 'utf-8' }).trim();
|
|
66
|
+
const result = execSync('which claude', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
53
67
|
if (result && fs.existsSync(result)) return result;
|
|
54
68
|
} catch {
|
|
55
69
|
// claude not found
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prerequisite detection and installation
|
|
2
|
+
* Prerequisite detection and auto-installation.
|
|
3
3
|
*
|
|
4
4
|
* Checks for required software (tmux, Claude CLI, Node.js)
|
|
5
|
-
* and
|
|
5
|
+
* and offers to install missing dependencies automatically.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import pc from 'picocolors';
|
|
11
|
+
import { confirm } from '@inquirer/prompts';
|
|
11
12
|
import { detectTmuxPath, detectClaudePath } from './Config.js';
|
|
12
13
|
|
|
13
14
|
export interface PrerequisiteResult {
|
|
@@ -16,6 +17,10 @@ export interface PrerequisiteResult {
|
|
|
16
17
|
path?: string;
|
|
17
18
|
version?: string;
|
|
18
19
|
installHint: string;
|
|
20
|
+
/** Whether this prerequisite can be auto-installed. */
|
|
21
|
+
canAutoInstall: boolean;
|
|
22
|
+
/** The command to run to auto-install this prerequisite. */
|
|
23
|
+
installCommand?: string;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export interface PrerequisiteCheck {
|
|
@@ -87,28 +92,47 @@ function getNodeVersion(): { version: string; major: number } {
|
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
/**
|
|
90
|
-
* Build install hint for tmux based on platform.
|
|
95
|
+
* Build install hint and command for tmux based on platform.
|
|
91
96
|
*/
|
|
92
|
-
function
|
|
97
|
+
function tmuxInstallInfo(): { hint: string; canAutoInstall: boolean; command?: string } {
|
|
93
98
|
const platform = detectPlatform();
|
|
94
99
|
switch (platform) {
|
|
95
100
|
case 'macos-arm':
|
|
96
101
|
case 'macos-intel':
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
if (hasHomebrew()) {
|
|
103
|
+
return {
|
|
104
|
+
hint: 'Install with: brew install tmux',
|
|
105
|
+
canAutoInstall: true,
|
|
106
|
+
command: 'brew install tmux',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
hint: 'Install Homebrew first (https://brew.sh), then: brew install tmux',
|
|
111
|
+
canAutoInstall: false,
|
|
112
|
+
};
|
|
100
113
|
case 'linux':
|
|
101
|
-
return
|
|
114
|
+
return {
|
|
115
|
+
hint: 'Install with: sudo apt install tmux (Debian/Ubuntu) or sudo yum install tmux (RHEL/CentOS)',
|
|
116
|
+
canAutoInstall: true,
|
|
117
|
+
command: 'sudo apt install -y tmux',
|
|
118
|
+
};
|
|
102
119
|
default:
|
|
103
|
-
return
|
|
120
|
+
return {
|
|
121
|
+
hint: 'Install tmux: https://github.com/tmux/tmux/wiki/Installing',
|
|
122
|
+
canAutoInstall: false,
|
|
123
|
+
};
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
/**
|
|
108
|
-
* Build install hint for Claude CLI
|
|
128
|
+
* Build install hint and command for Claude CLI.
|
|
109
129
|
*/
|
|
110
|
-
function
|
|
111
|
-
return
|
|
130
|
+
function claudeInstallInfo(): { hint: string; canAutoInstall: boolean; command: string } {
|
|
131
|
+
return {
|
|
132
|
+
hint: 'Install Claude Code: npm install -g @anthropic-ai/claude-code',
|
|
133
|
+
canAutoInstall: true,
|
|
134
|
+
command: 'npm install -g @anthropic-ai/claude-code',
|
|
135
|
+
};
|
|
112
136
|
}
|
|
113
137
|
|
|
114
138
|
/**
|
|
@@ -126,26 +150,33 @@ export function checkPrerequisites(): PrerequisiteCheck {
|
|
|
126
150
|
installHint: node.major < 18
|
|
127
151
|
? `Node.js 18+ required (found ${node.version}). Update: https://nodejs.org`
|
|
128
152
|
: '',
|
|
153
|
+
canAutoInstall: false,
|
|
129
154
|
});
|
|
130
155
|
|
|
131
156
|
// 2. tmux
|
|
132
157
|
const tmuxPath = detectTmuxPath();
|
|
158
|
+
const tmuxInfo = tmuxInstallInfo();
|
|
133
159
|
results.push({
|
|
134
160
|
name: 'tmux',
|
|
135
161
|
found: !!tmuxPath,
|
|
136
162
|
path: tmuxPath || undefined,
|
|
137
163
|
version: tmuxPath ? getTmuxVersion(tmuxPath) : undefined,
|
|
138
|
-
installHint:
|
|
164
|
+
installHint: tmuxInfo.hint,
|
|
165
|
+
canAutoInstall: tmuxInfo.canAutoInstall,
|
|
166
|
+
installCommand: tmuxInfo.command,
|
|
139
167
|
});
|
|
140
168
|
|
|
141
169
|
// 3. Claude CLI
|
|
142
170
|
const claudePath = detectClaudePath();
|
|
171
|
+
const claudeInfo = claudeInstallInfo();
|
|
143
172
|
results.push({
|
|
144
173
|
name: 'Claude CLI',
|
|
145
174
|
found: !!claudePath,
|
|
146
175
|
path: claudePath || undefined,
|
|
147
176
|
version: claudePath ? getClaudeVersion(claudePath) : undefined,
|
|
148
|
-
installHint:
|
|
177
|
+
installHint: claudeInfo.hint,
|
|
178
|
+
canAutoInstall: claudeInfo.canAutoInstall,
|
|
179
|
+
installCommand: claudeInfo.command,
|
|
149
180
|
});
|
|
150
181
|
|
|
151
182
|
const missing = results.filter(r => !r.found);
|
|
@@ -157,6 +188,26 @@ export function checkPrerequisites(): PrerequisiteCheck {
|
|
|
157
188
|
};
|
|
158
189
|
}
|
|
159
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Attempt to install a missing prerequisite.
|
|
193
|
+
* Returns true if installation succeeded.
|
|
194
|
+
*/
|
|
195
|
+
function installPrerequisite(result: PrerequisiteResult): boolean {
|
|
196
|
+
if (!result.installCommand) return false;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
console.log(pc.dim(` Running: ${result.installCommand}`));
|
|
200
|
+
execSync(result.installCommand, {
|
|
201
|
+
encoding: 'utf-8',
|
|
202
|
+
stdio: 'inherit',
|
|
203
|
+
timeout: 120000, // 2 min timeout
|
|
204
|
+
});
|
|
205
|
+
return true;
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
160
211
|
/**
|
|
161
212
|
* Print prerequisite check results to console.
|
|
162
213
|
* Returns true if all prerequisites are met.
|
|
@@ -185,3 +236,67 @@ export function printPrerequisiteCheck(check: PrerequisiteCheck): boolean {
|
|
|
185
236
|
|
|
186
237
|
return check.allMet;
|
|
187
238
|
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Interactive prerequisite check that offers to install missing dependencies.
|
|
242
|
+
* Returns a fresh PrerequisiteCheck after any installations.
|
|
243
|
+
*/
|
|
244
|
+
export async function ensurePrerequisites(): Promise<PrerequisiteCheck> {
|
|
245
|
+
let check = checkPrerequisites();
|
|
246
|
+
|
|
247
|
+
console.log(pc.bold(' Checking prerequisites...'));
|
|
248
|
+
console.log();
|
|
249
|
+
|
|
250
|
+
for (const result of check.results) {
|
|
251
|
+
if (result.found) {
|
|
252
|
+
const versionStr = result.version ? ` (${result.version})` : '';
|
|
253
|
+
const pathStr = result.path ? pc.dim(` ${result.path}`) : '';
|
|
254
|
+
console.log(` ${pc.green('✓')} ${result.name}${versionStr}${pathStr}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (check.allMet) {
|
|
259
|
+
console.log();
|
|
260
|
+
return check;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Handle missing prerequisites
|
|
264
|
+
for (const missing of check.missing) {
|
|
265
|
+
console.log();
|
|
266
|
+
console.log(` ${pc.red('✗')} ${missing.name} — not found`);
|
|
267
|
+
|
|
268
|
+
if (missing.canAutoInstall && missing.installCommand) {
|
|
269
|
+
const install = await confirm({
|
|
270
|
+
message: `Install ${missing.name}? (${pc.dim(missing.installCommand)})`,
|
|
271
|
+
default: true,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (install) {
|
|
275
|
+
const success = installPrerequisite(missing);
|
|
276
|
+
if (success) {
|
|
277
|
+
console.log(` ${pc.green('✓')} ${missing.name} installed successfully`);
|
|
278
|
+
} else {
|
|
279
|
+
console.log(` ${pc.red('✗')} Failed to install ${missing.name}`);
|
|
280
|
+
console.log(` Try manually: ${missing.installHint}`);
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
console.log(` ${missing.installHint}`);
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
console.log(` ${missing.installHint}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Re-check after installations
|
|
291
|
+
check = checkPrerequisites();
|
|
292
|
+
console.log();
|
|
293
|
+
|
|
294
|
+
if (!check.allMet) {
|
|
295
|
+
const stillMissing = check.missing.map(r => r.name).join(', ');
|
|
296
|
+
console.log(pc.red(` Still missing: ${stillMissing}`));
|
|
297
|
+
console.log(pc.dim(' Install the missing prerequisites and run instar again.'));
|
|
298
|
+
console.log();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return check;
|
|
302
|
+
}
|