@online5880/opensession 0.1.4 → 0.1.6
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/package.json +1 -1
- package/sql/schema.sql +12 -0
- package/src/cli.js +15 -11
- package/src/supabase.js +23 -0
- package/src/tui.js +17 -14
package/package.json
CHANGED
package/sql/schema.sql
CHANGED
|
@@ -36,3 +36,15 @@ create index if not exists session_events_idempotency_idx on session_events(sess
|
|
|
36
36
|
create unique index if not exists session_events_session_type_idempotency_key_uidx
|
|
37
37
|
on session_events(session_id, type, idempotency_key)
|
|
38
38
|
where idempotency_key is not null;
|
|
39
|
+
|
|
40
|
+
DO $$
|
|
41
|
+
BEGIN
|
|
42
|
+
IF EXISTS (
|
|
43
|
+
SELECT 1 FROM pg_publication WHERE pubname = 'supabase_realtime'
|
|
44
|
+
) THEN
|
|
45
|
+
ALTER PUBLICATION supabase_realtime ADD TABLE session_events;
|
|
46
|
+
END IF;
|
|
47
|
+
EXCEPTION WHEN OTHERS THEN
|
|
48
|
+
-- Ignore if table is already in publication or other error
|
|
49
|
+
END;
|
|
50
|
+
$$;
|
package/src/cli.js
CHANGED
|
@@ -559,26 +559,23 @@ async function runOpsConsole(options) {
|
|
|
559
559
|
}
|
|
560
560
|
input.on('keypress', onKeypress);
|
|
561
561
|
|
|
562
|
-
|
|
562
|
+
const unsubscribe = subscribeToSessionEvents(client, selectedSession.id, async (event) => {
|
|
563
563
|
if (closed) {
|
|
564
564
|
return;
|
|
565
565
|
}
|
|
566
566
|
await refresh();
|
|
567
567
|
redraw();
|
|
568
|
-
}
|
|
568
|
+
});
|
|
569
569
|
|
|
570
570
|
await new Promise((resolve) => {
|
|
571
571
|
const done = () => {
|
|
572
|
+
unsubscribe();
|
|
572
573
|
close();
|
|
573
574
|
resolve();
|
|
574
575
|
};
|
|
575
576
|
input.once('end', done);
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
clearInterval(poll);
|
|
579
|
-
resolve();
|
|
580
|
-
}
|
|
581
|
-
}, 100);
|
|
577
|
+
// 폴링 제거
|
|
578
|
+
resolve();
|
|
582
579
|
});
|
|
583
580
|
}
|
|
584
581
|
|
|
@@ -677,21 +674,28 @@ program
|
|
|
677
674
|
.command('start')
|
|
678
675
|
.alias('st')
|
|
679
676
|
.description('Start a new session and emit a start event')
|
|
680
|
-
.
|
|
677
|
+
.option('--project-key <projectKey>', 'Project key (defaults to configured project key)')
|
|
678
|
+
.option('--project <projectKey>', 'Alias of --project-key')
|
|
681
679
|
.option('--project-name <projectName>', 'Project display name')
|
|
682
680
|
.option('--actor <actor>', 'Actor override')
|
|
683
681
|
.action(async (options) => {
|
|
684
682
|
const config = await readConfig();
|
|
685
683
|
const client = getClient(config);
|
|
684
|
+
const projectKey = options.project ?? options.projectKey ?? config.defaultProjectKey ?? config.syncStatus?.project;
|
|
685
|
+
|
|
686
|
+
if (!projectKey) {
|
|
687
|
+
throw new Error("Missing project key. Pass --project-key or set a default via 'opss init'.");
|
|
688
|
+
}
|
|
689
|
+
|
|
686
690
|
const actor = options.actor ?? config.actor ?? 'anonymous';
|
|
687
691
|
const operationId = randomUUID();
|
|
688
692
|
|
|
689
|
-
const project = await ensureProject(client,
|
|
693
|
+
const project = await ensureProject(client, projectKey, options.projectName);
|
|
690
694
|
const session = await startSession(client, project.id, actor, { operationId });
|
|
691
695
|
|
|
692
696
|
await writeConfig(
|
|
693
697
|
mergeConfig(config, {
|
|
694
|
-
defaultProjectKey:
|
|
698
|
+
defaultProjectKey: projectKey,
|
|
695
699
|
lastSessionId: session.id
|
|
696
700
|
})
|
|
697
701
|
);
|
package/src/supabase.js
CHANGED
|
@@ -307,3 +307,26 @@ export async function listSessions(client, projectId, limit = 100) {
|
|
|
307
307
|
|
|
308
308
|
return data;
|
|
309
309
|
}
|
|
310
|
+
|
|
311
|
+
export function subscribeToSessionEvents(client, sessionId, onEvent) {
|
|
312
|
+
const channel = client.channel(`session-${sessionId}`);
|
|
313
|
+
|
|
314
|
+
channel
|
|
315
|
+
.on(
|
|
316
|
+
'postgres_changes',
|
|
317
|
+
{
|
|
318
|
+
event: 'INSERT',
|
|
319
|
+
schema: 'public',
|
|
320
|
+
table: 'session_events',
|
|
321
|
+
filter: `session_id=eq.${sessionId}`
|
|
322
|
+
},
|
|
323
|
+
(payload) => {
|
|
324
|
+
onEvent(payload.new);
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
.subscribe();
|
|
328
|
+
|
|
329
|
+
return () => {
|
|
330
|
+
client.removeChannel(channel);
|
|
331
|
+
};
|
|
332
|
+
}
|
package/src/tui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import blessed from 'blessed';
|
|
2
|
-
import { listActiveSessions, getSessionEvents } from './supabase.js';
|
|
2
|
+
import { listActiveSessions, getSessionEvents, subscribeToSessionEvents } from './supabase.js';
|
|
3
3
|
|
|
4
4
|
export async function startTui(client, options = {}) {
|
|
5
5
|
const screen = blessed.screen({
|
|
@@ -89,7 +89,7 @@ export async function startTui(client, options = {}) {
|
|
|
89
89
|
|
|
90
90
|
let sessions = [];
|
|
91
91
|
let selectedSessionId = null;
|
|
92
|
-
let
|
|
92
|
+
let unsubscribeRealtime = null;
|
|
93
93
|
|
|
94
94
|
async function refreshSessions() {
|
|
95
95
|
try {
|
|
@@ -108,13 +108,11 @@ export async function startTui(client, options = {}) {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
async function
|
|
111
|
+
async function loadInitialEvents() {
|
|
112
112
|
if (!selectedSessionId) return;
|
|
113
113
|
|
|
114
114
|
try {
|
|
115
115
|
const events = await getSessionEvents(client, selectedSessionId);
|
|
116
|
-
const currentScroll = eventLog.childBase;
|
|
117
|
-
|
|
118
116
|
eventLog.clear();
|
|
119
117
|
if (events && events.length > 0) {
|
|
120
118
|
events.forEach(e => {
|
|
@@ -123,12 +121,10 @@ export async function startTui(client, options = {}) {
|
|
|
123
121
|
} else {
|
|
124
122
|
eventLog.log(' No events found for this session.');
|
|
125
123
|
}
|
|
126
|
-
|
|
127
|
-
// Maintain scroll position if needed or scroll to bottom
|
|
128
|
-
eventLog.setScroll(currentScroll);
|
|
129
124
|
screen.render();
|
|
130
125
|
} catch (error) {
|
|
131
|
-
eventLog.log(`
|
|
126
|
+
eventLog.log(` Error loading events: ${error.message}`);
|
|
127
|
+
screen.render();
|
|
132
128
|
}
|
|
133
129
|
}
|
|
134
130
|
|
|
@@ -140,16 +136,23 @@ export async function startTui(client, options = {}) {
|
|
|
140
136
|
eventLog.setContent(` Loading events for ${session.id}... \n`);
|
|
141
137
|
screen.render();
|
|
142
138
|
|
|
143
|
-
if (
|
|
139
|
+
if (unsubscribeRealtime) {
|
|
140
|
+
unsubscribeRealtime();
|
|
141
|
+
unsubscribeRealtime = null;
|
|
142
|
+
}
|
|
144
143
|
|
|
145
|
-
await
|
|
144
|
+
await loadInitialEvents();
|
|
146
145
|
|
|
147
|
-
// Set up
|
|
148
|
-
|
|
146
|
+
// Set up Realtime subscription for the selected session
|
|
147
|
+
unsubscribeRealtime = subscribeToSessionEvents(client, selectedSessionId, (newEvent) => {
|
|
148
|
+
// Append the new event to the log instantly
|
|
149
|
+
eventLog.log(`[${new Date(newEvent.created_at).toLocaleTimeString()}] ${newEvent.type}: ${JSON.stringify(newEvent.payload)}`);
|
|
150
|
+
screen.render();
|
|
151
|
+
});
|
|
149
152
|
});
|
|
150
153
|
|
|
151
154
|
screen.key(['escape', 'q', 'C-c'], () => {
|
|
152
|
-
if (
|
|
155
|
+
if (unsubscribeRealtime) unsubscribeRealtime();
|
|
153
156
|
process.exit(0);
|
|
154
157
|
});
|
|
155
158
|
screen.key(['r'], () => refreshSessions());
|