@intrect/openswarm 0.2.2 → 0.3.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 +236 -331
- package/config.example.yaml +36 -13
- package/dist/adapters/base.d.ts.map +1 -1
- package/dist/adapters/base.js +4 -0
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/cryptoQuantAdapter.js +1 -1
- package/dist/adapters/cryptoQuantAdapter.js.map +1 -1
- package/dist/adapters/gpt.d.ts +15 -0
- package/dist/adapters/gpt.d.ts.map +1 -0
- package/dist/adapters/gpt.js +274 -0
- package/dist/adapters/gpt.js.map +1 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/local.d.ts +22 -0
- package/dist/adapters/local.d.ts.map +1 -0
- package/dist/adapters/local.js +277 -0
- package/dist/adapters/local.js.map +1 -0
- package/dist/adapters/types.d.ts +6 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/agents/pairPipeline.d.ts.map +1 -1
- package/dist/agents/pairPipeline.js +21 -6
- package/dist/agents/pairPipeline.js.map +1 -1
- package/dist/agents/pipelineGuards.d.ts.map +1 -1
- package/dist/agents/pipelineGuards.js +84 -2
- package/dist/agents/pipelineGuards.js.map +1 -1
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauthPkce.d.ts +21 -0
- package/dist/auth/oauthPkce.d.ts.map +1 -0
- package/dist/auth/oauthPkce.js +212 -0
- package/dist/auth/oauthPkce.js.map +1 -0
- package/dist/auth/oauthStore.d.ts +24 -0
- package/dist/auth/oauthStore.d.ts.map +1 -0
- package/dist/auth/oauthStore.js +96 -0
- package/dist/auth/oauthStore.js.map +1 -0
- package/dist/automation/autonomousRunner.d.ts +5 -5
- package/dist/automation/runnerTypes.d.ts +1 -1
- package/dist/automation/runnerTypes.d.ts.map +1 -1
- package/dist/cli/authHandler.d.ts +16 -0
- package/dist/cli/authHandler.d.ts.map +1 -0
- package/dist/cli/authHandler.js +93 -0
- package/dist/cli/authHandler.js.map +1 -0
- package/dist/cli/checkHandler.d.ts +25 -0
- package/dist/cli/checkHandler.d.ts.map +1 -0
- package/dist/cli/checkHandler.js +465 -0
- package/dist/cli/checkHandler.js.map +1 -0
- package/dist/cli.js +64 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/config.d.ts +17 -4
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +21 -8
- package/dist/core/config.js.map +1 -1
- package/dist/core/service.d.ts.map +1 -1
- package/dist/core/service.js +18 -8
- package/dist/core/service.js.map +1 -1
- package/dist/core/types.d.ts +4 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/issues/graphql/resolvers.d.ts +252 -0
- package/dist/issues/graphql/resolvers.d.ts.map +1 -0
- package/dist/issues/graphql/resolvers.js +88 -0
- package/dist/issues/graphql/resolvers.js.map +1 -0
- package/dist/issues/graphql/server.d.ts +13 -0
- package/dist/issues/graphql/server.d.ts.map +1 -0
- package/dist/issues/graphql/server.js +56 -0
- package/dist/issues/graphql/server.js.map +1 -0
- package/dist/issues/graphql/typeDefs.d.ts +2 -0
- package/dist/issues/graphql/typeDefs.d.ts.map +1 -0
- package/dist/issues/graphql/typeDefs.js +251 -0
- package/dist/issues/graphql/typeDefs.js.map +1 -0
- package/dist/issues/index.d.ts +8 -0
- package/dist/issues/index.d.ts.map +1 -0
- package/dist/issues/index.js +11 -0
- package/dist/issues/index.js.map +1 -0
- package/dist/issues/issueBoardHtml.d.ts +2 -0
- package/dist/issues/issueBoardHtml.d.ts.map +1 -0
- package/dist/issues/issueBoardHtml.js +677 -0
- package/dist/issues/issueBoardHtml.js.map +1 -0
- package/dist/issues/linearBridge.d.ts +27 -0
- package/dist/issues/linearBridge.d.ts.map +1 -0
- package/dist/issues/linearBridge.js +211 -0
- package/dist/issues/linearBridge.js.map +1 -0
- package/dist/issues/memoryBridge.d.ts +35 -0
- package/dist/issues/memoryBridge.d.ts.map +1 -0
- package/dist/issues/memoryBridge.js +184 -0
- package/dist/issues/memoryBridge.js.map +1 -0
- package/dist/issues/schema.d.ts +162 -0
- package/dist/issues/schema.d.ts.map +1 -0
- package/dist/issues/schema.js +121 -0
- package/dist/issues/schema.js.map +1 -0
- package/dist/issues/sqliteStore.d.ts +90 -0
- package/dist/issues/sqliteStore.d.ts.map +1 -0
- package/dist/issues/sqliteStore.js +488 -0
- package/dist/issues/sqliteStore.js.map +1 -0
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +9 -3
- package/dist/knowledge/index.js.map +1 -1
- package/dist/linear/linear.d.ts +4 -0
- package/dist/linear/linear.d.ts.map +1 -1
- package/dist/linear/linear.js +27 -0
- package/dist/linear/linear.js.map +1 -1
- package/dist/registry/bsDetector.d.ts +24 -0
- package/dist/registry/bsDetector.d.ts.map +1 -0
- package/dist/registry/bsDetector.js +276 -0
- package/dist/registry/bsDetector.js.map +1 -0
- package/dist/registry/entityScanner.d.ts +36 -0
- package/dist/registry/entityScanner.d.ts.map +1 -0
- package/dist/registry/entityScanner.js +693 -0
- package/dist/registry/entityScanner.js.map +1 -0
- package/dist/registry/graphql/resolvers.d.ts +778 -0
- package/dist/registry/graphql/resolvers.d.ts.map +1 -0
- package/dist/registry/graphql/resolvers.js +127 -0
- package/dist/registry/graphql/resolvers.js.map +1 -0
- package/dist/registry/graphql/typeDefs.d.ts +2 -0
- package/dist/registry/graphql/typeDefs.d.ts.map +1 -0
- package/dist/registry/graphql/typeDefs.js +276 -0
- package/dist/registry/graphql/typeDefs.js.map +1 -0
- package/dist/registry/index.d.ts +12 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +18 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/issueBridge.d.ts +8 -0
- package/dist/registry/issueBridge.d.ts.map +1 -0
- package/dist/registry/issueBridge.js +30 -0
- package/dist/registry/issueBridge.js.map +1 -0
- package/dist/registry/memoryBridge.d.ts +13 -0
- package/dist/registry/memoryBridge.d.ts.map +1 -0
- package/dist/registry/memoryBridge.js +60 -0
- package/dist/registry/memoryBridge.js.map +1 -0
- package/dist/registry/schema.d.ts +307 -0
- package/dist/registry/schema.d.ts.map +1 -0
- package/dist/registry/schema.js +139 -0
- package/dist/registry/schema.js.map +1 -0
- package/dist/registry/sqliteStore.d.ts +101 -0
- package/dist/registry/sqliteStore.d.ts.map +1 -0
- package/dist/registry/sqliteStore.js +688 -0
- package/dist/registry/sqliteStore.js.map +1 -0
- package/dist/support/chatBackend.d.ts.map +1 -1
- package/dist/support/chatBackend.js +35 -4
- package/dist/support/chatBackend.js.map +1 -1
- package/dist/support/chatTui.d.ts.map +1 -1
- package/dist/support/chatTui.js +109 -3
- package/dist/support/chatTui.js.map +1 -1
- package/dist/support/dashboardHtml.d.ts +1 -1
- package/dist/support/dashboardHtml.d.ts.map +1 -1
- package/dist/support/dashboardHtml.js +1 -0
- package/dist/support/dashboardHtml.js.map +1 -1
- package/dist/support/web.d.ts.map +1 -1
- package/dist/support/web.js +16 -3
- package/dist/support/web.js.map +1 -1
- package/package.json +8 -2
- package/templates/TOOLS.md +2 -2
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// OpenSwarm - Issue Board HTML Template
|
|
3
|
+
// Created: 2026-04-03
|
|
4
|
+
// Purpose: 칸반 보드 + 이슈 생성/편집 웹 UI
|
|
5
|
+
// ============================================
|
|
6
|
+
export const ISSUE_BOARD_HTML = `<!DOCTYPE html>
|
|
7
|
+
<html lang="ko">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset="UTF-8">
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
11
|
+
<title>OpenSwarm :: Issues</title>
|
|
12
|
+
<style>
|
|
13
|
+
:root {
|
|
14
|
+
--bg: #0a0c0a;
|
|
15
|
+
--bg2: #0d100d;
|
|
16
|
+
--bg3: #111411;
|
|
17
|
+
--green: #00ff41;
|
|
18
|
+
--green-dim: #003a00;
|
|
19
|
+
--green-mid: #00aa00;
|
|
20
|
+
--green-lo: #005500;
|
|
21
|
+
--cyan: #00ccdd;
|
|
22
|
+
--cyan-dim: #003344;
|
|
23
|
+
--amber: #ffaa00;
|
|
24
|
+
--red: #ff3333;
|
|
25
|
+
--white: #ccddcc;
|
|
26
|
+
--dim: #445544;
|
|
27
|
+
--border: #1a2a1a;
|
|
28
|
+
}
|
|
29
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
30
|
+
body {
|
|
31
|
+
font-family: 'Cascadia Code', 'JetBrains Mono', 'Fira Code', monospace;
|
|
32
|
+
background: var(--bg);
|
|
33
|
+
color: var(--white);
|
|
34
|
+
font-size: 13px;
|
|
35
|
+
line-height: 1.4;
|
|
36
|
+
height: 100vh;
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Header */
|
|
42
|
+
header {
|
|
43
|
+
height: 38px;
|
|
44
|
+
background: var(--bg2);
|
|
45
|
+
border-bottom: 1px solid var(--border);
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
padding: 0 1rem;
|
|
49
|
+
gap: 0.75rem;
|
|
50
|
+
flex-shrink: 0;
|
|
51
|
+
}
|
|
52
|
+
.hdr-logo { color: var(--green); font-weight: bold; font-size: 14px; letter-spacing: 0.15em; text-decoration: none; }
|
|
53
|
+
.hdr-sep { color: var(--dim); margin: 0 0.5rem; }
|
|
54
|
+
.hdr-sub { color: var(--cyan); font-size: 12px; letter-spacing: 0.1em; }
|
|
55
|
+
.hdr-right { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }
|
|
56
|
+
|
|
57
|
+
/* Toolbar */
|
|
58
|
+
.toolbar {
|
|
59
|
+
background: var(--bg2);
|
|
60
|
+
border-bottom: 1px solid var(--border);
|
|
61
|
+
padding: 6px 1rem;
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 0.5rem;
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
}
|
|
67
|
+
.btn {
|
|
68
|
+
background: var(--bg3);
|
|
69
|
+
color: var(--green);
|
|
70
|
+
border: 1px solid var(--green-dim);
|
|
71
|
+
padding: 3px 10px;
|
|
72
|
+
font-family: inherit;
|
|
73
|
+
font-size: 11px;
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
border-radius: 3px;
|
|
76
|
+
}
|
|
77
|
+
.btn:hover { background: var(--green-dim); }
|
|
78
|
+
.btn-primary { color: var(--cyan); border-color: var(--cyan-dim); }
|
|
79
|
+
.btn-primary:hover { background: var(--cyan-dim); }
|
|
80
|
+
.filter-select {
|
|
81
|
+
background: var(--bg3);
|
|
82
|
+
color: var(--white);
|
|
83
|
+
border: 1px solid var(--border);
|
|
84
|
+
padding: 3px 6px;
|
|
85
|
+
font-family: inherit;
|
|
86
|
+
font-size: 11px;
|
|
87
|
+
border-radius: 3px;
|
|
88
|
+
}
|
|
89
|
+
.search-input {
|
|
90
|
+
background: var(--bg3);
|
|
91
|
+
color: var(--white);
|
|
92
|
+
border: 1px solid var(--border);
|
|
93
|
+
padding: 3px 8px;
|
|
94
|
+
font-family: inherit;
|
|
95
|
+
font-size: 11px;
|
|
96
|
+
width: 200px;
|
|
97
|
+
border-radius: 3px;
|
|
98
|
+
}
|
|
99
|
+
.search-input::placeholder { color: var(--dim); }
|
|
100
|
+
.stats-bar { margin-left: auto; color: var(--dim); font-size: 11px; }
|
|
101
|
+
.stats-bar span { margin: 0 0.5rem; }
|
|
102
|
+
.stats-val { color: var(--green); }
|
|
103
|
+
|
|
104
|
+
/* Kanban Board */
|
|
105
|
+
.board {
|
|
106
|
+
flex: 1;
|
|
107
|
+
display: flex;
|
|
108
|
+
gap: 2px;
|
|
109
|
+
padding: 8px;
|
|
110
|
+
overflow-x: auto;
|
|
111
|
+
min-height: 0;
|
|
112
|
+
}
|
|
113
|
+
.column {
|
|
114
|
+
flex: 1;
|
|
115
|
+
min-width: 220px;
|
|
116
|
+
max-width: 320px;
|
|
117
|
+
background: var(--bg2);
|
|
118
|
+
border: 1px solid var(--border);
|
|
119
|
+
border-radius: 4px;
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
}
|
|
123
|
+
.col-header {
|
|
124
|
+
padding: 6px 10px;
|
|
125
|
+
background: var(--bg3);
|
|
126
|
+
border-bottom: 1px solid var(--border);
|
|
127
|
+
font-size: 11px;
|
|
128
|
+
letter-spacing: 0.1em;
|
|
129
|
+
display: flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
gap: 6px;
|
|
132
|
+
}
|
|
133
|
+
.col-header .count {
|
|
134
|
+
background: var(--green-dim);
|
|
135
|
+
color: var(--green);
|
|
136
|
+
padding: 1px 6px;
|
|
137
|
+
border-radius: 8px;
|
|
138
|
+
font-size: 10px;
|
|
139
|
+
}
|
|
140
|
+
.col-body {
|
|
141
|
+
flex: 1;
|
|
142
|
+
overflow-y: auto;
|
|
143
|
+
padding: 4px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Issue Card */
|
|
147
|
+
.card {
|
|
148
|
+
background: var(--bg3);
|
|
149
|
+
border: 1px solid var(--border);
|
|
150
|
+
border-radius: 3px;
|
|
151
|
+
padding: 8px;
|
|
152
|
+
margin-bottom: 4px;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
transition: border-color 0.15s;
|
|
155
|
+
}
|
|
156
|
+
.card:hover { border-color: var(--green-lo); }
|
|
157
|
+
.card-title { font-size: 12px; color: var(--white); margin-bottom: 4px; }
|
|
158
|
+
.card-meta { font-size: 10px; color: var(--dim); display: flex; gap: 6px; flex-wrap: wrap; }
|
|
159
|
+
.card-priority {
|
|
160
|
+
display: inline-block;
|
|
161
|
+
width: 6px;
|
|
162
|
+
height: 6px;
|
|
163
|
+
border-radius: 50%;
|
|
164
|
+
margin-right: 4px;
|
|
165
|
+
}
|
|
166
|
+
.p-urgent { background: var(--red); }
|
|
167
|
+
.p-high { background: var(--amber); }
|
|
168
|
+
.p-medium { background: var(--cyan); }
|
|
169
|
+
.p-low { background: var(--dim); }
|
|
170
|
+
.p-none { background: transparent; border: 1px solid var(--dim); }
|
|
171
|
+
.card-label {
|
|
172
|
+
font-size: 9px;
|
|
173
|
+
padding: 1px 4px;
|
|
174
|
+
border-radius: 2px;
|
|
175
|
+
background: var(--green-dim);
|
|
176
|
+
color: var(--green-mid);
|
|
177
|
+
}
|
|
178
|
+
.card-id { color: var(--dim); font-size: 9px; }
|
|
179
|
+
|
|
180
|
+
/* Modal */
|
|
181
|
+
.modal-overlay {
|
|
182
|
+
display: none;
|
|
183
|
+
position: fixed;
|
|
184
|
+
inset: 0;
|
|
185
|
+
background: rgba(0,0,0,0.7);
|
|
186
|
+
z-index: 100;
|
|
187
|
+
justify-content: center;
|
|
188
|
+
align-items: center;
|
|
189
|
+
}
|
|
190
|
+
.modal-overlay.active { display: flex; }
|
|
191
|
+
.modal {
|
|
192
|
+
background: var(--bg2);
|
|
193
|
+
border: 1px solid var(--green-dim);
|
|
194
|
+
border-radius: 6px;
|
|
195
|
+
width: 90%;
|
|
196
|
+
max-width: 600px;
|
|
197
|
+
max-height: 85vh;
|
|
198
|
+
overflow-y: auto;
|
|
199
|
+
padding: 1.5rem;
|
|
200
|
+
}
|
|
201
|
+
.modal h3 { color: var(--green); font-size: 14px; margin-bottom: 1rem; letter-spacing: 0.1em; }
|
|
202
|
+
.form-group { margin-bottom: 0.75rem; }
|
|
203
|
+
.form-group label { display: block; color: var(--dim); font-size: 10px; margin-bottom: 3px; letter-spacing: 0.05em; }
|
|
204
|
+
.form-group input, .form-group textarea, .form-group select {
|
|
205
|
+
width: 100%;
|
|
206
|
+
background: var(--bg3);
|
|
207
|
+
color: var(--white);
|
|
208
|
+
border: 1px solid var(--border);
|
|
209
|
+
padding: 6px 8px;
|
|
210
|
+
font-family: inherit;
|
|
211
|
+
font-size: 12px;
|
|
212
|
+
border-radius: 3px;
|
|
213
|
+
}
|
|
214
|
+
.form-group textarea { min-height: 80px; resize: vertical; }
|
|
215
|
+
.form-actions { display: flex; gap: 0.5rem; margin-top: 1rem; justify-content: flex-end; }
|
|
216
|
+
.form-actions .btn { padding: 5px 16px; }
|
|
217
|
+
|
|
218
|
+
/* Detail panel */
|
|
219
|
+
.detail-panel {
|
|
220
|
+
display: none;
|
|
221
|
+
position: fixed;
|
|
222
|
+
top: 0;
|
|
223
|
+
right: 0;
|
|
224
|
+
width: 420px;
|
|
225
|
+
height: 100%;
|
|
226
|
+
background: var(--bg2);
|
|
227
|
+
border-left: 1px solid var(--green-dim);
|
|
228
|
+
z-index: 90;
|
|
229
|
+
overflow-y: auto;
|
|
230
|
+
padding: 1rem;
|
|
231
|
+
}
|
|
232
|
+
.detail-panel.active { display: block; }
|
|
233
|
+
.detail-close { float: right; cursor: pointer; color: var(--dim); font-size: 16px; }
|
|
234
|
+
.detail-title { color: var(--green); font-size: 14px; margin-bottom: 0.5rem; margin-right: 2rem; }
|
|
235
|
+
.detail-section { margin-top: 1rem; }
|
|
236
|
+
.detail-section h4 { color: var(--cyan); font-size: 11px; letter-spacing: 0.05em; margin-bottom: 0.25rem; }
|
|
237
|
+
.detail-section p, .detail-section ul { color: var(--white); font-size: 12px; }
|
|
238
|
+
.detail-section ul { padding-left: 1rem; }
|
|
239
|
+
.event-item { font-size: 11px; color: var(--dim); padding: 3px 0; border-bottom: 1px solid var(--border); }
|
|
240
|
+
.event-item .ev-type { color: var(--amber); }
|
|
241
|
+
</style>
|
|
242
|
+
</head>
|
|
243
|
+
<body>
|
|
244
|
+
<header>
|
|
245
|
+
<a href="/" class="hdr-logo">OpenSwarm</a>
|
|
246
|
+
<span class="hdr-sep">::</span>
|
|
247
|
+
<span class="hdr-sub">ISSUE TRACKER</span>
|
|
248
|
+
<div class="hdr-right">
|
|
249
|
+
<span id="stats-summary" style="color:var(--dim);font-size:11px"></span>
|
|
250
|
+
</div>
|
|
251
|
+
</header>
|
|
252
|
+
|
|
253
|
+
<div class="toolbar">
|
|
254
|
+
<button class="btn btn-primary" onclick="openCreateModal()">+ NEW ISSUE</button>
|
|
255
|
+
<select class="filter-select" id="filter-project" onchange="applyFilter()">
|
|
256
|
+
<option value="">all projects</option>
|
|
257
|
+
</select>
|
|
258
|
+
<select class="filter-select" id="filter-priority" onchange="applyFilter()">
|
|
259
|
+
<option value="">all priorities</option>
|
|
260
|
+
<option value="urgent">urgent</option>
|
|
261
|
+
<option value="high">high</option>
|
|
262
|
+
<option value="medium">medium</option>
|
|
263
|
+
<option value="low">low</option>
|
|
264
|
+
</select>
|
|
265
|
+
<input type="text" class="search-input" id="search-input" placeholder="search issues..." oninput="debounceSearch()">
|
|
266
|
+
<div class="stats-bar">
|
|
267
|
+
<span>total: <span class="stats-val" id="stat-total">0</span></span>
|
|
268
|
+
<span>open: <span class="stats-val" id="stat-open">0</span></span>
|
|
269
|
+
<span>done: <span class="stats-val" id="stat-done">0</span></span>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<div class="board" id="board">
|
|
274
|
+
<!-- 칸반 칼럼은 JS에서 동적 생성 -->
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<!-- 이슈 생성 모달 -->
|
|
278
|
+
<div class="modal-overlay" id="create-modal">
|
|
279
|
+
<div class="modal">
|
|
280
|
+
<h3>NEW ISSUE</h3>
|
|
281
|
+
<div class="form-group">
|
|
282
|
+
<label>PROJECT</label>
|
|
283
|
+
<select id="new-project"></select>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="form-group">
|
|
286
|
+
<label>TITLE</label>
|
|
287
|
+
<input type="text" id="new-title" placeholder="이슈 제목">
|
|
288
|
+
</div>
|
|
289
|
+
<div class="form-group">
|
|
290
|
+
<label>DESCRIPTION</label>
|
|
291
|
+
<textarea id="new-desc" placeholder="상세 설명..."></textarea>
|
|
292
|
+
</div>
|
|
293
|
+
<div style="display:flex;gap:0.75rem">
|
|
294
|
+
<div class="form-group" style="flex:1">
|
|
295
|
+
<label>PRIORITY</label>
|
|
296
|
+
<select id="new-priority">
|
|
297
|
+
<option value="medium">medium</option>
|
|
298
|
+
<option value="urgent">urgent</option>
|
|
299
|
+
<option value="high">high</option>
|
|
300
|
+
<option value="low">low</option>
|
|
301
|
+
<option value="none">none</option>
|
|
302
|
+
</select>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="form-group" style="flex:1">
|
|
305
|
+
<label>STATUS</label>
|
|
306
|
+
<select id="new-status">
|
|
307
|
+
<option value="backlog">backlog</option>
|
|
308
|
+
<option value="todo">todo</option>
|
|
309
|
+
<option value="in_progress">in_progress</option>
|
|
310
|
+
</select>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="form-group">
|
|
314
|
+
<label>LABELS (comma separated)</label>
|
|
315
|
+
<input type="text" id="new-labels" placeholder="bug, feature, ...">
|
|
316
|
+
</div>
|
|
317
|
+
<div class="form-group">
|
|
318
|
+
<label>RELEVANT FILES (comma separated)</label>
|
|
319
|
+
<input type="text" id="new-files" placeholder="src/foo.ts, src/bar.ts">
|
|
320
|
+
</div>
|
|
321
|
+
<div class="form-actions">
|
|
322
|
+
<button class="btn" onclick="closeCreateModal()">CANCEL</button>
|
|
323
|
+
<button class="btn btn-primary" onclick="createIssue()">CREATE</button>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
<!-- 이슈 상세 패널 -->
|
|
329
|
+
<div class="detail-panel" id="detail-panel">
|
|
330
|
+
<span class="detail-close" onclick="closeDetail()">×</span>
|
|
331
|
+
<div id="detail-content"></div>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<script>
|
|
335
|
+
const COLUMNS = [
|
|
336
|
+
{ status: 'backlog', label: 'BACKLOG', color: 'var(--dim)' },
|
|
337
|
+
{ status: 'todo', label: 'TODO', color: 'var(--white)' },
|
|
338
|
+
{ status: 'in_progress', label: 'IN PROGRESS', color: 'var(--amber)' },
|
|
339
|
+
{ status: 'in_review', label: 'IN REVIEW', color: 'var(--cyan)' },
|
|
340
|
+
{ status: 'done', label: 'DONE', color: 'var(--green)' },
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
let allIssues = [];
|
|
344
|
+
let projects = new Set();
|
|
345
|
+
|
|
346
|
+
// GraphQL helper
|
|
347
|
+
async function gql(query, variables = {}) {
|
|
348
|
+
const res = await fetch('/graphql', {
|
|
349
|
+
method: 'POST',
|
|
350
|
+
headers: { 'Content-Type': 'application/json' },
|
|
351
|
+
body: JSON.stringify({ query, variables }),
|
|
352
|
+
});
|
|
353
|
+
const json = await res.json();
|
|
354
|
+
if (json.errors) {
|
|
355
|
+
console.error('GraphQL errors:', json.errors);
|
|
356
|
+
throw new Error(json.errors[0].message);
|
|
357
|
+
}
|
|
358
|
+
return json.data;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 이슈 목록 로드
|
|
362
|
+
async function loadIssues() {
|
|
363
|
+
const projectId = document.getElementById('filter-project').value || undefined;
|
|
364
|
+
const priority = document.getElementById('filter-priority').value || undefined;
|
|
365
|
+
const search = document.getElementById('search-input').value || undefined;
|
|
366
|
+
|
|
367
|
+
const filter = {};
|
|
368
|
+
if (projectId) filter.projectId = projectId;
|
|
369
|
+
if (priority) filter.priority = [priority];
|
|
370
|
+
if (search) filter.search = search;
|
|
371
|
+
|
|
372
|
+
const data = await gql(\`
|
|
373
|
+
query ListIssues($filter: IssueFilterInput) {
|
|
374
|
+
issues(filter: $filter) {
|
|
375
|
+
issues {
|
|
376
|
+
id projectId title description status priority source
|
|
377
|
+
labels assignee relevantFiles dependencies childIds
|
|
378
|
+
linearIdentifier memoryIds createdAt updatedAt closedAt
|
|
379
|
+
}
|
|
380
|
+
total
|
|
381
|
+
}
|
|
382
|
+
issueStats {
|
|
383
|
+
total
|
|
384
|
+
byStatus { status count }
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
\`, { filter: Object.keys(filter).length > 0 ? filter : null });
|
|
388
|
+
|
|
389
|
+
allIssues = data.issues.issues;
|
|
390
|
+
|
|
391
|
+
// 프로젝트 목록 갱신
|
|
392
|
+
for (const iss of allIssues) projects.add(iss.projectId);
|
|
393
|
+
updateProjectFilter();
|
|
394
|
+
|
|
395
|
+
// 통계 갱신
|
|
396
|
+
const stats = data.issueStats;
|
|
397
|
+
document.getElementById('stat-total').textContent = stats.total;
|
|
398
|
+
const openCount = stats.byStatus
|
|
399
|
+
.filter(s => !['done', 'cancelled'].includes(s.status))
|
|
400
|
+
.reduce((a, s) => a + s.count, 0);
|
|
401
|
+
const doneCount = stats.byStatus.find(s => s.status === 'done')?.count || 0;
|
|
402
|
+
document.getElementById('stat-open').textContent = openCount;
|
|
403
|
+
document.getElementById('stat-done').textContent = doneCount;
|
|
404
|
+
|
|
405
|
+
renderBoard();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function updateProjectFilter() {
|
|
409
|
+
const sel = document.getElementById('filter-project');
|
|
410
|
+
const current = sel.value;
|
|
411
|
+
const opts = ['<option value="">all projects</option>'];
|
|
412
|
+
for (const p of projects) {
|
|
413
|
+
opts.push('<option value="' + p + '"' + (p === current ? ' selected' : '') + '>' + p + '</option>');
|
|
414
|
+
}
|
|
415
|
+
sel.innerHTML = opts.join('');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 칸반 보드 렌더링
|
|
419
|
+
function renderBoard() {
|
|
420
|
+
const board = document.getElementById('board');
|
|
421
|
+
board.innerHTML = '';
|
|
422
|
+
|
|
423
|
+
for (const col of COLUMNS) {
|
|
424
|
+
const issues = allIssues.filter(i => i.status === col.status);
|
|
425
|
+
const colEl = document.createElement('div');
|
|
426
|
+
colEl.className = 'column';
|
|
427
|
+
colEl.innerHTML = \`
|
|
428
|
+
<div class="col-header">
|
|
429
|
+
<span style="color:\${col.color}">\${col.label}</span>
|
|
430
|
+
<span class="count">\${issues.length}</span>
|
|
431
|
+
</div>
|
|
432
|
+
<div class="col-body" data-status="\${col.status}"></div>
|
|
433
|
+
\`;
|
|
434
|
+
|
|
435
|
+
const body = colEl.querySelector('.col-body');
|
|
436
|
+
for (const iss of issues) {
|
|
437
|
+
body.appendChild(createCard(iss));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 드래그 드롭 수신
|
|
441
|
+
body.addEventListener('dragover', e => { e.preventDefault(); body.style.background = 'var(--green-dim)'; });
|
|
442
|
+
body.addEventListener('dragleave', () => { body.style.background = ''; });
|
|
443
|
+
body.addEventListener('drop', async e => {
|
|
444
|
+
e.preventDefault();
|
|
445
|
+
body.style.background = '';
|
|
446
|
+
const issueId = e.dataTransfer.getData('text/plain');
|
|
447
|
+
if (issueId && col.status) {
|
|
448
|
+
await gql(\`mutation($id:ID!,$s:IssueStatus!){changeIssueStatus(id:$id,status:$s){id}}\`,
|
|
449
|
+
{ id: issueId, s: col.status });
|
|
450
|
+
loadIssues();
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
board.appendChild(colEl);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function createCard(iss) {
|
|
459
|
+
const card = document.createElement('div');
|
|
460
|
+
card.className = 'card';
|
|
461
|
+
card.draggable = true;
|
|
462
|
+
card.addEventListener('dragstart', e => { e.dataTransfer.setData('text/plain', iss.id); });
|
|
463
|
+
card.addEventListener('click', () => openDetail(iss.id));
|
|
464
|
+
|
|
465
|
+
const priorityClass = 'p-' + iss.priority;
|
|
466
|
+
const labels = (iss.labels || []).map(l => '<span class="card-label">' + l + '</span>').join('');
|
|
467
|
+
const timeAgo = formatTimeAgo(iss.updatedAt);
|
|
468
|
+
|
|
469
|
+
card.innerHTML = \`
|
|
470
|
+
<div class="card-title"><span class="card-priority \${priorityClass}"></span>\${escHtml(iss.title)}</div>
|
|
471
|
+
<div class="card-meta">
|
|
472
|
+
<span class="card-id">\${iss.id.slice(0, 6)}</span>
|
|
473
|
+
<span>\${iss.projectId}</span>
|
|
474
|
+
\${iss.assignee ? '<span>' + iss.assignee + '</span>' : ''}
|
|
475
|
+
<span>\${timeAgo}</span>
|
|
476
|
+
</div>
|
|
477
|
+
\${labels ? '<div class="card-meta" style="margin-top:3px">' + labels + '</div>' : ''}
|
|
478
|
+
\`;
|
|
479
|
+
return card;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// 이슈 상세 패널
|
|
483
|
+
async function openDetail(id) {
|
|
484
|
+
const data = await gql(\`
|
|
485
|
+
query IssueDetail($id:ID!) {
|
|
486
|
+
issue(id:$id) {
|
|
487
|
+
id projectId title description status priority source
|
|
488
|
+
labels assignee relevantFiles acceptanceCriteria
|
|
489
|
+
dependencies childIds memoryIds createdAt updatedAt closedAt
|
|
490
|
+
linearIdentifier linearUrl
|
|
491
|
+
}
|
|
492
|
+
issueEvents(issueId:$id, limit:20) {
|
|
493
|
+
id type oldValue newValue content actor createdAt
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
\`, { id });
|
|
497
|
+
|
|
498
|
+
const iss = data.issue;
|
|
499
|
+
if (!iss) return;
|
|
500
|
+
const events = data.issueEvents;
|
|
501
|
+
|
|
502
|
+
const panel = document.getElementById('detail-panel');
|
|
503
|
+
const content = document.getElementById('detail-content');
|
|
504
|
+
|
|
505
|
+
const statusOptions = COLUMNS.map(c =>
|
|
506
|
+
'<option value="' + c.status + '"' + (c.status === iss.status ? ' selected' : '') + '>' + c.label + '</option>'
|
|
507
|
+
).join('');
|
|
508
|
+
|
|
509
|
+
content.innerHTML = \`
|
|
510
|
+
<div class="detail-title">\${escHtml(iss.title)}</div>
|
|
511
|
+
<div style="color:var(--dim);font-size:10px;margin-bottom:1rem">
|
|
512
|
+
\${iss.id} | \${iss.projectId}
|
|
513
|
+
\${iss.linearIdentifier ? ' | <a href="' + iss.linearUrl + '" style="color:var(--cyan)" target="_blank">' + iss.linearIdentifier + '</a>' : ''}
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<div class="detail-section">
|
|
517
|
+
<h4>STATUS</h4>
|
|
518
|
+
<select class="filter-select" onchange="changeStatus('\${iss.id}', this.value)" style="width:100%">
|
|
519
|
+
\${statusOptions}
|
|
520
|
+
<option value="cancelled"\${iss.status==='cancelled'?' selected':''}>CANCELLED</option>
|
|
521
|
+
</select>
|
|
522
|
+
</div>
|
|
523
|
+
|
|
524
|
+
<div class="detail-section">
|
|
525
|
+
<h4>DESCRIPTION</h4>
|
|
526
|
+
<p style="white-space:pre-wrap">\${escHtml(iss.description) || '<span style="color:var(--dim)">no description</span>'}</p>
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
\${iss.relevantFiles.length ? '<div class="detail-section"><h4>RELEVANT FILES</h4><ul>' + iss.relevantFiles.map(f => '<li>' + escHtml(f) + '</li>').join('') + '</ul></div>' : ''}
|
|
530
|
+
|
|
531
|
+
\${iss.acceptanceCriteria.length ? '<div class="detail-section"><h4>ACCEPTANCE CRITERIA</h4><ul>' + iss.acceptanceCriteria.map(c => '<li>' + escHtml(c) + '</li>').join('') + '</ul></div>' : ''}
|
|
532
|
+
|
|
533
|
+
\${iss.dependencies.length ? '<div class="detail-section"><h4>DEPENDENCIES</h4><p>' + iss.dependencies.join(', ') + '</p></div>' : ''}
|
|
534
|
+
|
|
535
|
+
<div class="detail-section">
|
|
536
|
+
<h4>ACTIVITY (\${events.length})</h4>
|
|
537
|
+
\${events.map(ev => \`
|
|
538
|
+
<div class="event-item">
|
|
539
|
+
<span class="ev-type">\${ev.type}</span>
|
|
540
|
+
\${ev.content ? ': ' + escHtml(ev.content).slice(0, 100) : ''}
|
|
541
|
+
\${ev.oldValue && ev.newValue ? ': ' + ev.oldValue + ' → ' + ev.newValue : ''}
|
|
542
|
+
<span style="float:right">\${formatTimeAgo(ev.createdAt)}</span>
|
|
543
|
+
</div>
|
|
544
|
+
\`).join('')}
|
|
545
|
+
</div>
|
|
546
|
+
|
|
547
|
+
<div class="detail-section" style="margin-top:1.5rem">
|
|
548
|
+
<h4>ADD COMMENT</h4>
|
|
549
|
+
<textarea id="comment-input" style="width:100%;background:var(--bg3);color:var(--white);border:1px solid var(--border);padding:6px;font-family:inherit;font-size:12px;min-height:60px;border-radius:3px" placeholder="코멘트 입력..."></textarea>
|
|
550
|
+
<button class="btn btn-primary" style="margin-top:4px" onclick="addComment('\${iss.id}')">COMMENT</button>
|
|
551
|
+
</div>
|
|
552
|
+
|
|
553
|
+
<div class="form-actions" style="margin-top:1.5rem;justify-content:flex-start">
|
|
554
|
+
<button class="btn" style="color:var(--red);border-color:var(--red)" onclick="deleteIssue('\${iss.id}')">DELETE</button>
|
|
555
|
+
</div>
|
|
556
|
+
\`;
|
|
557
|
+
|
|
558
|
+
panel.classList.add('active');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function closeDetail() {
|
|
562
|
+
document.getElementById('detail-panel').classList.remove('active');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function changeStatus(id, status) {
|
|
566
|
+
await gql(\`mutation($id:ID!,$s:IssueStatus!){changeIssueStatus(id:$id,status:$s){id}}\`, { id, s: status });
|
|
567
|
+
loadIssues();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function addComment(id) {
|
|
571
|
+
const input = document.getElementById('comment-input');
|
|
572
|
+
const content = input.value.trim();
|
|
573
|
+
if (!content) return;
|
|
574
|
+
await gql(\`mutation($id:ID!,$c:String!){addComment(issueId:$id,content:$c){id}}\`, { id, c: content });
|
|
575
|
+
input.value = '';
|
|
576
|
+
openDetail(id);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async function deleteIssue(id) {
|
|
580
|
+
if (!confirm('Delete this issue?')) return;
|
|
581
|
+
await gql(\`mutation($id:ID!){deleteIssue(id:$id)}\`, { id });
|
|
582
|
+
closeDetail();
|
|
583
|
+
loadIssues();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// 이슈 생성
|
|
587
|
+
function openCreateModal() {
|
|
588
|
+
const sel = document.getElementById('new-project');
|
|
589
|
+
sel.innerHTML = [...projects].map(p => '<option value="' + p + '">' + p + '</option>').join('');
|
|
590
|
+
if (sel.options.length === 0) {
|
|
591
|
+
sel.innerHTML = '<option value="default">default</option>';
|
|
592
|
+
}
|
|
593
|
+
document.getElementById('create-modal').classList.add('active');
|
|
594
|
+
document.getElementById('new-title').focus();
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function closeCreateModal() {
|
|
598
|
+
document.getElementById('create-modal').classList.remove('active');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function createIssue() {
|
|
602
|
+
const title = document.getElementById('new-title').value.trim();
|
|
603
|
+
if (!title) { alert('Title required'); return; }
|
|
604
|
+
|
|
605
|
+
const input = {
|
|
606
|
+
projectId: document.getElementById('new-project').value || 'default',
|
|
607
|
+
title,
|
|
608
|
+
description: document.getElementById('new-desc').value,
|
|
609
|
+
priority: document.getElementById('new-priority').value,
|
|
610
|
+
status: document.getElementById('new-status').value,
|
|
611
|
+
labels: document.getElementById('new-labels').value.split(',').map(s => s.trim()).filter(Boolean),
|
|
612
|
+
relevantFiles: document.getElementById('new-files').value.split(',').map(s => s.trim()).filter(Boolean),
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
await gql(\`
|
|
616
|
+
mutation CreateIssue($input: CreateIssueInput!) {
|
|
617
|
+
createIssue(input: $input) { id }
|
|
618
|
+
}
|
|
619
|
+
\`, { input });
|
|
620
|
+
|
|
621
|
+
closeCreateModal();
|
|
622
|
+
// 폼 초기화
|
|
623
|
+
document.getElementById('new-title').value = '';
|
|
624
|
+
document.getElementById('new-desc').value = '';
|
|
625
|
+
document.getElementById('new-labels').value = '';
|
|
626
|
+
document.getElementById('new-files').value = '';
|
|
627
|
+
loadIssues();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// 유틸
|
|
631
|
+
function escHtml(s) {
|
|
632
|
+
if (!s) return '';
|
|
633
|
+
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function formatTimeAgo(iso) {
|
|
637
|
+
if (!iso) return '';
|
|
638
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
639
|
+
const mins = Math.floor(diff / 60000);
|
|
640
|
+
if (mins < 1) return 'just now';
|
|
641
|
+
if (mins < 60) return mins + 'm ago';
|
|
642
|
+
const hrs = Math.floor(mins / 60);
|
|
643
|
+
if (hrs < 24) return hrs + 'h ago';
|
|
644
|
+
const days = Math.floor(hrs / 24);
|
|
645
|
+
return days + 'd ago';
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
let searchTimer;
|
|
649
|
+
function debounceSearch() {
|
|
650
|
+
clearTimeout(searchTimer);
|
|
651
|
+
searchTimer = setTimeout(loadIssues, 300);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function applyFilter() {
|
|
655
|
+
loadIssues();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// 키보드 단축키
|
|
659
|
+
document.addEventListener('keydown', e => {
|
|
660
|
+
if (e.key === 'Escape') {
|
|
661
|
+
closeCreateModal();
|
|
662
|
+
closeDetail();
|
|
663
|
+
}
|
|
664
|
+
if (e.key === 'n' && !e.target.closest('input,textarea,select')) {
|
|
665
|
+
e.preventDefault();
|
|
666
|
+
openCreateModal();
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// 초기 로드
|
|
671
|
+
loadIssues();
|
|
672
|
+
// 30초 간격 자동 새로고침
|
|
673
|
+
setInterval(loadIssues, 30000);
|
|
674
|
+
</script>
|
|
675
|
+
</body>
|
|
676
|
+
</html>`;
|
|
677
|
+
//# sourceMappingURL=issueBoardHtml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issueBoardHtml.js","sourceRoot":"","sources":["../../src/issues/issueBoardHtml.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,wCAAwC;AACxC,sBAAsB;AACtB,iCAAiC;AACjC,+CAA+C;AAE/C,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8pBxB,CAAC"}
|