openplexer 0.1.0 → 0.2.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/README.md +195 -0
- package/dist/acp-client.d.ts +13 -8
- package/dist/acp-client.d.ts.map +1 -1
- package/dist/acp-client.js +127 -23
- package/dist/acp-client.test.d.ts +2 -0
- package/dist/acp-client.test.d.ts.map +1 -0
- package/dist/acp-client.test.js +91 -0
- package/dist/cli.js +14 -15
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/notion.d.ts +4 -6
- package/dist/notion.d.ts.map +1 -1
- package/dist/notion.js +149 -7
- package/dist/sync.d.ts +2 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +51 -33
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +68 -11
- package/package.json +11 -7
- package/src/acp-client.test.ts +95 -0
- package/src/acp-client.ts +158 -35
- package/src/cli.ts +16 -16
- package/src/config.ts +1 -1
- package/src/notion.ts +160 -7
- package/src/sync.ts +52 -35
- package/src/worker.ts +71 -11
- package/LICENSE +0 -21
package/src/sync.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { SessionInfo } from '@agentclientprotocol/sdk'
|
|
6
6
|
import type { OpenplexerBoard, OpenplexerConfig, AcpClient } from './config.ts'
|
|
7
7
|
import { writeConfig } from './config.ts'
|
|
8
|
-
import {
|
|
8
|
+
import { type AgentConnection } from './acp-client.ts'
|
|
9
9
|
import { getRepoInfo } from './git.ts'
|
|
10
10
|
import {
|
|
11
11
|
createNotionClient,
|
|
@@ -24,7 +24,7 @@ export async function startSyncLoop({
|
|
|
24
24
|
acpConnections,
|
|
25
25
|
}: {
|
|
26
26
|
config: OpenplexerConfig
|
|
27
|
-
acpConnections:
|
|
27
|
+
acpConnections: AgentConnection[]
|
|
28
28
|
}): Promise<void> {
|
|
29
29
|
console.log(`Syncing ${config.boards.length} board(s) every ${SYNC_INTERVAL_MS / 1000}s`)
|
|
30
30
|
|
|
@@ -48,19 +48,23 @@ async function syncOnce({
|
|
|
48
48
|
acpConnections,
|
|
49
49
|
}: {
|
|
50
50
|
config: OpenplexerConfig
|
|
51
|
-
acpConnections:
|
|
51
|
+
acpConnections: AgentConnection[]
|
|
52
52
|
}): Promise<void> {
|
|
53
|
-
// Collect sessions from all
|
|
53
|
+
// Collect sessions from all agent connections, tagged with their source
|
|
54
54
|
const sessions: TaggedSession[] = []
|
|
55
55
|
const seenIds = new Set<string>()
|
|
56
56
|
|
|
57
|
-
for (const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
seenIds.
|
|
62
|
-
|
|
57
|
+
for (const agent of acpConnections) {
|
|
58
|
+
try {
|
|
59
|
+
const clientSessions = await agent.listSessions()
|
|
60
|
+
for (const session of clientSessions) {
|
|
61
|
+
if (!seenIds.has(session.sessionId)) {
|
|
62
|
+
seenIds.add(session.sessionId)
|
|
63
|
+
sessions.push({ ...session, source: agent.client })
|
|
64
|
+
}
|
|
63
65
|
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error(`Error listing sessions from ${agent.client}:`, err instanceof Error ? err.message : err)
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
|
|
@@ -122,49 +126,62 @@ async function syncBoard({
|
|
|
122
126
|
for (const { session, repoSlug, repoUrl, branch } of filteredSessions) {
|
|
123
127
|
const existingPageId = board.syncedSessions[session.sessionId]
|
|
124
128
|
|
|
129
|
+
const title = session.title || `Session ${session.sessionId.slice(0, 8)}`
|
|
130
|
+
|
|
125
131
|
if (existingPageId) {
|
|
126
132
|
// Update existing page
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
try {
|
|
134
|
+
await rateLimitedCall(() => {
|
|
135
|
+
return updateSessionPage({
|
|
136
|
+
notion,
|
|
137
|
+
pageId: existingPageId,
|
|
138
|
+
title: session.title || undefined,
|
|
139
|
+
updatedAt: session.updatedAt || undefined,
|
|
140
|
+
})
|
|
133
141
|
})
|
|
134
|
-
})
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error(`Error updating "${title}" (${repoSlug}):`, err instanceof Error ? err.message : err)
|
|
144
|
+
}
|
|
135
145
|
} else {
|
|
136
146
|
// Create new page
|
|
137
|
-
const title = session.title || `Session ${session.sessionId.slice(0, 8)}`
|
|
138
147
|
const branchUrl = `${repoUrl}/tree/${branch}`
|
|
139
148
|
const resumeCommand = (() => {
|
|
140
149
|
if (session.source === 'opencode') {
|
|
141
150
|
return `opencode --session ${session.sessionId}`
|
|
142
151
|
}
|
|
152
|
+
if (session.source === 'codex') {
|
|
153
|
+
return `codex resume ${session.sessionId}`
|
|
154
|
+
}
|
|
143
155
|
return `claude --resume ${session.sessionId}`
|
|
144
156
|
})()
|
|
145
157
|
|
|
146
158
|
// Try to get Discord URL if kimaki is available
|
|
147
159
|
const discordUrl = await getKimakiDiscordUrl(session.sessionId)
|
|
148
160
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
try {
|
|
162
|
+
const pageId = await rateLimitedCall(() => {
|
|
163
|
+
return createSessionPage({
|
|
164
|
+
notion,
|
|
165
|
+
databaseId: board.notionDatabaseId,
|
|
166
|
+
title,
|
|
167
|
+
sessionId: session.sessionId,
|
|
168
|
+
status: 'In Progress',
|
|
169
|
+
repoSlug,
|
|
170
|
+
branchUrl,
|
|
171
|
+
resumeCommand,
|
|
172
|
+
assigneeId: board.notionUserId,
|
|
173
|
+
folder: session.cwd || '',
|
|
174
|
+
discordUrl: discordUrl || undefined,
|
|
175
|
+
updatedAt: session.updatedAt || undefined,
|
|
176
|
+
})
|
|
163
177
|
})
|
|
164
|
-
})
|
|
165
178
|
|
|
166
|
-
|
|
167
|
-
|
|
179
|
+
board.syncedSessions[session.sessionId] = pageId
|
|
180
|
+
const notionUrl = `https://notion.so/${pageId.replace(/-/g, '')}`
|
|
181
|
+
console.log(`+ Added "${title}" (${repoSlug}) → ${notionUrl}`)
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error(`Error adding "${title}" (${repoSlug}):`, err instanceof Error ? err.message : err)
|
|
184
|
+
}
|
|
168
185
|
}
|
|
169
186
|
}
|
|
170
187
|
}
|
package/src/worker.ts
CHANGED
|
@@ -23,7 +23,7 @@ const app = new Spiceflow()
|
|
|
23
23
|
handler() {
|
|
24
24
|
return new Response(null, {
|
|
25
25
|
status: 302,
|
|
26
|
-
headers: { Location: 'https://github.com/remorses/
|
|
26
|
+
headers: { Location: 'https://github.com/remorses/openplexer' },
|
|
27
27
|
})
|
|
28
28
|
},
|
|
29
29
|
})
|
|
@@ -112,8 +112,15 @@ const app = new Spiceflow()
|
|
|
112
112
|
workspace_id: string
|
|
113
113
|
workspace_name: string
|
|
114
114
|
owner: { type: string; user?: { id: string; name: string } }
|
|
115
|
+
duplicated_template_id?: string | null
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// Build Notion page URL from duplicated template ID (if present)
|
|
119
|
+
const duplicatedTemplateId = tokenData.duplicated_template_id ?? null
|
|
120
|
+
const notionPageUrl = duplicatedTemplateId
|
|
121
|
+
? `https://notion.so/${duplicatedTemplateId.replace(/-/g, '')}`
|
|
122
|
+
: null
|
|
123
|
+
|
|
117
124
|
// Store tokens in KV with 5 minute TTL
|
|
118
125
|
const kvPayload = {
|
|
119
126
|
accessToken: tokenData.access_token,
|
|
@@ -122,30 +129,83 @@ const app = new Spiceflow()
|
|
|
122
129
|
workspaceName: tokenData.workspace_name,
|
|
123
130
|
notionUserId: tokenData.owner?.user?.id,
|
|
124
131
|
notionUserName: tokenData.owner?.user?.name,
|
|
132
|
+
duplicatedTemplateId,
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
await env.OPENPLEXER_KV.put(`auth:${stateParam}`, JSON.stringify(kvPayload), {
|
|
128
136
|
expirationTtl: 300,
|
|
129
137
|
})
|
|
130
138
|
|
|
131
|
-
// Show success page
|
|
139
|
+
// Show success page with link to the created Notion page (if template was used)
|
|
140
|
+
const pageLink = notionPageUrl
|
|
141
|
+
? `<a class="button" href="${notionPageUrl}" target="_blank" rel="noopener">Open in Notion <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 3h7v7M13 3L5 11"/></svg></a>`
|
|
142
|
+
: ''
|
|
143
|
+
const subtitle = notionPageUrl
|
|
144
|
+
? 'Your board page has been created. You can close this tab and return to the CLI.'
|
|
145
|
+
: 'You can close this tab and return to the CLI.'
|
|
146
|
+
|
|
132
147
|
return new Response(
|
|
133
148
|
`<!DOCTYPE html>
|
|
134
149
|
<html>
|
|
135
|
-
<head
|
|
150
|
+
<head>
|
|
151
|
+
<title>openplexer - Connected</title>
|
|
152
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
153
|
+
<meta name="color-scheme" content="light dark">
|
|
136
154
|
<style>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
155
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
156
|
+
:root {
|
|
157
|
+
--bg: #fff;
|
|
158
|
+
--fg: #000;
|
|
159
|
+
--muted: #666;
|
|
160
|
+
--border: #eaeaea;
|
|
161
|
+
--link: #000;
|
|
162
|
+
--link-hover: #666;
|
|
163
|
+
--checkmark-bg: #000;
|
|
164
|
+
--checkmark-fg: #fff;
|
|
165
|
+
}
|
|
166
|
+
@media (prefers-color-scheme: dark) {
|
|
167
|
+
:root {
|
|
168
|
+
--bg: #000;
|
|
169
|
+
--fg: #fff;
|
|
170
|
+
--muted: #888;
|
|
171
|
+
--border: #333;
|
|
172
|
+
--link: #fff;
|
|
173
|
+
--link-hover: #999;
|
|
174
|
+
--checkmark-bg: #fff;
|
|
175
|
+
--checkmark-fg: #000;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
body {
|
|
179
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
180
|
+
display: flex; justify-content: center; align-items: center;
|
|
181
|
+
min-height: 100vh; background: var(--bg); color: var(--fg);
|
|
182
|
+
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
183
|
+
}
|
|
184
|
+
.container { text-align: center; padding: 32px; max-width: 380px; }
|
|
185
|
+
.checkmark {
|
|
186
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
187
|
+
background: var(--checkmark-bg); color: var(--checkmark-fg);
|
|
188
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
189
|
+
margin-bottom: 24px; font-size: 20px;
|
|
190
|
+
}
|
|
191
|
+
h1 { font-size: 20px; font-weight: 600; letter-spacing: -0.02em; margin-bottom: 8px; }
|
|
192
|
+
p { font-size: 14px; color: var(--muted); line-height: 1.5; margin-bottom: 24px; }
|
|
193
|
+
a.button {
|
|
194
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
195
|
+
padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500;
|
|
196
|
+
color: var(--link); text-decoration: none;
|
|
197
|
+
border: 1px solid var(--border); transition: color 0.15s;
|
|
198
|
+
}
|
|
199
|
+
a.button:hover { color: var(--link-hover); }
|
|
200
|
+
a.button svg { width: 16px; height: 16px; }
|
|
143
201
|
</style>
|
|
144
202
|
</head>
|
|
145
203
|
<body>
|
|
146
|
-
<div class="
|
|
204
|
+
<div class="container">
|
|
205
|
+
<div class="checkmark">✓</div>
|
|
147
206
|
<h1>Connected to Notion</h1>
|
|
148
|
-
<p
|
|
207
|
+
<p>${subtitle}</p>
|
|
208
|
+
${pageLink}
|
|
149
209
|
</div>
|
|
150
210
|
</body>
|
|
151
211
|
</html>`,
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Kimaki
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|