@online5880/opensession 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/package.json +1 -1
- package/src/cli.js +126 -14
- package/src/supabase.js +11 -1
package/README.md
CHANGED
|
@@ -4,18 +4,20 @@ MVP CLI for session continuity with Supabase.
|
|
|
4
4
|
|
|
5
5
|
## Commands
|
|
6
6
|
|
|
7
|
-
- `init
|
|
7
|
+
- `init [--project-key] [--actor]` (프롬프트로 URL/ANON KEY 입력)
|
|
8
8
|
- `login --actor`
|
|
9
9
|
- `start --project-key [--project-name] [--actor]`
|
|
10
10
|
- `resume --session-id [--actor]`
|
|
11
11
|
- `status [--project-key]`
|
|
12
|
+
- `sync --project <name>`
|
|
12
13
|
- `log [--session-id] [--limit]`
|
|
13
14
|
|
|
14
15
|
## Quick start
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
18
|
npm install
|
|
18
|
-
node src/cli.js init --
|
|
19
|
+
node src/cli.js init --project-key demo --actor mane
|
|
20
|
+
node src/cli.js sync --project demo
|
|
19
21
|
node src/cli.js start --project-key demo
|
|
20
22
|
node src/cli.js status
|
|
21
23
|
node src/cli.js log
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
4
7
|
import {
|
|
5
8
|
appendEvent,
|
|
6
9
|
ensureProject,
|
|
@@ -8,12 +11,55 @@ import {
|
|
|
8
11
|
getSession,
|
|
9
12
|
getSessionEvents,
|
|
10
13
|
listActiveSessions,
|
|
11
|
-
startSession
|
|
14
|
+
startSession,
|
|
15
|
+
validateConnection
|
|
12
16
|
} from './supabase.js';
|
|
13
17
|
import { getConfigPath, mergeConfig, readConfig, writeConfig } from './config.js';
|
|
14
18
|
|
|
15
19
|
const program = new Command();
|
|
16
20
|
|
|
21
|
+
function formatError(error) {
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
return error.message;
|
|
24
|
+
}
|
|
25
|
+
if (error && typeof error === 'object') {
|
|
26
|
+
const message = error.message;
|
|
27
|
+
const details = error.details;
|
|
28
|
+
const hint = error.hint;
|
|
29
|
+
const code = error.code;
|
|
30
|
+
const parts = [message, details, hint, code].filter(Boolean);
|
|
31
|
+
if (parts.length > 0) {
|
|
32
|
+
return parts.join(' | ');
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return JSON.stringify(error);
|
|
36
|
+
} catch {
|
|
37
|
+
return 'Unknown object error';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return String(error);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function readInitSecrets() {
|
|
44
|
+
if (input.isTTY) {
|
|
45
|
+
const rl = createInterface({ input, output });
|
|
46
|
+
const url = (await rl.question('Supabase URL을 입력하세요: ')).trim();
|
|
47
|
+
const anonKey = (await rl.question('Supabase ANON KEY를 입력하세요: ')).trim();
|
|
48
|
+
rl.close();
|
|
49
|
+
return { url, anonKey };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const raw = await readFile('/dev/stdin', 'utf8');
|
|
53
|
+
const lines = raw
|
|
54
|
+
.split(/\r?\n/)
|
|
55
|
+
.map((line) => line.trim())
|
|
56
|
+
.filter((line) => line.length > 0);
|
|
57
|
+
return {
|
|
58
|
+
url: lines[0] ?? '',
|
|
59
|
+
anonKey: lines[1] ?? ''
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
17
63
|
program
|
|
18
64
|
.name('opensession')
|
|
19
65
|
.description('Session continuity bridge CLI for Supabase')
|
|
@@ -21,22 +67,34 @@ program
|
|
|
21
67
|
|
|
22
68
|
program
|
|
23
69
|
.command('init')
|
|
24
|
-
.description('Initialize CLI config')
|
|
25
|
-
.requiredOption('--url <url>', 'Supabase project URL')
|
|
26
|
-
.requiredOption('--anon-key <anonKey>', 'Supabase anon key')
|
|
70
|
+
.description('Initialize CLI config with interactive prompt')
|
|
27
71
|
.option('--project-key <projectKey>', 'Default project key')
|
|
28
72
|
.option('--actor <actor>', 'Default actor/username')
|
|
29
73
|
.action(async (options) => {
|
|
74
|
+
const { url, anonKey } = await readInitSecrets();
|
|
75
|
+
|
|
76
|
+
if (!url || !anonKey) {
|
|
77
|
+
throw new Error('Supabase URL과 ANON KEY는 필수입니다.');
|
|
78
|
+
}
|
|
79
|
+
|
|
30
80
|
const current = await readConfig();
|
|
31
81
|
const next = mergeConfig(current, {
|
|
32
|
-
supabaseUrl:
|
|
33
|
-
supabaseAnonKey:
|
|
82
|
+
supabaseUrl: url,
|
|
83
|
+
supabaseAnonKey: anonKey,
|
|
34
84
|
defaultProjectKey: options.projectKey ?? current.defaultProjectKey,
|
|
35
85
|
actor: options.actor ?? current.actor
|
|
36
86
|
});
|
|
37
87
|
|
|
38
88
|
const path = await writeConfig(next);
|
|
39
|
-
console.log(
|
|
89
|
+
console.log(`설정 저장 완료: ${path}`);
|
|
90
|
+
try {
|
|
91
|
+
await validateConnection(url, anonKey);
|
|
92
|
+
console.log('연결 검증: 성공');
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const message = formatError(error);
|
|
95
|
+
console.log(`연결 검증: 실패 (${message})`);
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
}
|
|
40
98
|
});
|
|
41
99
|
|
|
42
100
|
program
|
|
@@ -100,22 +158,35 @@ program
|
|
|
100
158
|
|
|
101
159
|
program
|
|
102
160
|
.command('status')
|
|
103
|
-
.description('Show active sessions
|
|
161
|
+
.description('Show active sessions and latest sync status')
|
|
104
162
|
.option('--project-key <projectKey>', 'Project key (defaults to configured project key)')
|
|
105
163
|
.action(async (options) => {
|
|
106
164
|
const config = await readConfig();
|
|
107
|
-
const
|
|
108
|
-
const
|
|
165
|
+
const projectKey = options.projectKey ?? config.defaultProjectKey ?? config.syncStatus?.project;
|
|
166
|
+
const syncStatus = config.syncStatus ?? {};
|
|
109
167
|
|
|
110
168
|
if (!projectKey) {
|
|
111
|
-
throw new Error('Missing project key. Pass --project-key or run start first.');
|
|
169
|
+
throw new Error('Missing project key. Pass --project-key, sync --project, or run start first.');
|
|
112
170
|
}
|
|
113
171
|
|
|
172
|
+
console.log(`Project: ${projectKey}`);
|
|
173
|
+
console.log(`Last sync: ${syncStatus.lastSyncAt ?? '-'}`);
|
|
174
|
+
console.log(`Sync project: ${syncStatus.project ?? '-'}`);
|
|
175
|
+
console.log(`Pending events: ${syncStatus.pendingEvents ?? 0}`);
|
|
176
|
+
console.log(`Recent sync error: ${syncStatus.lastError ?? '-'}`);
|
|
177
|
+
|
|
178
|
+
let client;
|
|
179
|
+
try {
|
|
180
|
+
client = getClient(config);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const message = formatError(error);
|
|
183
|
+
console.log(`원격 상태 확인 불가: ${message}`);
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
114
187
|
const project = await ensureProject(client, projectKey, projectKey);
|
|
115
188
|
const active = await listActiveSessions(client, project.id);
|
|
116
189
|
|
|
117
|
-
console.log(`Project: ${project.project_key}`);
|
|
118
|
-
|
|
119
190
|
if (active.length === 0) {
|
|
120
191
|
console.log('No active sessions');
|
|
121
192
|
return;
|
|
@@ -126,6 +197,47 @@ program
|
|
|
126
197
|
}
|
|
127
198
|
});
|
|
128
199
|
|
|
200
|
+
program
|
|
201
|
+
.command('sync')
|
|
202
|
+
.description('Sync local/remote session state for a project')
|
|
203
|
+
.requiredOption('--project <projectKey>', 'Project key')
|
|
204
|
+
.action(async (options) => {
|
|
205
|
+
const config = await readConfig();
|
|
206
|
+
const now = new Date().toISOString();
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const client = getClient(config);
|
|
210
|
+
const project = await ensureProject(client, options.project, options.project);
|
|
211
|
+
const active = await listActiveSessions(client, project.id);
|
|
212
|
+
const next = mergeConfig(config, {
|
|
213
|
+
defaultProjectKey: options.project,
|
|
214
|
+
syncStatus: {
|
|
215
|
+
lastSyncAt: now,
|
|
216
|
+
project: options.project,
|
|
217
|
+
pendingEvents: 0,
|
|
218
|
+
lastError: null
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
await writeConfig(next);
|
|
222
|
+
console.log(`동기화 완료: project=${options.project}`);
|
|
223
|
+
console.log(`active sessions=${active.length}`);
|
|
224
|
+
console.log(`pending events=0`);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
const message = formatError(error);
|
|
227
|
+
const next = mergeConfig(config, {
|
|
228
|
+
syncStatus: {
|
|
229
|
+
lastSyncAt: now,
|
|
230
|
+
project: options.project,
|
|
231
|
+
pendingEvents: 0,
|
|
232
|
+
lastError: message
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
await writeConfig(next);
|
|
236
|
+
console.log(`동기화 실패: ${message}`);
|
|
237
|
+
process.exitCode = 1;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
129
241
|
program
|
|
130
242
|
.command('log')
|
|
131
243
|
.description('Show session event log')
|
|
@@ -163,7 +275,7 @@ async function main() {
|
|
|
163
275
|
try {
|
|
164
276
|
await program.parseAsync(process.argv);
|
|
165
277
|
} catch (error) {
|
|
166
|
-
const message =
|
|
278
|
+
const message = formatError(error);
|
|
167
279
|
console.error(message);
|
|
168
280
|
process.exitCode = 1;
|
|
169
281
|
}
|
package/src/supabase.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
2
2
|
|
|
3
3
|
export function getClient(config) {
|
|
4
4
|
if (!config.supabaseUrl || !config.supabaseAnonKey) {
|
|
5
|
-
throw new Error('Supabase is not configured. Run `opensession init
|
|
5
|
+
throw new Error('Supabase is not configured. Run `opensession init` first.');
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
return createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
@@ -74,6 +74,16 @@ export async function appendEvent(client, sessionId, type, payload = {}) {
|
|
|
74
74
|
return data;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
export async function validateConnection(supabaseUrl, supabaseAnonKey) {
|
|
78
|
+
const client = createClient(supabaseUrl, supabaseAnonKey, {
|
|
79
|
+
auth: { persistSession: false }
|
|
80
|
+
});
|
|
81
|
+
const { error } = await client.from('projects').select('id').limit(1);
|
|
82
|
+
if (error) {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
export async function getSession(client, sessionId) {
|
|
78
88
|
const { data, error } = await client
|
|
79
89
|
.from('sessions')
|