instar 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.
- package/.claude/settings.local.json +7 -0
- package/.claude/skills/setup-wizard/skill.md +343 -0
- package/.github/workflows/ci.yml +78 -0
- package/CLAUDE.md +82 -0
- package/README.md +194 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +141 -0
- package/dist/commands/init.d.ts +40 -0
- package/dist/commands/init.js +568 -0
- package/dist/commands/job.d.ts +20 -0
- package/dist/commands/job.js +84 -0
- package/dist/commands/server.d.ts +19 -0
- package/dist/commands/server.js +273 -0
- package/dist/commands/setup.d.ts +24 -0
- package/dist/commands/setup.js +865 -0
- package/dist/commands/status.d.ts +11 -0
- package/dist/commands/status.js +114 -0
- package/dist/commands/user.d.ts +17 -0
- package/dist/commands/user.js +53 -0
- package/dist/core/Config.d.ts +16 -0
- package/dist/core/Config.js +144 -0
- package/dist/core/Prerequisites.d.ts +28 -0
- package/dist/core/Prerequisites.js +159 -0
- package/dist/core/RelationshipManager.d.ts +73 -0
- package/dist/core/RelationshipManager.js +318 -0
- package/dist/core/SessionManager.d.ts +89 -0
- package/dist/core/SessionManager.js +326 -0
- package/dist/core/StateManager.d.ts +28 -0
- package/dist/core/StateManager.js +96 -0
- package/dist/core/types.d.ts +279 -0
- package/dist/core/types.js +8 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +23 -0
- package/dist/messaging/TelegramAdapter.d.ts +73 -0
- package/dist/messaging/TelegramAdapter.js +288 -0
- package/dist/monitoring/HealthChecker.d.ts +38 -0
- package/dist/monitoring/HealthChecker.js +148 -0
- package/dist/scaffold/bootstrap.d.ts +21 -0
- package/dist/scaffold/bootstrap.js +110 -0
- package/dist/scaffold/templates.d.ts +34 -0
- package/dist/scaffold/templates.js +187 -0
- package/dist/scheduler/JobLoader.d.ts +18 -0
- package/dist/scheduler/JobLoader.js +70 -0
- package/dist/scheduler/JobScheduler.d.ts +111 -0
- package/dist/scheduler/JobScheduler.js +402 -0
- package/dist/server/AgentServer.d.ts +40 -0
- package/dist/server/AgentServer.js +73 -0
- package/dist/server/middleware.d.ts +12 -0
- package/dist/server/middleware.js +50 -0
- package/dist/server/routes.d.ts +25 -0
- package/dist/server/routes.js +224 -0
- package/dist/users/UserManager.d.ts +45 -0
- package/dist/users/UserManager.js +113 -0
- package/docs/dawn-audit-report.md +412 -0
- package/docs/positioning-vs-openclaw.md +246 -0
- package/package.json +52 -0
- package/src/cli.ts +169 -0
- package/src/commands/init.ts +654 -0
- package/src/commands/job.ts +110 -0
- package/src/commands/server.ts +325 -0
- package/src/commands/setup.ts +958 -0
- package/src/commands/status.ts +125 -0
- package/src/commands/user.ts +71 -0
- package/src/core/Config.ts +161 -0
- package/src/core/Prerequisites.ts +187 -0
- package/src/core/RelationshipManager.ts +366 -0
- package/src/core/SessionManager.ts +385 -0
- package/src/core/StateManager.ts +121 -0
- package/src/core/types.ts +320 -0
- package/src/index.ts +58 -0
- package/src/messaging/TelegramAdapter.ts +365 -0
- package/src/monitoring/HealthChecker.ts +172 -0
- package/src/scaffold/bootstrap.ts +122 -0
- package/src/scaffold/templates.ts +204 -0
- package/src/scheduler/JobLoader.ts +85 -0
- package/src/scheduler/JobScheduler.ts +476 -0
- package/src/server/AgentServer.ts +93 -0
- package/src/server/middleware.ts +58 -0
- package/src/server/routes.ts +278 -0
- package/src/templates/default-jobs.json +47 -0
- package/src/templates/hooks/compaction-recovery.sh +23 -0
- package/src/templates/hooks/dangerous-command-guard.sh +35 -0
- package/src/templates/hooks/grounding-before-messaging.sh +22 -0
- package/src/templates/hooks/session-start.sh +37 -0
- package/src/templates/hooks/settings-template.json +45 -0
- package/src/templates/scripts/health-watchdog.sh +63 -0
- package/src/templates/scripts/telegram-reply.sh +54 -0
- package/src/users/UserManager.ts +129 -0
- package/tests/e2e/lifecycle.test.ts +376 -0
- package/tests/fixtures/test-repo/CLAUDE.md +3 -0
- package/tests/fixtures/test-repo/README.md +1 -0
- package/tests/helpers/setup.ts +209 -0
- package/tests/integration/fresh-install.test.ts +218 -0
- package/tests/integration/scheduler-basic.test.ts +109 -0
- package/tests/integration/server-full.test.ts +284 -0
- package/tests/integration/session-lifecycle.test.ts +181 -0
- package/tests/unit/Config.test.ts +22 -0
- package/tests/unit/HealthChecker.test.ts +168 -0
- package/tests/unit/JobLoader.test.ts +151 -0
- package/tests/unit/JobScheduler.test.ts +267 -0
- package/tests/unit/Prerequisites.test.ts +59 -0
- package/tests/unit/RelationshipManager.test.ts +345 -0
- package/tests/unit/StateManager.test.ts +143 -0
- package/tests/unit/TelegramAdapter.test.ts +165 -0
- package/tests/unit/UserManager.test.ts +131 -0
- package/tests/unit/bootstrap.test.ts +28 -0
- package/tests/unit/commands.test.ts +138 -0
- package/tests/unit/middleware.test.ts +92 -0
- package/tests/unit/relationship-routes.test.ts +131 -0
- package/tests/unit/scaffold-templates.test.ts +132 -0
- package/tests/unit/server.test.ts +163 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +9 -0
- package/vitest.e2e.config.ts +9 -0
- package/vitest.integration.config.ts +9 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `instar status` — Show agent infrastructure status.
|
|
3
|
+
*
|
|
4
|
+
* Checks for: config, tmux, server, sessions, scheduler.
|
|
5
|
+
*/
|
|
6
|
+
interface StatusOptions {
|
|
7
|
+
dir?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function showStatus(options: StatusOptions): Promise<void>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `instar status` — Show agent infrastructure status.
|
|
3
|
+
*
|
|
4
|
+
* Checks for: config, tmux, server, sessions, scheduler.
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import pc from 'picocolors';
|
|
8
|
+
import { loadConfig, detectTmuxPath } from '../core/Config.js';
|
|
9
|
+
import { StateManager } from '../core/StateManager.js';
|
|
10
|
+
export async function showStatus(options) {
|
|
11
|
+
let config;
|
|
12
|
+
try {
|
|
13
|
+
config = loadConfig(options.dir);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
console.log(pc.red(`Not initialized: ${err.message}`));
|
|
17
|
+
console.log(`Run ${pc.cyan('instar init')} first.`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(pc.bold(`\nInstar Status: ${pc.cyan(config.projectName)}`));
|
|
21
|
+
console.log(` Project: ${config.projectDir}`);
|
|
22
|
+
console.log(` State: ${config.stateDir}`);
|
|
23
|
+
console.log();
|
|
24
|
+
// Server status
|
|
25
|
+
const serverSessionName = `${config.projectName}-server`;
|
|
26
|
+
const tmuxPath = detectTmuxPath();
|
|
27
|
+
let serverRunning = false;
|
|
28
|
+
if (tmuxPath) {
|
|
29
|
+
try {
|
|
30
|
+
execSync(`${tmuxPath} has-session -t '=${serverSessionName}' 2>/dev/null`);
|
|
31
|
+
serverRunning = true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// not running
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
console.log(pc.bold(' Server:'));
|
|
38
|
+
if (serverRunning) {
|
|
39
|
+
console.log(` ${pc.green('●')} Running (tmux: ${serverSessionName}, port: ${config.port})`);
|
|
40
|
+
// Try to hit health endpoint
|
|
41
|
+
try {
|
|
42
|
+
const resp = execSync(`curl -s http://localhost:${config.port}/health`, { encoding: 'utf-8', timeout: 3000 });
|
|
43
|
+
const health = JSON.parse(resp);
|
|
44
|
+
console.log(` Uptime: ${health.uptimeHuman}`);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
console.log(` ${pc.yellow('Could not reach health endpoint')}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(` ${pc.red('●')} Not running`);
|
|
52
|
+
}
|
|
53
|
+
// Sessions
|
|
54
|
+
const state = new StateManager(config.stateDir);
|
|
55
|
+
const runningSessions = state.listSessions({ status: 'running' });
|
|
56
|
+
const allSessions = state.listSessions();
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(pc.bold(' Sessions:'));
|
|
59
|
+
console.log(` Running: ${runningSessions.length} / ${config.sessions.maxSessions} max`);
|
|
60
|
+
console.log(` Total: ${allSessions.length}`);
|
|
61
|
+
if (runningSessions.length > 0) {
|
|
62
|
+
for (const s of runningSessions) {
|
|
63
|
+
const age = timeSince(new Date(s.startedAt));
|
|
64
|
+
console.log(` ${pc.green('●')} ${s.name} (${age}${s.jobSlug ? `, job: ${s.jobSlug}` : ''})`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Scheduler
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(pc.bold(' Scheduler:'));
|
|
70
|
+
console.log(` Enabled: ${config.scheduler.enabled ? pc.green('yes') : pc.dim('no')}`);
|
|
71
|
+
// Jobs
|
|
72
|
+
try {
|
|
73
|
+
const { loadJobs } = await import('../scheduler/JobLoader.js');
|
|
74
|
+
const jobs = loadJobs(config.scheduler.jobsFile);
|
|
75
|
+
const enabled = jobs.filter(j => j.enabled);
|
|
76
|
+
console.log(` Jobs: ${enabled.length} enabled / ${jobs.length} total`);
|
|
77
|
+
if (jobs.length > 0) {
|
|
78
|
+
for (const job of jobs) {
|
|
79
|
+
const jobState = state.getJobState(job.slug);
|
|
80
|
+
const icon = job.enabled ? pc.green('●') : pc.dim('○');
|
|
81
|
+
const lastRun = jobState?.lastRun ? timeSince(new Date(jobState.lastRun)) + ' ago' : 'never';
|
|
82
|
+
console.log(` ${icon} ${job.slug} [${job.priority}] — last: ${lastRun}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
console.log(` ${pc.dim('No jobs configured')}`);
|
|
88
|
+
}
|
|
89
|
+
// Recent activity
|
|
90
|
+
const recentEvents = state.queryEvents({ limit: 5 });
|
|
91
|
+
if (recentEvents.length > 0) {
|
|
92
|
+
console.log();
|
|
93
|
+
console.log(pc.bold(' Recent Activity:'));
|
|
94
|
+
for (const event of recentEvents) {
|
|
95
|
+
const age = timeSince(new Date(event.timestamp));
|
|
96
|
+
console.log(` ${pc.dim(age + ' ago')} ${event.type}: ${event.summary}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
console.log();
|
|
100
|
+
}
|
|
101
|
+
function timeSince(date) {
|
|
102
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
103
|
+
if (seconds < 60)
|
|
104
|
+
return `${seconds}s`;
|
|
105
|
+
const minutes = Math.floor(seconds / 60);
|
|
106
|
+
if (minutes < 60)
|
|
107
|
+
return `${minutes}m`;
|
|
108
|
+
const hours = Math.floor(minutes / 60);
|
|
109
|
+
if (hours < 24)
|
|
110
|
+
return `${hours}h`;
|
|
111
|
+
const days = Math.floor(hours / 24);
|
|
112
|
+
return `${days}d`;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `instar user add|list` — Manage user profiles.
|
|
3
|
+
*/
|
|
4
|
+
interface UserAddOptions {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
telegram?: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
slack?: string;
|
|
10
|
+
permissions?: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function addUser(options: UserAddOptions): Promise<void>;
|
|
13
|
+
export declare function listUsers(_options: {
|
|
14
|
+
dir?: string;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=user.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `instar user add|list` — Manage user profiles.
|
|
3
|
+
*/
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { loadConfig, ensureStateDir } from '../core/Config.js';
|
|
6
|
+
import { UserManager } from '../users/UserManager.js';
|
|
7
|
+
export async function addUser(options) {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
ensureStateDir(config.stateDir);
|
|
10
|
+
const userManager = new UserManager(config.stateDir);
|
|
11
|
+
const channels = [];
|
|
12
|
+
if (options.telegram) {
|
|
13
|
+
channels.push({ type: 'telegram', identifier: options.telegram });
|
|
14
|
+
}
|
|
15
|
+
if (options.email) {
|
|
16
|
+
channels.push({ type: 'email', identifier: options.email });
|
|
17
|
+
}
|
|
18
|
+
if (options.slack) {
|
|
19
|
+
channels.push({ type: 'slack', identifier: options.slack });
|
|
20
|
+
}
|
|
21
|
+
const profile = {
|
|
22
|
+
id: options.id,
|
|
23
|
+
name: options.name,
|
|
24
|
+
channels,
|
|
25
|
+
permissions: options.permissions || ['user'],
|
|
26
|
+
preferences: {},
|
|
27
|
+
};
|
|
28
|
+
userManager.upsertUser(profile);
|
|
29
|
+
console.log(pc.green(`User "${options.name}" (${options.id}) added.`));
|
|
30
|
+
if (channels.length > 0) {
|
|
31
|
+
console.log(` Channels: ${channels.map(c => `${c.type}:${c.identifier}`).join(', ')}`);
|
|
32
|
+
}
|
|
33
|
+
console.log(` Permissions: ${profile.permissions.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
export async function listUsers(_options) {
|
|
36
|
+
const config = loadConfig();
|
|
37
|
+
const userManager = new UserManager(config.stateDir);
|
|
38
|
+
const users = userManager.listUsers();
|
|
39
|
+
if (users.length === 0) {
|
|
40
|
+
console.log(pc.dim('No users configured.'));
|
|
41
|
+
console.log(`Add one: ${pc.cyan('instar user add --id justin --name Justin')}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(pc.bold(`Users (${users.length}):\n`));
|
|
45
|
+
for (const user of users) {
|
|
46
|
+
console.log(` ${pc.bold(user.name)} (${pc.dim(user.id)})`);
|
|
47
|
+
if (user.channels.length > 0) {
|
|
48
|
+
console.log(` Channels: ${user.channels.map(c => `${c.type}:${c.identifier}`).join(', ')}`);
|
|
49
|
+
}
|
|
50
|
+
console.log(` Permissions: ${user.permissions.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=user.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-detection and configuration management.
|
|
3
|
+
*
|
|
4
|
+
* Finds tmux, Claude CLI, and project structure automatically.
|
|
5
|
+
* Adapted from dawn-server's config.ts — the battle-tested version.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentKitConfig } from './types.js';
|
|
8
|
+
export declare function detectTmuxPath(): string | null;
|
|
9
|
+
export declare function detectClaudePath(): string | null;
|
|
10
|
+
export declare function detectProjectDir(startDir?: string): string;
|
|
11
|
+
export declare function loadConfig(projectDir?: string): AgentKitConfig;
|
|
12
|
+
/**
|
|
13
|
+
* Ensure the state directory structure exists.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ensureStateDir(stateDir: string): void;
|
|
16
|
+
//# sourceMappingURL=Config.d.ts.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-detection and configuration management.
|
|
3
|
+
*
|
|
4
|
+
* Finds tmux, Claude CLI, and project structure automatically.
|
|
5
|
+
* Adapted from dawn-server's config.ts — the battle-tested version.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
const DEFAULT_PORT = 4040;
|
|
11
|
+
const DEFAULT_MAX_SESSIONS = 3;
|
|
12
|
+
const DEFAULT_MAX_PARALLEL_JOBS = 2;
|
|
13
|
+
export function detectTmuxPath() {
|
|
14
|
+
const candidates = [
|
|
15
|
+
'/opt/homebrew/bin/tmux', // macOS ARM (Homebrew)
|
|
16
|
+
'/usr/local/bin/tmux', // macOS Intel / Linux
|
|
17
|
+
'/usr/bin/tmux', // Linux system
|
|
18
|
+
];
|
|
19
|
+
for (const candidate of candidates) {
|
|
20
|
+
if (fs.existsSync(candidate))
|
|
21
|
+
return candidate;
|
|
22
|
+
}
|
|
23
|
+
// Fallback: check PATH
|
|
24
|
+
try {
|
|
25
|
+
const result = execSync('which tmux', { encoding: 'utf-8' }).trim();
|
|
26
|
+
if (result && fs.existsSync(result))
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// tmux not found
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
export function detectClaudePath() {
|
|
35
|
+
const candidates = [
|
|
36
|
+
path.join(process.env.HOME || '', '.claude', 'local', 'claude'),
|
|
37
|
+
'/usr/local/bin/claude',
|
|
38
|
+
'/opt/homebrew/bin/claude',
|
|
39
|
+
];
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (fs.existsSync(candidate))
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
// Fallback: check PATH
|
|
45
|
+
try {
|
|
46
|
+
const result = execSync('which claude', { encoding: 'utf-8' }).trim();
|
|
47
|
+
if (result && fs.existsSync(result))
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// claude not found
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
export function detectProjectDir(startDir) {
|
|
56
|
+
let dir = startDir || process.cwd();
|
|
57
|
+
// Walk up to find a directory with CLAUDE.md or .git
|
|
58
|
+
while (dir !== path.dirname(dir)) {
|
|
59
|
+
if (fs.existsSync(path.join(dir, 'CLAUDE.md')) || fs.existsSync(path.join(dir, '.git'))) {
|
|
60
|
+
return dir;
|
|
61
|
+
}
|
|
62
|
+
dir = path.dirname(dir);
|
|
63
|
+
}
|
|
64
|
+
return process.cwd();
|
|
65
|
+
}
|
|
66
|
+
export function loadConfig(projectDir) {
|
|
67
|
+
const resolvedProjectDir = projectDir || detectProjectDir();
|
|
68
|
+
const configPath = path.join(resolvedProjectDir, '.instar', 'config.json');
|
|
69
|
+
const stateDir = path.join(resolvedProjectDir, '.instar');
|
|
70
|
+
// Load config file if it exists
|
|
71
|
+
let fileConfig = {};
|
|
72
|
+
if (fs.existsSync(configPath)) {
|
|
73
|
+
fileConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
74
|
+
}
|
|
75
|
+
const tmuxPath = detectTmuxPath();
|
|
76
|
+
const claudePath = detectClaudePath();
|
|
77
|
+
if (!tmuxPath) {
|
|
78
|
+
throw new Error('tmux not found. Install with: brew install tmux (macOS) or apt install tmux (Linux)');
|
|
79
|
+
}
|
|
80
|
+
if (!claudePath) {
|
|
81
|
+
throw new Error('Claude CLI not found. Install from: https://docs.anthropic.com/en/docs/claude-code');
|
|
82
|
+
}
|
|
83
|
+
const projectName = fileConfig.projectName || path.basename(resolvedProjectDir);
|
|
84
|
+
const sessions = {
|
|
85
|
+
tmuxPath,
|
|
86
|
+
claudePath,
|
|
87
|
+
projectDir: resolvedProjectDir,
|
|
88
|
+
maxSessions: fileConfig.sessions?.maxSessions || DEFAULT_MAX_SESSIONS,
|
|
89
|
+
protectedSessions: fileConfig.sessions?.protectedSessions || [`${projectName}-server`],
|
|
90
|
+
completionPatterns: fileConfig.sessions?.completionPatterns || [
|
|
91
|
+
'has been automatically paused',
|
|
92
|
+
'Session ended',
|
|
93
|
+
'Interrupted by user',
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
const scheduler = {
|
|
97
|
+
jobsFile: fileConfig.scheduler?.jobsFile || path.join(stateDir, 'jobs.json'),
|
|
98
|
+
enabled: fileConfig.scheduler?.enabled ?? false,
|
|
99
|
+
maxParallelJobs: fileConfig.scheduler?.maxParallelJobs ?? DEFAULT_MAX_PARALLEL_JOBS,
|
|
100
|
+
quotaThresholds: fileConfig.scheduler?.quotaThresholds || {
|
|
101
|
+
normal: 50,
|
|
102
|
+
elevated: 70,
|
|
103
|
+
critical: 85,
|
|
104
|
+
shutdown: 95,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
projectName,
|
|
109
|
+
projectDir: resolvedProjectDir,
|
|
110
|
+
stateDir,
|
|
111
|
+
port: fileConfig.port || DEFAULT_PORT,
|
|
112
|
+
sessions,
|
|
113
|
+
scheduler,
|
|
114
|
+
users: fileConfig.users || [],
|
|
115
|
+
messaging: fileConfig.messaging || [],
|
|
116
|
+
monitoring: fileConfig.monitoring || {
|
|
117
|
+
quotaTracking: true,
|
|
118
|
+
memoryMonitoring: true,
|
|
119
|
+
healthCheckIntervalMs: 30000,
|
|
120
|
+
},
|
|
121
|
+
authToken: fileConfig.authToken,
|
|
122
|
+
relationships: fileConfig.relationships || {
|
|
123
|
+
relationshipsDir: path.join(stateDir, 'relationships'),
|
|
124
|
+
maxRecentInteractions: 20,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Ensure the state directory structure exists.
|
|
130
|
+
*/
|
|
131
|
+
export function ensureStateDir(stateDir) {
|
|
132
|
+
const dirs = [
|
|
133
|
+
stateDir,
|
|
134
|
+
path.join(stateDir, 'state'),
|
|
135
|
+
path.join(stateDir, 'state', 'sessions'),
|
|
136
|
+
path.join(stateDir, 'state', 'jobs'),
|
|
137
|
+
path.join(stateDir, 'relationships'),
|
|
138
|
+
path.join(stateDir, 'logs'),
|
|
139
|
+
];
|
|
140
|
+
for (const dir of dirs) {
|
|
141
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=Config.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prerequisite detection and installation guidance.
|
|
3
|
+
*
|
|
4
|
+
* Checks for required software (tmux, Claude CLI, Node.js)
|
|
5
|
+
* and provides clear installation instructions when something is missing.
|
|
6
|
+
*/
|
|
7
|
+
export interface PrerequisiteResult {
|
|
8
|
+
name: string;
|
|
9
|
+
found: boolean;
|
|
10
|
+
path?: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
installHint: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PrerequisiteCheck {
|
|
15
|
+
allMet: boolean;
|
|
16
|
+
results: PrerequisiteResult[];
|
|
17
|
+
missing: PrerequisiteResult[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check all prerequisites and return a structured result.
|
|
21
|
+
*/
|
|
22
|
+
export declare function checkPrerequisites(): PrerequisiteCheck;
|
|
23
|
+
/**
|
|
24
|
+
* Print prerequisite check results to console.
|
|
25
|
+
* Returns true if all prerequisites are met.
|
|
26
|
+
*/
|
|
27
|
+
export declare function printPrerequisiteCheck(check: PrerequisiteCheck): boolean;
|
|
28
|
+
//# sourceMappingURL=Prerequisites.d.ts.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prerequisite detection and installation guidance.
|
|
3
|
+
*
|
|
4
|
+
* Checks for required software (tmux, Claude CLI, Node.js)
|
|
5
|
+
* and provides clear installation instructions when something is missing.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { detectTmuxPath, detectClaudePath } from './Config.js';
|
|
10
|
+
/**
|
|
11
|
+
* Detect the current platform for install guidance.
|
|
12
|
+
*/
|
|
13
|
+
function detectPlatform() {
|
|
14
|
+
const platform = process.platform;
|
|
15
|
+
if (platform === 'darwin') {
|
|
16
|
+
const arch = process.arch;
|
|
17
|
+
return arch === 'arm64' ? 'macos-arm' : 'macos-intel';
|
|
18
|
+
}
|
|
19
|
+
if (platform === 'linux')
|
|
20
|
+
return 'linux';
|
|
21
|
+
return 'unknown';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if Homebrew is available (macOS).
|
|
25
|
+
*/
|
|
26
|
+
function hasHomebrew() {
|
|
27
|
+
try {
|
|
28
|
+
execSync('which brew', { encoding: 'utf-8', stdio: 'pipe' });
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get tmux version if installed.
|
|
37
|
+
*/
|
|
38
|
+
function getTmuxVersion(tmuxPath) {
|
|
39
|
+
try {
|
|
40
|
+
const output = execSync(`${tmuxPath} -V`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
41
|
+
return output.replace('tmux ', '');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get Claude CLI version if installed.
|
|
49
|
+
*/
|
|
50
|
+
function getClaudeVersion(claudePath) {
|
|
51
|
+
try {
|
|
52
|
+
const output = execSync(`${claudePath} --version 2>/dev/null || echo unknown`, {
|
|
53
|
+
encoding: 'utf-8',
|
|
54
|
+
stdio: 'pipe',
|
|
55
|
+
timeout: 5000,
|
|
56
|
+
}).trim();
|
|
57
|
+
return output || undefined;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get Node.js version.
|
|
65
|
+
*/
|
|
66
|
+
function getNodeVersion() {
|
|
67
|
+
const version = process.version; // e.g., "v20.11.0"
|
|
68
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
69
|
+
return { version, major };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build install hint for tmux based on platform.
|
|
73
|
+
*/
|
|
74
|
+
function tmuxInstallHint() {
|
|
75
|
+
const platform = detectPlatform();
|
|
76
|
+
switch (platform) {
|
|
77
|
+
case 'macos-arm':
|
|
78
|
+
case 'macos-intel':
|
|
79
|
+
return hasHomebrew()
|
|
80
|
+
? 'Install with: brew install tmux'
|
|
81
|
+
: 'Install Homebrew first (https://brew.sh), then: brew install tmux';
|
|
82
|
+
case 'linux':
|
|
83
|
+
return 'Install with: sudo apt install tmux (Debian/Ubuntu) or sudo yum install tmux (RHEL/CentOS)';
|
|
84
|
+
default:
|
|
85
|
+
return 'Install tmux: https://github.com/tmux/tmux/wiki/Installing';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build install hint for Claude CLI based on platform.
|
|
90
|
+
*/
|
|
91
|
+
function claudeInstallHint() {
|
|
92
|
+
return 'Install Claude Code: npm install -g @anthropic-ai/claude-code\n Docs: https://docs.anthropic.com/en/docs/claude-code';
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check all prerequisites and return a structured result.
|
|
96
|
+
*/
|
|
97
|
+
export function checkPrerequisites() {
|
|
98
|
+
const results = [];
|
|
99
|
+
// 1. Node.js >= 18
|
|
100
|
+
const node = getNodeVersion();
|
|
101
|
+
results.push({
|
|
102
|
+
name: 'Node.js',
|
|
103
|
+
found: node.major >= 18,
|
|
104
|
+
version: node.version,
|
|
105
|
+
installHint: node.major < 18
|
|
106
|
+
? `Node.js 18+ required (found ${node.version}). Update: https://nodejs.org`
|
|
107
|
+
: '',
|
|
108
|
+
});
|
|
109
|
+
// 2. tmux
|
|
110
|
+
const tmuxPath = detectTmuxPath();
|
|
111
|
+
results.push({
|
|
112
|
+
name: 'tmux',
|
|
113
|
+
found: !!tmuxPath,
|
|
114
|
+
path: tmuxPath || undefined,
|
|
115
|
+
version: tmuxPath ? getTmuxVersion(tmuxPath) : undefined,
|
|
116
|
+
installHint: tmuxInstallHint(),
|
|
117
|
+
});
|
|
118
|
+
// 3. Claude CLI
|
|
119
|
+
const claudePath = detectClaudePath();
|
|
120
|
+
results.push({
|
|
121
|
+
name: 'Claude CLI',
|
|
122
|
+
found: !!claudePath,
|
|
123
|
+
path: claudePath || undefined,
|
|
124
|
+
version: claudePath ? getClaudeVersion(claudePath) : undefined,
|
|
125
|
+
installHint: claudeInstallHint(),
|
|
126
|
+
});
|
|
127
|
+
const missing = results.filter(r => !r.found);
|
|
128
|
+
return {
|
|
129
|
+
allMet: missing.length === 0,
|
|
130
|
+
results,
|
|
131
|
+
missing,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Print prerequisite check results to console.
|
|
136
|
+
* Returns true if all prerequisites are met.
|
|
137
|
+
*/
|
|
138
|
+
export function printPrerequisiteCheck(check) {
|
|
139
|
+
console.log(pc.bold(' Checking prerequisites...'));
|
|
140
|
+
console.log();
|
|
141
|
+
for (const result of check.results) {
|
|
142
|
+
if (result.found) {
|
|
143
|
+
const versionStr = result.version ? ` (${result.version})` : '';
|
|
144
|
+
const pathStr = result.path ? pc.dim(` ${result.path}`) : '';
|
|
145
|
+
console.log(` ${pc.green('✓')} ${result.name}${versionStr}${pathStr}`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.log(` ${pc.red('✗')} ${result.name} — not found`);
|
|
149
|
+
console.log(` ${result.installHint}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
console.log();
|
|
153
|
+
if (!check.allMet) {
|
|
154
|
+
console.log(pc.red(` ${check.missing.length} prerequisite(s) missing. Install them and try again.`));
|
|
155
|
+
console.log();
|
|
156
|
+
}
|
|
157
|
+
return check.allMet;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=Prerequisites.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelationshipManager — Core system for tracking everyone the agent interacts with.
|
|
3
|
+
*
|
|
4
|
+
* Relationships are fundamental, not a plugin. Same tier as identity and memory.
|
|
5
|
+
* Every person the agent interacts with — across any channel/platform — gets a
|
|
6
|
+
* relationship record that grows over time.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - One JSON file per person in .instar/relationships/
|
|
10
|
+
* - Cross-platform identity resolution via channel index
|
|
11
|
+
* - Auto-enrichment from every interaction
|
|
12
|
+
* - Context injection before any interaction with a known person
|
|
13
|
+
*/
|
|
14
|
+
import type { RelationshipRecord, RelationshipManagerConfig, InteractionSummary, UserChannel } from './types.js';
|
|
15
|
+
export declare class RelationshipManager {
|
|
16
|
+
private relationships;
|
|
17
|
+
/** Maps "channel_type:identifier" -> relationship ID for cross-platform resolution */
|
|
18
|
+
private channelIndex;
|
|
19
|
+
private config;
|
|
20
|
+
constructor(config: RelationshipManagerConfig);
|
|
21
|
+
/**
|
|
22
|
+
* Find or create a relationship from an incoming interaction.
|
|
23
|
+
* Resolves cross-platform: if the same person messages from Telegram and email,
|
|
24
|
+
* this returns the same relationship.
|
|
25
|
+
*/
|
|
26
|
+
findOrCreate(name: string, channel: UserChannel): RelationshipRecord;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a channel identifier to an existing relationship, or null.
|
|
29
|
+
*/
|
|
30
|
+
resolveByChannel(channel: UserChannel): RelationshipRecord | null;
|
|
31
|
+
/**
|
|
32
|
+
* Get a relationship by ID.
|
|
33
|
+
*/
|
|
34
|
+
get(id: string): RelationshipRecord | null;
|
|
35
|
+
/**
|
|
36
|
+
* Get all relationships, optionally sorted by significance or recency.
|
|
37
|
+
*/
|
|
38
|
+
getAll(sortBy?: 'significance' | 'recent' | 'name'): RelationshipRecord[];
|
|
39
|
+
/**
|
|
40
|
+
* Record an interaction with a person. Updates recency, count, and interaction log.
|
|
41
|
+
*/
|
|
42
|
+
recordInteraction(id: string, interaction: InteractionSummary): void;
|
|
43
|
+
/**
|
|
44
|
+
* Update notes or other metadata for a relationship.
|
|
45
|
+
*/
|
|
46
|
+
updateNotes(id: string, notes: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Update the arc summary for a relationship.
|
|
49
|
+
*/
|
|
50
|
+
updateArcSummary(id: string, arcSummary: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Link a new channel to an existing relationship (cross-platform identity merge).
|
|
53
|
+
*/
|
|
54
|
+
linkChannel(id: string, channel: UserChannel): void;
|
|
55
|
+
/**
|
|
56
|
+
* Merge two relationship records (when we discover two channels are the same person).
|
|
57
|
+
*/
|
|
58
|
+
mergeRelationships(keepId: string, mergeId: string): void;
|
|
59
|
+
/**
|
|
60
|
+
* Generate context string for injection into a Claude session before interacting
|
|
61
|
+
* with a known person. This is what makes the agent "know" who it's talking to.
|
|
62
|
+
*/
|
|
63
|
+
getContextForPerson(id: string): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Find relationships that haven't been contacted in a while.
|
|
66
|
+
*/
|
|
67
|
+
getStaleRelationships(daysThreshold?: number): RelationshipRecord[];
|
|
68
|
+
private loadAll;
|
|
69
|
+
private save;
|
|
70
|
+
private deleteFile;
|
|
71
|
+
private calculateSignificance;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=RelationshipManager.d.ts.map
|