instar 0.1.0 → 0.1.2
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/README.md +5 -1
- package/dist/cli.js +1 -1
- package/dist/commands/init.js +7 -7
- package/dist/commands/setup.js +58 -18
- package/dist/core/Prerequisites.d.ts +11 -2
- package/dist/core/Prerequisites.js +118 -14
- package/dist/core/SessionManager.js +4 -5
- 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/Prerequisites.ts +129 -14
- package/src/core/SessionManager.ts +4 -5
package/README.md
CHANGED
|
@@ -187,7 +187,11 @@ The goal: make it possible for anyone to give their Claude Code project the kind
|
|
|
187
187
|
- Node.js 18+
|
|
188
188
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
|
|
189
189
|
- tmux (`brew install tmux` on macOS, `apt install tmux` on Linux)
|
|
190
|
-
-
|
|
190
|
+
- Claude authentication -- either:
|
|
191
|
+
- An [Anthropic API key](https://console.anthropic.com/) (recommended for production/commercial use)
|
|
192
|
+
- A Claude subscription (Max or Pro) with Claude Code logged in (for personal use)
|
|
193
|
+
|
|
194
|
+
Instar spawns the official Claude Code CLI and respects whatever authentication you have configured. It never extracts or proxies your credentials.
|
|
191
195
|
|
|
192
196
|
## License
|
|
193
197
|
|
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.2')
|
|
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.
|
|
@@ -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
|
|
@@ -68,9 +68,8 @@ export class SessionManager extends EventEmitter {
|
|
|
68
68
|
}
|
|
69
69
|
claudeArgs.push('-p', options.prompt);
|
|
70
70
|
// Create tmux session and run claude
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
const claudeCmd = `${cleanEnv} ${this.config.claudePath} ${claudeArgs.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ')}`;
|
|
71
|
+
// Respect the user's configured auth method (API key or OAuth subscription)
|
|
72
|
+
const claudeCmd = `${this.config.claudePath} ${claudeArgs.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ')}`;
|
|
74
73
|
const tmuxCmd = [
|
|
75
74
|
this.config.tmuxPath,
|
|
76
75
|
'new-session',
|
|
@@ -223,9 +222,9 @@ export class SessionManager extends EventEmitter {
|
|
|
223
222
|
}
|
|
224
223
|
return tmuxSession;
|
|
225
224
|
}
|
|
226
|
-
//
|
|
225
|
+
// Respect the user's configured auth method (API key or OAuth subscription)
|
|
227
226
|
const claudeCmd = `${this.config.claudePath} --dangerously-skip-permissions`;
|
|
228
|
-
const shellCmd = `cd '${this.config.projectDir}' &&
|
|
227
|
+
const shellCmd = `cd '${this.config.projectDir}' && ${claudeCmd}`;
|
|
229
228
|
const tmuxCmd = `${this.config.tmuxPath} new-session -d -s '${tmuxSession}' -x 200 -y 50 'bash -c "${shellCmd.replace(/"/g, '\\"')}"'`;
|
|
230
229
|
try {
|
|
231
230
|
execSync(tmuxCmd, { encoding: 'utf-8' });
|
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.2')
|
|
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
|
/**
|
|
@@ -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
|
+
}
|
|
@@ -92,9 +92,8 @@ export class SessionManager extends EventEmitter {
|
|
|
92
92
|
claudeArgs.push('-p', options.prompt);
|
|
93
93
|
|
|
94
94
|
// Create tmux session and run claude
|
|
95
|
-
//
|
|
96
|
-
const
|
|
97
|
-
const claudeCmd = `${cleanEnv} ${this.config.claudePath} ${claudeArgs.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ')}`;
|
|
95
|
+
// Respect the user's configured auth method (API key or OAuth subscription)
|
|
96
|
+
const claudeCmd = `${this.config.claudePath} ${claudeArgs.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ')}`;
|
|
98
97
|
const tmuxCmd = [
|
|
99
98
|
this.config.tmuxPath,
|
|
100
99
|
'new-session',
|
|
@@ -269,9 +268,9 @@ export class SessionManager extends EventEmitter {
|
|
|
269
268
|
return tmuxSession;
|
|
270
269
|
}
|
|
271
270
|
|
|
272
|
-
//
|
|
271
|
+
// Respect the user's configured auth method (API key or OAuth subscription)
|
|
273
272
|
const claudeCmd = `${this.config.claudePath} --dangerously-skip-permissions`;
|
|
274
|
-
const shellCmd = `cd '${this.config.projectDir}' &&
|
|
273
|
+
const shellCmd = `cd '${this.config.projectDir}' && ${claudeCmd}`;
|
|
275
274
|
const tmuxCmd = `${this.config.tmuxPath} new-session -d -s '${tmuxSession}' -x 200 -y 50 'bash -c "${shellCmd.replace(/"/g, '\\"')}"'`;
|
|
276
275
|
|
|
277
276
|
try {
|