@iamcoder18/huly-cli 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.
- package/README.md +2576 -0
- package/bin/huly +9 -0
- package/dist/auth/cache.js +129 -0
- package/dist/auth/cache.js.map +1 -0
- package/dist/auth/client.js +192 -0
- package/dist/auth/client.js.map +1 -0
- package/dist/auth/env.js +101 -0
- package/dist/auth/env.js.map +1 -0
- package/dist/auth/prompts.js +68 -0
- package/dist/auth/prompts.js.map +1 -0
- package/dist/cli.js +1959 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/dry-run.js +39 -0
- package/dist/commands/dry-run.js.map +1 -0
- package/dist/commands/login.js +92 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/whoami.js +64 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/output/errors.js +99 -0
- package/dist/output/errors.js.map +1 -0
- package/dist/output/format.js +607 -0
- package/dist/output/format.js.map +1 -0
- package/dist/output/progress.js +30 -0
- package/dist/output/progress.js.map +1 -0
- package/dist/raw/api.js +67 -0
- package/dist/raw/api.js.map +1 -0
- package/dist/raw/ws.js +157 -0
- package/dist/raw/ws.js.map +1 -0
- package/dist/resources/_helpers.js +258 -0
- package/dist/resources/_helpers.js.map +1 -0
- package/dist/resources/_project-resolve.js +24 -0
- package/dist/resources/_project-resolve.js.map +1 -0
- package/dist/resources/calendar.js +659 -0
- package/dist/resources/calendar.js.map +1 -0
- package/dist/resources/card.js +358 -0
- package/dist/resources/card.js.map +1 -0
- package/dist/resources/channel.js +709 -0
- package/dist/resources/channel.js.map +1 -0
- package/dist/resources/comment.js +142 -0
- package/dist/resources/comment.js.map +1 -0
- package/dist/resources/component.js +154 -0
- package/dist/resources/component.js.map +1 -0
- package/dist/resources/document.js +584 -0
- package/dist/resources/document.js.map +1 -0
- package/dist/resources/issue-template.js +228 -0
- package/dist/resources/issue-template.js.map +1 -0
- package/dist/resources/issue.js +909 -0
- package/dist/resources/issue.js.map +1 -0
- package/dist/resources/milestone.js +177 -0
- package/dist/resources/milestone.js.map +1 -0
- package/dist/resources/misc.js +2 -0
- package/dist/resources/misc.js.map +1 -0
- package/dist/resources/project.js +341 -0
- package/dist/resources/project.js.map +1 -0
- package/dist/resources/project.parse.js +25 -0
- package/dist/resources/project.parse.js.map +1 -0
- package/dist/resources/time.js +148 -0
- package/dist/resources/time.js.map +1 -0
- package/dist/resources/todo.js +463 -0
- package/dist/resources/todo.js.map +1 -0
- package/dist/resources/user.js +131 -0
- package/dist/resources/user.js.map +1 -0
- package/dist/resources/workspace.js +252 -0
- package/dist/resources/workspace.js.map +1 -0
- package/dist/transport/identifiers.js +67 -0
- package/dist/transport/identifiers.js.map +1 -0
- package/dist/transport/ref-resolver.js +108 -0
- package/dist/transport/ref-resolver.js.map +1 -0
- package/dist/transport/sdk.js +69 -0
- package/dist/transport/sdk.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1959 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { handleError } from './output/errors.js';
|
|
7
|
+
import { isNonInteractive, markNonInteractive } from './auth/env.js';
|
|
8
|
+
import { loginCommand } from './commands/login.js';
|
|
9
|
+
import { whoamiCommand } from './commands/whoami.js';
|
|
10
|
+
import { listWorkspaces, currentWorkspace, useWorkspace, createWorkspace, deleteWorkspace, listMembers, updateMemberRole, workspaceInfo, updateWorkspaceName, workspaceGuests, createAccessLink, listRegions } from './resources/workspace.js';
|
|
11
|
+
import { getUser, updateUser, findUser } from './resources/user.js';
|
|
12
|
+
import { listProjects, getProject, createProject, updateProject, deleteProjects, listStatuses, listTargetPreferences, upsertTargetPreference } from './resources/project.js';
|
|
13
|
+
import { listIssues, getIssue, createIssue, updateIssue, deleteIssues, addIssueLabel, removeIssueLabel, addIssueRelation, removeIssueRelation, listIssueRelations, linkDocument, unlinkDocument, moveIssue, previewDelete, relatedTargets, setRelatedTarget } from './resources/issue.js';
|
|
14
|
+
import { listComponents, getComponent, createComponent, updateComponent, deleteComponents } from './resources/component.js';
|
|
15
|
+
import { listMilestones, getMilestone, createMilestone, updateMilestone, deleteMilestones } from './resources/milestone.js';
|
|
16
|
+
import { listIssueTemplates, getIssueTemplate, createIssueTemplate, updateIssueTemplate, deleteIssueTemplates, addTemplateChild, removeTemplateChild } from './resources/issue-template.js';
|
|
17
|
+
import { listComments, addComment, updateComment, deleteComments } from './resources/comment.js';
|
|
18
|
+
import { listCalendars, listSchedules, getSchedule, createSchedule, updateSchedule, deleteSchedules, listEvents, getEvent, createEvent, updateEvent, deleteEvents, listRecurringEvents, listRecurringInstances, createCalendar, deleteCalendar } from './resources/calendar.js';
|
|
19
|
+
import { listTimeEntries, logTime, deleteTimeEntries, timeReport } from './resources/time.js';
|
|
20
|
+
import { listCards, getCard, createCard, updateCard, deleteCards, listCardSpaces, getCardSpace, createCardSpace, deleteCardSpaces, listMasterTags } from './resources/card.js';
|
|
21
|
+
import { listDocuments, getDocument, createDocument, updateDocument, deleteDocuments, listSnapshots, getSnapshot, listInlineComments, listTeamspaces, getTeamspace, createTeamspace, updateTeamspace, deleteTeamspaces } from './resources/document.js';
|
|
22
|
+
import { listActions, getAction, createAction, updateAction, deleteActions, completeAction, reopenAction, scheduleAction, unscheduleAction } from './resources/todo.js';
|
|
23
|
+
import { listChannels, getChannel, createChannel, updateChannel, deleteChannels, archiveChannel, listChannelMembers, joinChannel, leaveChannel, addChannelMembers, removeChannelMembers, listChannelMessages, sendChannelMessage, updateChannelMessage, deleteChannelMessages, listThreadReplies, addThreadReply, updateThreadReply, deleteThreadReplies, listDms, createDm, listDmMessages, sendDmMessage } from './resources/channel.js';
|
|
24
|
+
import { apiCommand } from './raw/api.js';
|
|
25
|
+
import { wsCommand } from './raw/ws.js';
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
28
|
+
export function globalsFrom(cmd) {
|
|
29
|
+
return cmd.optsWithGlobals();
|
|
30
|
+
}
|
|
31
|
+
function attachGlobalOpts(cmd, opts = {}) {
|
|
32
|
+
let c = cmd
|
|
33
|
+
.option('--url <url>', 'Huly server URL')
|
|
34
|
+
.option('--workspace <name>', 'workspace URL name or UUID')
|
|
35
|
+
.option('--json', 'output JSON')
|
|
36
|
+
.option('--ci', 'CI mode (JSON output)')
|
|
37
|
+
.option('--markdown', 'output body as markdown')
|
|
38
|
+
.option('--dry-run', 'print intended tx, do not apply')
|
|
39
|
+
.option('--minimal', 'minimal payload (no smart defaults)')
|
|
40
|
+
.option('-y, --yes', 'skip confirmation prompts');
|
|
41
|
+
if (!opts.skipNonInteractive) {
|
|
42
|
+
c = c.option('--non-interactive', 'disable interactive prompts');
|
|
43
|
+
}
|
|
44
|
+
return c;
|
|
45
|
+
}
|
|
46
|
+
function attachToChildren(cmd) {
|
|
47
|
+
for (const child of cmd.commands) {
|
|
48
|
+
attachGlobalOpts(child);
|
|
49
|
+
attachToChildren(child);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const GLOBAL_OPTS_HELP = `
|
|
53
|
+
Global options (also available on parent commands):
|
|
54
|
+
--url <url> Huly server URL
|
|
55
|
+
--workspace <name> workspace URL name or UUID
|
|
56
|
+
--json / --ci output JSON
|
|
57
|
+
--markdown output body as markdown
|
|
58
|
+
--dry-run print intended tx, do not apply
|
|
59
|
+
--minimal minimal payload (no smart defaults)
|
|
60
|
+
-y, --yes skip confirmation prompts
|
|
61
|
+
--non-interactive disable interactive prompts
|
|
62
|
+
`;
|
|
63
|
+
function withGlobalHelp(cmd) {
|
|
64
|
+
cmd.addHelpText('after', GLOBAL_OPTS_HELP);
|
|
65
|
+
return cmd;
|
|
66
|
+
}
|
|
67
|
+
function preAction(cmd) {
|
|
68
|
+
const opts = cmd.optsWithGlobals();
|
|
69
|
+
if (opts.nonInteractive || opts.headless || opts.ci || isNonInteractive()) {
|
|
70
|
+
markNonInteractive();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function run(argv = process.argv) {
|
|
74
|
+
const program = new Command();
|
|
75
|
+
program
|
|
76
|
+
.name('huly')
|
|
77
|
+
.description('AI-agent-first CLI for self-hosted Huly')
|
|
78
|
+
.version(pkg.version)
|
|
79
|
+
.option('--non-interactive', 'disable interactive prompts')
|
|
80
|
+
.hook('preAction', (thisCmd) => preAction(thisCmd));
|
|
81
|
+
program
|
|
82
|
+
.command('login')
|
|
83
|
+
.description('Log in and cache credentials')
|
|
84
|
+
.option('--headless', 'use env vars only, no prompts')
|
|
85
|
+
.option('--email <email>')
|
|
86
|
+
.option('--password <pwd>')
|
|
87
|
+
.action(async (opts, cmd) => {
|
|
88
|
+
try {
|
|
89
|
+
const g = globalsFrom(cmd);
|
|
90
|
+
await loginCommand({
|
|
91
|
+
url: g.url,
|
|
92
|
+
workspace: g.workspace,
|
|
93
|
+
email: opts.email ?? g.email,
|
|
94
|
+
password: opts.password ?? g.password,
|
|
95
|
+
nonInteractive: g.nonInteractive,
|
|
96
|
+
headless: opts.headless ?? g.headless,
|
|
97
|
+
json: g.json ?? g.ci
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
handleError(e);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
program
|
|
105
|
+
.command('whoami')
|
|
106
|
+
.description('Show current account and workspace')
|
|
107
|
+
.action(async (_opts, cmd) => {
|
|
108
|
+
try {
|
|
109
|
+
const g = globalsFrom(cmd);
|
|
110
|
+
await whoamiCommand({ url: g.url, workspace: g.workspace, json: g.json ?? g.ci });
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
handleError(e);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
const ws = program.command('workspace').description('Manage workspaces');
|
|
117
|
+
withGlobalHelp(ws);
|
|
118
|
+
ws.command('list').description('List accessible workspaces')
|
|
119
|
+
.addHelpText('after', `
|
|
120
|
+
Examples:
|
|
121
|
+
$ huly workspace list
|
|
122
|
+
$ huly workspace list --json | jq -r '.[].name'`)
|
|
123
|
+
.action(async (_o, cmd) => {
|
|
124
|
+
try {
|
|
125
|
+
await listWorkspaces(globalsFrom(cmd));
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
handleError(e);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
ws.command('current').description('Show current workspace').action(async (_o, cmd) => {
|
|
132
|
+
try {
|
|
133
|
+
await currentWorkspace(globalsFrom(cmd));
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
handleError(e);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
ws.command('use <name>').description('Set active workspace')
|
|
140
|
+
.addHelpText('after', `
|
|
141
|
+
Examples:
|
|
142
|
+
$ huly workspace use production
|
|
143
|
+
$ huly workspace use life # switch workspace for subsequent commands
|
|
144
|
+
$ huly --workspace life issue list # one-off without switching`)
|
|
145
|
+
.action(async (name, _o, cmd) => {
|
|
146
|
+
try {
|
|
147
|
+
await useWorkspace(name, globalsFrom(cmd));
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
handleError(e);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
ws.command('create').description('Create a new workspace (requires --yes)')
|
|
154
|
+
.requiredOption('--name <name>')
|
|
155
|
+
.option('--region <region>')
|
|
156
|
+
.addHelpText('after', `
|
|
157
|
+
Examples:
|
|
158
|
+
$ huly workspace create --name "My new workspace" --yes
|
|
159
|
+
$ huly workspace create --name "EU workspace" --region eu-west --yes
|
|
160
|
+
|
|
161
|
+
Note: workspace creation runs the tracker migration. May take 30-60s.`)
|
|
162
|
+
.action(async (opts, cmd) => {
|
|
163
|
+
try {
|
|
164
|
+
await createWorkspace({ ...opts, ...globalsFrom(cmd) });
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
handleError(e);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
ws.command('delete [name]').description('Delete a workspace (DESTRUCTIVE; requires --yes; --force to delete the active workspace)')
|
|
171
|
+
.option('--force', 'delete even if this is the active workspace')
|
|
172
|
+
.addHelpText('after', `
|
|
173
|
+
Examples:
|
|
174
|
+
$ huly workspace delete my-old-workspace --yes
|
|
175
|
+
$ huly workspace delete life --yes --force # delete active workspace
|
|
176
|
+
|
|
177
|
+
WARNING: server-side hard-delete may take several minutes. Worker calls
|
|
178
|
+
doCleanup which drops all docs in all per-workspace tables.`)
|
|
179
|
+
.action(async (name, opts, cmd) => {
|
|
180
|
+
try {
|
|
181
|
+
await deleteWorkspace({ ...opts, ...globalsFrom(cmd), name });
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
handleError(e);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
ws.command('members').description('List workspace members')
|
|
188
|
+
.option('--role <r>', 'filter by role (Owner|Admin|Guest|ReadOnlyGuest|DocGuest)')
|
|
189
|
+
.addHelpText('after', `
|
|
190
|
+
Examples:
|
|
191
|
+
$ huly workspace members
|
|
192
|
+
$ huly workspace members --role Owner --json
|
|
193
|
+
$ huly workspace members --role Guest`)
|
|
194
|
+
.action(async (opts, cmd) => {
|
|
195
|
+
try {
|
|
196
|
+
await listMembers({ ...opts, ...globalsFrom(cmd) });
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
handleError(e);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// N2: alias `member add` for consistency with channel.add-member.
|
|
203
|
+
// (No `member remove` — the SDK has no remove-member API; users must
|
|
204
|
+
// leave the workspace via huly workspace leave.)
|
|
205
|
+
const wsMember = ws.command('member').description('Manage a single member (alias for `workspace member`)');
|
|
206
|
+
wsMember.command('add <account>').description('Add or change a member\'s role (requires OWNER)')
|
|
207
|
+
.requiredOption('--role <r>', 'Owner|Admin|Guest|ReadOnlyGuest|DocGuest')
|
|
208
|
+
.addHelpText('after', `
|
|
209
|
+
Examples:
|
|
210
|
+
$ huly workspace member add alice@example.com --role MAINTAINER
|
|
211
|
+
$ huly workspace member add bob@example.com --role GUEST
|
|
212
|
+
$ huly workspace member add 86d46120-594e-4c10-8996-821ac2a7001a --role OWNER`)
|
|
213
|
+
.action(async (account, opts, cmd) => {
|
|
214
|
+
try {
|
|
215
|
+
await updateMemberRole({ ...opts, target: account, ...globalsFrom(cmd) });
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
handleError(e);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
ws.command('info').description('Show current workspace info (name, uuid, region, mode)')
|
|
222
|
+
.addHelpText('after', `
|
|
223
|
+
Examples:
|
|
224
|
+
$ huly workspace info
|
|
225
|
+
$ huly workspace info --json | jq -r .uuid`)
|
|
226
|
+
.action(async (_o, cmd) => {
|
|
227
|
+
try {
|
|
228
|
+
await workspaceInfo(globalsFrom(cmd));
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
handleError(e);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
ws.command('rename').description('Rename current workspace')
|
|
235
|
+
.requiredOption('--name <name>')
|
|
236
|
+
.action(async (opts, cmd) => {
|
|
237
|
+
try {
|
|
238
|
+
await updateWorkspaceName({ ...opts, ...globalsFrom(cmd) });
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
handleError(e);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
ws.command('guests').description('Update guest settings (--read-only and/or --sign-up, true|false)')
|
|
245
|
+
.option('--read-only <bool>')
|
|
246
|
+
.option('--sign-up <bool>')
|
|
247
|
+
.addHelpText('after', `
|
|
248
|
+
Examples:
|
|
249
|
+
$ huly workspace guests --read-only true
|
|
250
|
+
$ huly workspace guests --sign-up false --read-only true
|
|
251
|
+
$ huly workspace guests --sign-up true
|
|
252
|
+
|
|
253
|
+
Note: 'guests' (plural) is for workspace-level guest *settings*.
|
|
254
|
+
For individual guest role assignment, use \`huly workspace member add --role GUEST\`.`)
|
|
255
|
+
.action(async (opts, cmd) => {
|
|
256
|
+
try {
|
|
257
|
+
const readOnly = opts.readOnly === undefined ? undefined : opts.readOnly !== 'false' && opts.readOnly !== '0';
|
|
258
|
+
const signUp = opts.signUp === undefined ? undefined : opts.signUp !== 'false' && opts.signUp !== '0';
|
|
259
|
+
await workspaceGuests({ ...globalsFrom(cmd), readOnly, signUp });
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
handleError(e);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
ws.command('access-link').description('Create an access link (signup invite) for a role')
|
|
266
|
+
.requiredOption('--role <r>', 'Guest|ReadOnlyGuest|DocGuest|Admin|Owner')
|
|
267
|
+
.option('--exp-hours <n>', 'expiration in hours', (v) => parseInt(v, 10))
|
|
268
|
+
.option('--auto-join')
|
|
269
|
+
.option('--email <email>')
|
|
270
|
+
.addHelpText('after', `
|
|
271
|
+
Examples:
|
|
272
|
+
$ huly workspace access-link --role GUEST
|
|
273
|
+
$ huly workspace access-link --role MAINTAINER --exp-hours 48
|
|
274
|
+
$ huly workspace access-link --role GUEST --auto-join
|
|
275
|
+
$ huly workspace access-link --role GUEST --email alice@example.com`)
|
|
276
|
+
.action(async (opts, cmd) => {
|
|
277
|
+
try {
|
|
278
|
+
await createAccessLink({ ...opts, ...globalsFrom(cmd) });
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
handleError(e);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
ws.command('regions').description('List available regions')
|
|
285
|
+
.action(async (_o, cmd) => {
|
|
286
|
+
try {
|
|
287
|
+
await listRegions(globalsFrom(cmd));
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
handleError(e);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
const user = program.command('user').description('Manage user profile');
|
|
294
|
+
// N7: harmonize ref spec — accept positional <ref> OR --ref flag (matches project get).
|
|
295
|
+
user.command('get [ref]').description('Show user profile (current user by default, or by ref/uuid)')
|
|
296
|
+
.option('--ref <id>', 'account uuid (overrides positional ref)')
|
|
297
|
+
.addHelpText('after', `
|
|
298
|
+
Examples:
|
|
299
|
+
$ huly user get # current user profile
|
|
300
|
+
$ huly user get --ref 86d46120-594e-4c10-8996-821ac2a7001a
|
|
301
|
+
$ huly user get 86d46120-594e-4c10-8996-821ac2a7001a # positional form (N7)`)
|
|
302
|
+
.action(async (ref, opts, cmd) => {
|
|
303
|
+
try {
|
|
304
|
+
await getUser({ ...opts, ref, ...globalsFrom(cmd) });
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
handleError(e);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
user.command('update').description('Update current user profile')
|
|
311
|
+
.option('--name <name>')
|
|
312
|
+
.option('--bio <text>')
|
|
313
|
+
.option('--city <city>')
|
|
314
|
+
.option('--country <country>')
|
|
315
|
+
.addHelpText('after', `
|
|
316
|
+
Examples:
|
|
317
|
+
$ huly user update --city "Berlin"
|
|
318
|
+
$ huly user update --bio "New bio" --country "DE"`)
|
|
319
|
+
.action(async (opts, cmd) => {
|
|
320
|
+
try {
|
|
321
|
+
await updateUser({ ...opts, ...globalsFrom(cmd) });
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
handleError(e);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
user.command('find <email>').description('Look up a user by email (account-level or workspace-local)')
|
|
328
|
+
.addHelpText('after', `
|
|
329
|
+
Examples:
|
|
330
|
+
$ huly user find alice@example.com
|
|
331
|
+
$ huly user find alice@example.com --json
|
|
332
|
+
|
|
333
|
+
Resolution order: accountClient.findPersonBySocialKey → workspace-local
|
|
334
|
+
Person scan by name. Either may fail if the user is not in your workspace.`)
|
|
335
|
+
.action(async (email, _o, cmd) => {
|
|
336
|
+
try {
|
|
337
|
+
await findUser(email, globalsFrom(cmd));
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
handleError(e);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
const project = program.command('project').description('Manage tracker projects');
|
|
344
|
+
withGlobalHelp(project);
|
|
345
|
+
project.command('list').description('List projects').option('--limit <n>', 'limit', (v) => parseInt(v, 10)).option('--offset <n>', 'offset', (v) => parseInt(v, 10))
|
|
346
|
+
.addHelpText('after', `
|
|
347
|
+
Examples:
|
|
348
|
+
$ huly project list
|
|
349
|
+
$ huly project list --limit 10 --json
|
|
350
|
+
$ huly project list --offset 10 # pagination`)
|
|
351
|
+
.action(async (opts, cmd) => {
|
|
352
|
+
try {
|
|
353
|
+
await listProjects({ ...opts, ...globalsFrom(cmd) });
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
handleError(e);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
project.command('get <ref>').description('Get a project')
|
|
360
|
+
.addHelpText('after', `
|
|
361
|
+
Examples:
|
|
362
|
+
$ huly project get TSK
|
|
363
|
+
$ huly project get "Default project"
|
|
364
|
+
$ huly project get tracker:project:DefaultProject`)
|
|
365
|
+
.action(async (ref, opts, cmd) => {
|
|
366
|
+
try {
|
|
367
|
+
await getProject(ref, { ...opts, ...globalsFrom(cmd) });
|
|
368
|
+
}
|
|
369
|
+
catch (e) {
|
|
370
|
+
handleError(e);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
project
|
|
374
|
+
.command('create')
|
|
375
|
+
.description('Create a project')
|
|
376
|
+
.requiredOption('--name <name>')
|
|
377
|
+
.requiredOption('--identifier <id>', 'short uppercase identifier (1-5 chars typical)')
|
|
378
|
+
.option('--description <text>')
|
|
379
|
+
.option('--private')
|
|
380
|
+
.addHelpText('after', `
|
|
381
|
+
Examples:
|
|
382
|
+
$ huly project create --name "Q3 Goals" --identifier Q3G
|
|
383
|
+
$ huly project create --name "Internal" --identifier INT --private \\
|
|
384
|
+
--description "Internal projects"
|
|
385
|
+
|
|
386
|
+
Required: --name, --identifier. Identifier must be uppercase letters/digits.
|
|
387
|
+
The CLI pre-checks for duplicate identifiers (server may not enforce).`)
|
|
388
|
+
.action(async (opts, cmd) => {
|
|
389
|
+
try {
|
|
390
|
+
await createProject({ ...opts, ...globalsFrom(cmd) });
|
|
391
|
+
}
|
|
392
|
+
catch (e) {
|
|
393
|
+
handleError(e);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
project
|
|
397
|
+
.command('update <ref>')
|
|
398
|
+
.description('Update a project')
|
|
399
|
+
.option('--set <kv...>', 'set key=value (repeatable); value=null clears')
|
|
400
|
+
.option('--unset <key...>', 'unset key (repeatable)')
|
|
401
|
+
.addHelpText('after', `
|
|
402
|
+
Examples:
|
|
403
|
+
$ huly project update TSK --set description="Updated description"
|
|
404
|
+
$ huly project update TSK --set description=null # clear
|
|
405
|
+
$ huly project update TSK --set private=true
|
|
406
|
+
$ huly project update TSK --unset description`)
|
|
407
|
+
.action(async (ref, opts, cmd) => {
|
|
408
|
+
try {
|
|
409
|
+
await updateProject(ref, { ...opts, ...globalsFrom(cmd) });
|
|
410
|
+
}
|
|
411
|
+
catch (e) {
|
|
412
|
+
handleError(e);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
project.command('delete <ref...>').description('Delete projects (DESTRUCTIVE; requires --yes)').action(async (refs, opts, cmd) => {
|
|
416
|
+
try {
|
|
417
|
+
await deleteProjects(refs, { ...opts, ...globalsFrom(cmd) });
|
|
418
|
+
}
|
|
419
|
+
catch (e) {
|
|
420
|
+
handleError(e);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
project.command('statuses [ref]').description('List issue statuses for a project (defaults to $HULY_PROJECT or first arg)')
|
|
424
|
+
.option('--project <ref>')
|
|
425
|
+
.action(async (ref, opts, cmd) => {
|
|
426
|
+
try {
|
|
427
|
+
await listStatuses({ project: ref ?? opts.project, ...globalsFrom(cmd) });
|
|
428
|
+
}
|
|
429
|
+
catch (e) {
|
|
430
|
+
handleError(e);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
project.command('target-preferences').description('List project target preferences (alias for `target-preference list`)')
|
|
434
|
+
.option('--project <ref>')
|
|
435
|
+
.action(async (opts, cmd) => {
|
|
436
|
+
try {
|
|
437
|
+
await listTargetPreferences({ ...opts, ...globalsFrom(cmd) });
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
handleError(e);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
const tgtPref = project.command('target-preference').description('Manage project target preferences');
|
|
444
|
+
tgtPref.command('list').description('List project target preferences')
|
|
445
|
+
.option('--project <ref>')
|
|
446
|
+
.action(async (opts, cmd) => {
|
|
447
|
+
try {
|
|
448
|
+
await listTargetPreferences({ ...opts, ...globalsFrom(cmd) });
|
|
449
|
+
}
|
|
450
|
+
catch (e) {
|
|
451
|
+
handleError(e);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
tgtPref.command('upsert').description('Create or merge a project target preference')
|
|
455
|
+
.option('--project <ref>')
|
|
456
|
+
.option('--props <kv...>', 'key=value (repeatable)')
|
|
457
|
+
.action(async (opts, cmd) => {
|
|
458
|
+
try {
|
|
459
|
+
await upsertTargetPreference({ ...opts, ...globalsFrom(cmd) });
|
|
460
|
+
}
|
|
461
|
+
catch (e) {
|
|
462
|
+
handleError(e);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
const issue = program.command('issue').description('Manage tracker issues');
|
|
466
|
+
withGlobalHelp(issue);
|
|
467
|
+
issue
|
|
468
|
+
.command('list')
|
|
469
|
+
.description('List issues')
|
|
470
|
+
.option('--project <id>')
|
|
471
|
+
.option('--status <name>')
|
|
472
|
+
.option('--status-category <c>', 'UnStarted|ToDo|Active|Won|Lost')
|
|
473
|
+
.option('--description-search <q>')
|
|
474
|
+
.option('--parent <ref|null>', 'filter by parent ref (literal "null" for top-level)')
|
|
475
|
+
.option('--assignee <email>')
|
|
476
|
+
.option('--label <l...>')
|
|
477
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
478
|
+
.option('--offset <n>', 'offset', (v) => parseInt(v, 10))
|
|
479
|
+
.addHelpText('after', `
|
|
480
|
+
Examples:
|
|
481
|
+
$ huly issue list --project TSK
|
|
482
|
+
$ huly issue list --status Backlog --assignee alice@example.com
|
|
483
|
+
$ huly issue list --status-category Active --limit 50 --json
|
|
484
|
+
$ huly issue list --description-search "smoke"
|
|
485
|
+
$ huly issue list --parent null # top-level only
|
|
486
|
+
|
|
487
|
+
Ref formats accepted by --project, --assignee, --label: name, identifier, or _id.`)
|
|
488
|
+
.action(async (opts, cmd) => {
|
|
489
|
+
try {
|
|
490
|
+
await listIssues({ ...opts, ...globalsFrom(cmd) });
|
|
491
|
+
}
|
|
492
|
+
catch (e) {
|
|
493
|
+
handleError(e);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
issue
|
|
497
|
+
.command('get <ref>')
|
|
498
|
+
.description('Get an issue')
|
|
499
|
+
.addHelpText('after', `
|
|
500
|
+
Examples:
|
|
501
|
+
$ huly issue get TSK-1
|
|
502
|
+
$ huly issue get 1 # uses \$HULY_PROJECT
|
|
503
|
+
$ huly issue get TSK-1 --markdown
|
|
504
|
+
$ huly issue get tracker:issue:6a... # raw _id`)
|
|
505
|
+
.action(async (ref, opts, cmd) => {
|
|
506
|
+
try {
|
|
507
|
+
await getIssue(ref, { ...opts, ...globalsFrom(cmd) });
|
|
508
|
+
}
|
|
509
|
+
catch (e) {
|
|
510
|
+
handleError(e);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
issue
|
|
514
|
+
.command('create')
|
|
515
|
+
.description('Create an issue')
|
|
516
|
+
.option('--project <id>', 'project identifier (defaults to $HULY_PROJECT or interactive selection)')
|
|
517
|
+
.requiredOption('--title <t>')
|
|
518
|
+
.option('--description <text>')
|
|
519
|
+
.option('--body <md>')
|
|
520
|
+
.option('--body-file <path>')
|
|
521
|
+
.option('--status <name>')
|
|
522
|
+
.option('--priority <p>', 'Urgent | High | Normal | Low | None')
|
|
523
|
+
.option('--assignee <email>', 'must be a workspace member')
|
|
524
|
+
.option('--label <l...>', 'repeatable: --label bug --label auth')
|
|
525
|
+
.option('--due <iso>', 'ISO 8601 e.g. 2026-07-01T14:00:00Z')
|
|
526
|
+
.option('--parent <ref>')
|
|
527
|
+
.option('--task-type <name|id>')
|
|
528
|
+
.addHelpText('after', `
|
|
529
|
+
Examples:
|
|
530
|
+
$ huly issue create --project TSK --title "Add OAuth login"
|
|
531
|
+
$ huly issue create --project TSK --title "Bug" --priority High \\
|
|
532
|
+
--assignee alice@example.com --label bug --label p1
|
|
533
|
+
$ huly issue create --project TSK --title "..." --body-file ./spec.md \\
|
|
534
|
+
--due 2026-08-01T00:00:00Z
|
|
535
|
+
$ huly issue create --project TSK --title "Sub-task" --parent TSK-5
|
|
536
|
+
|
|
537
|
+
Required: --project, --title. Valid priority values: Urgent, High, Normal,
|
|
538
|
+
Low, None. Assignee must be a workspace member.`)
|
|
539
|
+
.action(async (opts, cmd) => {
|
|
540
|
+
try {
|
|
541
|
+
await createIssue({ ...opts, ...globalsFrom(cmd) });
|
|
542
|
+
}
|
|
543
|
+
catch (e) {
|
|
544
|
+
handleError(e);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
issue
|
|
548
|
+
.command('update <ref>')
|
|
549
|
+
.description('Update an issue')
|
|
550
|
+
.option('--set <kv...>', 'key=value (repeatable); key=null clears the field')
|
|
551
|
+
.option('--unset <key...>')
|
|
552
|
+
.option('--status <name>')
|
|
553
|
+
.option('--priority <p>', 'Urgent | High | Normal | Low | None')
|
|
554
|
+
.option('--assignee <email>')
|
|
555
|
+
.option('--title <t>')
|
|
556
|
+
.option('--description <text>')
|
|
557
|
+
.option('--task-type <name|id>')
|
|
558
|
+
.addHelpText('after', `
|
|
559
|
+
Examples:
|
|
560
|
+
$ huly issue update TSK-1 --status Done
|
|
561
|
+
$ huly issue update TSK-1 --description "Updated text"
|
|
562
|
+
$ huly issue update TSK-1 --set priority=High --set assignee=bob@example.com
|
|
563
|
+
$ huly issue update TSK-1 --set description=null # clear field
|
|
564
|
+
|
|
565
|
+
Pass any combination of --status/--priority/--assignee/--title/--description/--set.`)
|
|
566
|
+
.action(async (ref, opts, cmd) => {
|
|
567
|
+
try {
|
|
568
|
+
await updateIssue(ref, { ...opts, ...globalsFrom(cmd) });
|
|
569
|
+
}
|
|
570
|
+
catch (e) {
|
|
571
|
+
handleError(e);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
issue.command('delete <ref...>').description('Delete issues (requires --yes for multiple)').action(async (refs, opts, cmd) => {
|
|
575
|
+
try {
|
|
576
|
+
await deleteIssues(refs, { ...opts, ...globalsFrom(cmd) });
|
|
577
|
+
}
|
|
578
|
+
catch (e) {
|
|
579
|
+
handleError(e);
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
const issueLabel = issue.command('label').description('Manage labels on an issue');
|
|
583
|
+
issueLabel.command('add <ref>').description('Add a label to an issue')
|
|
584
|
+
.requiredOption('--label <name>')
|
|
585
|
+
.addHelpText('after', `
|
|
586
|
+
Examples:
|
|
587
|
+
$ huly issue label add TSK-1 --label bug
|
|
588
|
+
$ huly issue label add TSK-1 --label auth --label backend`)
|
|
589
|
+
.action(async (ref, opts, cmd) => {
|
|
590
|
+
try {
|
|
591
|
+
await addIssueLabel(ref, opts.label, globalsFrom(cmd));
|
|
592
|
+
}
|
|
593
|
+
catch (e) {
|
|
594
|
+
handleError(e);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
issueLabel.command('remove <ref>').description('Remove a label from an issue')
|
|
598
|
+
.requiredOption('--label <name>')
|
|
599
|
+
.addHelpText('after', `
|
|
600
|
+
Examples:
|
|
601
|
+
$ huly issue label remove TSK-1 --label bug`)
|
|
602
|
+
.action(async (ref, opts, cmd) => {
|
|
603
|
+
try {
|
|
604
|
+
await removeIssueLabel(ref, opts.label, globalsFrom(cmd));
|
|
605
|
+
}
|
|
606
|
+
catch (e) {
|
|
607
|
+
handleError(e);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
const issueRel = issue.command('relation').description('Manage relations on an issue');
|
|
611
|
+
issueRel.command('add <ref>').description('Add a relation')
|
|
612
|
+
.requiredOption('--type <t>', 'blocks|isBlockedBy|relatesTo')
|
|
613
|
+
.requiredOption('--target <ref>')
|
|
614
|
+
.action(async (ref, opts, cmd) => {
|
|
615
|
+
try {
|
|
616
|
+
await addIssueRelation(ref, opts.type, opts.target, globalsFrom(cmd));
|
|
617
|
+
}
|
|
618
|
+
catch (e) {
|
|
619
|
+
handleError(e);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
issueRel.command('remove <ref>').description('Remove a relation')
|
|
623
|
+
.requiredOption('--type <t>', 'blocks|isBlockedBy|relatesTo')
|
|
624
|
+
.requiredOption('--target <ref>')
|
|
625
|
+
.action(async (ref, opts, cmd) => {
|
|
626
|
+
try {
|
|
627
|
+
await removeIssueRelation(ref, opts.type, opts.target, globalsFrom(cmd));
|
|
628
|
+
}
|
|
629
|
+
catch (e) {
|
|
630
|
+
handleError(e);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
issueRel.command('list <ref>').description('List relations on an issue')
|
|
634
|
+
.action(async (ref, _opts, cmd) => {
|
|
635
|
+
try {
|
|
636
|
+
await listIssueRelations(ref, globalsFrom(cmd));
|
|
637
|
+
}
|
|
638
|
+
catch (e) {
|
|
639
|
+
handleError(e);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
issue.command('link-document <ref>').description('Link a document to an issue')
|
|
643
|
+
.requiredOption('--document <ref>')
|
|
644
|
+
.action(async (ref, opts, cmd) => {
|
|
645
|
+
try {
|
|
646
|
+
await linkDocument(ref, opts.document, globalsFrom(cmd));
|
|
647
|
+
}
|
|
648
|
+
catch (e) {
|
|
649
|
+
handleError(e);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
issue.command('unlink-document <ref>').description('Unlink a document from an issue')
|
|
653
|
+
.requiredOption('--document <ref>')
|
|
654
|
+
.action(async (ref, opts, cmd) => {
|
|
655
|
+
try {
|
|
656
|
+
await unlinkDocument(ref, opts.document, globalsFrom(cmd));
|
|
657
|
+
}
|
|
658
|
+
catch (e) {
|
|
659
|
+
handleError(e);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
issue.command('move <ref>').description('Move an issue (set/drop parent)')
|
|
663
|
+
.option('--parent <ref|null>', 'new parent ref, or literal "null" to drop')
|
|
664
|
+
.action(async (ref, opts, cmd) => {
|
|
665
|
+
try {
|
|
666
|
+
await moveIssue(ref, opts.parent ?? null, globalsFrom(cmd));
|
|
667
|
+
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
handleError(e);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
issue.command('preview-delete <ref...>').description('Preview the impact of deleting issues')
|
|
673
|
+
.action(async (refs, _opts, cmd) => {
|
|
674
|
+
try {
|
|
675
|
+
await previewDelete(refs, globalsFrom(cmd));
|
|
676
|
+
}
|
|
677
|
+
catch (e) {
|
|
678
|
+
handleError(e);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
issue.command('related-targets').description('List related-issue-targets for a project')
|
|
682
|
+
.option('--project <ref>')
|
|
683
|
+
.action(async (opts, cmd) => {
|
|
684
|
+
try {
|
|
685
|
+
await relatedTargets('', { ...opts, ...globalsFrom(cmd) });
|
|
686
|
+
}
|
|
687
|
+
catch (e) {
|
|
688
|
+
handleError(e);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
const relatedTarget = issue.command('related-target').description('Manage related-issue targets');
|
|
692
|
+
relatedTarget.command('set').description('Create a related-issue-target for a project')
|
|
693
|
+
.option('--project <ref>')
|
|
694
|
+
.requiredOption('--source <name>')
|
|
695
|
+
.requiredOption('--target <name>')
|
|
696
|
+
.action(async (opts, cmd) => {
|
|
697
|
+
try {
|
|
698
|
+
await setRelatedTarget({ ...opts, ...globalsFrom(cmd) });
|
|
699
|
+
}
|
|
700
|
+
catch (e) {
|
|
701
|
+
handleError(e);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
const component = program.command('component').description('Manage tracker components');
|
|
705
|
+
withGlobalHelp(component);
|
|
706
|
+
component.command('list').description('List components')
|
|
707
|
+
.option('--project <ref>')
|
|
708
|
+
.action(async (opts, cmd) => {
|
|
709
|
+
try {
|
|
710
|
+
await listComponents({ ...opts, ...globalsFrom(cmd) });
|
|
711
|
+
}
|
|
712
|
+
catch (e) {
|
|
713
|
+
handleError(e);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
component.command('get <ref>').description('Get a component').action(async (ref, opts, cmd) => {
|
|
717
|
+
try {
|
|
718
|
+
await getComponent(ref, { ...opts, ...globalsFrom(cmd) });
|
|
719
|
+
}
|
|
720
|
+
catch (e) {
|
|
721
|
+
handleError(e);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
component.command('create').description('Create a component')
|
|
725
|
+
.option('--project <ref>')
|
|
726
|
+
.requiredOption('--label <name>')
|
|
727
|
+
.option('--description <text>')
|
|
728
|
+
.action(async (opts, cmd) => {
|
|
729
|
+
try {
|
|
730
|
+
await createComponent({ ...opts, ...globalsFrom(cmd) });
|
|
731
|
+
}
|
|
732
|
+
catch (e) {
|
|
733
|
+
handleError(e);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
component.command('update <ref>').description('Update a component')
|
|
737
|
+
.option('--label <name>')
|
|
738
|
+
.option('--description <text>')
|
|
739
|
+
.action(async (ref, opts, cmd) => {
|
|
740
|
+
try {
|
|
741
|
+
await updateComponent(ref, { ...opts, ...globalsFrom(cmd) });
|
|
742
|
+
}
|
|
743
|
+
catch (e) {
|
|
744
|
+
handleError(e);
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
component.command('delete <ref...>').description('Delete components').action(async (refs, opts, cmd) => {
|
|
748
|
+
try {
|
|
749
|
+
await deleteComponents(refs, { ...opts, ...globalsFrom(cmd) });
|
|
750
|
+
}
|
|
751
|
+
catch (e) {
|
|
752
|
+
handleError(e);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
const milestone = program.command('milestone').description('Manage tracker milestones');
|
|
756
|
+
withGlobalHelp(milestone);
|
|
757
|
+
milestone.command('list').description('List milestones')
|
|
758
|
+
.option('--project <ref>')
|
|
759
|
+
.action(async (opts, cmd) => {
|
|
760
|
+
try {
|
|
761
|
+
await listMilestones({ ...opts, ...globalsFrom(cmd) });
|
|
762
|
+
}
|
|
763
|
+
catch (e) {
|
|
764
|
+
handleError(e);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
milestone.command('get <ref>').description('Get a milestone').action(async (ref, opts, cmd) => {
|
|
768
|
+
try {
|
|
769
|
+
await getMilestone(ref, { ...opts, ...globalsFrom(cmd) });
|
|
770
|
+
}
|
|
771
|
+
catch (e) {
|
|
772
|
+
handleError(e);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
milestone.command('create').description('Create a milestone')
|
|
776
|
+
.option('--project <ref>')
|
|
777
|
+
.requiredOption('--label <name>')
|
|
778
|
+
.option('--description <text>')
|
|
779
|
+
.option('--target-date <iso>')
|
|
780
|
+
.action(async (opts, cmd) => {
|
|
781
|
+
try {
|
|
782
|
+
await createMilestone({ ...opts, ...globalsFrom(cmd) });
|
|
783
|
+
}
|
|
784
|
+
catch (e) {
|
|
785
|
+
handleError(e);
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
milestone.command('update <ref>').description('Update a milestone')
|
|
789
|
+
.option('--label <name>')
|
|
790
|
+
.option('--description <text>')
|
|
791
|
+
.option('--target-date <iso>')
|
|
792
|
+
.option('--status <s>')
|
|
793
|
+
.action(async (ref, opts, cmd) => {
|
|
794
|
+
try {
|
|
795
|
+
await updateMilestone(ref, { ...opts, ...globalsFrom(cmd) });
|
|
796
|
+
}
|
|
797
|
+
catch (e) {
|
|
798
|
+
handleError(e);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
milestone.command('delete <ref...>').description('Delete milestones').action(async (refs, opts, cmd) => {
|
|
802
|
+
try {
|
|
803
|
+
await deleteMilestones(refs, { ...opts, ...globalsFrom(cmd) });
|
|
804
|
+
}
|
|
805
|
+
catch (e) {
|
|
806
|
+
handleError(e);
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
const tmpl = program.command('issue-template').description('Manage issue templates');
|
|
810
|
+
withGlobalHelp(tmpl);
|
|
811
|
+
tmpl.command('list').description('List templates')
|
|
812
|
+
.option('--project <ref>')
|
|
813
|
+
.action(async (opts, cmd) => {
|
|
814
|
+
try {
|
|
815
|
+
await listIssueTemplates({ ...opts, ...globalsFrom(cmd) });
|
|
816
|
+
}
|
|
817
|
+
catch (e) {
|
|
818
|
+
handleError(e);
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
tmpl.command('get <ref>').description('Get a template')
|
|
822
|
+
.action(async (ref, opts, cmd) => {
|
|
823
|
+
try {
|
|
824
|
+
await getIssueTemplate(ref, { ...opts, ...globalsFrom(cmd) });
|
|
825
|
+
}
|
|
826
|
+
catch (e) {
|
|
827
|
+
handleError(e);
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
tmpl.command('create').description('Create a template')
|
|
831
|
+
.option('--project <ref>')
|
|
832
|
+
.requiredOption('--title <t>')
|
|
833
|
+
.option('--description <text>')
|
|
834
|
+
.option('--body <md>')
|
|
835
|
+
.option('--body-file <path>')
|
|
836
|
+
.action(async (opts, cmd) => {
|
|
837
|
+
try {
|
|
838
|
+
await createIssueTemplate({ ...opts, ...globalsFrom(cmd) });
|
|
839
|
+
}
|
|
840
|
+
catch (e) {
|
|
841
|
+
handleError(e);
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
tmpl.command('update <ref>').description('Update a template')
|
|
845
|
+
.option('--title <t>')
|
|
846
|
+
.option('--description <text>')
|
|
847
|
+
.option('--body <md>')
|
|
848
|
+
.action(async (ref, opts, cmd) => {
|
|
849
|
+
try {
|
|
850
|
+
await updateIssueTemplate(ref, { ...opts, ...globalsFrom(cmd) });
|
|
851
|
+
}
|
|
852
|
+
catch (e) {
|
|
853
|
+
handleError(e);
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
tmpl.command('delete <ref...>').description('Delete templates').action(async (refs, opts, cmd) => {
|
|
857
|
+
try {
|
|
858
|
+
await deleteIssueTemplates(refs, { ...opts, ...globalsFrom(cmd) });
|
|
859
|
+
}
|
|
860
|
+
catch (e) {
|
|
861
|
+
handleError(e);
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
tmpl.command('add-child <template>').description('Add a child reference to a template')
|
|
865
|
+
.requiredOption('--child <ref>')
|
|
866
|
+
.action(async (template, opts, cmd) => {
|
|
867
|
+
try {
|
|
868
|
+
await addTemplateChild(template, opts.child, globalsFrom(cmd));
|
|
869
|
+
}
|
|
870
|
+
catch (e) {
|
|
871
|
+
handleError(e);
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
tmpl.command('remove-child <template>').description('Remove a child reference from a template')
|
|
875
|
+
.requiredOption('--child <ref>')
|
|
876
|
+
.action(async (template, opts, cmd) => {
|
|
877
|
+
try {
|
|
878
|
+
await removeTemplateChild(template, opts.child, globalsFrom(cmd));
|
|
879
|
+
}
|
|
880
|
+
catch (e) {
|
|
881
|
+
handleError(e);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
const comment = program.command('comment').description('Manage comments (issue comments are ChatMessages)');
|
|
885
|
+
withGlobalHelp(comment);
|
|
886
|
+
comment.command('list').description('List comments on an issue')
|
|
887
|
+
.requiredOption('--issue <ref>')
|
|
888
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
889
|
+
.option('--offset <n>', 'offset', (v) => parseInt(v, 10))
|
|
890
|
+
.action(async (opts, cmd) => {
|
|
891
|
+
try {
|
|
892
|
+
await listComments({ ...opts, ...globalsFrom(cmd) });
|
|
893
|
+
}
|
|
894
|
+
catch (e) {
|
|
895
|
+
handleError(e);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
comment.command('add').description('Add a comment to an issue')
|
|
899
|
+
.requiredOption('--issue <ref>')
|
|
900
|
+
.option('--body <md>')
|
|
901
|
+
.option('--body-file <path>')
|
|
902
|
+
.action(async (opts, cmd) => {
|
|
903
|
+
try {
|
|
904
|
+
await addComment({ ...opts, ...globalsFrom(cmd) });
|
|
905
|
+
}
|
|
906
|
+
catch (e) {
|
|
907
|
+
handleError(e);
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
comment.command('update <ref>').description('Update a comment\'s body')
|
|
911
|
+
.option('--body <md>')
|
|
912
|
+
.option('--body-file <path>')
|
|
913
|
+
.action(async (ref, opts, cmd) => {
|
|
914
|
+
try {
|
|
915
|
+
await updateComment(ref, { ...opts, ...globalsFrom(cmd) });
|
|
916
|
+
}
|
|
917
|
+
catch (e) {
|
|
918
|
+
handleError(e);
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
comment.command('delete <ref...>').description('Delete comments')
|
|
922
|
+
.action(async (refs, opts, cmd) => {
|
|
923
|
+
try {
|
|
924
|
+
await deleteComments(refs, { ...opts, ...globalsFrom(cmd) });
|
|
925
|
+
}
|
|
926
|
+
catch (e) {
|
|
927
|
+
handleError(e);
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
const channel = program.command('channel').description('Manage chunter channels (and their messages/threads)');
|
|
931
|
+
withGlobalHelp(channel);
|
|
932
|
+
channel.command('list').description('List channels')
|
|
933
|
+
.option('--archived <bool>', 'filter by archived state (true|false)', (v) => v !== 'false' && v !== '0')
|
|
934
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
935
|
+
.addHelpText('after', `
|
|
936
|
+
Examples:
|
|
937
|
+
$ huly channel list
|
|
938
|
+
$ huly channel list --archived false
|
|
939
|
+
$ huly channel list --json | jq -r '.[] | select(.topic != null) | .name'`)
|
|
940
|
+
.action(async (opts, cmd) => {
|
|
941
|
+
try {
|
|
942
|
+
await listChannels({ ...opts, ...globalsFrom(cmd) });
|
|
943
|
+
}
|
|
944
|
+
catch (e) {
|
|
945
|
+
handleError(e);
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
channel.command('get <ref>').description('Get a channel')
|
|
949
|
+
.addHelpText('after', `
|
|
950
|
+
Examples:
|
|
951
|
+
$ huly channel get engineering
|
|
952
|
+
$ huly channel get chunter:space.General # raw _id`)
|
|
953
|
+
.action(async (ref, opts, cmd) => {
|
|
954
|
+
try {
|
|
955
|
+
await getChannel(ref, { ...opts, ...globalsFrom(cmd) });
|
|
956
|
+
}
|
|
957
|
+
catch (e) {
|
|
958
|
+
handleError(e);
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
channel.command('create').description('Create a channel')
|
|
962
|
+
.requiredOption('--name <n>')
|
|
963
|
+
.option('--description <text>')
|
|
964
|
+
.option('--topic <text>')
|
|
965
|
+
.option('--private', 'private channel (members only)')
|
|
966
|
+
.option('--auto-join', 'new workspace members join automatically')
|
|
967
|
+
.option('--members <email...>', 'initial members (workspace members only)')
|
|
968
|
+
.addHelpText('after', `
|
|
969
|
+
Examples:
|
|
970
|
+
$ huly channel create --name engineering --topic "Eng discussions"
|
|
971
|
+
$ huly channel create --name leads --private --members alice@.. bob@..
|
|
972
|
+
$ huly channel create --name general --auto-join
|
|
973
|
+
|
|
974
|
+
Required: --name. Optional: --description, --topic, --private,
|
|
975
|
+
--auto-join, --members (space-separated emails).`)
|
|
976
|
+
.action(async (opts, cmd) => {
|
|
977
|
+
try {
|
|
978
|
+
await createChannel({ ...opts, ...globalsFrom(cmd) });
|
|
979
|
+
}
|
|
980
|
+
catch (e) {
|
|
981
|
+
handleError(e);
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
channel.command('update <ref>').description('Update a channel')
|
|
985
|
+
.option('--name <n>')
|
|
986
|
+
.option('--description <text>')
|
|
987
|
+
.option('--topic <text>')
|
|
988
|
+
.option('--private <bool>', 'true|false', (v) => v !== 'false' && v !== '0')
|
|
989
|
+
.option('--auto-join <bool>', 'true|false', (v) => v !== 'false' && v !== '0')
|
|
990
|
+
.addHelpText('after', `
|
|
991
|
+
Examples:
|
|
992
|
+
$ huly channel update engineering --topic "New topic"
|
|
993
|
+
$ huly channel update engineering --private true`)
|
|
994
|
+
.action(async (ref, opts, cmd) => {
|
|
995
|
+
try {
|
|
996
|
+
await updateChannel(ref, { ...opts, ...globalsFrom(cmd) });
|
|
997
|
+
}
|
|
998
|
+
catch (e) {
|
|
999
|
+
handleError(e);
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
channel.command('delete <ref...>').description('Delete channels (DESTRUCTIVE; requires --yes)')
|
|
1003
|
+
.action(async (refs, opts, cmd) => {
|
|
1004
|
+
try {
|
|
1005
|
+
await deleteChannels(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1006
|
+
}
|
|
1007
|
+
catch (e) {
|
|
1008
|
+
handleError(e);
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
channel.command('archive <ref>').description('Archive a channel (--value false to unarchive)')
|
|
1012
|
+
.option('--value <bool>', 'true|false', (v) => v !== 'false' && v !== '0')
|
|
1013
|
+
.addHelpText('after', `
|
|
1014
|
+
Examples:
|
|
1015
|
+
$ huly channel archive engineering
|
|
1016
|
+
$ huly channel archive engineering --value false # unarchive`)
|
|
1017
|
+
.action(async (ref, opts, cmd) => {
|
|
1018
|
+
try {
|
|
1019
|
+
await archiveChannel(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1020
|
+
}
|
|
1021
|
+
catch (e) {
|
|
1022
|
+
handleError(e);
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
channel.command('unarchive <ref>').description('Unarchive a channel')
|
|
1026
|
+
.action(async (ref, opts, cmd) => {
|
|
1027
|
+
try {
|
|
1028
|
+
await archiveChannel(ref, { ...opts, value: false, ...globalsFrom(cmd) });
|
|
1029
|
+
}
|
|
1030
|
+
catch (e) {
|
|
1031
|
+
handleError(e);
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
channel.command('members <ref>').description('List channel members')
|
|
1035
|
+
.addHelpText('after', `
|
|
1036
|
+
Examples:
|
|
1037
|
+
$ huly channel members engineering
|
|
1038
|
+
$ huly channel members engineering --json`)
|
|
1039
|
+
.action(async (ref, opts, cmd) => {
|
|
1040
|
+
try {
|
|
1041
|
+
await listChannelMembers(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1042
|
+
}
|
|
1043
|
+
catch (e) {
|
|
1044
|
+
handleError(e);
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
channel.command('join <ref>').description('Join a channel (--member <email> for a specific user)')
|
|
1048
|
+
.option('--member <email>', 'add a specific user (OWNER/admin only)')
|
|
1049
|
+
.addHelpText('after', `
|
|
1050
|
+
Examples:
|
|
1051
|
+
$ huly channel join engineering
|
|
1052
|
+
$ huly channel join engineering --member alice@example.com`)
|
|
1053
|
+
.action(async (ref, opts, cmd) => {
|
|
1054
|
+
try {
|
|
1055
|
+
await joinChannel(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1056
|
+
}
|
|
1057
|
+
catch (e) {
|
|
1058
|
+
handleError(e);
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
channel.command('leave <ref>').description('Leave a channel')
|
|
1062
|
+
.option('--member <email>', 'remove a specific user (OWNER/admin only)')
|
|
1063
|
+
.action(async (ref, opts, cmd) => {
|
|
1064
|
+
try {
|
|
1065
|
+
await leaveChannel(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1066
|
+
}
|
|
1067
|
+
catch (e) {
|
|
1068
|
+
handleError(e);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
channel.command('add-member <ref>').description('Add one or more members')
|
|
1072
|
+
.requiredOption('--members <email...>', 'space-separated list')
|
|
1073
|
+
.addHelpText('after', `
|
|
1074
|
+
Examples:
|
|
1075
|
+
$ huly channel add-member engineering --members alice@example.com
|
|
1076
|
+
$ huly channel add-member engineering --members alice@.. bob@.. carol@..`)
|
|
1077
|
+
.action(async (ref, opts, cmd) => {
|
|
1078
|
+
try {
|
|
1079
|
+
await addChannelMembers(ref, opts.members, globalsFrom(cmd));
|
|
1080
|
+
}
|
|
1081
|
+
catch (e) {
|
|
1082
|
+
handleError(e);
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
channel.command('remove-member <ref>').description('Remove one or more members')
|
|
1086
|
+
.requiredOption('--members <email...>', 'space-separated list')
|
|
1087
|
+
.addHelpText('after', `
|
|
1088
|
+
Examples:
|
|
1089
|
+
$ huly channel remove-member engineering --members alice@example.com
|
|
1090
|
+
$ huly channel remove-member engineering --members alice@.. bob@..`)
|
|
1091
|
+
.action(async (ref, opts, cmd) => {
|
|
1092
|
+
try {
|
|
1093
|
+
await removeChannelMembers(ref, opts.members, globalsFrom(cmd));
|
|
1094
|
+
}
|
|
1095
|
+
catch (e) {
|
|
1096
|
+
handleError(e);
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
const cmsg = channel.command('message').description('Manage messages within a channel');
|
|
1100
|
+
cmsg.command('list <ref>').description('List messages in a channel')
|
|
1101
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1102
|
+
.addHelpText('after', `
|
|
1103
|
+
Examples:
|
|
1104
|
+
$ huly channel message list engineering
|
|
1105
|
+
$ huly channel message list engineering --limit 50
|
|
1106
|
+
$ huly channel message list engineering --json`)
|
|
1107
|
+
.action(async (ref, opts, cmd) => {
|
|
1108
|
+
try {
|
|
1109
|
+
await listChannelMessages(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1110
|
+
}
|
|
1111
|
+
catch (e) {
|
|
1112
|
+
handleError(e);
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
cmsg.command('send <ref>').description('Send a message to a channel')
|
|
1116
|
+
.option('--body <md>')
|
|
1117
|
+
.option('--body-file <path>')
|
|
1118
|
+
.action(async (ref, opts, cmd) => {
|
|
1119
|
+
try {
|
|
1120
|
+
await sendChannelMessage(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1121
|
+
}
|
|
1122
|
+
catch (e) {
|
|
1123
|
+
handleError(e);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
cmsg.command('update <ref> <id>').description('Update a message')
|
|
1127
|
+
.option('--body <md>')
|
|
1128
|
+
.option('--body-file <path>')
|
|
1129
|
+
.action(async (ref, id, opts, cmd) => {
|
|
1130
|
+
try {
|
|
1131
|
+
await updateChannelMessage(ref, id, { ...opts, ...globalsFrom(cmd) });
|
|
1132
|
+
}
|
|
1133
|
+
catch (e) {
|
|
1134
|
+
handleError(e);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
cmsg.command('delete <ref> <messageIds...>').description('Delete one or more messages')
|
|
1138
|
+
.action(async (ref, messageIds, opts, cmd) => {
|
|
1139
|
+
try {
|
|
1140
|
+
await deleteChannelMessages(ref, messageIds, globalsFrom(cmd));
|
|
1141
|
+
}
|
|
1142
|
+
catch (e) {
|
|
1143
|
+
handleError(e);
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
const dm = program.command('dm').description('Manage direct messages');
|
|
1147
|
+
withGlobalHelp(dm);
|
|
1148
|
+
dm.command('list').description('List DMs (spaces)')
|
|
1149
|
+
.action(async (opts, cmd) => {
|
|
1150
|
+
try {
|
|
1151
|
+
await listDms(globalsFrom(cmd));
|
|
1152
|
+
}
|
|
1153
|
+
catch (e) {
|
|
1154
|
+
handleError(e);
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
dm.command('create').description('Create a DM (with --person or --members)')
|
|
1158
|
+
.option('--person <email>')
|
|
1159
|
+
.option('--members <email...>')
|
|
1160
|
+
.addHelpText('after', `
|
|
1161
|
+
Examples:
|
|
1162
|
+
$ huly dm create --person alice@example.com
|
|
1163
|
+
$ huly dm create --members alice@.. bob@.. # group DM`)
|
|
1164
|
+
.action(async (opts, cmd) => {
|
|
1165
|
+
try {
|
|
1166
|
+
await createDm({ ...opts, ...globalsFrom(cmd) });
|
|
1167
|
+
}
|
|
1168
|
+
catch (e) {
|
|
1169
|
+
handleError(e);
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
// N1: dm.message mirrors channel.message (consistent nesting). The flat
|
|
1173
|
+
// 'dm messages' and 'dm send' commands remain as backward-compatible aliases.
|
|
1174
|
+
const dmMsg = dm.command('message').description('Manage messages within a DM (alias for `dm messages`/`dm send`)');
|
|
1175
|
+
dmMsg.command('list <dm>').description('List messages in a DM')
|
|
1176
|
+
.action(async (dm, opts, cmd) => {
|
|
1177
|
+
try {
|
|
1178
|
+
await listDmMessages(dm, { ...opts, ...globalsFrom(cmd) });
|
|
1179
|
+
}
|
|
1180
|
+
catch (e) {
|
|
1181
|
+
handleError(e);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
dmMsg.command('send <dm>').description('Send a DM message (or to <email> via --person)')
|
|
1185
|
+
.option('--body <md>')
|
|
1186
|
+
.option('--body-file <path>')
|
|
1187
|
+
.option('--person <email>', 'recipient email (auto-creates DM if needed)')
|
|
1188
|
+
.addHelpText('after', `
|
|
1189
|
+
Examples:
|
|
1190
|
+
$ huly dm message send <dmId> --body "hello"
|
|
1191
|
+
$ huly dm message send placeholder --person alice@.. --body "hi"
|
|
1192
|
+
$ huly dm message list <dmId>`)
|
|
1193
|
+
.action(async (dm, opts, cmd) => {
|
|
1194
|
+
try {
|
|
1195
|
+
await sendDmMessage(dm, { ...opts, ...globalsFrom(cmd) });
|
|
1196
|
+
}
|
|
1197
|
+
catch (e) {
|
|
1198
|
+
handleError(e);
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
// Backward-compatible flat commands (deprecated; use dm message <verb>)
|
|
1202
|
+
dm.command('messages <dm>').description('[alias] List messages in a DM (use `dm message list`)')
|
|
1203
|
+
.action(async (dm, opts, cmd) => {
|
|
1204
|
+
try {
|
|
1205
|
+
await listDmMessages(dm, { ...opts, ...globalsFrom(cmd) });
|
|
1206
|
+
}
|
|
1207
|
+
catch (e) {
|
|
1208
|
+
handleError(e);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
dm.command('send <dm>').description('[alias] Send a DM message (use `dm message send`)')
|
|
1212
|
+
.option('--body <md>')
|
|
1213
|
+
.option('--body-file <path>')
|
|
1214
|
+
.option('--person <email>', 'recipient email (auto-creates DM if needed)')
|
|
1215
|
+
.action(async (dm, opts, cmd) => {
|
|
1216
|
+
try {
|
|
1217
|
+
await sendDmMessage(dm, { ...opts, ...globalsFrom(cmd) });
|
|
1218
|
+
}
|
|
1219
|
+
catch (e) {
|
|
1220
|
+
handleError(e);
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
const thread = program.command('thread').description('Manage thread replies on a chat message');
|
|
1224
|
+
withGlobalHelp(thread);
|
|
1225
|
+
thread.command('list <target>').description('List replies on a target message')
|
|
1226
|
+
.action(async (target, opts, cmd) => {
|
|
1227
|
+
try {
|
|
1228
|
+
await listThreadReplies(target, globalsFrom(cmd));
|
|
1229
|
+
}
|
|
1230
|
+
catch (e) {
|
|
1231
|
+
handleError(e);
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
thread.command('add <target>').description('Add a reply')
|
|
1235
|
+
.option('--body <md>')
|
|
1236
|
+
.option('--body-file <path>')
|
|
1237
|
+
.action(async (target, opts, cmd) => {
|
|
1238
|
+
try {
|
|
1239
|
+
await addThreadReply(target, { ...opts, ...globalsFrom(cmd) });
|
|
1240
|
+
}
|
|
1241
|
+
catch (e) {
|
|
1242
|
+
handleError(e);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
thread.command('update <replyId>').description('Update a reply')
|
|
1246
|
+
.option('--body <md>')
|
|
1247
|
+
.option('--body-file <path>')
|
|
1248
|
+
.action(async (replyId, opts, cmd) => {
|
|
1249
|
+
try {
|
|
1250
|
+
await updateThreadReply(replyId, { ...opts, ...globalsFrom(cmd) });
|
|
1251
|
+
}
|
|
1252
|
+
catch (e) {
|
|
1253
|
+
handleError(e);
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
thread.command('delete <replyId...>').description('Delete replies')
|
|
1257
|
+
.action(async (replies, opts, cmd) => {
|
|
1258
|
+
try {
|
|
1259
|
+
await deleteThreadReplies(replies, globalsFrom(cmd));
|
|
1260
|
+
}
|
|
1261
|
+
catch (e) {
|
|
1262
|
+
handleError(e);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
// Note: the old `board:class:Card` (board module) commands were removed —
|
|
1266
|
+
// they're out of scope per the parity plan. The new `card:class:Card`
|
|
1267
|
+
// commands live below under the `card` command (Phase 12).
|
|
1268
|
+
const card = program.command('card').description('Manage Kanban cards (card module)');
|
|
1269
|
+
withGlobalHelp(card);
|
|
1270
|
+
card
|
|
1271
|
+
.command('list')
|
|
1272
|
+
.description('List cards')
|
|
1273
|
+
.option('--card-space <ref>')
|
|
1274
|
+
.option('--master-tag <ref>')
|
|
1275
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1276
|
+
.option('--offset <n>', 'offset', (v) => parseInt(v, 10))
|
|
1277
|
+
.action(async (opts, cmd) => {
|
|
1278
|
+
try {
|
|
1279
|
+
await listCards({ ...opts, ...globalsFrom(cmd) });
|
|
1280
|
+
}
|
|
1281
|
+
catch (e) {
|
|
1282
|
+
handleError(e);
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
card.command('get <ref>').description('Get a card')
|
|
1286
|
+
.action(async (ref, opts, cmd) => {
|
|
1287
|
+
try {
|
|
1288
|
+
await getCard(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1289
|
+
}
|
|
1290
|
+
catch (e) {
|
|
1291
|
+
handleError(e);
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
card
|
|
1295
|
+
.command('create')
|
|
1296
|
+
.description('Create a card (requires --master-tag)')
|
|
1297
|
+
.requiredOption('--title <t>')
|
|
1298
|
+
.requiredOption('--master-tag <name|ref>', 'master-tag name (e.g. "Task") or _id')
|
|
1299
|
+
.option('--card-space <ref>', 'card space; defaults to card:space:Default')
|
|
1300
|
+
.option('--description <text>')
|
|
1301
|
+
.option('--body <md>')
|
|
1302
|
+
.option('--body-file <path>')
|
|
1303
|
+
.addHelpText('after', `
|
|
1304
|
+
Examples:
|
|
1305
|
+
$ huly card create --title "My card" --master-tag "Task"
|
|
1306
|
+
$ huly card create --title "..." --master-tag card:master-tag.Task \\
|
|
1307
|
+
--card-space card:space:Default --body-file ./spec.md
|
|
1308
|
+
|
|
1309
|
+
N9: --master-tag is REQUIRED. First-time setup usually requires creating a
|
|
1310
|
+
master-tag via the web UI (the CLI doesn't expose master-tag creation).
|
|
1311
|
+
|
|
1312
|
+
List available tags with \`huly master-tag list\`.`)
|
|
1313
|
+
.action(async (opts, cmd) => {
|
|
1314
|
+
try {
|
|
1315
|
+
await createCard({ ...opts, ...globalsFrom(cmd) });
|
|
1316
|
+
}
|
|
1317
|
+
catch (e) {
|
|
1318
|
+
handleError(e);
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1321
|
+
card.command('update <ref>').description('Update a card')
|
|
1322
|
+
.option('--title <t>')
|
|
1323
|
+
.option('--description <text>')
|
|
1324
|
+
.option('--body <md>')
|
|
1325
|
+
.option('--body-file <path>')
|
|
1326
|
+
.action(async (ref, opts, cmd) => {
|
|
1327
|
+
try {
|
|
1328
|
+
await updateCard(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1329
|
+
}
|
|
1330
|
+
catch (e) {
|
|
1331
|
+
handleError(e);
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
card.command('delete <ref...>').description('Delete cards').action(async (refs, opts, cmd) => {
|
|
1335
|
+
try {
|
|
1336
|
+
await deleteCards(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1337
|
+
}
|
|
1338
|
+
catch (e) {
|
|
1339
|
+
handleError(e);
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
const cardSpace = program.command('card-space').description('Manage card spaces');
|
|
1343
|
+
withGlobalHelp(cardSpace);
|
|
1344
|
+
cardSpace.command('list').description('List card spaces')
|
|
1345
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1346
|
+
.action(async (opts, cmd) => {
|
|
1347
|
+
try {
|
|
1348
|
+
await listCardSpaces({ ...opts, ...globalsFrom(cmd) });
|
|
1349
|
+
}
|
|
1350
|
+
catch (e) {
|
|
1351
|
+
handleError(e);
|
|
1352
|
+
}
|
|
1353
|
+
});
|
|
1354
|
+
cardSpace.command('get <ref>').description('Get a card-space').action(async (ref, opts, cmd) => {
|
|
1355
|
+
try {
|
|
1356
|
+
await getCardSpace(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1357
|
+
}
|
|
1358
|
+
catch (e) {
|
|
1359
|
+
handleError(e);
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
cardSpace.command('create').description('Create a card-space')
|
|
1363
|
+
.requiredOption('--name <name>')
|
|
1364
|
+
.option('--description <text>')
|
|
1365
|
+
.action(async (opts, cmd) => {
|
|
1366
|
+
try {
|
|
1367
|
+
await createCardSpace({ ...opts, ...globalsFrom(cmd) });
|
|
1368
|
+
}
|
|
1369
|
+
catch (e) {
|
|
1370
|
+
handleError(e);
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
cardSpace.command('delete <ref...>').description('Delete card-spaces')
|
|
1374
|
+
.action(async (refs, opts, cmd) => {
|
|
1375
|
+
try {
|
|
1376
|
+
await deleteCardSpaces(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1377
|
+
}
|
|
1378
|
+
catch (e) {
|
|
1379
|
+
handleError(e);
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
const mt = program.command('master-tag').description('Manage card master tags');
|
|
1383
|
+
withGlobalHelp(mt);
|
|
1384
|
+
mt.command('list').description('List master tags')
|
|
1385
|
+
.option('--card-space <ref>')
|
|
1386
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1387
|
+
.action(async (opts, cmd) => {
|
|
1388
|
+
try {
|
|
1389
|
+
await listMasterTags({ ...opts, ...globalsFrom(cmd) });
|
|
1390
|
+
}
|
|
1391
|
+
catch (e) {
|
|
1392
|
+
handleError(e);
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
const action = program.command('action').description('Manage tasks (Planner ToDos)');
|
|
1396
|
+
withGlobalHelp(action);
|
|
1397
|
+
action
|
|
1398
|
+
.command('list')
|
|
1399
|
+
.description('List tasks')
|
|
1400
|
+
.option('--owner <email>')
|
|
1401
|
+
.option('--issue <ref>', 'filter to todos attached to an issue')
|
|
1402
|
+
.option('--title <q>', 'regex match on title')
|
|
1403
|
+
.option('--priority <p>', 'High|Medium|Low|NoPriority|Urgent')
|
|
1404
|
+
.option('--visibility <v>', 'public|busy|private')
|
|
1405
|
+
.option('--due-from <iso>')
|
|
1406
|
+
.option('--due-to <iso>')
|
|
1407
|
+
.option('--completed <bool>', 'true|false|all', (v) => v === 'all' ? 'all' : v !== 'false' && v !== '0')
|
|
1408
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1409
|
+
.option('--offset <n>', 'offset', (v) => parseInt(v, 10))
|
|
1410
|
+
.action(async (opts, cmd) => {
|
|
1411
|
+
try {
|
|
1412
|
+
await listActions({ ...opts, ...globalsFrom(cmd) });
|
|
1413
|
+
}
|
|
1414
|
+
catch (e) {
|
|
1415
|
+
handleError(e);
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
action.command('get <ref>').description('Get a task')
|
|
1419
|
+
.action(async (ref, opts, cmd) => {
|
|
1420
|
+
try {
|
|
1421
|
+
await getAction(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1422
|
+
}
|
|
1423
|
+
catch (e) {
|
|
1424
|
+
handleError(e);
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
action
|
|
1428
|
+
.command('create')
|
|
1429
|
+
.description('Create a task')
|
|
1430
|
+
.requiredOption('--title <t>')
|
|
1431
|
+
.option('--description <text>')
|
|
1432
|
+
.option('--body <md>')
|
|
1433
|
+
.option('--body-file <path>')
|
|
1434
|
+
.option('--due <iso>')
|
|
1435
|
+
.option('--priority <p>')
|
|
1436
|
+
.option('--visibility <v>')
|
|
1437
|
+
.option('--owner <email>')
|
|
1438
|
+
.option('--attached-to <ref>')
|
|
1439
|
+
.option('--attached-to-class <class>')
|
|
1440
|
+
.action(async (opts, cmd) => {
|
|
1441
|
+
try {
|
|
1442
|
+
await createAction({ ...opts, ...globalsFrom(cmd) });
|
|
1443
|
+
}
|
|
1444
|
+
catch (e) {
|
|
1445
|
+
handleError(e);
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
action.command('update <ref>').description('Update a task')
|
|
1449
|
+
.option('--title <t>')
|
|
1450
|
+
.option('--description <text>')
|
|
1451
|
+
.option('--body <md>')
|
|
1452
|
+
.option('--body-file <path>')
|
|
1453
|
+
.option('--due <iso>')
|
|
1454
|
+
.option('--priority <p>')
|
|
1455
|
+
.option('--visibility <v>')
|
|
1456
|
+
.option('--owner <email>')
|
|
1457
|
+
.action(async (ref, opts, cmd) => {
|
|
1458
|
+
try {
|
|
1459
|
+
await updateAction(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1460
|
+
}
|
|
1461
|
+
catch (e) {
|
|
1462
|
+
handleError(e);
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
action.command('complete <ref>').description('Mark a task done (sets doneOn=now)')
|
|
1466
|
+
.action(async (ref, opts, cmd) => {
|
|
1467
|
+
try {
|
|
1468
|
+
await completeAction(ref, globalsFrom(cmd));
|
|
1469
|
+
}
|
|
1470
|
+
catch (e) {
|
|
1471
|
+
handleError(e);
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
action.command('reopen <ref>').description('Reopen a task (clears doneOn)')
|
|
1475
|
+
.action(async (ref, opts, cmd) => {
|
|
1476
|
+
try {
|
|
1477
|
+
await reopenAction(ref, globalsFrom(cmd));
|
|
1478
|
+
}
|
|
1479
|
+
catch (e) {
|
|
1480
|
+
handleError(e);
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
action.command('schedule <ref>').description('Create a WorkSlot for the task')
|
|
1484
|
+
.requiredOption('--start <iso>')
|
|
1485
|
+
.requiredOption('--duration <minutes>', '', (v) => parseInt(v, 10))
|
|
1486
|
+
.option('--all-day')
|
|
1487
|
+
.action(async (ref, opts, cmd) => {
|
|
1488
|
+
try {
|
|
1489
|
+
await scheduleAction(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1490
|
+
}
|
|
1491
|
+
catch (e) {
|
|
1492
|
+
handleError(e);
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
action.command('unschedule <ref>').description('Remove WorkSlots for the task')
|
|
1496
|
+
.option('--slot-id <id>', 'remove a specific slot only')
|
|
1497
|
+
.action(async (ref, opts, cmd) => {
|
|
1498
|
+
try {
|
|
1499
|
+
await unscheduleAction(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1500
|
+
}
|
|
1501
|
+
catch (e) {
|
|
1502
|
+
handleError(e);
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
action.command('delete <ref...>').description('Delete tasks').action(async (refs, opts, cmd) => {
|
|
1506
|
+
try {
|
|
1507
|
+
await deleteActions(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1508
|
+
}
|
|
1509
|
+
catch (e) {
|
|
1510
|
+
handleError(e);
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
const doc = program.command('document').description('Manage documents (and their snapshots/inline comments)');
|
|
1514
|
+
withGlobalHelp(doc);
|
|
1515
|
+
doc
|
|
1516
|
+
.command('list')
|
|
1517
|
+
.description('List documents')
|
|
1518
|
+
.option('--teamspace <name|id>')
|
|
1519
|
+
.option('--title-search <q>', 'regex match on title')
|
|
1520
|
+
.option('--content-search <q>', 'best-effort regex match on content')
|
|
1521
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1522
|
+
.option('--offset <n>', 'offset', (v) => parseInt(v, 10))
|
|
1523
|
+
.action(async (opts, cmd) => {
|
|
1524
|
+
try {
|
|
1525
|
+
await listDocuments({ ...opts, ...globalsFrom(cmd) });
|
|
1526
|
+
}
|
|
1527
|
+
catch (e) {
|
|
1528
|
+
handleError(e);
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
doc.command('get <ref>').description('Get a document (use --markdown to render the body)')
|
|
1532
|
+
.action(async (ref, opts, cmd) => {
|
|
1533
|
+
try {
|
|
1534
|
+
await getDocument(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1535
|
+
}
|
|
1536
|
+
catch (e) {
|
|
1537
|
+
handleError(e);
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
doc
|
|
1541
|
+
.command('create')
|
|
1542
|
+
.description('Create a document')
|
|
1543
|
+
.requiredOption('--title <t>')
|
|
1544
|
+
.option('--teamspace <name|id>', 'defaults to the first available teamspace')
|
|
1545
|
+
.option('--body <md>')
|
|
1546
|
+
.option('--body-file <path>')
|
|
1547
|
+
.option('--parent <ref|title>', 'parent ref or title (resolved within teamspace)')
|
|
1548
|
+
.action(async (opts, cmd) => {
|
|
1549
|
+
try {
|
|
1550
|
+
await createDocument({ ...opts, ...globalsFrom(cmd) });
|
|
1551
|
+
}
|
|
1552
|
+
catch (e) {
|
|
1553
|
+
handleError(e);
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
doc
|
|
1557
|
+
.command('update <ref>')
|
|
1558
|
+
.description('Update a document (full body replace OR targeted --old-text/--new-text)')
|
|
1559
|
+
.option('--body <md>')
|
|
1560
|
+
.option('--body-file <path>')
|
|
1561
|
+
.option('--old-text <s>')
|
|
1562
|
+
.option('--new-text <s>')
|
|
1563
|
+
.option('--replace-all')
|
|
1564
|
+
.option('--title <t>')
|
|
1565
|
+
.option('--archived')
|
|
1566
|
+
.action(async (ref, opts, cmd) => {
|
|
1567
|
+
try {
|
|
1568
|
+
await updateDocument(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1569
|
+
}
|
|
1570
|
+
catch (e) {
|
|
1571
|
+
handleError(e);
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
doc.command('delete <ref...>').description('Delete documents').action(async (refs, opts, cmd) => {
|
|
1575
|
+
try {
|
|
1576
|
+
await deleteDocuments(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1577
|
+
}
|
|
1578
|
+
catch (e) {
|
|
1579
|
+
handleError(e);
|
|
1580
|
+
}
|
|
1581
|
+
});
|
|
1582
|
+
doc.command('snapshots <ref>').description('List all snapshots for a document')
|
|
1583
|
+
.addHelpText('after', `
|
|
1584
|
+
Examples:
|
|
1585
|
+
$ huly document snapshots <docRef>
|
|
1586
|
+
$ huly document snapshots <docRef> --json
|
|
1587
|
+
|
|
1588
|
+
Use \`document snapshot --snapshot-id <id>\` to fetch a specific snapshot.`)
|
|
1589
|
+
.action(async (ref, opts, cmd) => {
|
|
1590
|
+
try {
|
|
1591
|
+
await listSnapshots(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1592
|
+
}
|
|
1593
|
+
catch (e) {
|
|
1594
|
+
handleError(e);
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
doc.command('snapshot <ref>').description('Get a specific snapshot (by --snapshot-id)')
|
|
1598
|
+
.requiredOption('--snapshot-id <id>')
|
|
1599
|
+
.addHelpText('after', `
|
|
1600
|
+
Examples:
|
|
1601
|
+
$ huly document snapshot <docRef> --snapshot-id 6a41527f12a078ec98cf64d5
|
|
1602
|
+
$ huly document snapshot <docRef> --snapshot-id 6a41527f12a078ec98cf64d5 --markdown
|
|
1603
|
+
|
|
1604
|
+
N4: \`snapshot\` (singular) gets one; \`snapshots\` (plural) lists all.`)
|
|
1605
|
+
.action(async (ref, opts, cmd) => {
|
|
1606
|
+
try {
|
|
1607
|
+
await getSnapshot(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1608
|
+
}
|
|
1609
|
+
catch (e) {
|
|
1610
|
+
handleError(e);
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
doc.command('inline-comments <ref>').description('List inline comments for a document')
|
|
1614
|
+
.action(async (ref, opts, cmd) => {
|
|
1615
|
+
try {
|
|
1616
|
+
await listInlineComments(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1617
|
+
}
|
|
1618
|
+
catch (e) {
|
|
1619
|
+
handleError(e);
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
const ts = program.command('teamspace').description('Manage document teamspaces');
|
|
1623
|
+
withGlobalHelp(ts);
|
|
1624
|
+
ts.command('list').description('List teamspaces')
|
|
1625
|
+
.action(async (_o, cmd) => {
|
|
1626
|
+
try {
|
|
1627
|
+
await listTeamspaces(globalsFrom(cmd));
|
|
1628
|
+
}
|
|
1629
|
+
catch (e) {
|
|
1630
|
+
handleError(e);
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
ts.command('get <ref>').description('Get a teamspace')
|
|
1634
|
+
.action(async (ref, opts, cmd) => {
|
|
1635
|
+
try {
|
|
1636
|
+
await getTeamspace(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1637
|
+
}
|
|
1638
|
+
catch (e) {
|
|
1639
|
+
handleError(e);
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
ts.command('create').description('Create a teamspace')
|
|
1643
|
+
.requiredOption('--name <n>')
|
|
1644
|
+
.option('--description <text>')
|
|
1645
|
+
.option('--type <t>', 'public|private (default public)')
|
|
1646
|
+
.option('--private')
|
|
1647
|
+
.action(async (opts, cmd) => {
|
|
1648
|
+
try {
|
|
1649
|
+
await createTeamspace({ ...opts, ...globalsFrom(cmd) });
|
|
1650
|
+
}
|
|
1651
|
+
catch (e) {
|
|
1652
|
+
handleError(e);
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
ts.command('update <ref>').description('Update a teamspace')
|
|
1656
|
+
.option('--name <n>')
|
|
1657
|
+
.option('--description <text>')
|
|
1658
|
+
.option('--archived <bool>', 'true|false', (v) => v !== 'false' && v !== '0')
|
|
1659
|
+
.action(async (ref, opts, cmd) => {
|
|
1660
|
+
try {
|
|
1661
|
+
await updateTeamspace(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1662
|
+
}
|
|
1663
|
+
catch (e) {
|
|
1664
|
+
handleError(e);
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
ts.command('delete <ref...>').description('Delete teamspaces').action(async (refs, opts, cmd) => {
|
|
1668
|
+
try {
|
|
1669
|
+
await deleteTeamspaces(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1670
|
+
}
|
|
1671
|
+
catch (e) {
|
|
1672
|
+
handleError(e);
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
const cal = program.command('calendar').description('Manage calendar events, schedules, calendars');
|
|
1676
|
+
withGlobalHelp(cal);
|
|
1677
|
+
// N5: 'calendars' (plural noun) lists CALENDAR OBJECTS; 'list' (verb) lists EVENTS.
|
|
1678
|
+
// 'get' (verb) gets an EVENT. To fetch a calendar's metadata, use 'calendars --json'.
|
|
1679
|
+
cal.command('calendars').description('List calendar objects (not events — see `calendar list` for events)')
|
|
1680
|
+
.addHelpText('after', `
|
|
1681
|
+
Examples:
|
|
1682
|
+
$ huly calendar calendars
|
|
1683
|
+
$ huly calendar calendars --json | jq -r '.[].name'`)
|
|
1684
|
+
.action(async (_o, cmd) => {
|
|
1685
|
+
try {
|
|
1686
|
+
await listCalendars(globalsFrom(cmd));
|
|
1687
|
+
}
|
|
1688
|
+
catch (e) {
|
|
1689
|
+
handleError(e);
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
cal.command('create-calendar').description('Create a calendar')
|
|
1693
|
+
.requiredOption('--name <name>')
|
|
1694
|
+
.option('--description <text>')
|
|
1695
|
+
.option('--private', 'private calendar (members only)')
|
|
1696
|
+
.option('--access <a>', 'owner|team|public')
|
|
1697
|
+
.addHelpText('after', `
|
|
1698
|
+
Examples:
|
|
1699
|
+
$ huly calendar create-calendar --name "Work"
|
|
1700
|
+
$ huly calendar create-calendar --name "Personal" --private --access owner`)
|
|
1701
|
+
.action(async (opts, cmd) => {
|
|
1702
|
+
try {
|
|
1703
|
+
await createCalendar({ ...opts, ...globalsFrom(cmd) });
|
|
1704
|
+
}
|
|
1705
|
+
catch (e) {
|
|
1706
|
+
handleError(e);
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
cal.command('delete-calendar <ref>').description('Delete a calendar')
|
|
1710
|
+
.action(async (ref, opts, cmd) => {
|
|
1711
|
+
try {
|
|
1712
|
+
await deleteCalendar(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1713
|
+
}
|
|
1714
|
+
catch (e) {
|
|
1715
|
+
handleError(e);
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
cal
|
|
1719
|
+
.command('list')
|
|
1720
|
+
.description('List EVENTS (not calendars — see `calendar calendars` for calendars)')
|
|
1721
|
+
.option('--start <iso>', 'ISO 8601 start date filter')
|
|
1722
|
+
.option('--end <iso>', 'ISO 8601 end date filter')
|
|
1723
|
+
.option('--calendar <id|name>', 'filter to a specific calendar')
|
|
1724
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1725
|
+
.addHelpText('after', `
|
|
1726
|
+
Examples:
|
|
1727
|
+
$ huly calendar list
|
|
1728
|
+
$ huly calendar list --start 2026-06-01 --end 2026-06-30
|
|
1729
|
+
$ huly calendar list --calendar "Work" --limit 20
|
|
1730
|
+
$ huly calendar list --json | jq -r '.[] | "\(.title): \(.date)"'`)
|
|
1731
|
+
.action(async (opts, cmd) => {
|
|
1732
|
+
try {
|
|
1733
|
+
await listEvents({ ...opts, ...globalsFrom(cmd) });
|
|
1734
|
+
}
|
|
1735
|
+
catch (e) {
|
|
1736
|
+
handleError(e);
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
cal.command('get <ref>').description('Get an EVENT (not a calendar)')
|
|
1740
|
+
.addHelpText('after', `
|
|
1741
|
+
Examples:
|
|
1742
|
+
$ huly calendar get <eventRef>
|
|
1743
|
+
$ huly calendar get <eventRef> --markdown
|
|
1744
|
+
|
|
1745
|
+
To fetch a calendar (the container, not an event inside it), use
|
|
1746
|
+
\`calendar calendars --json\` and grep for the calendar id.`)
|
|
1747
|
+
.action(async (ref, opts, cmd) => {
|
|
1748
|
+
try {
|
|
1749
|
+
await getEvent(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1750
|
+
}
|
|
1751
|
+
catch (e) {
|
|
1752
|
+
handleError(e);
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
cal
|
|
1756
|
+
.command('create')
|
|
1757
|
+
.description('Create an event (or recurring event with --rrule)')
|
|
1758
|
+
.requiredOption('--title <t>')
|
|
1759
|
+
.requiredOption('--start <iso>')
|
|
1760
|
+
.requiredOption('--end <iso>')
|
|
1761
|
+
.option('--attendee <email>')
|
|
1762
|
+
.option('--location <text>')
|
|
1763
|
+
.option('--all-day')
|
|
1764
|
+
.option('--description <text>')
|
|
1765
|
+
.option('--body <md>')
|
|
1766
|
+
.option('--calendar-id <id>')
|
|
1767
|
+
.option('--rrule <string>', 'RRULE e.g. FREQ=DAILY;COUNT=3')
|
|
1768
|
+
.action(async (opts, cmd) => {
|
|
1769
|
+
try {
|
|
1770
|
+
await createEvent({ ...opts, ...globalsFrom(cmd) });
|
|
1771
|
+
}
|
|
1772
|
+
catch (e) {
|
|
1773
|
+
handleError(e);
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
cal.command('update <ref>').description('Update an event')
|
|
1777
|
+
.option('--title <t>')
|
|
1778
|
+
.option('--description <text>')
|
|
1779
|
+
.option('--start <iso>')
|
|
1780
|
+
.option('--end <iso>')
|
|
1781
|
+
.option('--all-day')
|
|
1782
|
+
.option('--location <text>')
|
|
1783
|
+
.option('--attendee <email>')
|
|
1784
|
+
.action(async (ref, opts, cmd) => {
|
|
1785
|
+
try {
|
|
1786
|
+
await updateEvent(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1787
|
+
}
|
|
1788
|
+
catch (e) {
|
|
1789
|
+
handleError(e);
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
cal.command('delete <ref...>').description('Delete events').action(async (refs, opts, cmd) => {
|
|
1793
|
+
try {
|
|
1794
|
+
await deleteEvents(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1795
|
+
}
|
|
1796
|
+
catch (e) {
|
|
1797
|
+
handleError(e);
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
cal.command('recurring').description('List recurring events')
|
|
1801
|
+
.action(async (_o, cmd) => {
|
|
1802
|
+
try {
|
|
1803
|
+
await listRecurringEvents(globalsFrom(cmd));
|
|
1804
|
+
}
|
|
1805
|
+
catch (e) {
|
|
1806
|
+
handleError(e);
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
cal.command('recurring-instances <ref>').description('List instances of a recurring event')
|
|
1810
|
+
.option('--start <iso>')
|
|
1811
|
+
.option('--end <iso>')
|
|
1812
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1813
|
+
.action(async (ref, opts, cmd) => {
|
|
1814
|
+
try {
|
|
1815
|
+
await listRecurringInstances(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1816
|
+
}
|
|
1817
|
+
catch (e) {
|
|
1818
|
+
handleError(e);
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
const schedule = program.command('schedule').description('Manage calendar schedules');
|
|
1822
|
+
withGlobalHelp(schedule);
|
|
1823
|
+
schedule.command('list').description('List schedules')
|
|
1824
|
+
.action(async (_o, cmd) => {
|
|
1825
|
+
try {
|
|
1826
|
+
await listSchedules(globalsFrom(cmd));
|
|
1827
|
+
}
|
|
1828
|
+
catch (e) {
|
|
1829
|
+
handleError(e);
|
|
1830
|
+
}
|
|
1831
|
+
});
|
|
1832
|
+
schedule.command('get <ref>').description('Get a schedule')
|
|
1833
|
+
.action(async (ref, opts, cmd) => {
|
|
1834
|
+
try {
|
|
1835
|
+
await getSchedule(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1836
|
+
}
|
|
1837
|
+
catch (e) {
|
|
1838
|
+
handleError(e);
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
schedule.command('create').description('Create a schedule')
|
|
1842
|
+
.requiredOption('--title <t>')
|
|
1843
|
+
.requiredOption('--owner <uuid>')
|
|
1844
|
+
.requiredOption('--time-zone <tz>')
|
|
1845
|
+
.option('--description <text>')
|
|
1846
|
+
.option('--duration <minutes>', 'meetingDuration', (v) => parseInt(v, 10))
|
|
1847
|
+
.option('--interval <minutes>', 'meetingInterval', (v) => parseInt(v, 10))
|
|
1848
|
+
.action(async (opts, cmd) => {
|
|
1849
|
+
try {
|
|
1850
|
+
await createSchedule({ ...opts, ...globalsFrom(cmd) });
|
|
1851
|
+
}
|
|
1852
|
+
catch (e) {
|
|
1853
|
+
handleError(e);
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
schedule.command('update <ref>').description('Update a schedule')
|
|
1857
|
+
.option('--title <t>')
|
|
1858
|
+
.option('--description <text>')
|
|
1859
|
+
.option('--time-zone <tz>')
|
|
1860
|
+
.option('--duration <minutes>', '', (v) => parseInt(v, 10))
|
|
1861
|
+
.option('--interval <minutes>', '', (v) => parseInt(v, 10))
|
|
1862
|
+
.action(async (ref, opts, cmd) => {
|
|
1863
|
+
try {
|
|
1864
|
+
await updateSchedule(ref, { ...opts, ...globalsFrom(cmd) });
|
|
1865
|
+
}
|
|
1866
|
+
catch (e) {
|
|
1867
|
+
handleError(e);
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
schedule.command('delete <ref...>').description('Delete schedules')
|
|
1871
|
+
.action(async (refs, opts, cmd) => {
|
|
1872
|
+
try {
|
|
1873
|
+
await deleteSchedules(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1874
|
+
}
|
|
1875
|
+
catch (e) {
|
|
1876
|
+
handleError(e);
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
const time = program.command('time').description('Time tracking on issues');
|
|
1880
|
+
withGlobalHelp(time);
|
|
1881
|
+
time.command('list').description('List time entries')
|
|
1882
|
+
.option('--issue <ref>')
|
|
1883
|
+
.option('--start <iso>')
|
|
1884
|
+
.option('--end <iso>')
|
|
1885
|
+
.option('--limit <n>', 'limit', (v) => parseInt(v, 10))
|
|
1886
|
+
.option('--offset <n>', 'offset', (v) => parseInt(v, 10))
|
|
1887
|
+
.action(async (opts, cmd) => {
|
|
1888
|
+
try {
|
|
1889
|
+
await listTimeEntries({ ...opts, ...globalsFrom(cmd) });
|
|
1890
|
+
}
|
|
1891
|
+
catch (e) {
|
|
1892
|
+
handleError(e);
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
time.command('log').description('Log time on an issue')
|
|
1896
|
+
.requiredOption('--issue <ref>')
|
|
1897
|
+
.option('--minutes <n>', 'minutes spent', (v) => parseInt(v, 10))
|
|
1898
|
+
.option('--hours <n>', 'hours spent (decimal ok)', (v) => Number(v))
|
|
1899
|
+
.option('--description <text>')
|
|
1900
|
+
.option('--date <iso>', 'default now')
|
|
1901
|
+
.action(async (opts, cmd) => {
|
|
1902
|
+
try {
|
|
1903
|
+
await logTime({ ...opts, ...globalsFrom(cmd) });
|
|
1904
|
+
}
|
|
1905
|
+
catch (e) {
|
|
1906
|
+
handleError(e);
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
time.command('report <issue>').description('Time report for a single issue')
|
|
1910
|
+
.action(async (issue, opts, cmd) => {
|
|
1911
|
+
try {
|
|
1912
|
+
await timeReport(issue, { ...opts, ...globalsFrom(cmd) });
|
|
1913
|
+
}
|
|
1914
|
+
catch (e) {
|
|
1915
|
+
handleError(e);
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
time.command('delete <ref...>').description('Delete time entries')
|
|
1919
|
+
.action(async (refs, opts, cmd) => {
|
|
1920
|
+
try {
|
|
1921
|
+
await deleteTimeEntries(refs, { ...opts, ...globalsFrom(cmd) });
|
|
1922
|
+
}
|
|
1923
|
+
catch (e) {
|
|
1924
|
+
handleError(e);
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
const raw = program.command('api').description('Raw HTTP escape hatch');
|
|
1928
|
+
raw
|
|
1929
|
+
.argument('<method>', 'HTTP method')
|
|
1930
|
+
.argument('<path>', 'URL path (e.g. /_accounts, /_transactor/...)')
|
|
1931
|
+
.option('--body <json>', 'request body')
|
|
1932
|
+
.option('--query <kv...>', 'query params k=v')
|
|
1933
|
+
.option('--header <kv...>', 'headers k=v')
|
|
1934
|
+
.action(async (method, path, opts) => {
|
|
1935
|
+
try {
|
|
1936
|
+
await apiCommand(method, path, opts);
|
|
1937
|
+
}
|
|
1938
|
+
catch (e) {
|
|
1939
|
+
handleError(e);
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
const wsCmd = program.command('ws').description('Raw WebSocket escape hatch (text JSON only)');
|
|
1943
|
+
wsCmd
|
|
1944
|
+
.argument('<method>', 'RPC method (e.g. findAll, tx)')
|
|
1945
|
+
.argument('[params]', 'JSON-encoded array of positional params')
|
|
1946
|
+
.option('--no-ping', 'disable ping/pong')
|
|
1947
|
+
.action(async (method, params, opts) => {
|
|
1948
|
+
try {
|
|
1949
|
+
await wsCommand(method, params, opts);
|
|
1950
|
+
}
|
|
1951
|
+
catch (e) {
|
|
1952
|
+
handleError(e);
|
|
1953
|
+
}
|
|
1954
|
+
});
|
|
1955
|
+
attachToChildren(program);
|
|
1956
|
+
attachGlobalOpts(program, { skipNonInteractive: true });
|
|
1957
|
+
await program.parseAsync(argv);
|
|
1958
|
+
}
|
|
1959
|
+
//# sourceMappingURL=cli.js.map
|