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.
Files changed (38) hide show
  1. package/dist/adapters/baseAdapter.d.ts +35 -0
  2. package/dist/adapters/baseAdapter.d.ts.map +1 -0
  3. package/dist/adapters/baseAdapter.js +16 -0
  4. package/dist/adapters/baseAdapter.js.map +1 -0
  5. package/dist/adapters/claudeAdapter.d.ts +9 -0
  6. package/dist/adapters/claudeAdapter.d.ts.map +1 -0
  7. package/dist/adapters/claudeAdapter.js +88 -0
  8. package/dist/adapters/claudeAdapter.js.map +1 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +119 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/notifications/notifier.d.ts +16 -0
  14. package/dist/notifications/notifier.d.ts.map +1 -0
  15. package/dist/notifications/notifier.js +3 -0
  16. package/dist/notifications/notifier.js.map +1 -0
  17. package/dist/notifications/ntfyNotifier.d.ts +7 -0
  18. package/dist/notifications/ntfyNotifier.d.ts.map +1 -0
  19. package/dist/notifications/ntfyNotifier.js +59 -0
  20. package/dist/notifications/ntfyNotifier.js.map +1 -0
  21. package/dist/runner/runSession.d.ts +15 -0
  22. package/dist/runner/runSession.d.ts.map +1 -0
  23. package/dist/runner/runSession.js +120 -0
  24. package/dist/runner/runSession.js.map +1 -0
  25. package/dist/runner/sessionState.d.ts +15 -0
  26. package/dist/runner/sessionState.d.ts.map +1 -0
  27. package/dist/runner/sessionState.js +54 -0
  28. package/dist/runner/sessionState.js.map +1 -0
  29. package/dist/types/events.d.ts +31 -0
  30. package/dist/types/events.d.ts.map +1 -0
  31. package/dist/types/events.js +3 -0
  32. package/dist/types/events.js.map +1 -0
  33. package/dist/types/session.d.ts +11 -0
  34. package/dist/types/session.d.ts.map +1 -0
  35. package/dist/types/session.js +3 -0
  36. package/dist/types/session.js.map +1 -0
  37. package/package.json +33 -0
  38. 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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -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
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=notifier.js.map
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=events.js.map
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=session.js.map
@@ -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
+ }