@uzukko/agentpm 0.1.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.
Files changed (70) hide show
  1. package/bin/taskapp.js +2 -0
  2. package/dist/api-client.d.ts +2 -0
  3. package/dist/api-client.js +27 -0
  4. package/dist/api-client.js.map +1 -0
  5. package/dist/commands/activity.d.ts +2 -0
  6. package/dist/commands/activity.js +88 -0
  7. package/dist/commands/activity.js.map +1 -0
  8. package/dist/commands/ball.d.ts +2 -0
  9. package/dist/commands/ball.js +69 -0
  10. package/dist/commands/ball.js.map +1 -0
  11. package/dist/commands/client.d.ts +2 -0
  12. package/dist/commands/client.js +149 -0
  13. package/dist/commands/client.js.map +1 -0
  14. package/dist/commands/config-cmd.d.ts +2 -0
  15. package/dist/commands/config-cmd.js +95 -0
  16. package/dist/commands/config-cmd.js.map +1 -0
  17. package/dist/commands/meeting.d.ts +2 -0
  18. package/dist/commands/meeting.js +100 -0
  19. package/dist/commands/meeting.js.map +1 -0
  20. package/dist/commands/milestone.d.ts +2 -0
  21. package/dist/commands/milestone.js +98 -0
  22. package/dist/commands/milestone.js.map +1 -0
  23. package/dist/commands/minutes.d.ts +2 -0
  24. package/dist/commands/minutes.js +62 -0
  25. package/dist/commands/minutes.js.map +1 -0
  26. package/dist/commands/review.d.ts +2 -0
  27. package/dist/commands/review.js +98 -0
  28. package/dist/commands/review.js.map +1 -0
  29. package/dist/commands/scheduling.d.ts +2 -0
  30. package/dist/commands/scheduling.js +184 -0
  31. package/dist/commands/scheduling.js.map +1 -0
  32. package/dist/commands/space.d.ts +2 -0
  33. package/dist/commands/space.js +69 -0
  34. package/dist/commands/space.js.map +1 -0
  35. package/dist/commands/task.d.ts +2 -0
  36. package/dist/commands/task.js +192 -0
  37. package/dist/commands/task.js.map +1 -0
  38. package/dist/commands/wiki.d.ts +2 -0
  39. package/dist/commands/wiki.js +121 -0
  40. package/dist/commands/wiki.js.map +1 -0
  41. package/dist/config.d.ts +8 -0
  42. package/dist/config.js +41 -0
  43. package/dist/config.js.map +1 -0
  44. package/dist/defaults.d.ts +4 -0
  45. package/dist/defaults.js +8 -0
  46. package/dist/defaults.js.map +1 -0
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.js +48 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/output.d.ts +2 -0
  51. package/dist/output.js +140 -0
  52. package/dist/output.js.map +1 -0
  53. package/package.json +31 -0
  54. package/src/api-client.ts +32 -0
  55. package/src/commands/activity.ts +83 -0
  56. package/src/commands/ball.ts +64 -0
  57. package/src/commands/client.ts +135 -0
  58. package/src/commands/config-cmd.ts +106 -0
  59. package/src/commands/meeting.ts +91 -0
  60. package/src/commands/milestone.ts +89 -0
  61. package/src/commands/minutes.ts +57 -0
  62. package/src/commands/review.ts +89 -0
  63. package/src/commands/scheduling.ts +171 -0
  64. package/src/commands/space.ts +62 -0
  65. package/src/commands/task.ts +179 -0
  66. package/src/commands/wiki.ts +110 -0
  67. package/src/config.ts +56 -0
  68. package/src/index.ts +51 -0
  69. package/src/output.ts +139 -0
  70. package/tsconfig.json +19 -0
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@uzukko/agentpm",
3
+ "version": "0.1.0",
4
+ "description": "CLI for TaskApp - AI-first task management",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "agentpm": "./bin/taskapp.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "dependencies": {
16
+ "commander": "^13.0.0",
17
+ "chalk": "^5.3.0",
18
+ "cli-table3": "^0.6.5"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.10.0",
22
+ "typescript": "^5.3.0"
23
+ },
24
+ "engines": {
25
+ "node": ">=18.0.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "license": "MIT"
31
+ }
@@ -0,0 +1,32 @@
1
+ let _apiUrl: string | undefined
2
+ let _apiKey: string | undefined
3
+
4
+ export function setApiConfig(apiUrl: string, apiKey: string): void {
5
+ _apiUrl = apiUrl
6
+ _apiKey = apiKey
7
+ }
8
+
9
+ export async function callTool(toolName: string, params: Record<string, unknown>): Promise<unknown> {
10
+ if (!_apiUrl || !_apiKey) {
11
+ console.error('Error: Not configured. Run: agentpm login')
12
+ process.exit(1)
13
+ }
14
+
15
+ const url = `${_apiUrl.replace(/\/$/, '')}/api/tools`
16
+
17
+ const response = await fetch(url, {
18
+ method: 'POST',
19
+ headers: {
20
+ 'Content-Type': 'application/json',
21
+ 'Authorization': `Bearer ${_apiKey}`,
22
+ },
23
+ body: JSON.stringify({ tool: toolName, params }),
24
+ })
25
+
26
+ if (!response.ok) {
27
+ const body = await response.json().catch(() => ({ error: `HTTP ${response.status}` })) as { error?: string; details?: string }
28
+ throw new Error(body.error || body.details || `HTTP ${response.status}`)
29
+ }
30
+
31
+ return response.json()
32
+ }
@@ -0,0 +1,83 @@
1
+ import { Command } from 'commander'
2
+ import { resolveSpaceId } from '../config.js'
3
+ import { callTool } from '../api-client.js'
4
+ import { output, outputError } from '../output.js'
5
+
6
+ export function registerActivityCommands(program: Command): void {
7
+ const activity = program.command('activity').description('Activity log')
8
+
9
+ activity
10
+ .command('search')
11
+ .description('Search activity logs')
12
+ .option('-s, --space-id <uuid>', 'Space UUID')
13
+ .option('--entity-table <table>', 'Filter by table name')
14
+ .option('--entity-id <uuid>', 'Filter by entity ID')
15
+ .option('--actor-id <uuid>', 'Filter by actor ID')
16
+ .option('--action <action>', 'Filter by action')
17
+ .option('--from <datetime>', 'Start datetime (ISO8601)')
18
+ .option('--to <datetime>', 'End datetime (ISO8601)')
19
+ .option('--session-id <uuid>', 'Filter by session ID')
20
+ .option('--limit <n>', 'Max results', '100')
21
+ .action(async (opts) => {
22
+ try {
23
+ const result = await callTool('activity_search', {
24
+ spaceId: resolveSpaceId(opts),
25
+ entityTable: opts.entityTable,
26
+ entityId: opts.entityId,
27
+ actorId: opts.actorId,
28
+ action: opts.action,
29
+ from: opts.from,
30
+ to: opts.to,
31
+ sessionId: opts.sessionId,
32
+ limit: parseInt(opts.limit),
33
+ })
34
+ output(result, program.opts().json)
35
+ } catch (e) { outputError(e, program.opts().json) }
36
+ })
37
+
38
+ activity
39
+ .command('log')
40
+ .description('Create an activity log entry')
41
+ .option('-s, --space-id <uuid>', 'Space UUID')
42
+ .requiredOption('--entity-table <table>', 'Table name')
43
+ .requiredOption('--entity-id <uuid>', 'Entity ID')
44
+ .requiredOption('--action <action>', 'Action')
45
+ .option('--actor-type <type>', 'user|system|ai|service', 'ai')
46
+ .option('--actor-service <service>', 'Service name')
47
+ .option('--entity-display <name>', 'Display name')
48
+ .option('--reason <reason>', 'Reason')
49
+ .option('--status <status>', 'ok|error|warning', 'ok')
50
+ .action(async (opts) => {
51
+ try {
52
+ const result = await callTool('activity_log', {
53
+ spaceId: resolveSpaceId(opts),
54
+ entityTable: opts.entityTable,
55
+ entityId: opts.entityId,
56
+ action: opts.action,
57
+ actorType: opts.actorType,
58
+ actorService: opts.actorService,
59
+ entityDisplay: opts.entityDisplay,
60
+ reason: opts.reason,
61
+ status: opts.status,
62
+ })
63
+ output(result, program.opts().json)
64
+ } catch (e) { outputError(e, program.opts().json) }
65
+ })
66
+
67
+ activity
68
+ .command('history')
69
+ .description('Get entity change history')
70
+ .requiredOption('--entity-table <table>', 'Table name')
71
+ .requiredOption('--entity-id <uuid>', 'Entity ID')
72
+ .option('--limit <n>', 'Max results', '50')
73
+ .action(async (opts) => {
74
+ try {
75
+ const result = await callTool('activity_entity_history', {
76
+ entityTable: opts.entityTable,
77
+ entityId: opts.entityId,
78
+ limit: parseInt(opts.limit),
79
+ })
80
+ output(result, program.opts().json)
81
+ } catch (e) { outputError(e, program.opts().json) }
82
+ })
83
+ }
@@ -0,0 +1,64 @@
1
+ import { Command } from 'commander'
2
+ import { resolveSpaceId } from '../config.js'
3
+ import { callTool } from '../api-client.js'
4
+ import { output, outputError } from '../output.js'
5
+
6
+ export function registerBallCommands(program: Command): void {
7
+ const ball = program.command('ball').description('Ball (ownership) management')
8
+
9
+ ball
10
+ .command('pass')
11
+ .description('Pass ball ownership')
12
+ .option('-s, --space-id <uuid>', 'Space UUID')
13
+ .requiredOption('--task-id <uuid>', 'Task UUID')
14
+ .requiredOption('--ball <side>', 'New ball owner: client|internal')
15
+ .option('--client-owner-ids <ids...>', 'Client owner UUIDs')
16
+ .option('--internal-owner-ids <ids...>', 'Internal owner UUIDs')
17
+ .option('--reason <reason>', 'Reason for passing')
18
+ .action(async (opts) => {
19
+ try {
20
+ const result = await callTool('ball_pass', {
21
+ spaceId: resolveSpaceId(opts),
22
+ taskId: opts.taskId,
23
+ ball: opts.ball,
24
+ clientOwnerIds: opts.clientOwnerIds || [],
25
+ internalOwnerIds: opts.internalOwnerIds || [],
26
+ reason: opts.reason,
27
+ })
28
+ output(result, program.opts().json)
29
+ } catch (e) { outputError(e, program.opts().json) }
30
+ })
31
+
32
+ ball
33
+ .command('query')
34
+ .description('Query tasks by ball side')
35
+ .option('-s, --space-id <uuid>', 'Space UUID')
36
+ .requiredOption('--ball <side>', 'Ball side: client|internal')
37
+ .option('--include-owners', 'Include owner info')
38
+ .option('--limit <n>', 'Max results', '50')
39
+ .action(async (opts) => {
40
+ try {
41
+ const result = await callTool('ball_query', {
42
+ spaceId: resolveSpaceId(opts),
43
+ ball: opts.ball,
44
+ includeOwners: opts.includeOwners || false,
45
+ limit: parseInt(opts.limit),
46
+ })
47
+ output(result, program.opts().json)
48
+ } catch (e) { outputError(e, program.opts().json) }
49
+ })
50
+
51
+ // Dashboard as top-level shortcut
52
+ program
53
+ .command('dashboard')
54
+ .description('Get project dashboard')
55
+ .option('-s, --space-id <uuid>', 'Space UUID')
56
+ .action(async (opts) => {
57
+ try {
58
+ const result = await callTool('dashboard_get', {
59
+ spaceId: resolveSpaceId(opts),
60
+ })
61
+ output(result, program.opts().json)
62
+ } catch (e) { outputError(e, program.opts().json) }
63
+ })
64
+ }
@@ -0,0 +1,135 @@
1
+ import { Command } from 'commander'
2
+ import { resolveSpaceId } from '../config.js'
3
+ import { callTool } from '../api-client.js'
4
+ import { output, outputError } from '../output.js'
5
+
6
+ export function registerClientCommands(program: Command): void {
7
+ const client = program.command('client').description('Client management')
8
+
9
+ client
10
+ .command('list')
11
+ .description('List clients')
12
+ .option('-s, --space-id <uuid>', 'Filter by space UUID')
13
+ .option('--no-include-invites', 'Exclude pending invites')
14
+ .action(async (opts) => {
15
+ try {
16
+ const result = await callTool('client_list', {
17
+ spaceId: opts.spaceId,
18
+ includeInvites: opts.includeInvites !== false,
19
+ })
20
+ output(result, program.opts().json)
21
+ } catch (e) { outputError(e, program.opts().json) }
22
+ })
23
+
24
+ client
25
+ .command('get')
26
+ .description('Get client details')
27
+ .requiredOption('--user-id <uuid>', 'Client user UUID')
28
+ .action(async (opts) => {
29
+ try {
30
+ const result = await callTool('client_get', { userId: opts.userId })
31
+ output(result, program.opts().json)
32
+ } catch (e) { outputError(e, program.opts().json) }
33
+ })
34
+
35
+ client
36
+ .command('update')
37
+ .description('Update client role in a space')
38
+ .requiredOption('--user-id <uuid>', 'Client user UUID')
39
+ .option('-s, --space-id <uuid>', 'Space UUID')
40
+ .requiredOption('--role <role>', 'New role: client|viewer')
41
+ .action(async (opts) => {
42
+ try {
43
+ const result = await callTool('client_update', {
44
+ userId: opts.userId,
45
+ spaceId: resolveSpaceId(opts),
46
+ role: opts.role,
47
+ })
48
+ output(result, program.opts().json)
49
+ } catch (e) { outputError(e, program.opts().json) }
50
+ })
51
+
52
+ client
53
+ .command('add-to-space')
54
+ .description('Add client to a space')
55
+ .requiredOption('--user-id <uuid>', 'Client user UUID')
56
+ .option('-s, --space-id <uuid>', 'Space UUID')
57
+ .option('--role <role>', 'Role: client|viewer', 'client')
58
+ .action(async (opts) => {
59
+ try {
60
+ const result = await callTool('client_add_to_space', {
61
+ userId: opts.userId,
62
+ spaceId: resolveSpaceId(opts),
63
+ role: opts.role,
64
+ })
65
+ output(result, program.opts().json)
66
+ } catch (e) { outputError(e, program.opts().json) }
67
+ })
68
+
69
+ // Invite subcommands
70
+ const invite = client.command('invite').description('Client invitations')
71
+
72
+ invite
73
+ .command('create')
74
+ .description('Create a client invite')
75
+ .option('-s, --space-id <uuid>', 'Space UUID')
76
+ .requiredOption('--email <email>', 'Client email')
77
+ .option('--expires-in-days <n>', 'Expiry days', '7')
78
+ .action(async (opts) => {
79
+ try {
80
+ const result = await callTool('client_invite_create', {
81
+ email: opts.email,
82
+ spaceId: resolveSpaceId(opts),
83
+ expiresInDays: parseInt(opts.expiresInDays),
84
+ })
85
+ output(result, program.opts().json)
86
+ } catch (e) { outputError(e, program.opts().json) }
87
+ })
88
+
89
+ invite
90
+ .command('bulk-create')
91
+ .description('Bulk create client invites')
92
+ .option('-s, --space-id <uuid>', 'Space UUID')
93
+ .requiredOption('--emails <emails...>', 'Client emails (max 50)')
94
+ .option('--expires-in-days <n>', 'Expiry days', '7')
95
+ .action(async (opts) => {
96
+ try {
97
+ const result = await callTool('client_invite_bulk_create', {
98
+ emails: opts.emails,
99
+ spaceId: resolveSpaceId(opts),
100
+ expiresInDays: parseInt(opts.expiresInDays),
101
+ })
102
+ output(result, program.opts().json)
103
+ } catch (e) { outputError(e, program.opts().json) }
104
+ })
105
+
106
+ invite
107
+ .command('list')
108
+ .description('List client invites')
109
+ .option('-s, --space-id <uuid>', 'Filter by space')
110
+ .option('--status <status>', 'pending|accepted|expired|all', 'pending')
111
+ .action(async (opts) => {
112
+ try {
113
+ const result = await callTool('client_invite_list', {
114
+ spaceId: opts.spaceId,
115
+ status: opts.status,
116
+ })
117
+ output(result, program.opts().json)
118
+ } catch (e) { outputError(e, program.opts().json) }
119
+ })
120
+
121
+ invite
122
+ .command('resend')
123
+ .description('Resend a client invite')
124
+ .requiredOption('--invite-id <uuid>', 'Invite UUID')
125
+ .option('--expires-in-days <n>', 'New expiry days', '7')
126
+ .action(async (opts) => {
127
+ try {
128
+ const result = await callTool('client_invite_resend', {
129
+ inviteId: opts.inviteId,
130
+ expiresInDays: parseInt(opts.expiresInDays),
131
+ })
132
+ output(result, program.opts().json)
133
+ } catch (e) { outputError(e, program.opts().json) }
134
+ })
135
+ }
@@ -0,0 +1,106 @@
1
+ import { Command } from 'commander'
2
+ import { writeFileSync, existsSync, readFileSync, unlinkSync } from 'node:fs'
3
+ import { createInterface } from 'node:readline'
4
+ import { getConfigPath } from '../config.js'
5
+
6
+ async function prompt(question: string, defaultValue?: string): Promise<string> {
7
+ const rl = createInterface({ input: process.stdin, output: process.stderr })
8
+ const suffix = defaultValue ? ` [${defaultValue}]` : ''
9
+ return new Promise((resolve) => {
10
+ rl.question(`${question}${suffix}: `, (answer) => {
11
+ rl.close()
12
+ resolve(answer.trim() || defaultValue || '')
13
+ })
14
+ })
15
+ }
16
+
17
+ function loadExisting(): Record<string, string> {
18
+ const configPath = getConfigPath()
19
+ if (existsSync(configPath)) {
20
+ try {
21
+ return JSON.parse(readFileSync(configPath, 'utf-8'))
22
+ } catch { /* ignore */ }
23
+ }
24
+ return {}
25
+ }
26
+
27
+ function maskSecret(value: string | undefined): string | undefined {
28
+ if (!value) return undefined
29
+ return value.slice(0, 8) + '...' + value.slice(-4)
30
+ }
31
+
32
+ export function registerConfigCommand(program: Command): void {
33
+ // agentpm login
34
+ program
35
+ .command('login')
36
+ .description('Login with your API Key (get it from Web UI → Settings → API Keys)')
37
+ .action(async () => {
38
+ const configPath = getConfigPath()
39
+ const existing = loadExisting()
40
+
41
+ console.error('AgentPM CLI Login')
42
+ console.error('─'.repeat(40))
43
+
44
+ const config: Record<string, string> = { ...existing }
45
+
46
+ const apiUrl = await prompt('API URL', existing.apiUrl || undefined)
47
+ if (apiUrl) config.apiUrl = apiUrl
48
+
49
+ console.error('\nPaste your API Key from Web UI → Settings → API Keys:')
50
+ const apiKey = await prompt('API Key', existing.apiKey ? maskSecret(existing.apiKey) : undefined)
51
+ if (apiKey && !apiKey.includes('...')) {
52
+ config.apiKey = apiKey
53
+ }
54
+
55
+ const defaultSpaceId = await prompt('Default Space ID (optional, press Enter to skip)', existing.defaultSpaceId)
56
+ if (defaultSpaceId) config.defaultSpaceId = defaultSpaceId
57
+
58
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n')
59
+ console.error(`\n✓ Config saved to ${configPath}`)
60
+
61
+ if (config.apiKey || existing.apiKey) {
62
+ console.error('✓ Run `agentpm space list` to verify your setup')
63
+ }
64
+ })
65
+
66
+ // agentpm config — advanced config management
67
+ const cfg = program.command('config').description('CLI configuration')
68
+
69
+ cfg
70
+ .command('show')
71
+ .description('Show current config (secrets masked)')
72
+ .action(() => {
73
+ const configPath = getConfigPath()
74
+
75
+ if (!existsSync(configPath)) {
76
+ console.error(`No config file at ${configPath}`)
77
+ console.error('Run: agentpm login')
78
+ process.exit(1)
79
+ }
80
+
81
+ const content = JSON.parse(readFileSync(configPath, 'utf-8'))
82
+ const masked = { ...content }
83
+ if (masked.apiKey) masked.apiKey = maskSecret(masked.apiKey)
84
+ console.log(JSON.stringify(masked, null, 2))
85
+ })
86
+
87
+ cfg
88
+ .command('path')
89
+ .description('Print config file path')
90
+ .action(() => {
91
+ console.log(getConfigPath())
92
+ })
93
+
94
+ cfg
95
+ .command('reset')
96
+ .description('Delete config file')
97
+ .action(() => {
98
+ const configPath = getConfigPath()
99
+ if (existsSync(configPath)) {
100
+ unlinkSync(configPath)
101
+ console.error(`Deleted ${configPath}`)
102
+ } else {
103
+ console.error('No config file to delete')
104
+ }
105
+ })
106
+ }
@@ -0,0 +1,91 @@
1
+ import { Command } from 'commander'
2
+ import { resolveSpaceId } from '../config.js'
3
+ import { callTool } from '../api-client.js'
4
+ import { output, outputError } from '../output.js'
5
+
6
+ export function registerMeetingCommands(program: Command): void {
7
+ const meeting = program.command('meeting').description('Meeting management')
8
+
9
+ meeting
10
+ .command('list')
11
+ .description('List meetings')
12
+ .option('-s, --space-id <uuid>', 'Space UUID')
13
+ .option('--status <status>', 'Filter: planned|in_progress|ended')
14
+ .option('--limit <n>', 'Max results', '20')
15
+ .action(async (opts) => {
16
+ try {
17
+ const result = await callTool('meeting_list', {
18
+ spaceId: resolveSpaceId(opts),
19
+ status: opts.status,
20
+ limit: parseInt(opts.limit),
21
+ })
22
+ output(result, program.opts().json)
23
+ } catch (e) { outputError(e, program.opts().json) }
24
+ })
25
+
26
+ meeting
27
+ .command('create')
28
+ .description('Create a meeting')
29
+ .option('-s, --space-id <uuid>', 'Space UUID')
30
+ .requiredOption('--title <title>', 'Meeting title')
31
+ .option('--held-at <datetime>', 'Date/time (ISO8601)')
32
+ .option('--notes <notes>', 'Pre-meeting notes')
33
+ .option('--participant-ids <ids...>', 'Participant UUIDs')
34
+ .action(async (opts) => {
35
+ try {
36
+ const result = await callTool('meeting_create', {
37
+ spaceId: resolveSpaceId(opts),
38
+ title: opts.title,
39
+ heldAt: opts.heldAt,
40
+ notes: opts.notes,
41
+ participantIds: opts.participantIds || [],
42
+ })
43
+ output(result, program.opts().json)
44
+ } catch (e) { outputError(e, program.opts().json) }
45
+ })
46
+
47
+ meeting
48
+ .command('start')
49
+ .description('Start a meeting')
50
+ .option('-s, --space-id <uuid>', 'Space UUID')
51
+ .requiredOption('--meeting-id <uuid>', 'Meeting UUID')
52
+ .action(async (opts) => {
53
+ try {
54
+ const result = await callTool('meeting_start', {
55
+ spaceId: resolveSpaceId(opts),
56
+ meetingId: opts.meetingId,
57
+ })
58
+ output(result, program.opts().json)
59
+ } catch (e) { outputError(e, program.opts().json) }
60
+ })
61
+
62
+ meeting
63
+ .command('end')
64
+ .description('End a meeting')
65
+ .option('-s, --space-id <uuid>', 'Space UUID')
66
+ .requiredOption('--meeting-id <uuid>', 'Meeting UUID')
67
+ .action(async (opts) => {
68
+ try {
69
+ const result = await callTool('meeting_end', {
70
+ spaceId: resolveSpaceId(opts),
71
+ meetingId: opts.meetingId,
72
+ })
73
+ output(result, program.opts().json)
74
+ } catch (e) { outputError(e, program.opts().json) }
75
+ })
76
+
77
+ meeting
78
+ .command('get')
79
+ .description('Get meeting details')
80
+ .option('-s, --space-id <uuid>', 'Space UUID')
81
+ .requiredOption('--meeting-id <uuid>', 'Meeting UUID')
82
+ .action(async (opts) => {
83
+ try {
84
+ const result = await callTool('meeting_get', {
85
+ spaceId: resolveSpaceId(opts),
86
+ meetingId: opts.meetingId,
87
+ })
88
+ output(result, program.opts().json)
89
+ } catch (e) { outputError(e, program.opts().json) }
90
+ })
91
+ }
@@ -0,0 +1,89 @@
1
+ import { Command } from 'commander'
2
+ import { resolveSpaceId } from '../config.js'
3
+ import { callTool } from '../api-client.js'
4
+ import { output, outputError } from '../output.js'
5
+
6
+ export function registerMilestoneCommands(program: Command): void {
7
+ const ms = program.command('milestone').description('Milestone management')
8
+
9
+ ms
10
+ .command('list')
11
+ .description('List milestones')
12
+ .option('-s, --space-id <uuid>', 'Space UUID')
13
+ .action(async (opts) => {
14
+ try {
15
+ const result = await callTool('milestone_list', {
16
+ spaceId: resolveSpaceId(opts),
17
+ })
18
+ output(result, program.opts().json)
19
+ } catch (e) { outputError(e, program.opts().json) }
20
+ })
21
+
22
+ ms
23
+ .command('create')
24
+ .description('Create a milestone')
25
+ .option('-s, --space-id <uuid>', 'Space UUID')
26
+ .requiredOption('--name <name>', 'Milestone name')
27
+ .option('--due-date <date>', 'Due date (YYYY-MM-DD)')
28
+ .action(async (opts) => {
29
+ try {
30
+ const result = await callTool('milestone_create', {
31
+ spaceId: resolveSpaceId(opts),
32
+ name: opts.name,
33
+ dueDate: opts.dueDate,
34
+ })
35
+ output(result, program.opts().json)
36
+ } catch (e) { outputError(e, program.opts().json) }
37
+ })
38
+
39
+ ms
40
+ .command('update')
41
+ .description('Update a milestone')
42
+ .option('-s, --space-id <uuid>', 'Space UUID')
43
+ .requiredOption('--milestone-id <uuid>', 'Milestone UUID')
44
+ .option('--name <name>', 'New name')
45
+ .option('--due-date <date>', 'New due date')
46
+ .option('--order-key <n>', 'Display order key')
47
+ .action(async (opts) => {
48
+ try {
49
+ const result = await callTool('milestone_update', {
50
+ spaceId: resolveSpaceId(opts),
51
+ milestoneId: opts.milestoneId,
52
+ name: opts.name,
53
+ dueDate: opts.dueDate,
54
+ orderKey: opts.orderKey !== undefined ? parseInt(opts.orderKey) : undefined,
55
+ })
56
+ output(result, program.opts().json)
57
+ } catch (e) { outputError(e, program.opts().json) }
58
+ })
59
+
60
+ ms
61
+ .command('get')
62
+ .description('Get milestone details')
63
+ .option('-s, --space-id <uuid>', 'Space UUID')
64
+ .requiredOption('--milestone-id <uuid>', 'Milestone UUID')
65
+ .action(async (opts) => {
66
+ try {
67
+ const result = await callTool('milestone_get', {
68
+ spaceId: resolveSpaceId(opts),
69
+ milestoneId: opts.milestoneId,
70
+ })
71
+ output(result, program.opts().json)
72
+ } catch (e) { outputError(e, program.opts().json) }
73
+ })
74
+
75
+ ms
76
+ .command('delete')
77
+ .description('Delete a milestone')
78
+ .option('-s, --space-id <uuid>', 'Space UUID')
79
+ .requiredOption('--milestone-id <uuid>', 'Milestone UUID')
80
+ .action(async (opts) => {
81
+ try {
82
+ const result = await callTool('milestone_delete', {
83
+ spaceId: resolveSpaceId(opts),
84
+ milestoneId: opts.milestoneId,
85
+ })
86
+ output(result, program.opts().json)
87
+ } catch (e) { outputError(e, program.opts().json) }
88
+ })
89
+ }