ashral 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/dist/adapters/baseAdapter.d.ts +35 -0
- package/dist/adapters/baseAdapter.d.ts.map +1 -0
- package/dist/adapters/baseAdapter.js +16 -0
- package/dist/adapters/baseAdapter.js.map +1 -0
- package/dist/adapters/claudeAdapter.d.ts +9 -0
- package/dist/adapters/claudeAdapter.d.ts.map +1 -0
- package/dist/adapters/claudeAdapter.js +88 -0
- package/dist/adapters/claudeAdapter.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +119 -0
- package/dist/cli.js.map +1 -0
- package/dist/notifications/notifier.d.ts +16 -0
- package/dist/notifications/notifier.d.ts.map +1 -0
- package/dist/notifications/notifier.js +3 -0
- package/dist/notifications/notifier.js.map +1 -0
- package/dist/notifications/ntfyNotifier.d.ts +7 -0
- package/dist/notifications/ntfyNotifier.d.ts.map +1 -0
- package/dist/notifications/ntfyNotifier.js +59 -0
- package/dist/notifications/ntfyNotifier.js.map +1 -0
- package/dist/runner/runSession.d.ts +15 -0
- package/dist/runner/runSession.d.ts.map +1 -0
- package/dist/runner/runSession.js +120 -0
- package/dist/runner/runSession.js.map +1 -0
- package/dist/runner/sessionState.d.ts +15 -0
- package/dist/runner/sessionState.d.ts.map +1 -0
- package/dist/runner/sessionState.js +54 -0
- package/dist/runner/sessionState.js.map +1 -0
- package/dist/types/events.d.ts +31 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +3 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/session.d.ts +11 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +3 -0
- package/dist/types/session.js.map +1 -0
- package/package.json +33 -0
- package/scripts/postinstall.js +23 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SessionStatus } from '../types/session';
|
|
2
|
+
export interface AdapterCommand {
|
|
3
|
+
/** The executable to spawn (must be on PATH or an absolute path) */
|
|
4
|
+
command: string;
|
|
5
|
+
/** Arguments passed directly to the executable */
|
|
6
|
+
args: string[];
|
|
7
|
+
/** Additional env vars merged into process.env for this session */
|
|
8
|
+
env?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* BaseAdapter defines the contract every agent adapter must implement.
|
|
12
|
+
*
|
|
13
|
+
* Each adapter is responsible for two things:
|
|
14
|
+
* 1. Knowing how to build the spawn command for its agent.
|
|
15
|
+
* 2. Inspecting raw PTY output and mapping it to a session status.
|
|
16
|
+
*
|
|
17
|
+
* Keep all agent-specific knowledge inside its adapter. The runner stays generic.
|
|
18
|
+
*/
|
|
19
|
+
export declare abstract class BaseAdapter {
|
|
20
|
+
/** Human-readable agent identifier, e.g. "claude" */
|
|
21
|
+
abstract readonly agentName: string;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the command and args needed to start the agent.
|
|
24
|
+
* @param passthroughArgs - extra args forwarded from the CLI (after --)
|
|
25
|
+
*/
|
|
26
|
+
abstract getCommand(passthroughArgs: string[]): AdapterCommand;
|
|
27
|
+
/**
|
|
28
|
+
* Inspects a chunk of raw PTY output and returns a new status if a
|
|
29
|
+
* transition should occur, or null to leave the status unchanged.
|
|
30
|
+
*
|
|
31
|
+
* This is called on every data chunk so keep it fast.
|
|
32
|
+
*/
|
|
33
|
+
abstract detectStatus(output: string, currentStatus: SessionStatus): SessionStatus | null;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=baseAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/baseAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,8BAAsB,WAAW;IAC/B,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAEpC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,cAAc;IAE9D;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI;CAC1F"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* BaseAdapter defines the contract every agent adapter must implement.
|
|
6
|
+
*
|
|
7
|
+
* Each adapter is responsible for two things:
|
|
8
|
+
* 1. Knowing how to build the spawn command for its agent.
|
|
9
|
+
* 2. Inspecting raw PTY output and mapping it to a session status.
|
|
10
|
+
*
|
|
11
|
+
* Keep all agent-specific knowledge inside its adapter. The runner stays generic.
|
|
12
|
+
*/
|
|
13
|
+
class BaseAdapter {
|
|
14
|
+
}
|
|
15
|
+
exports.BaseAdapter = BaseAdapter;
|
|
16
|
+
//# sourceMappingURL=baseAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseAdapter.js","sourceRoot":"","sources":["../../src/adapters/baseAdapter.ts"],"names":[],"mappings":";;;AAWA;;;;;;;;GAQG;AACH,MAAsB,WAAW;CAiBhC;AAjBD,kCAiBC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseAdapter, type AdapterCommand } from './baseAdapter';
|
|
2
|
+
import type { SessionStatus } from '../types/session';
|
|
3
|
+
export declare class ClaudeAdapter extends BaseAdapter {
|
|
4
|
+
readonly agentName = "claude";
|
|
5
|
+
getCommand(passthroughArgs: string[]): AdapterCommand;
|
|
6
|
+
detectStatus(raw: string, currentStatus: SessionStatus): SessionStatus | null;
|
|
7
|
+
private matches;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=claudeAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/claudeAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAyDtD,qBAAa,aAAc,SAAQ,WAAW;IAC5C,QAAQ,CAAC,SAAS,YAAY;IAE9B,UAAU,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,cAAc;IAQrD,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI;IAqB7E,OAAO,CAAC,OAAO;CAGhB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClaudeAdapter = void 0;
|
|
4
|
+
const baseAdapter_1 = require("./baseAdapter");
|
|
5
|
+
// Strip ANSI/VT escape sequences so regex patterns match clean text
|
|
6
|
+
const ANSI_RE = /\x1B\[[0-9;]*[A-Za-z]|\x1B[@-_][0-?]*[ -/]*[@-~]/g;
|
|
7
|
+
function stripAnsi(raw) {
|
|
8
|
+
return raw.replace(ANSI_RE, '');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Heuristic patterns extracted from Claude Code's real terminal output.
|
|
12
|
+
*
|
|
13
|
+
* These are intentionally conservative — a false negative (missed transition)
|
|
14
|
+
* is safer than a false positive (wrong state). Tune as you collect more samples.
|
|
15
|
+
*/
|
|
16
|
+
// Claude is prompting the user to approve a tool call or destructive action
|
|
17
|
+
const APPROVAL_PATTERNS = [
|
|
18
|
+
/do you want to/i,
|
|
19
|
+
/allow this/i,
|
|
20
|
+
/\(y\/n\)/i,
|
|
21
|
+
/\[y\/n\]/i,
|
|
22
|
+
/yes\/no/i,
|
|
23
|
+
/press enter to confirm/i,
|
|
24
|
+
/approve|deny/i,
|
|
25
|
+
];
|
|
26
|
+
// Claude is waiting for the user to type a new message
|
|
27
|
+
const WAITING_PATTERNS = [
|
|
28
|
+
/^>\s*$/m, // bare ">" prompt line
|
|
29
|
+
/\?\s*$/m, // line ending with "?" — note: needs `m` flag so $ matches end-of-line, not end-of-string
|
|
30
|
+
/^\s*>\s*\d+\./m, // Claude's numbered-choice menu cursor: "> 1. Option"
|
|
31
|
+
/^\s*\d+\.\s+\S/m, // numbered list of options presented to the user
|
|
32
|
+
/type something/i, // Claude Code's "5. Type something." freeform option
|
|
33
|
+
/what would you like/i,
|
|
34
|
+
/how can i (help|assist)/i,
|
|
35
|
+
/human:\s*$/im,
|
|
36
|
+
];
|
|
37
|
+
// Claude is actively doing work
|
|
38
|
+
const RUNNING_PATTERNS = [
|
|
39
|
+
/running|executing|calling tool/i,
|
|
40
|
+
/writing|creating|updating|deleting/i,
|
|
41
|
+
/reading|fetching|searching/i,
|
|
42
|
+
/thinking\.\.\./i,
|
|
43
|
+
];
|
|
44
|
+
// Something clearly went wrong
|
|
45
|
+
const ERROR_PATTERNS = [
|
|
46
|
+
/^error:/im,
|
|
47
|
+
/fatal error/i,
|
|
48
|
+
/uncaught exception/i,
|
|
49
|
+
/command not found/i,
|
|
50
|
+
/permission denied/i,
|
|
51
|
+
/ENOENT|EACCES|ECONNREFUSED/,
|
|
52
|
+
];
|
|
53
|
+
class ClaudeAdapter extends baseAdapter_1.BaseAdapter {
|
|
54
|
+
constructor() {
|
|
55
|
+
super(...arguments);
|
|
56
|
+
this.agentName = 'claude';
|
|
57
|
+
}
|
|
58
|
+
getCommand(passthroughArgs) {
|
|
59
|
+
return {
|
|
60
|
+
command: 'claude',
|
|
61
|
+
args: passthroughArgs,
|
|
62
|
+
// No extra env needed — claude picks up the caller's environment
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
detectStatus(raw, currentStatus) {
|
|
66
|
+
// Never transition out of completed/error via output alone
|
|
67
|
+
if (currentStatus === 'completed')
|
|
68
|
+
return null;
|
|
69
|
+
const text = stripAnsi(raw);
|
|
70
|
+
if (this.matches(text, APPROVAL_PATTERNS))
|
|
71
|
+
return 'approval_required';
|
|
72
|
+
if (this.matches(text, ERROR_PATTERNS))
|
|
73
|
+
return 'error';
|
|
74
|
+
if (this.matches(text, WAITING_PATTERNS))
|
|
75
|
+
return 'waiting_for_input';
|
|
76
|
+
// Transition back to running only if we were previously paused
|
|
77
|
+
if (this.matches(text, RUNNING_PATTERNS) &&
|
|
78
|
+
(currentStatus === 'waiting_for_input' || currentStatus === 'approval_required')) {
|
|
79
|
+
return 'running';
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
matches(text, patterns) {
|
|
84
|
+
return patterns.some((p) => p.test(text));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.ClaudeAdapter = ClaudeAdapter;
|
|
88
|
+
//# sourceMappingURL=claudeAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeAdapter.js","sourceRoot":"","sources":["../../src/adapters/claudeAdapter.ts"],"names":[],"mappings":";;;AAAA,+CAAiE;AAGjE,oEAAoE;AACpE,MAAM,OAAO,GAAG,mDAAmD,CAAC;AAEpE,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AAEH,4EAA4E;AAC5E,MAAM,iBAAiB,GAAG;IACxB,iBAAiB;IACjB,aAAa;IACb,WAAW;IACX,WAAW;IACX,UAAU;IACV,yBAAyB;IACzB,eAAe;CAChB,CAAC;AAEF,uDAAuD;AACvD,MAAM,gBAAgB,GAAG;IACvB,SAAS,EAAgB,uBAAuB;IAChD,SAAS,EAAgB,0FAA0F;IACnH,gBAAgB,EAAS,sDAAsD;IAC/E,iBAAiB,EAAQ,iDAAiD;IAC1E,iBAAiB,EAAS,qDAAqD;IAC/E,sBAAsB;IACtB,0BAA0B;IAC1B,cAAc;CACf,CAAC;AAEF,gCAAgC;AAChC,MAAM,gBAAgB,GAAG;IACvB,iCAAiC;IACjC,qCAAqC;IACrC,6BAA6B;IAC7B,iBAAiB;CAClB,CAAC;AAEF,+BAA+B;AAC/B,MAAM,cAAc,GAAG;IACrB,WAAW;IACX,cAAc;IACd,qBAAqB;IACrB,oBAAoB;IACpB,oBAAoB;IACpB,4BAA4B;CAC7B,CAAC;AAEF,MAAa,aAAc,SAAQ,yBAAW;IAA9C;;QACW,cAAS,GAAG,QAAQ,CAAC;IAkChC,CAAC;IAhCC,UAAU,CAAC,eAAyB;QAClC,OAAO;YACL,OAAO,EAAE,QAAQ;YACjB,IAAI,EAAE,eAAe;YACrB,iEAAiE;SAClE,CAAC;IACJ,CAAC;IAED,YAAY,CAAC,GAAW,EAAE,aAA4B;QACpD,2DAA2D;QAC3D,IAAI,aAAa,KAAK,WAAW;YAAE,OAAO,IAAI,CAAC;QAE/C,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAE5B,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,iBAAiB,CAAC;YAAE,OAAO,mBAAmB,CAAC;QACtE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC;YAAE,OAAO,OAAO,CAAC;QACvD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,gBAAgB,CAAC;YAAE,OAAO,mBAAmB,CAAC;QAErE,+DAA+D;QAC/D,IACE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,gBAAgB,CAAC;YACpC,CAAC,aAAa,KAAK,mBAAmB,IAAI,aAAa,KAAK,mBAAmB,CAAC,EAChF,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,OAAO,CAAC,IAAY,EAAE,QAAkB;QAC9C,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5C,CAAC;CACF;AAnCD,sCAmCC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const runSession_1 = require("./runner/runSession");
|
|
6
|
+
const claudeAdapter_1 = require("./adapters/claudeAdapter");
|
|
7
|
+
const ntfyNotifier_1 = require("./notifications/ntfyNotifier");
|
|
8
|
+
// ── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
9
|
+
const DIM = '\x1b[2m';
|
|
10
|
+
const RESET = '\x1b[0m';
|
|
11
|
+
const CYAN = '\x1b[36m';
|
|
12
|
+
const YELLOW = '\x1b[33m';
|
|
13
|
+
const RED = '\x1b[31m';
|
|
14
|
+
const GREEN = '\x1b[32m';
|
|
15
|
+
function timestamp() {
|
|
16
|
+
return new Date().toISOString().split('T')[1].replace('Z', '');
|
|
17
|
+
}
|
|
18
|
+
// ── Event handler factory ────────────────────────────────────────────────────
|
|
19
|
+
// Returns an onEvent callback. Logs to stderr and fires push notifications
|
|
20
|
+
// when Claude needs attention.
|
|
21
|
+
function makeEventHandler(sessionName, notifier) {
|
|
22
|
+
const tag = `${DIM}[ashral]${RESET}`;
|
|
23
|
+
const label = sessionName ? `"${sessionName}"` : 'session';
|
|
24
|
+
return function onEvent(event) {
|
|
25
|
+
// Raw output is already mirrored to stdout — skip it here
|
|
26
|
+
if (event.type === 'output')
|
|
27
|
+
return;
|
|
28
|
+
const ts = `${DIM}${timestamp()}${RESET}`;
|
|
29
|
+
switch (event.type) {
|
|
30
|
+
case 'status_changed': {
|
|
31
|
+
process.stderr.write(`\n${tag} ${ts} ${CYAN}status${RESET} ${event.from} → ${event.to}\n`);
|
|
32
|
+
if (!notifier)
|
|
33
|
+
break;
|
|
34
|
+
// Notify when Claude needs the user's attention
|
|
35
|
+
if (event.to === 'waiting_for_input') {
|
|
36
|
+
notifier.send({
|
|
37
|
+
title: `Ashral - ${label}`,
|
|
38
|
+
body: 'Claude is waiting for your input.',
|
|
39
|
+
priority: 'high',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else if (event.to === 'approval_required') {
|
|
43
|
+
notifier.send({
|
|
44
|
+
title: `Ashral - ${label} [approval]`,
|
|
45
|
+
body: 'Claude needs your approval before continuing.',
|
|
46
|
+
priority: 'urgent',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else if (event.to === 'error') {
|
|
50
|
+
notifier.send({
|
|
51
|
+
title: `Ashral - ${label} [error]`,
|
|
52
|
+
body: 'Claude encountered an error.',
|
|
53
|
+
priority: 'high',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case 'agent_prompt':
|
|
59
|
+
process.stderr.write(`${tag} ${ts} ${YELLOW}prompt${RESET} ${event.prompt}\n`);
|
|
60
|
+
break;
|
|
61
|
+
case 'error':
|
|
62
|
+
process.stderr.write(`${tag} ${ts} ${RED}error${RESET} ${event.message}\n`);
|
|
63
|
+
break;
|
|
64
|
+
case 'completed':
|
|
65
|
+
process.stderr.write(`\n${tag} ${ts} ${GREEN}done${RESET} exit code ${event.exitCode}\n`);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// ── Notifier setup ────────────────────────────────────────────────────────────
|
|
71
|
+
// Resolves a notifier from --notify-url flag or ASHRAL_NTFY_URL env var.
|
|
72
|
+
// Returns null (silent) if neither is set.
|
|
73
|
+
function resolveNotifier(flagUrl) {
|
|
74
|
+
const url = flagUrl ?? process.env.ASHRAL_NTFY_URL;
|
|
75
|
+
if (!url)
|
|
76
|
+
return null;
|
|
77
|
+
return new ntfyNotifier_1.NtfyNotifier(url);
|
|
78
|
+
}
|
|
79
|
+
// ── CLI definition ────────────────────────────────────────────────────────────
|
|
80
|
+
const program = new commander_1.Command();
|
|
81
|
+
program
|
|
82
|
+
.name('ashral')
|
|
83
|
+
.description('Control center for AI coding agents')
|
|
84
|
+
.version('0.1.0');
|
|
85
|
+
const runCmd = program.command('run').description('Run an AI coding agent');
|
|
86
|
+
runCmd
|
|
87
|
+
.command('claude')
|
|
88
|
+
.description('Start a Claude Code session')
|
|
89
|
+
.option('--name <name>', 'human-readable session name')
|
|
90
|
+
.option('--notify-url <url>', 'ntfy.sh topic URL for push notifications (or set ASHRAL_NTFY_URL)')
|
|
91
|
+
.allowUnknownOption()
|
|
92
|
+
.allowExcessArguments()
|
|
93
|
+
.action(async (options, command) => {
|
|
94
|
+
const passthroughArgs = command.args;
|
|
95
|
+
const adapter = new claudeAdapter_1.ClaudeAdapter();
|
|
96
|
+
const notifier = resolveNotifier(options.notifyUrl);
|
|
97
|
+
if (options.name) {
|
|
98
|
+
process.stderr.write(`\n[ashral] Starting session: ${options.name}\n`);
|
|
99
|
+
}
|
|
100
|
+
if (notifier) {
|
|
101
|
+
process.stderr.write(`[ashral] Push notifications enabled.\n`);
|
|
102
|
+
}
|
|
103
|
+
process.stderr.write('\n');
|
|
104
|
+
try {
|
|
105
|
+
await (0, runSession_1.runSession)({
|
|
106
|
+
adapter,
|
|
107
|
+
name: options.name,
|
|
108
|
+
passthroughArgs,
|
|
109
|
+
onEvent: makeEventHandler(options.name, notifier),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
114
|
+
process.stderr.write(`[ashral] Fatal: ${message}\n`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
program.parse(process.argv);
|
|
119
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface NotificationPayload {
|
|
2
|
+
title: string;
|
|
3
|
+
body: string;
|
|
4
|
+
/** Optional urgency hint — providers map this to their own priority scale */
|
|
5
|
+
priority?: 'low' | 'normal' | 'high' | 'urgent';
|
|
6
|
+
/** Optional deep-link or action URL */
|
|
7
|
+
url?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Minimal interface every push provider must satisfy.
|
|
11
|
+
* Implementations should be fire-and-forget (errors logged, never thrown).
|
|
12
|
+
*/
|
|
13
|
+
export interface Notifier {
|
|
14
|
+
send(payload: NotificationPayload): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=notifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifier.d.ts","sourceRoot":"","sources":["../../src/notifications/notifier.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;IAChD,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifier.js","sourceRoot":"","sources":["../../src/notifications/notifier.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Notifier, NotificationPayload } from './notifier';
|
|
2
|
+
export declare class NtfyNotifier implements Notifier {
|
|
3
|
+
private readonly url;
|
|
4
|
+
constructor(url: string);
|
|
5
|
+
send(payload: NotificationPayload): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=ntfyNotifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ntfyNotifier.d.ts","sourceRoot":"","sources":["../../src/notifications/ntfyNotifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA4BhE,qBAAa,YAAa,YAAW,QAAQ;IAC3C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,GAAG,EAAE,MAAM;IAIjB,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CA6BxD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NtfyNotifier = void 0;
|
|
4
|
+
// ntfy.sh priority values: https://docs.ntfy.sh/publish/#message-priority
|
|
5
|
+
const PRIORITY_MAP = {
|
|
6
|
+
low: '2',
|
|
7
|
+
normal: '3',
|
|
8
|
+
high: '4',
|
|
9
|
+
urgent: '5',
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Sends push notifications via ntfy.sh (or any self-hosted ntfy instance).
|
|
13
|
+
*
|
|
14
|
+
* Setup:
|
|
15
|
+
* 1. Install the ntfy app on your phone (iOS / Android — free).
|
|
16
|
+
* 2. Pick a topic name — something unguessable like "ashral-simon-7x3k".
|
|
17
|
+
* 3. Subscribe to that topic in the app.
|
|
18
|
+
* 4. Set ASHRAL_NTFY_URL=https://ntfy.sh/<your-topic> (or pass it to the constructor).
|
|
19
|
+
*
|
|
20
|
+
* No account required for public topics on ntfy.sh.
|
|
21
|
+
* For privacy, run a self-hosted ntfy instance and point the URL there.
|
|
22
|
+
*/
|
|
23
|
+
// HTTP headers must be Latin-1 (ISO-8859-1). Strip anything outside that range
|
|
24
|
+
// so unicode chars like em dashes don't cause a ByteString conversion error.
|
|
25
|
+
function toHeaderSafeAscii(str) {
|
|
26
|
+
return str.replace(/[^\x00-\xFF]/g, '').trim();
|
|
27
|
+
}
|
|
28
|
+
class NtfyNotifier {
|
|
29
|
+
constructor(url) {
|
|
30
|
+
this.url = url;
|
|
31
|
+
}
|
|
32
|
+
async send(payload) {
|
|
33
|
+
const headers = {
|
|
34
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
35
|
+
'Title': toHeaderSafeAscii(payload.title),
|
|
36
|
+
'Priority': PRIORITY_MAP[payload.priority ?? 'normal'],
|
|
37
|
+
};
|
|
38
|
+
if (payload.url) {
|
|
39
|
+
headers['Actions'] = `view, Open, ${payload.url}`;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(this.url, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers,
|
|
45
|
+
body: payload.body,
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
process.stderr.write(`[ashral] ntfy notification failed: HTTP ${res.status} ${res.statusText}\n`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
// Never let a notification failure crash the session
|
|
53
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54
|
+
process.stderr.write(`[ashral] ntfy notification error: ${msg}\n`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.NtfyNotifier = NtfyNotifier;
|
|
59
|
+
//# sourceMappingURL=ntfyNotifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ntfyNotifier.js","sourceRoot":"","sources":["../../src/notifications/ntfyNotifier.ts"],"names":[],"mappings":";;;AAEA,0EAA0E;AAC1E,MAAM,YAAY,GAAiE;IACjF,GAAG,EAAE,GAAG;IACR,MAAM,EAAE,GAAG;IACX,IAAI,EAAE,GAAG;IACT,MAAM,EAAE,GAAG;CACZ,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,+EAA+E;AAC/E,6EAA6E;AAC7E,SAAS,iBAAiB,CAAC,GAAW;IACpC,OAAO,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,MAAa,YAAY;IAGvB,YAAY,GAAW;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA4B;QACrC,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,2BAA2B;YAC3C,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC;YACzC,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC;SACvD,CAAC;QAEF,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAChB,OAAO,CAAC,SAAS,CAAC,GAAG,eAAe,OAAO,CAAC,GAAG,EAAE,CAAC;QACpD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,OAAO,CAAC,IAAI;aACnB,CAAC,CAAC;YAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2CAA2C,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,IAAI,CAC5E,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,qDAAqD;YACrD,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,GAAG,IAAI,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;CACF;AApCD,oCAoCC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BaseAdapter } from '../adapters/baseAdapter';
|
|
2
|
+
import type { AshralEvent } from '../types/events';
|
|
3
|
+
export interface RunSessionOptions {
|
|
4
|
+
adapter: BaseAdapter;
|
|
5
|
+
name?: string;
|
|
6
|
+
/** Extra args forwarded verbatim to the agent CLI (everything after --) */
|
|
7
|
+
passthroughArgs: string[];
|
|
8
|
+
onEvent: (event: AshralEvent) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Spawns the agent inside a PTY, bridges all I/O, and drives the session
|
|
12
|
+
* state machine. Resolves when the agent process exits.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runSession(options: RunSessionOptions): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=runSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runSession.d.ts","sourceRoot":"","sources":["../../src/runner/runSession.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyF1E"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runSession = runSession;
|
|
37
|
+
const pty = __importStar(require("node-pty"));
|
|
38
|
+
const sessionState_1 = require("./sessionState");
|
|
39
|
+
/**
|
|
40
|
+
* Spawns the agent inside a PTY, bridges all I/O, and drives the session
|
|
41
|
+
* state machine. Resolves when the agent process exits.
|
|
42
|
+
*/
|
|
43
|
+
async function runSession(options) {
|
|
44
|
+
const { adapter, name, passthroughArgs, onEvent } = options;
|
|
45
|
+
const cwd = process.cwd();
|
|
46
|
+
const { columns = 80, rows = 24 } = process.stdout;
|
|
47
|
+
const state = new sessionState_1.SessionState(adapter.agentName, name, cwd, onEvent);
|
|
48
|
+
const config = adapter.getCommand(passthroughArgs);
|
|
49
|
+
// Merge adapter env overrides on top of the current environment
|
|
50
|
+
const env = { ...process.env, ...(config.env ?? {}) };
|
|
51
|
+
const term = pty.spawn(config.command, config.args, {
|
|
52
|
+
name: 'xterm-color',
|
|
53
|
+
cols: columns,
|
|
54
|
+
rows,
|
|
55
|
+
cwd,
|
|
56
|
+
env,
|
|
57
|
+
});
|
|
58
|
+
state.transition('running');
|
|
59
|
+
// ── Output: mirror PTY → stdout, inspect for state changes ─────────────────
|
|
60
|
+
term.onData((data) => {
|
|
61
|
+
// Write raw (ANSI-preserved) data so the terminal renders correctly
|
|
62
|
+
process.stdout.write(data);
|
|
63
|
+
onEvent({
|
|
64
|
+
type: 'output',
|
|
65
|
+
sessionId: state.id,
|
|
66
|
+
timestamp: new Date(),
|
|
67
|
+
data,
|
|
68
|
+
});
|
|
69
|
+
const next = adapter.detectStatus(data, state.status);
|
|
70
|
+
if (next !== null) {
|
|
71
|
+
state.transition(next);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// ── Input: forward stdin → PTY ──────────────────────────────────────────────
|
|
75
|
+
// Raw mode disables line buffering and lets control characters (Ctrl+C etc.)
|
|
76
|
+
// pass through as data to the PTY rather than being handled by Node.
|
|
77
|
+
const isTTY = process.stdin.isTTY ?? false;
|
|
78
|
+
if (isTTY)
|
|
79
|
+
process.stdin.setRawMode(true);
|
|
80
|
+
process.stdin.resume();
|
|
81
|
+
const onStdinData = (chunk) => term.write(chunk.toString('binary'));
|
|
82
|
+
process.stdin.on('data', onStdinData);
|
|
83
|
+
// ── Resize: keep PTY columns/rows in sync with the terminal ─────────────────
|
|
84
|
+
const onResize = () => {
|
|
85
|
+
const { columns: cols = 80, rows: r = 24 } = process.stdout;
|
|
86
|
+
term.resize(cols, r);
|
|
87
|
+
};
|
|
88
|
+
process.stdout.on('resize', onResize);
|
|
89
|
+
// ── Cleanup helper ───────────────────────────────────────────────────────────
|
|
90
|
+
function teardown() {
|
|
91
|
+
process.stdin.removeListener('data', onStdinData);
|
|
92
|
+
process.stdout.removeListener('resize', onResize);
|
|
93
|
+
if (isTTY)
|
|
94
|
+
process.stdin.setRawMode(false);
|
|
95
|
+
process.stdin.pause();
|
|
96
|
+
}
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
term.onExit(({ exitCode, signal }) => {
|
|
99
|
+
teardown();
|
|
100
|
+
if (signal && exitCode !== 0) {
|
|
101
|
+
// Process was killed by a signal — surface as an error event
|
|
102
|
+
onEvent({
|
|
103
|
+
type: 'error',
|
|
104
|
+
sessionId: state.id,
|
|
105
|
+
timestamp: new Date(),
|
|
106
|
+
message: `Agent killed by signal ${signal}`,
|
|
107
|
+
});
|
|
108
|
+
state.transition('error');
|
|
109
|
+
}
|
|
110
|
+
state.complete(exitCode);
|
|
111
|
+
resolve();
|
|
112
|
+
});
|
|
113
|
+
// Catch spawn errors (e.g. command not found)
|
|
114
|
+
process.nextTick(() => {
|
|
115
|
+
// node-pty doesn't emit an 'error' event; spawn failures surface as
|
|
116
|
+
// immediate exits with code 127 or throw synchronously — handled above.
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=runSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runSession.js","sourceRoot":"","sources":["../../src/runner/runSession.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Session, SessionStatus } from '../types/session';
|
|
2
|
+
import type { AshralEvent } from '../types/events';
|
|
3
|
+
export declare class SessionState {
|
|
4
|
+
private session;
|
|
5
|
+
private readonly emit;
|
|
6
|
+
constructor(agent: string, name: string | undefined, cwd: string, emit: (event: AshralEvent) => void);
|
|
7
|
+
get id(): string;
|
|
8
|
+
get status(): SessionStatus;
|
|
9
|
+
/** Transition to a new status and emit a status_changed event. No-ops on same status. */
|
|
10
|
+
transition(to: SessionStatus): void;
|
|
11
|
+
/** Mark the session as done. Sets endedAt and transitions to completed. */
|
|
12
|
+
complete(exitCode: number): void;
|
|
13
|
+
snapshot(): Readonly<Session>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=sessionState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionState.d.ts","sourceRoot":"","sources":["../../src/runner/sessionState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,iBAAiB,CAAC;AAEvE,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA+B;gBAGlD,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI;IAapC,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,IAAI,MAAM,IAAI,aAAa,CAE1B;IAED,yFAAyF;IACzF,UAAU,CAAC,EAAE,EAAE,aAAa,GAAG,IAAI;IAgBnC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWhC,QAAQ,IAAI,QAAQ,CAAC,OAAO,CAAC;CAG9B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionState = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
class SessionState {
|
|
6
|
+
constructor(agent, name, cwd, emit) {
|
|
7
|
+
this.emit = emit;
|
|
8
|
+
this.session = {
|
|
9
|
+
id: (0, crypto_1.randomUUID)(),
|
|
10
|
+
name,
|
|
11
|
+
agent,
|
|
12
|
+
status: 'starting',
|
|
13
|
+
startedAt: new Date(),
|
|
14
|
+
cwd,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
get id() {
|
|
18
|
+
return this.session.id;
|
|
19
|
+
}
|
|
20
|
+
get status() {
|
|
21
|
+
return this.session.status;
|
|
22
|
+
}
|
|
23
|
+
/** Transition to a new status and emit a status_changed event. No-ops on same status. */
|
|
24
|
+
transition(to) {
|
|
25
|
+
const from = this.session.status;
|
|
26
|
+
if (from === to)
|
|
27
|
+
return;
|
|
28
|
+
this.session.status = to;
|
|
29
|
+
const event = {
|
|
30
|
+
type: 'status_changed',
|
|
31
|
+
sessionId: this.session.id,
|
|
32
|
+
timestamp: new Date(),
|
|
33
|
+
from,
|
|
34
|
+
to,
|
|
35
|
+
};
|
|
36
|
+
this.emit(event);
|
|
37
|
+
}
|
|
38
|
+
/** Mark the session as done. Sets endedAt and transitions to completed. */
|
|
39
|
+
complete(exitCode) {
|
|
40
|
+
this.session.endedAt = new Date();
|
|
41
|
+
this.transition('completed');
|
|
42
|
+
this.emit({
|
|
43
|
+
type: 'completed',
|
|
44
|
+
sessionId: this.session.id,
|
|
45
|
+
timestamp: new Date(),
|
|
46
|
+
exitCode,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
snapshot() {
|
|
50
|
+
return { ...this.session };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.SessionState = SessionState;
|
|
54
|
+
//# sourceMappingURL=sessionState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionState.js","sourceRoot":"","sources":["../../src/runner/sessionState.ts"],"names":[],"mappings":";;;AAAA,mCAAoC;AAIpC,MAAa,YAAY;IAIvB,YACE,KAAa,EACb,IAAwB,EACxB,GAAW,EACX,IAAkC;QAElC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG;YACb,EAAE,EAAE,IAAA,mBAAU,GAAE;YAChB,IAAI;YACJ,KAAK;YACL,MAAM,EAAE,UAAU;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,GAAG;SACJ,CAAC;IACJ,CAAC;IAED,IAAI,EAAE;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IACzB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED,yFAAyF;IACzF,UAAU,CAAC,EAAiB;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QACjC,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO;QAExB,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC;QAEzB,MAAM,KAAK,GAAuB;YAChC,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE;YAC1B,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,IAAI;YACJ,EAAE;SACH,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAED,2EAA2E;IAC3E,QAAQ,CAAC,QAAgB;QACvB,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE;YAC1B,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAED,QAAQ;QACN,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;CACF;AA7DD,oCA6DC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SessionStatus } from './session';
|
|
2
|
+
export type EventType = 'output' | 'status_changed' | 'agent_prompt' | 'error' | 'completed';
|
|
3
|
+
interface BaseEvent {
|
|
4
|
+
type: EventType;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
timestamp: Date;
|
|
7
|
+
}
|
|
8
|
+
export interface OutputEvent extends BaseEvent {
|
|
9
|
+
type: 'output';
|
|
10
|
+
data: string;
|
|
11
|
+
}
|
|
12
|
+
export interface StatusChangedEvent extends BaseEvent {
|
|
13
|
+
type: 'status_changed';
|
|
14
|
+
from: SessionStatus;
|
|
15
|
+
to: SessionStatus;
|
|
16
|
+
}
|
|
17
|
+
export interface AgentPromptEvent extends BaseEvent {
|
|
18
|
+
type: 'agent_prompt';
|
|
19
|
+
prompt: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ErrorEvent extends BaseEvent {
|
|
22
|
+
type: 'error';
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CompletedEvent extends BaseEvent {
|
|
26
|
+
type: 'completed';
|
|
27
|
+
exitCode: number;
|
|
28
|
+
}
|
|
29
|
+
export type AshralEvent = OutputEvent | StatusChangedEvent | AgentPromptEvent | ErrorEvent | CompletedEvent;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,gBAAgB,GAChB,cAAc,GACd,OAAO,GACP,WAAW,CAAC;AAEhB,UAAU,SAAS;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,IAAI,EAAE,QAAQ,CAAC;IAEf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;IACpB,EAAE,EAAE,aAAa,CAAC;CACnB;AAGD,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,kBAAkB,GAClB,gBAAgB,GAChB,UAAU,GACV,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type SessionStatus = 'starting' | 'running' | 'waiting_for_input' | 'approval_required' | 'error' | 'completed';
|
|
2
|
+
export interface Session {
|
|
3
|
+
id: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
agent: string;
|
|
6
|
+
status: SessionStatus;
|
|
7
|
+
startedAt: Date;
|
|
8
|
+
endedAt?: Date;
|
|
9
|
+
cwd: string;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/types/session.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GACrB,UAAU,GACV,SAAS,GACT,mBAAmB,GACnB,mBAAmB,GACnB,OAAO,GACP,WAAW,CAAC;AAEhB,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/types/session.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ashral",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Control center for AI coding agents",
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ashral": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"scripts/postinstall.js"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "ts-node src/cli.ts",
|
|
16
|
+
"start": "node dist/cli.js",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"prepare": "tsc",
|
|
19
|
+
"postinstall": "node scripts/postinstall.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^12.1.0",
|
|
23
|
+
"node-pty": "^1.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^20.0.0",
|
|
27
|
+
"ts-node": "^10.9.2",
|
|
28
|
+
"typescript": "^5.4.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fixes missing execute bit on node-pty prebuilt binaries.
|
|
3
|
+
// npm strips +x from native files on install — this restores it.
|
|
4
|
+
// No-op on Windows (spawn-helper is not used there).
|
|
5
|
+
|
|
6
|
+
const { chmodSync, existsSync } = require('fs');
|
|
7
|
+
const { join } = require('path');
|
|
8
|
+
|
|
9
|
+
if (process.platform === 'win32') process.exit(0);
|
|
10
|
+
|
|
11
|
+
const platformKey = `${process.platform}-${process.arch}`;
|
|
12
|
+
const prebuildDir = join(__dirname, '..', 'node_modules', 'node-pty', 'prebuilds', platformKey);
|
|
13
|
+
|
|
14
|
+
for (const file of ['spawn-helper', 'pty.node']) {
|
|
15
|
+
const filePath = join(prebuildDir, file);
|
|
16
|
+
if (existsSync(filePath)) {
|
|
17
|
+
try {
|
|
18
|
+
chmodSync(filePath, 0o755);
|
|
19
|
+
} catch (_) {
|
|
20
|
+
// Best-effort — don't fail the install
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|