kanban-lite 1.0.4
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/.editorconfig +9 -0
- package/.github/workflows/ci.yml +59 -0
- package/.github/workflows/release.yml +75 -0
- package/.prettierignore +6 -0
- package/.prettierrc.yaml +4 -0
- package/.vscode/extensions.json +3 -0
- package/.vscode/launch.json +17 -0
- package/.vscode/settings.json +21 -0
- package/.vscode/tasks.json +22 -0
- package/.vscodeignore +11 -0
- package/CHANGELOG.md +184 -0
- package/CLAUDE.md +58 -0
- package/CONTRIBUTING.md +114 -0
- package/LICENSE +22 -0
- package/README.md +482 -0
- package/SKILL.md +237 -0
- package/dist/cli.js +8716 -0
- package/dist/extension.js +8463 -0
- package/dist/mcp-server.js +1327 -0
- package/dist/standalone-webview/icons-Dx9MGYqN.js +180 -0
- package/dist/standalone-webview/icons-Dx9MGYqN.js.map +1 -0
- package/dist/standalone-webview/index.js +85 -0
- package/dist/standalone-webview/index.js.map +1 -0
- package/dist/standalone-webview/react-vendor-DkYdDBET.js +25 -0
- package/dist/standalone-webview/react-vendor-DkYdDBET.js.map +1 -0
- package/dist/standalone-webview/style.css +1 -0
- package/dist/standalone.js +7513 -0
- package/dist/webview/icons-Dx9MGYqN.js +180 -0
- package/dist/webview/icons-Dx9MGYqN.js.map +1 -0
- package/dist/webview/index.js +85 -0
- package/dist/webview/index.js.map +1 -0
- package/dist/webview/react-vendor-DkYdDBET.js +25 -0
- package/dist/webview/react-vendor-DkYdDBET.js.map +1 -0
- package/dist/webview/style.css +1 -0
- package/docs/images/board-overview.png +0 -0
- package/docs/images/editor-view.png +0 -0
- package/docs/plans/2026-02-20-kanban-json-config-design.md +74 -0
- package/docs/plans/2026-02-20-kanban-json-config.md +690 -0
- package/eslint.config.mjs +31 -0
- package/package.json +161 -0
- package/postcss.config.js +6 -0
- package/resources/icon-light.png +0 -0
- package/resources/icon-light.svg +105 -0
- package/resources/icon.png +0 -0
- package/resources/icon.svg +105 -0
- package/resources/kanban-dark.svg +21 -0
- package/resources/kanban-light.svg +21 -0
- package/resources/kanban.svg +21 -0
- package/src/cli/index.ts +846 -0
- package/src/extension/FeatureHeaderProvider.ts +370 -0
- package/src/extension/KanbanPanel.ts +973 -0
- package/src/extension/SidebarViewProvider.ts +507 -0
- package/src/extension/featureFileUtils.ts +82 -0
- package/src/extension/index.ts +234 -0
- package/src/mcp-server/index.ts +632 -0
- package/src/sdk/KanbanSDK.ts +349 -0
- package/src/sdk/__tests__/KanbanSDK.test.ts +468 -0
- package/src/sdk/__tests__/parser.test.ts +170 -0
- package/src/sdk/fileUtils.ts +76 -0
- package/src/sdk/index.ts +6 -0
- package/src/sdk/parser.ts +70 -0
- package/src/sdk/types.ts +15 -0
- package/src/shared/config.ts +113 -0
- package/src/shared/editorTypes.ts +14 -0
- package/src/shared/types.ts +120 -0
- package/src/standalone/__tests__/server.integration.test.ts +1916 -0
- package/src/standalone/__tests__/webhooks.test.ts +357 -0
- package/src/standalone/fileUtils.ts +70 -0
- package/src/standalone/index.ts +71 -0
- package/src/standalone/server.ts +1046 -0
- package/src/standalone/webhooks.ts +135 -0
- package/src/webview/App.tsx +469 -0
- package/src/webview/assets/main.css +329 -0
- package/src/webview/assets/standalone-theme.css +130 -0
- package/src/webview/components/ColumnDialog.tsx +119 -0
- package/src/webview/components/CreateFeatureDialog.tsx +524 -0
- package/src/webview/components/DatePicker.tsx +185 -0
- package/src/webview/components/FeatureCard.tsx +186 -0
- package/src/webview/components/FeatureEditor.tsx +623 -0
- package/src/webview/components/KanbanBoard.tsx +144 -0
- package/src/webview/components/KanbanColumn.tsx +159 -0
- package/src/webview/components/MarkdownEditor.tsx +291 -0
- package/src/webview/components/PrioritySelect.tsx +39 -0
- package/src/webview/components/QuickAddInput.tsx +72 -0
- package/src/webview/components/SettingsPanel.tsx +284 -0
- package/src/webview/components/Toolbar.tsx +175 -0
- package/src/webview/components/UndoToast.tsx +70 -0
- package/src/webview/index.html +12 -0
- package/src/webview/lib/utils.ts +6 -0
- package/src/webview/main.tsx +11 -0
- package/src/webview/standalone-main.tsx +13 -0
- package/src/webview/standalone-shim.ts +132 -0
- package/src/webview/standalone.html +12 -0
- package/src/webview/store/index.ts +241 -0
- package/tailwind.config.js +53 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +36 -0
- package/vite.standalone.config.ts +62 -0
- package/vitest.config.ts +15 -0
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
import * as path from 'path'
|
|
2
|
+
import * as fs from 'fs/promises'
|
|
3
|
+
import { KanbanSDK } from '../sdk/KanbanSDK'
|
|
4
|
+
import type { Feature, FeatureStatus, Priority } from '../shared/types'
|
|
5
|
+
import { loadWebhooks, createWebhook, deleteWebhook } from '../standalone/webhooks'
|
|
6
|
+
import { readConfig, writeConfig, configToSettings, settingsToConfig } from '../shared/config'
|
|
7
|
+
import type { CardDisplaySettings } from '../shared/types'
|
|
8
|
+
|
|
9
|
+
const VALID_STATUSES: FeatureStatus[] = ['backlog', 'todo', 'in-progress', 'review', 'done']
|
|
10
|
+
const VALID_PRIORITIES: Priority[] = ['critical', 'high', 'medium', 'low']
|
|
11
|
+
|
|
12
|
+
// --- Arg parsing ---
|
|
13
|
+
|
|
14
|
+
function parseArgs(argv: string[]): { command: string; positional: string[]; flags: Record<string, string | true> } {
|
|
15
|
+
const args = argv.slice(2)
|
|
16
|
+
const command = args[0] || 'help'
|
|
17
|
+
const positional: string[] = []
|
|
18
|
+
const flags: Record<string, string | true> = {}
|
|
19
|
+
|
|
20
|
+
for (let i = 1; i < args.length; i++) {
|
|
21
|
+
const arg = args[i]
|
|
22
|
+
if (arg.startsWith('--')) {
|
|
23
|
+
const key = arg.slice(2)
|
|
24
|
+
const next = args[i + 1]
|
|
25
|
+
if (next && !next.startsWith('--')) {
|
|
26
|
+
flags[key] = next
|
|
27
|
+
i++
|
|
28
|
+
} else {
|
|
29
|
+
flags[key] = true
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
positional.push(arg)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { command, positional, flags }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- Resolve features directory ---
|
|
40
|
+
|
|
41
|
+
async function findWorkspaceRoot(startDir: string): Promise<string> {
|
|
42
|
+
let dir = startDir
|
|
43
|
+
while (true) {
|
|
44
|
+
try {
|
|
45
|
+
await fs.access(path.join(dir, '.git'))
|
|
46
|
+
return dir
|
|
47
|
+
} catch {
|
|
48
|
+
// try package.json
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await fs.access(path.join(dir, 'package.json'))
|
|
52
|
+
return dir
|
|
53
|
+
} catch {
|
|
54
|
+
// continue up
|
|
55
|
+
}
|
|
56
|
+
const parent = path.dirname(dir)
|
|
57
|
+
if (parent === dir) return startDir // reached filesystem root
|
|
58
|
+
dir = parent
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function resolveFeaturesDir(flags: Record<string, string | true>): Promise<string> {
|
|
63
|
+
if (typeof flags.dir === 'string') {
|
|
64
|
+
return path.resolve(flags.dir)
|
|
65
|
+
}
|
|
66
|
+
const root = await findWorkspaceRoot(process.cwd())
|
|
67
|
+
return path.join(root, '.kanban')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Colors ---
|
|
71
|
+
|
|
72
|
+
const bold = (s: string) => `\x1b[1m${s}\x1b[0m`
|
|
73
|
+
const dim = (s: string) => `\x1b[2m${s}\x1b[0m`
|
|
74
|
+
const green = (s: string) => `\x1b[32m${s}\x1b[0m`
|
|
75
|
+
const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`
|
|
76
|
+
const red = (s: string) => `\x1b[31m${s}\x1b[0m`
|
|
77
|
+
const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`
|
|
78
|
+
const magenta = (s: string) => `\x1b[35m${s}\x1b[0m`
|
|
79
|
+
|
|
80
|
+
function colorStatus(status: string): string {
|
|
81
|
+
switch (status) {
|
|
82
|
+
case 'backlog': return dim(status)
|
|
83
|
+
case 'todo': return cyan(status)
|
|
84
|
+
case 'in-progress': return yellow(status)
|
|
85
|
+
case 'review': return magenta(status)
|
|
86
|
+
case 'done': return green(status)
|
|
87
|
+
default: return status
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function colorPriority(priority: string): string {
|
|
92
|
+
switch (priority) {
|
|
93
|
+
case 'critical': return red(priority)
|
|
94
|
+
case 'high': return yellow(priority)
|
|
95
|
+
case 'medium': return priority
|
|
96
|
+
case 'low': return dim(priority)
|
|
97
|
+
default: return priority
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// --- Formatters ---
|
|
102
|
+
|
|
103
|
+
function getTitleFromContent(content: string): string {
|
|
104
|
+
const match = content.match(/^#\s+(.+)$/m)
|
|
105
|
+
if (match) return match[1].trim()
|
|
106
|
+
const firstLine = content.split('\n').map(l => l.trim()).find(l => l.length > 0)
|
|
107
|
+
return firstLine || 'Untitled'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatCardRow(c: Feature): string {
|
|
111
|
+
const title = getTitleFromContent(c.content)
|
|
112
|
+
const truncTitle = title.length > 40 ? title.slice(0, 37) + '...' : title
|
|
113
|
+
const assignee = c.assignee || '-'
|
|
114
|
+
return ` ${bold(c.id.slice(0, 30).padEnd(30))} ${colorStatus(c.status.padEnd(12))} ${colorPriority(c.priority.padEnd(8))} ${assignee.padEnd(12)} ${truncTitle}`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function formatCardDetail(c: Feature): string {
|
|
118
|
+
const title = getTitleFromContent(c.content)
|
|
119
|
+
const lines = [
|
|
120
|
+
`${bold(title)}`,
|
|
121
|
+
'',
|
|
122
|
+
` ID: ${c.id}`,
|
|
123
|
+
` Status: ${colorStatus(c.status)}`,
|
|
124
|
+
` Priority: ${colorPriority(c.priority)}`,
|
|
125
|
+
` Assignee: ${c.assignee || '-'}`,
|
|
126
|
+
` Due: ${c.dueDate || '-'}`,
|
|
127
|
+
` Labels: ${c.labels.length > 0 ? c.labels.join(', ') : '-'}`,
|
|
128
|
+
` Created: ${c.created}`,
|
|
129
|
+
` Modified: ${c.modified}`,
|
|
130
|
+
` File: ${c.filePath}`,
|
|
131
|
+
]
|
|
132
|
+
if (c.completedAt) {
|
|
133
|
+
lines.push(` Completed: ${c.completedAt}`)
|
|
134
|
+
}
|
|
135
|
+
// Show body content (minus the title heading)
|
|
136
|
+
const body = c.content.replace(/^#\s+.+\n?/, '').trim()
|
|
137
|
+
if (body) {
|
|
138
|
+
lines.push('', dim(' --- Content ---'), '', ' ' + body.split('\n').join('\n '))
|
|
139
|
+
}
|
|
140
|
+
return lines.join('\n')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// --- Card Commands ---
|
|
144
|
+
|
|
145
|
+
async function cmdList(sdk: KanbanSDK, flags: Record<string, string | true>): Promise<void> {
|
|
146
|
+
let cards = await sdk.listCards()
|
|
147
|
+
|
|
148
|
+
if (typeof flags.status === 'string') {
|
|
149
|
+
cards = cards.filter(c => c.status === flags.status)
|
|
150
|
+
}
|
|
151
|
+
if (typeof flags.priority === 'string') {
|
|
152
|
+
cards = cards.filter(c => c.priority === flags.priority)
|
|
153
|
+
}
|
|
154
|
+
if (typeof flags.assignee === 'string') {
|
|
155
|
+
cards = cards.filter(c => c.assignee === flags.assignee)
|
|
156
|
+
}
|
|
157
|
+
if (typeof flags.label === 'string') {
|
|
158
|
+
cards = cards.filter(c => c.labels.includes(flags.label as string))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (flags.json) {
|
|
162
|
+
console.log(JSON.stringify(cards, null, 2))
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (cards.length === 0) {
|
|
167
|
+
console.log(dim(' No cards found.'))
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(` ${dim('ID'.padEnd(30))} ${dim('STATUS'.padEnd(12))} ${dim('PRIORITY'.padEnd(8))} ${dim('ASSIGNEE'.padEnd(12))} ${dim('TITLE')}`)
|
|
172
|
+
console.log(dim(' ' + '-'.repeat(90)))
|
|
173
|
+
for (const c of cards) {
|
|
174
|
+
console.log(formatCardRow(c))
|
|
175
|
+
}
|
|
176
|
+
console.log(dim(`\n ${cards.length} card(s)`))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function cmdShow(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
|
|
180
|
+
const cardId = positional[0]
|
|
181
|
+
if (!cardId) {
|
|
182
|
+
console.error(red('Error: card ID required. Usage: kl show <id>'))
|
|
183
|
+
process.exit(1)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const card = await sdk.getCard(cardId)
|
|
187
|
+
if (!card) {
|
|
188
|
+
// Try partial match
|
|
189
|
+
const all = await sdk.listCards()
|
|
190
|
+
const matches = all.filter(c => c.id.includes(cardId))
|
|
191
|
+
if (matches.length === 1) {
|
|
192
|
+
if (flags.json) {
|
|
193
|
+
console.log(JSON.stringify(matches[0], null, 2))
|
|
194
|
+
} else {
|
|
195
|
+
console.log(formatCardDetail(matches[0]))
|
|
196
|
+
}
|
|
197
|
+
return
|
|
198
|
+
} else if (matches.length > 1) {
|
|
199
|
+
console.error(red(`Multiple cards match "${cardId}":`))
|
|
200
|
+
for (const m of matches) console.error(` ${m.id}`)
|
|
201
|
+
process.exit(1)
|
|
202
|
+
}
|
|
203
|
+
console.error(red(`Card not found: ${cardId}`))
|
|
204
|
+
process.exit(1)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (flags.json) {
|
|
208
|
+
console.log(JSON.stringify(card, null, 2))
|
|
209
|
+
} else {
|
|
210
|
+
console.log(formatCardDetail(card))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function cmdAdd(sdk: KanbanSDK, flags: Record<string, string | true>): Promise<void> {
|
|
215
|
+
const title = typeof flags.title === 'string' ? flags.title : ''
|
|
216
|
+
if (!title) {
|
|
217
|
+
console.error(red('Error: --title is required. Usage: kl add --title "My card"'))
|
|
218
|
+
process.exit(1)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const status = (typeof flags.status === 'string' ? flags.status : 'backlog') as FeatureStatus
|
|
222
|
+
if (!VALID_STATUSES.includes(status)) {
|
|
223
|
+
console.error(red(`Invalid status: ${status}. Must be one of: ${VALID_STATUSES.join(', ')}`))
|
|
224
|
+
process.exit(1)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const priority = (typeof flags.priority === 'string' ? flags.priority : 'medium') as Priority
|
|
228
|
+
if (!VALID_PRIORITIES.includes(priority)) {
|
|
229
|
+
console.error(red(`Invalid priority: ${priority}. Must be one of: ${VALID_PRIORITIES.join(', ')}`))
|
|
230
|
+
process.exit(1)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const assignee = typeof flags.assignee === 'string' ? flags.assignee : null
|
|
234
|
+
const dueDate = typeof flags.due === 'string' ? flags.due : null
|
|
235
|
+
const labels = typeof flags.label === 'string' ? flags.label.split(',').map(l => l.trim()) : []
|
|
236
|
+
const body = typeof flags.body === 'string' ? flags.body : ''
|
|
237
|
+
|
|
238
|
+
const content = `# ${title}${body ? '\n\n' + body : ''}`
|
|
239
|
+
|
|
240
|
+
const card = await sdk.createCard({ content, status, priority, assignee, dueDate, labels })
|
|
241
|
+
|
|
242
|
+
if (flags.json) {
|
|
243
|
+
console.log(JSON.stringify(card, null, 2))
|
|
244
|
+
} else {
|
|
245
|
+
console.log(green(`Created: ${card.id}`))
|
|
246
|
+
console.log(` Status: ${colorStatus(card.status)}, Priority: ${colorPriority(card.priority)}`)
|
|
247
|
+
console.log(` File: ${dim(card.filePath)}`)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function cmdMove(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
|
|
252
|
+
const cardId = positional[0]
|
|
253
|
+
const newStatus = positional[1] as FeatureStatus
|
|
254
|
+
|
|
255
|
+
if (!cardId || !newStatus) {
|
|
256
|
+
console.error(red('Usage: kl move <id> <status> [--position <n>]'))
|
|
257
|
+
process.exit(1)
|
|
258
|
+
}
|
|
259
|
+
if (!VALID_STATUSES.includes(newStatus)) {
|
|
260
|
+
console.error(red(`Invalid status: ${newStatus}. Must be one of: ${VALID_STATUSES.join(', ')}`))
|
|
261
|
+
process.exit(1)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Support partial ID match
|
|
265
|
+
let resolvedId = cardId
|
|
266
|
+
const card = await sdk.getCard(cardId)
|
|
267
|
+
if (!card) {
|
|
268
|
+
const all = await sdk.listCards()
|
|
269
|
+
const matches = all.filter(c => c.id.includes(cardId))
|
|
270
|
+
if (matches.length === 1) {
|
|
271
|
+
resolvedId = matches[0].id
|
|
272
|
+
} else if (matches.length > 1) {
|
|
273
|
+
console.error(red(`Multiple cards match "${cardId}":`))
|
|
274
|
+
for (const m of matches) console.error(` ${m.id}`)
|
|
275
|
+
process.exit(1)
|
|
276
|
+
} else {
|
|
277
|
+
console.error(red(`Card not found: ${cardId}`))
|
|
278
|
+
process.exit(1)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const position = typeof flags.position === 'string' ? parseInt(flags.position, 10) : undefined
|
|
283
|
+
const updated = await sdk.moveCard(resolvedId, newStatus, position)
|
|
284
|
+
console.log(green(`Moved ${updated.id} → ${colorStatus(newStatus)}`))
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function cmdEdit(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
|
|
288
|
+
const cardId = positional[0]
|
|
289
|
+
if (!cardId) {
|
|
290
|
+
console.error(red('Usage: kl edit <id> [--status ...] [--priority ...] [--assignee ...] [--due ...] [--label ...]'))
|
|
291
|
+
process.exit(1)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Support partial ID match
|
|
295
|
+
let resolvedId = cardId
|
|
296
|
+
const card = await sdk.getCard(cardId)
|
|
297
|
+
if (!card) {
|
|
298
|
+
const all = await sdk.listCards()
|
|
299
|
+
const matches = all.filter(c => c.id.includes(cardId))
|
|
300
|
+
if (matches.length === 1) {
|
|
301
|
+
resolvedId = matches[0].id
|
|
302
|
+
} else if (matches.length > 1) {
|
|
303
|
+
console.error(red(`Multiple cards match "${cardId}":`))
|
|
304
|
+
for (const m of matches) console.error(` ${m.id}`)
|
|
305
|
+
process.exit(1)
|
|
306
|
+
} else {
|
|
307
|
+
console.error(red(`Card not found: ${cardId}`))
|
|
308
|
+
process.exit(1)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const updates: Partial<Feature> = {}
|
|
313
|
+
if (typeof flags.status === 'string') {
|
|
314
|
+
if (!VALID_STATUSES.includes(flags.status as FeatureStatus)) {
|
|
315
|
+
console.error(red(`Invalid status: ${flags.status}`))
|
|
316
|
+
process.exit(1)
|
|
317
|
+
}
|
|
318
|
+
updates.status = flags.status as FeatureStatus
|
|
319
|
+
}
|
|
320
|
+
if (typeof flags.priority === 'string') {
|
|
321
|
+
if (!VALID_PRIORITIES.includes(flags.priority as Priority)) {
|
|
322
|
+
console.error(red(`Invalid priority: ${flags.priority}`))
|
|
323
|
+
process.exit(1)
|
|
324
|
+
}
|
|
325
|
+
updates.priority = flags.priority as Priority
|
|
326
|
+
}
|
|
327
|
+
if (typeof flags.assignee === 'string') updates.assignee = flags.assignee
|
|
328
|
+
if (typeof flags.due === 'string') updates.dueDate = flags.due
|
|
329
|
+
if (typeof flags.label === 'string') updates.labels = flags.label.split(',').map(l => l.trim())
|
|
330
|
+
|
|
331
|
+
if (Object.keys(updates).length === 0) {
|
|
332
|
+
console.error(red('No updates specified. Use --status, --priority, --assignee, --due, or --label'))
|
|
333
|
+
process.exit(1)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const updated = await sdk.updateCard(resolvedId, updates)
|
|
337
|
+
console.log(green(`Updated: ${updated.id}`))
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function cmdDelete(sdk: KanbanSDK, positional: string[]): Promise<void> {
|
|
341
|
+
const cardId = positional[0]
|
|
342
|
+
if (!cardId) {
|
|
343
|
+
console.error(red('Usage: kl delete <id>'))
|
|
344
|
+
process.exit(1)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Support partial ID match
|
|
348
|
+
let resolvedId = cardId
|
|
349
|
+
const card = await sdk.getCard(cardId)
|
|
350
|
+
if (!card) {
|
|
351
|
+
const all = await sdk.listCards()
|
|
352
|
+
const matches = all.filter(c => c.id.includes(cardId))
|
|
353
|
+
if (matches.length === 1) {
|
|
354
|
+
resolvedId = matches[0].id
|
|
355
|
+
} else if (matches.length > 1) {
|
|
356
|
+
console.error(red(`Multiple cards match "${cardId}":`))
|
|
357
|
+
for (const m of matches) console.error(` ${m.id}`)
|
|
358
|
+
process.exit(1)
|
|
359
|
+
} else {
|
|
360
|
+
console.error(red(`Card not found: ${cardId}`))
|
|
361
|
+
process.exit(1)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await sdk.deleteCard(resolvedId)
|
|
366
|
+
console.log(green(`Deleted: ${resolvedId}`))
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function cmdInit(sdk: KanbanSDK): Promise<void> {
|
|
370
|
+
await sdk.init()
|
|
371
|
+
console.log(green(`Initialized: ${sdk.featuresDir}`))
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// --- Attachment Commands ---
|
|
375
|
+
|
|
376
|
+
async function cmdAttach(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
|
|
377
|
+
const subcommand = positional[0] || 'list'
|
|
378
|
+
const cardId = positional[1]
|
|
379
|
+
|
|
380
|
+
if (subcommand !== 'list' && subcommand !== 'add' && subcommand !== 'rm' && subcommand !== 'remove') {
|
|
381
|
+
// If first positional looks like a card ID, treat it as "list <cardId>"
|
|
382
|
+
const resolvedId = await resolveCardId(sdk, subcommand)
|
|
383
|
+
const attachments = await sdk.listAttachments(resolvedId)
|
|
384
|
+
if (flags.json) {
|
|
385
|
+
console.log(JSON.stringify(attachments, null, 2))
|
|
386
|
+
} else if (attachments.length === 0) {
|
|
387
|
+
console.log(dim(' No attachments.'))
|
|
388
|
+
} else {
|
|
389
|
+
for (const a of attachments) console.log(` ${a}`)
|
|
390
|
+
}
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
switch (subcommand) {
|
|
395
|
+
case 'list': {
|
|
396
|
+
if (!cardId) {
|
|
397
|
+
console.error(red('Usage: kl attach list <card-id>'))
|
|
398
|
+
process.exit(1)
|
|
399
|
+
}
|
|
400
|
+
const resolvedId = await resolveCardId(sdk, cardId)
|
|
401
|
+
const attachments = await sdk.listAttachments(resolvedId)
|
|
402
|
+
if (flags.json) {
|
|
403
|
+
console.log(JSON.stringify(attachments, null, 2))
|
|
404
|
+
} else if (attachments.length === 0) {
|
|
405
|
+
console.log(dim(' No attachments.'))
|
|
406
|
+
} else {
|
|
407
|
+
for (const a of attachments) console.log(` ${a}`)
|
|
408
|
+
}
|
|
409
|
+
break
|
|
410
|
+
}
|
|
411
|
+
case 'add': {
|
|
412
|
+
if (!cardId) {
|
|
413
|
+
console.error(red('Usage: kl attach add <card-id> <file-path>'))
|
|
414
|
+
process.exit(1)
|
|
415
|
+
}
|
|
416
|
+
const filePath = positional[2]
|
|
417
|
+
if (!filePath) {
|
|
418
|
+
console.error(red('Usage: kl attach add <card-id> <file-path>'))
|
|
419
|
+
process.exit(1)
|
|
420
|
+
}
|
|
421
|
+
const resolvedId = await resolveCardId(sdk, cardId)
|
|
422
|
+
const updated = await sdk.addAttachment(resolvedId, filePath)
|
|
423
|
+
console.log(green(`Attached to ${updated.id}: ${path.basename(filePath)}`))
|
|
424
|
+
break
|
|
425
|
+
}
|
|
426
|
+
case 'remove':
|
|
427
|
+
case 'rm': {
|
|
428
|
+
if (!cardId) {
|
|
429
|
+
console.error(red('Usage: kl attach remove <card-id> <filename>'))
|
|
430
|
+
process.exit(1)
|
|
431
|
+
}
|
|
432
|
+
const filename = positional[2]
|
|
433
|
+
if (!filename) {
|
|
434
|
+
console.error(red('Usage: kl attach remove <card-id> <filename>'))
|
|
435
|
+
process.exit(1)
|
|
436
|
+
}
|
|
437
|
+
const resolvedId = await resolveCardId(sdk, cardId)
|
|
438
|
+
const updated = await sdk.removeAttachment(resolvedId, filename)
|
|
439
|
+
console.log(green(`Removed from ${updated.id}: ${filename}`))
|
|
440
|
+
break
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function resolveCardId(sdk: KanbanSDK, cardId: string): Promise<string> {
|
|
446
|
+
const card = await sdk.getCard(cardId)
|
|
447
|
+
if (card) return cardId
|
|
448
|
+
|
|
449
|
+
const all = await sdk.listCards()
|
|
450
|
+
const matches = all.filter(c => c.id.includes(cardId))
|
|
451
|
+
if (matches.length === 1) return matches[0].id
|
|
452
|
+
if (matches.length > 1) {
|
|
453
|
+
console.error(red(`Multiple cards match "${cardId}":`))
|
|
454
|
+
for (const m of matches) console.error(` ${m.id}`)
|
|
455
|
+
process.exit(1)
|
|
456
|
+
}
|
|
457
|
+
console.error(red(`Card not found: ${cardId}`))
|
|
458
|
+
process.exit(1)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// --- Column Commands ---
|
|
462
|
+
|
|
463
|
+
async function cmdColumns(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
|
|
464
|
+
const subcommand = positional[0] || 'list'
|
|
465
|
+
|
|
466
|
+
switch (subcommand) {
|
|
467
|
+
case 'list': {
|
|
468
|
+
const columns = await sdk.listColumns()
|
|
469
|
+
if (flags.json) {
|
|
470
|
+
console.log(JSON.stringify(columns, null, 2))
|
|
471
|
+
} else {
|
|
472
|
+
console.log(` ${dim('ID'.padEnd(20))} ${dim('NAME'.padEnd(20))} ${dim('COLOR')}`)
|
|
473
|
+
console.log(dim(' ' + '-'.repeat(50)))
|
|
474
|
+
for (const col of columns) {
|
|
475
|
+
console.log(` ${bold(col.id.padEnd(20))} ${col.name.padEnd(20)} ${col.color}`)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
break
|
|
479
|
+
}
|
|
480
|
+
case 'add': {
|
|
481
|
+
const id = typeof flags.id === 'string' ? flags.id : ''
|
|
482
|
+
const name = typeof flags.name === 'string' ? flags.name : ''
|
|
483
|
+
const color = typeof flags.color === 'string' ? flags.color : '#6b7280'
|
|
484
|
+
if (!id || !name) {
|
|
485
|
+
console.error(red('Usage: kl columns add --id <id> --name <name> [--color <hex>]'))
|
|
486
|
+
process.exit(1)
|
|
487
|
+
}
|
|
488
|
+
const columns = await sdk.addColumn({ id, name, color })
|
|
489
|
+
console.log(green(`Added column: ${id} (${name})`))
|
|
490
|
+
if (flags.json) console.log(JSON.stringify(columns, null, 2))
|
|
491
|
+
break
|
|
492
|
+
}
|
|
493
|
+
case 'update': {
|
|
494
|
+
const columnId = positional[1]
|
|
495
|
+
if (!columnId) {
|
|
496
|
+
console.error(red('Usage: kl columns update <id> [--name <name>] [--color <hex>]'))
|
|
497
|
+
process.exit(1)
|
|
498
|
+
}
|
|
499
|
+
const updates: Record<string, string> = {}
|
|
500
|
+
if (typeof flags.name === 'string') updates.name = flags.name
|
|
501
|
+
if (typeof flags.color === 'string') updates.color = flags.color
|
|
502
|
+
if (Object.keys(updates).length === 0) {
|
|
503
|
+
console.error(red('No updates specified. Use --name or --color'))
|
|
504
|
+
process.exit(1)
|
|
505
|
+
}
|
|
506
|
+
const columns = await sdk.updateColumn(columnId, updates)
|
|
507
|
+
console.log(green(`Updated column: ${columnId}`))
|
|
508
|
+
if (flags.json) console.log(JSON.stringify(columns, null, 2))
|
|
509
|
+
break
|
|
510
|
+
}
|
|
511
|
+
case 'remove':
|
|
512
|
+
case 'rm': {
|
|
513
|
+
const columnId = positional[1]
|
|
514
|
+
if (!columnId) {
|
|
515
|
+
console.error(red('Usage: kl columns remove <id>'))
|
|
516
|
+
process.exit(1)
|
|
517
|
+
}
|
|
518
|
+
const columns = await sdk.removeColumn(columnId)
|
|
519
|
+
console.log(green(`Removed column: ${columnId}`))
|
|
520
|
+
if (flags.json) console.log(JSON.stringify(columns, null, 2))
|
|
521
|
+
break
|
|
522
|
+
}
|
|
523
|
+
default:
|
|
524
|
+
console.error(red(`Unknown columns subcommand: ${subcommand}`))
|
|
525
|
+
console.error('Available: list, add, update, remove')
|
|
526
|
+
process.exit(1)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// --- Webhook Commands ---
|
|
531
|
+
|
|
532
|
+
async function cmdWebhooks(positional: string[], flags: Record<string, string | true>, workspaceRoot: string): Promise<void> {
|
|
533
|
+
const subcommand = positional[0] || 'list'
|
|
534
|
+
|
|
535
|
+
switch (subcommand) {
|
|
536
|
+
case 'list': {
|
|
537
|
+
const webhooks = loadWebhooks(workspaceRoot)
|
|
538
|
+
if (flags.json) {
|
|
539
|
+
console.log(JSON.stringify(webhooks, null, 2))
|
|
540
|
+
} else if (webhooks.length === 0) {
|
|
541
|
+
console.log(dim(' No webhooks registered.'))
|
|
542
|
+
} else {
|
|
543
|
+
console.log(` ${dim('ID'.padEnd(22))} ${dim('URL'.padEnd(40))} ${dim('EVENTS'.padEnd(20))} ${dim('ACTIVE')}`)
|
|
544
|
+
console.log(dim(' ' + '-'.repeat(90)))
|
|
545
|
+
for (const w of webhooks) {
|
|
546
|
+
const events = w.events.join(', ')
|
|
547
|
+
const active = w.active ? green('yes') : red('no')
|
|
548
|
+
console.log(` ${bold(w.id.padEnd(22))} ${w.url.padEnd(40)} ${events.padEnd(20)} ${active}`)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
break
|
|
552
|
+
}
|
|
553
|
+
case 'add': {
|
|
554
|
+
const url = typeof flags.url === 'string' ? flags.url : ''
|
|
555
|
+
if (!url) {
|
|
556
|
+
console.error(red('Usage: kl webhooks add --url <url> [--events <event1,event2>] [--secret <key>]'))
|
|
557
|
+
process.exit(1)
|
|
558
|
+
}
|
|
559
|
+
const events = typeof flags.events === 'string' ? flags.events.split(',').map(e => e.trim()) : ['*']
|
|
560
|
+
const secret = typeof flags.secret === 'string' ? flags.secret : undefined
|
|
561
|
+
const webhook = createWebhook(workspaceRoot, { url, events, secret })
|
|
562
|
+
if (flags.json) {
|
|
563
|
+
console.log(JSON.stringify(webhook, null, 2))
|
|
564
|
+
} else {
|
|
565
|
+
console.log(green(`Created webhook: ${webhook.id}`))
|
|
566
|
+
console.log(` URL: ${webhook.url}`)
|
|
567
|
+
console.log(` Events: ${webhook.events.join(', ')}`)
|
|
568
|
+
if (webhook.secret) console.log(` Secret: ${dim('(configured)')}`)
|
|
569
|
+
}
|
|
570
|
+
break
|
|
571
|
+
}
|
|
572
|
+
case 'remove':
|
|
573
|
+
case 'rm': {
|
|
574
|
+
const webhookId = positional[1]
|
|
575
|
+
if (!webhookId) {
|
|
576
|
+
console.error(red('Usage: kl webhooks remove <id>'))
|
|
577
|
+
process.exit(1)
|
|
578
|
+
}
|
|
579
|
+
const removed = deleteWebhook(workspaceRoot, webhookId)
|
|
580
|
+
if (removed) {
|
|
581
|
+
console.log(green(`Removed webhook: ${webhookId}`))
|
|
582
|
+
} else {
|
|
583
|
+
console.error(red(`Webhook not found: ${webhookId}`))
|
|
584
|
+
process.exit(1)
|
|
585
|
+
}
|
|
586
|
+
break
|
|
587
|
+
}
|
|
588
|
+
default:
|
|
589
|
+
console.error(red(`Unknown webhooks subcommand: ${subcommand}`))
|
|
590
|
+
console.error('Available: list, add, remove')
|
|
591
|
+
process.exit(1)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// --- Settings Commands ---
|
|
596
|
+
|
|
597
|
+
const SETTINGS_KEYS = [
|
|
598
|
+
'showPriorityBadges', 'showAssignee', 'showDueDate', 'showLabels',
|
|
599
|
+
'showFileName', 'compactMode', 'defaultPriority', 'defaultStatus'
|
|
600
|
+
] as const
|
|
601
|
+
|
|
602
|
+
async function cmdSettings(positional: string[], flags: Record<string, string | true>, workspaceRoot: string): Promise<void> {
|
|
603
|
+
const subcommand = positional[0] || 'show'
|
|
604
|
+
|
|
605
|
+
switch (subcommand) {
|
|
606
|
+
case 'show':
|
|
607
|
+
case 'list': {
|
|
608
|
+
const config = readConfig(workspaceRoot)
|
|
609
|
+
const settings = configToSettings(config)
|
|
610
|
+
if (flags.json) {
|
|
611
|
+
console.log(JSON.stringify(settings, null, 2))
|
|
612
|
+
} else {
|
|
613
|
+
console.log(` ${dim('SETTING'.padEnd(24))} ${dim('VALUE')}`)
|
|
614
|
+
console.log(dim(' ' + '-'.repeat(40)))
|
|
615
|
+
for (const key of SETTINGS_KEYS) {
|
|
616
|
+
console.log(` ${bold(key.padEnd(24))} ${String(settings[key as keyof CardDisplaySettings])}`)
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
break
|
|
620
|
+
}
|
|
621
|
+
case 'update':
|
|
622
|
+
case 'set': {
|
|
623
|
+
const config = readConfig(workspaceRoot)
|
|
624
|
+
const settings = configToSettings(config)
|
|
625
|
+
let changed = false
|
|
626
|
+
const settingsAny = settings as unknown as Record<string, unknown>
|
|
627
|
+
for (const key of SETTINGS_KEYS) {
|
|
628
|
+
if (typeof flags[key] === 'string') {
|
|
629
|
+
const val = flags[key] as string
|
|
630
|
+
if (val === 'true') {
|
|
631
|
+
settingsAny[key] = true
|
|
632
|
+
} else if (val === 'false') {
|
|
633
|
+
settingsAny[key] = false
|
|
634
|
+
} else {
|
|
635
|
+
settingsAny[key] = val
|
|
636
|
+
}
|
|
637
|
+
changed = true
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (!changed) {
|
|
641
|
+
console.error(red('No settings specified. Use --<setting> <value>'))
|
|
642
|
+
console.error(`Available: ${SETTINGS_KEYS.join(', ')}`)
|
|
643
|
+
process.exit(1)
|
|
644
|
+
}
|
|
645
|
+
writeConfig(workspaceRoot, settingsToConfig(config, settings))
|
|
646
|
+
console.log(green('Settings updated.'))
|
|
647
|
+
if (flags.json) {
|
|
648
|
+
console.log(JSON.stringify(configToSettings(readConfig(workspaceRoot)), null, 2))
|
|
649
|
+
}
|
|
650
|
+
break
|
|
651
|
+
}
|
|
652
|
+
default:
|
|
653
|
+
console.error(red(`Unknown settings subcommand: ${subcommand}`))
|
|
654
|
+
console.error('Available: show, update')
|
|
655
|
+
process.exit(1)
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// --- Serve Command ---
|
|
660
|
+
|
|
661
|
+
async function cmdServe(flags: Record<string, string | true>): Promise<void> {
|
|
662
|
+
const dir = typeof flags.dir === 'string' ? flags.dir : '.kanban'
|
|
663
|
+
const port = typeof flags.port === 'string' ? parseInt(flags.port, 10) : 3000
|
|
664
|
+
const noBrowser = !!flags['no-browser']
|
|
665
|
+
|
|
666
|
+
// Dynamically import the standalone server
|
|
667
|
+
const { startServer } = await import('../standalone/server')
|
|
668
|
+
const server = startServer(dir, port)
|
|
669
|
+
|
|
670
|
+
if (!noBrowser) {
|
|
671
|
+
server.on('listening', async () => {
|
|
672
|
+
try {
|
|
673
|
+
const open = (await import('open')).default
|
|
674
|
+
open(`http://localhost:${port}`)
|
|
675
|
+
} catch {
|
|
676
|
+
// open is optional
|
|
677
|
+
}
|
|
678
|
+
})
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
process.on('SIGINT', () => {
|
|
682
|
+
console.log('\nShutting down...')
|
|
683
|
+
server.close()
|
|
684
|
+
process.exit(0)
|
|
685
|
+
})
|
|
686
|
+
process.on('SIGTERM', () => {
|
|
687
|
+
server.close()
|
|
688
|
+
process.exit(0)
|
|
689
|
+
})
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function showHelp(): void {
|
|
693
|
+
console.log(`
|
|
694
|
+
${bold('kanban-lite')} (${bold('kl')}) - Manage your kanban board from the command line
|
|
695
|
+
|
|
696
|
+
${bold('Usage:')}
|
|
697
|
+
kanban-lite <command> [options]
|
|
698
|
+
kl <command> [options]
|
|
699
|
+
|
|
700
|
+
${bold('Card Commands:')}
|
|
701
|
+
list List cards
|
|
702
|
+
show <id> Show card details
|
|
703
|
+
add --title "..." Create a new card
|
|
704
|
+
move <id> <status> Move card to a new status (--position <n>)
|
|
705
|
+
edit <id> [--field value] Update card fields
|
|
706
|
+
delete <id> Delete a card
|
|
707
|
+
|
|
708
|
+
${bold('Attachment Commands:')}
|
|
709
|
+
attach <id> List attachments on a card
|
|
710
|
+
attach add <id> <path> Attach a file to a card
|
|
711
|
+
attach remove <id> <name> Remove an attachment from a card
|
|
712
|
+
|
|
713
|
+
${bold('Column Commands:')}
|
|
714
|
+
columns List columns
|
|
715
|
+
columns add Add a column (--id, --name, --color)
|
|
716
|
+
columns update <id> Update a column (--name, --color)
|
|
717
|
+
columns remove <id> Remove a column
|
|
718
|
+
|
|
719
|
+
${bold('Webhook Commands:')}
|
|
720
|
+
webhooks List registered webhooks
|
|
721
|
+
webhooks add Register a webhook (--url, --events, --secret)
|
|
722
|
+
webhooks remove <id> Remove a webhook
|
|
723
|
+
|
|
724
|
+
${bold('Settings Commands:')}
|
|
725
|
+
settings Show current settings
|
|
726
|
+
settings update Update settings (--<key> <value>)
|
|
727
|
+
|
|
728
|
+
${bold('Server:')}
|
|
729
|
+
serve Start standalone web server with REST API
|
|
730
|
+
|
|
731
|
+
${bold('Other:')}
|
|
732
|
+
init Initialize features directory
|
|
733
|
+
pwd Print workspace root path
|
|
734
|
+
|
|
735
|
+
${bold('Global Options:')}
|
|
736
|
+
--dir <path> Features directory (default: .kanban)
|
|
737
|
+
--json Output as JSON
|
|
738
|
+
|
|
739
|
+
${bold('List Filters:')}
|
|
740
|
+
--status <status> Filter by status
|
|
741
|
+
--priority <priority> Filter by priority (critical, high, medium, low)
|
|
742
|
+
--assignee <name> Filter by assignee
|
|
743
|
+
--label <label> Filter by label
|
|
744
|
+
|
|
745
|
+
${bold('Add/Edit Options:')}
|
|
746
|
+
--title <title> Card title (required for add)
|
|
747
|
+
--body <text> Card body content
|
|
748
|
+
--status <status> Status
|
|
749
|
+
--priority <priority> Priority
|
|
750
|
+
--assignee <name> Assignee
|
|
751
|
+
--due <date> Due date
|
|
752
|
+
--label <l1,l2> Labels (comma-separated)
|
|
753
|
+
|
|
754
|
+
${bold('Webhook Options:')}
|
|
755
|
+
--url <url> Webhook target URL (required for add)
|
|
756
|
+
--events <e1,e2> Events to subscribe to (default: *)
|
|
757
|
+
--secret <key> HMAC-SHA256 signing secret
|
|
758
|
+
|
|
759
|
+
${bold('Serve Options:')}
|
|
760
|
+
--port <number> Port to listen on (default: 3000)
|
|
761
|
+
--no-browser Don't open browser automatically
|
|
762
|
+
`)
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// --- Main ---
|
|
766
|
+
|
|
767
|
+
async function main(): Promise<void> {
|
|
768
|
+
const { command, positional, flags } = parseArgs(process.argv)
|
|
769
|
+
|
|
770
|
+
if (command === 'help' || flags.help) {
|
|
771
|
+
showHelp()
|
|
772
|
+
return
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Serve doesn't need SDK
|
|
776
|
+
if (command === 'serve') {
|
|
777
|
+
await cmdServe(flags)
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const featuresDir = await resolveFeaturesDir(flags)
|
|
782
|
+
const workspaceRoot = path.dirname(featuresDir)
|
|
783
|
+
const sdk = new KanbanSDK(featuresDir)
|
|
784
|
+
|
|
785
|
+
switch (command) {
|
|
786
|
+
case 'list':
|
|
787
|
+
case 'ls':
|
|
788
|
+
await cmdList(sdk, flags)
|
|
789
|
+
break
|
|
790
|
+
case 'show':
|
|
791
|
+
case 'view':
|
|
792
|
+
await cmdShow(sdk, positional, flags)
|
|
793
|
+
break
|
|
794
|
+
case 'add':
|
|
795
|
+
case 'create':
|
|
796
|
+
case 'new':
|
|
797
|
+
await cmdAdd(sdk, flags)
|
|
798
|
+
break
|
|
799
|
+
case 'move':
|
|
800
|
+
case 'mv':
|
|
801
|
+
await cmdMove(sdk, positional, flags)
|
|
802
|
+
break
|
|
803
|
+
case 'edit':
|
|
804
|
+
case 'update':
|
|
805
|
+
await cmdEdit(sdk, positional, flags)
|
|
806
|
+
break
|
|
807
|
+
case 'delete':
|
|
808
|
+
case 'rm':
|
|
809
|
+
await cmdDelete(sdk, positional)
|
|
810
|
+
break
|
|
811
|
+
case 'attach':
|
|
812
|
+
await cmdAttach(sdk, positional, flags)
|
|
813
|
+
break
|
|
814
|
+
case 'columns':
|
|
815
|
+
case 'cols':
|
|
816
|
+
await cmdColumns(sdk, positional, flags)
|
|
817
|
+
break
|
|
818
|
+
case 'webhooks':
|
|
819
|
+
case 'webhook':
|
|
820
|
+
case 'wh':
|
|
821
|
+
await cmdWebhooks(positional, flags, workspaceRoot)
|
|
822
|
+
break
|
|
823
|
+
case 'settings':
|
|
824
|
+
await cmdSettings(positional, flags, workspaceRoot)
|
|
825
|
+
break
|
|
826
|
+
case 'pwd':
|
|
827
|
+
if (flags.json) {
|
|
828
|
+
console.log(JSON.stringify({ path: workspaceRoot }))
|
|
829
|
+
} else {
|
|
830
|
+
console.log(workspaceRoot)
|
|
831
|
+
}
|
|
832
|
+
break
|
|
833
|
+
case 'init':
|
|
834
|
+
await cmdInit(sdk)
|
|
835
|
+
break
|
|
836
|
+
default:
|
|
837
|
+
console.error(red(`Unknown command: ${command}`))
|
|
838
|
+
showHelp()
|
|
839
|
+
process.exit(1)
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
main().catch(err => {
|
|
844
|
+
console.error(red(`Error: ${err.message}`))
|
|
845
|
+
process.exit(1)
|
|
846
|
+
})
|