hap-cli 0.5.0__py3-none-any.whl

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 (58) hide show
  1. hap_cli/README.md +194 -0
  2. hap_cli/README_CN.md +601 -0
  3. hap_cli/__init__.py +3 -0
  4. hap_cli/commands/__init__.py +1 -0
  5. hap_cli/commands/ai_cmd.py +224 -0
  6. hap_cli/commands/app_cmd.py +308 -0
  7. hap_cli/commands/calendar_cmd.py +138 -0
  8. hap_cli/commands/chat_cmd.py +101 -0
  9. hap_cli/commands/config_cmd.py +169 -0
  10. hap_cli/commands/contact_cmd.py +125 -0
  11. hap_cli/commands/department_cmd.py +168 -0
  12. hap_cli/commands/group_cmd.py +128 -0
  13. hap_cli/commands/instance_cmd.py +310 -0
  14. hap_cli/commands/node_cmd.py +538 -0
  15. hap_cli/commands/optionset_cmd.py +99 -0
  16. hap_cli/commands/page_cmd.py +102 -0
  17. hap_cli/commands/plugin_cmd.py +133 -0
  18. hap_cli/commands/post_cmd.py +155 -0
  19. hap_cli/commands/record_cmd.py +228 -0
  20. hap_cli/commands/role_cmd.py +221 -0
  21. hap_cli/commands/workflow_cmd.py +284 -0
  22. hap_cli/commands/worksheet_cmd.py +342 -0
  23. hap_cli/context.py +43 -0
  24. hap_cli/core/__init__.py +1 -0
  25. hap_cli/core/ai.py +133 -0
  26. hap_cli/core/app.py +307 -0
  27. hap_cli/core/auth.py +219 -0
  28. hap_cli/core/calendar_mod.py +114 -0
  29. hap_cli/core/chat.py +73 -0
  30. hap_cli/core/contact.py +85 -0
  31. hap_cli/core/department.py +131 -0
  32. hap_cli/core/flow_node.py +1001 -0
  33. hap_cli/core/group.py +99 -0
  34. hap_cli/core/instance.py +572 -0
  35. hap_cli/core/optionset.py +112 -0
  36. hap_cli/core/page.py +138 -0
  37. hap_cli/core/plugin.py +87 -0
  38. hap_cli/core/post.py +118 -0
  39. hap_cli/core/record.py +268 -0
  40. hap_cli/core/role.py +227 -0
  41. hap_cli/core/session.py +348 -0
  42. hap_cli/core/workflow.py +556 -0
  43. hap_cli/core/worksheet.py +403 -0
  44. hap_cli/hap_cli.py +105 -0
  45. hap_cli/skills/SKILL.md +383 -0
  46. hap_cli/skills/__init__.py +0 -0
  47. hap_cli/tests/__init__.py +1 -0
  48. hap_cli/tests/test_core.py +1824 -0
  49. hap_cli/tests/test_full_e2e.py +136 -0
  50. hap_cli/tests/test_integration.py +805 -0
  51. hap_cli/utils/__init__.py +1 -0
  52. hap_cli/utils/formatting.py +111 -0
  53. hap_cli/utils/options.py +10 -0
  54. hap_cli-0.5.0.dist-info/METADATA +223 -0
  55. hap_cli-0.5.0.dist-info/RECORD +58 -0
  56. hap_cli-0.5.0.dist-info/WHEEL +5 -0
  57. hap_cli-0.5.0.dist-info/entry_points.txt +2 -0
  58. hap_cli-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,224 @@
1
+ """CLI commands for AI assistant and service management."""
2
+
3
+ import json
4
+
5
+ import click
6
+
7
+ from hap_cli.context import pass_context
8
+ from hap_cli.core import ai as ai_mod
9
+ from hap_cli.utils.formatting import output_json, output_kv
10
+
11
+
12
+ @click.group()
13
+ def ai():
14
+ """AI assistant and service management."""
15
+ pass
16
+
17
+
18
+ # ── AI Assistants ────────────────────────────────────────────────────────
19
+
20
+
21
+ @ai.command("assistants")
22
+ @click.option("--project-id", "-p", default="", help="Project/org ID")
23
+ @click.option("--page-size", "-n", default=20, help="Items per page")
24
+ @click.option("--page", default=1, help="Page number")
25
+ @pass_context
26
+ def ai_assistants(ctx, project_id, page_size, page):
27
+ """List AI assistants."""
28
+ try:
29
+ session = ctx.get_session()
30
+ pid = project_id or session.default_project_id
31
+ if not pid:
32
+ raise click.UsageError("Project ID required.")
33
+ data = ai_mod.get_assistants(session, pid, page_index=page, page_size=page_size)
34
+ ctx.output(data, lambda d: output_json(d))
35
+ except Exception as e:
36
+ ctx.handle_error(e)
37
+
38
+
39
+ @ai.command("assistant-get")
40
+ @click.argument("assistant_id")
41
+ @pass_context
42
+ def ai_assistant_get(ctx, assistant_id):
43
+ """Get AI assistant detail."""
44
+ try:
45
+ session = ctx.get_session()
46
+ data = ai_mod.get_assistant(session, assistant_id)
47
+ ctx.output(data, lambda d: output_kv(d))
48
+ except Exception as e:
49
+ ctx.handle_error(e)
50
+
51
+
52
+ @ai.command("assistant-create")
53
+ @click.option("--config", "-c", required=True, help="Assistant config as JSON string")
54
+ @pass_context
55
+ def ai_assistant_create(ctx, config):
56
+ """Create or update an AI assistant (JSON config)."""
57
+ try:
58
+ session = ctx.get_session()
59
+ data = ai_mod.upsert_assistant(session, json.loads(config))
60
+ ctx.output(data, lambda d: click.echo(f"Assistant created/updated: {d}"))
61
+ except json.JSONDecodeError as e:
62
+ ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
63
+ except Exception as e:
64
+ ctx.handle_error(e)
65
+
66
+
67
+ @ai.command("assistant-delete")
68
+ @click.argument("assistant_id")
69
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
70
+ @pass_context
71
+ def ai_assistant_delete(ctx, assistant_id, yes):
72
+ """Delete an AI assistant."""
73
+ try:
74
+ if not yes:
75
+ click.confirm(f"Delete assistant {assistant_id}?", abort=True)
76
+ session = ctx.get_session()
77
+ data = ai_mod.delete_assistant(session, assistant_id)
78
+ ctx.output(data, lambda d: click.echo("Assistant deleted."))
79
+ except Exception as e:
80
+ ctx.handle_error(e)
81
+
82
+
83
+ @ai.command("assistant-status")
84
+ @click.argument("assistant_id")
85
+ @click.option("--status", "-s", required=True, type=int, help="Status: 1=enabled, 0=disabled")
86
+ @pass_context
87
+ def ai_assistant_status(ctx, assistant_id, status):
88
+ """Enable or disable an AI assistant."""
89
+ try:
90
+ session = ctx.get_session()
91
+ data = ai_mod.set_assistant_status(session, assistant_id, status)
92
+ ctx.output(data, lambda d: click.echo(f"Status set to {status}."))
93
+ except Exception as e:
94
+ ctx.handle_error(e)
95
+
96
+
97
+ # ── Knowledge Base ───────────────────────────────────────────────────────
98
+
99
+
100
+ @ai.command("knowledge-list")
101
+ @click.option("--project-id", "-p", default="", help="Project/org ID")
102
+ @pass_context
103
+ def ai_knowledge_list(ctx, project_id):
104
+ """List knowledge bases."""
105
+ try:
106
+ session = ctx.get_session()
107
+ pid = project_id or session.default_project_id
108
+ if not pid:
109
+ raise click.UsageError("Project ID required.")
110
+ data = ai_mod.get_knowledge_bases(session, pid)
111
+ ctx.output(data, lambda d: output_json(d))
112
+ except Exception as e:
113
+ ctx.handle_error(e)
114
+
115
+
116
+ @ai.command("knowledge-get")
117
+ @click.argument("kb_id")
118
+ @pass_context
119
+ def ai_knowledge_get(ctx, kb_id):
120
+ """Get knowledge base detail."""
121
+ try:
122
+ session = ctx.get_session()
123
+ data = ai_mod.get_knowledge_base(session, kb_id)
124
+ ctx.output(data, lambda d: output_kv(d))
125
+ except Exception as e:
126
+ ctx.handle_error(e)
127
+
128
+
129
+ @ai.command("knowledge-create")
130
+ @click.option("--config", "-c", required=True, help="Knowledge base config as JSON string")
131
+ @pass_context
132
+ def ai_knowledge_create(ctx, config):
133
+ """Create or update a knowledge base (JSON config)."""
134
+ try:
135
+ session = ctx.get_session()
136
+ data = ai_mod.upsert_knowledge_base(session, json.loads(config))
137
+ ctx.output(data, lambda d: click.echo(f"Knowledge base created/updated: {d}"))
138
+ except json.JSONDecodeError as e:
139
+ ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
140
+ except Exception as e:
141
+ ctx.handle_error(e)
142
+
143
+
144
+ @ai.command("knowledge-delete")
145
+ @click.argument("kb_id")
146
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
147
+ @pass_context
148
+ def ai_knowledge_delete(ctx, kb_id, yes):
149
+ """Delete a knowledge base."""
150
+ try:
151
+ if not yes:
152
+ click.confirm(f"Delete knowledge base {kb_id}?", abort=True)
153
+ session = ctx.get_session()
154
+ data = ai_mod.delete_knowledge_base(session, kb_id)
155
+ ctx.output(data, lambda d: click.echo("Knowledge base deleted."))
156
+ except Exception as e:
157
+ ctx.handle_error(e)
158
+
159
+
160
+ # ── Chatbot ──────────────────────────────────────────────────────────────
161
+
162
+
163
+ @ai.command("chatbot-config")
164
+ @click.argument("process_id")
165
+ @pass_context
166
+ def ai_chatbot_config(ctx, process_id):
167
+ """Get chatbot configuration for a workflow."""
168
+ try:
169
+ session = ctx.get_session()
170
+ data = ai_mod.get_chatbot_config(session, process_id)
171
+ ctx.output(data, lambda d: output_json(d))
172
+ except Exception as e:
173
+ ctx.handle_error(e)
174
+
175
+
176
+ @ai.command("chatbot-save")
177
+ @click.argument("process_id")
178
+ @click.option("--config", "-c", required=True, help="Chatbot config as JSON string")
179
+ @pass_context
180
+ def ai_chatbot_save(ctx, process_id, config):
181
+ """Save chatbot configuration for a workflow."""
182
+ try:
183
+ session = ctx.get_session()
184
+ data = ai_mod.save_chatbot_config(session, process_id, json.loads(config))
185
+ ctx.output(data, lambda d: click.echo("Chatbot config saved."))
186
+ except json.JSONDecodeError as e:
187
+ ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
188
+ except Exception as e:
189
+ ctx.handle_error(e)
190
+
191
+
192
+ # ── AI Service ───────────────────────────────────────────────────────────
193
+
194
+
195
+ @ai.command("service-status")
196
+ @click.option("--project-id", "-p", default="", help="Project/org ID")
197
+ @pass_context
198
+ def ai_service_status(ctx, project_id):
199
+ """Get AI service status."""
200
+ try:
201
+ session = ctx.get_session()
202
+ pid = project_id or session.default_project_id
203
+ if not pid:
204
+ raise click.UsageError("Project ID required.")
205
+ data = ai_mod.get_ai_service_status(session, pid)
206
+ ctx.output(data, lambda d: output_json(d))
207
+ except Exception as e:
208
+ ctx.handle_error(e)
209
+
210
+
211
+ @ai.command("models")
212
+ @click.option("--project-id", "-p", default="", help="Project/org ID")
213
+ @pass_context
214
+ def ai_models(ctx, project_id):
215
+ """List available AI models."""
216
+ try:
217
+ session = ctx.get_session()
218
+ pid = project_id or session.default_project_id
219
+ if not pid:
220
+ raise click.UsageError("Project ID required.")
221
+ data = ai_mod.get_models(session, pid)
222
+ ctx.output(data, lambda d: output_json(d))
223
+ except Exception as e:
224
+ ctx.handle_error(e)
@@ -0,0 +1,308 @@
1
+ """CLI commands for application management."""
2
+
3
+ import click
4
+
5
+ from hap_cli.context import pass_context
6
+ from hap_cli.core import app as app_mod
7
+ from hap_cli.utils.formatting import output_json, output_table, output_kv
8
+
9
+
10
+ @click.group()
11
+ def app():
12
+ """Application management."""
13
+ pass
14
+
15
+
16
+ @app.command("list")
17
+ @click.option("--project-id", "-p", required=True, help="Project/org ID")
18
+ @pass_context
19
+ def app_list(ctx, project_id):
20
+ """List applications in a project."""
21
+ try:
22
+ session = ctx.get_session()
23
+ apps = app_mod.get_all_home_apps(session, project_id)
24
+ ctx.output(
25
+ apps,
26
+ lambda d: output_table(
27
+ d,
28
+ ["appId", "name", "orgName"],
29
+ ["App ID", "Name", "Org Name"],
30
+ ),
31
+ )
32
+ except Exception as e:
33
+ ctx.handle_error(e)
34
+
35
+
36
+ @app.command("list-managed")
37
+ @click.option("--project-id", "-p", required=True, help="Project/org ID")
38
+ @pass_context
39
+ def app_list_managed(ctx, project_id):
40
+ """List applications where the current user is a manager."""
41
+ try:
42
+ session = ctx.get_session()
43
+ apps = app_mod.get_managed_apps(session, project_id)
44
+ ctx.output(
45
+ apps,
46
+ lambda d: output_table(
47
+ d,
48
+ ["appId", "name", "worksheetCount"],
49
+ ["App ID", "Name", "Worksheets"],
50
+ ),
51
+ )
52
+ except Exception as e:
53
+ ctx.handle_error(e)
54
+
55
+
56
+ @app.command("info")
57
+ @click.argument("app_id", default="")
58
+ @pass_context
59
+ def app_info(ctx, app_id):
60
+ """Get application information."""
61
+ try:
62
+ session = ctx.get_session()
63
+ aid = app_id or session.default_app_id
64
+ if not aid:
65
+ raise click.UsageError("App ID required.")
66
+ data = app_mod.get_app_info(session, aid)
67
+ ctx.output(data, lambda d: output_kv(d))
68
+ except Exception as e:
69
+ ctx.handle_error(e)
70
+
71
+
72
+ @app.command("worksheets")
73
+ @click.argument("app_id", default="")
74
+ @pass_context
75
+ def app_worksheets(ctx, app_id):
76
+ """List worksheets in an application."""
77
+ try:
78
+ session = ctx.get_session()
79
+ aid = app_id or session.default_app_id
80
+ if not aid:
81
+ raise click.UsageError("App ID required.")
82
+ sheets = app_mod.get_app_worksheets(session, aid)
83
+ ctx.output(
84
+ sheets,
85
+ lambda d: output_table(
86
+ d,
87
+ ["workSheetId", "workSheetName"],
88
+ ["Worksheet ID", "Name"],
89
+ ),
90
+ )
91
+ except Exception as e:
92
+ ctx.handle_error(e)
93
+
94
+
95
+ # ── App Lifecycle ────────────────────────────────────────────────────────
96
+
97
+
98
+ @app.command("create")
99
+ @click.option("--project-id", "-p", required=True, help="Project/org ID")
100
+ @click.option("--name", "-n", required=True, help="Application name")
101
+ @click.option("--icon-color", default="", help="Icon color hex")
102
+ @pass_context
103
+ def app_create(ctx, project_id, name, icon_color):
104
+ """Create a new application."""
105
+ try:
106
+ session = ctx.get_session()
107
+ data = app_mod.create_app(session, project_id, name, icon_color=icon_color)
108
+ ctx.output(data, lambda d: click.echo(f"Created app: {d}"))
109
+ except Exception as e:
110
+ ctx.handle_error(e)
111
+
112
+
113
+ @app.command("update")
114
+ @click.argument("app_id")
115
+ @click.option("--name", "-n", default="", help="New name")
116
+ @click.option("--desc", "-d", default="", help="New description")
117
+ @click.option("--icon-color", default="", help="Icon color hex")
118
+ @pass_context
119
+ def app_update(ctx, app_id, name, desc, icon_color):
120
+ """Update application information."""
121
+ try:
122
+ session = ctx.get_session()
123
+ data = app_mod.update_app(session, app_id, name=name, description=desc, icon_color=icon_color)
124
+ ctx.output(data, lambda d: click.echo("App updated."))
125
+ except Exception as e:
126
+ ctx.handle_error(e)
127
+
128
+
129
+ @app.command("delete")
130
+ @click.argument("app_id")
131
+ @click.option("--project-id", "-p", required=True, help="Project/org ID")
132
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
133
+ @pass_context
134
+ def app_delete(ctx, app_id, project_id, yes):
135
+ """Delete an application."""
136
+ try:
137
+ if not yes:
138
+ click.confirm(f"Delete app {app_id}?", abort=True)
139
+ session = ctx.get_session()
140
+ data = app_mod.delete_app(session, app_id, project_id)
141
+ ctx.output(data, lambda d: click.echo("App deleted."))
142
+ except Exception as e:
143
+ ctx.handle_error(e)
144
+
145
+
146
+ # ── Section management ───────────────────────────────────────────────────
147
+
148
+
149
+ @app.command("add-section")
150
+ @click.argument("app_id")
151
+ @click.option("--name", "-n", default="", help="Section name")
152
+ @pass_context
153
+ def app_add_section(ctx, app_id, name):
154
+ """Add a section/group to an application."""
155
+ try:
156
+ session = ctx.get_session()
157
+ data = app_mod.add_section(session, app_id, name)
158
+ ctx.output(data, lambda d: click.echo(f"Section added: {d}"))
159
+ except Exception as e:
160
+ ctx.handle_error(e)
161
+
162
+
163
+ @app.command("edit-section")
164
+ @click.argument("app_id")
165
+ @click.argument("section_id")
166
+ @click.option("--name", "-n", required=True, help="New section name")
167
+ @pass_context
168
+ def app_edit_section(ctx, app_id, section_id, name):
169
+ """Edit a section name."""
170
+ try:
171
+ session = ctx.get_session()
172
+ data = app_mod.edit_section(session, app_id, section_id, name)
173
+ ctx.output(data, lambda d: click.echo("Section updated."))
174
+ except Exception as e:
175
+ ctx.handle_error(e)
176
+
177
+
178
+ @app.command("delete-section")
179
+ @click.argument("app_id")
180
+ @click.argument("section_id")
181
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
182
+ @pass_context
183
+ def app_delete_section(ctx, app_id, section_id, yes):
184
+ """Delete a section from an application."""
185
+ try:
186
+ if not yes:
187
+ click.confirm(f"Delete section {section_id}?", abort=True)
188
+ session = ctx.get_session()
189
+ data = app_mod.delete_section(session, app_id, section_id)
190
+ ctx.output(data, lambda d: click.echo("Section deleted."))
191
+ except Exception as e:
192
+ ctx.handle_error(e)
193
+
194
+
195
+ # ── Backup / Export ──────────────────────────────────────────────────────
196
+
197
+
198
+ @app.command("backup")
199
+ @click.argument("app_id")
200
+ @click.option("--project-id", "-p", required=True, help="Project/org ID")
201
+ @click.option("--no-data", is_flag=True, help="Backup without data")
202
+ @pass_context
203
+ def app_backup(ctx, app_id, project_id, no_data):
204
+ """Backup an application."""
205
+ try:
206
+ session = ctx.get_session()
207
+ data = app_mod.backup_app(session, app_id, project_id, contain_data=not no_data)
208
+ ctx.output(data, lambda d: click.echo(f"Backup started: {d}"))
209
+ except Exception as e:
210
+ ctx.handle_error(e)
211
+
212
+
213
+ @app.command("restore")
214
+ @click.argument("app_id")
215
+ @click.option("--project-id", "-p", required=True, help="Project/org ID")
216
+ @click.option("--backup-id", "-b", required=True, help="Backup ID")
217
+ @click.option("--file-url", required=True, help="Backup file URL")
218
+ @click.option("--file-name", required=True, help="Backup file name")
219
+ @click.option("--no-data", is_flag=True, help="Restore without data")
220
+ @click.option("--as-new", is_flag=True, help="Restore as a new app")
221
+ @pass_context
222
+ def app_restore(ctx, app_id, project_id, backup_id, file_url, file_name, no_data, as_new):
223
+ """Restore an application from backup."""
224
+ try:
225
+ session = ctx.get_session()
226
+ data = app_mod.restore_app(
227
+ session, app_id, project_id, backup_id,
228
+ file_url, file_name,
229
+ contain_data=not no_data, is_restore_new=as_new,
230
+ )
231
+ ctx.output(data, lambda d: click.echo(f"Restore started: {d}"))
232
+ except Exception as e:
233
+ ctx.handle_error(e)
234
+
235
+
236
+ @app.command("export")
237
+ @click.argument("app_ids", nargs=-1, required=True)
238
+ @pass_context
239
+ def app_export(ctx, app_ids):
240
+ """Export applications (batch)."""
241
+ try:
242
+ session = ctx.get_session()
243
+ data = app_mod.export_apps(session, list(app_ids))
244
+ ctx.output(data, lambda d: click.echo(f"Export started: {d}"))
245
+ except Exception as e:
246
+ ctx.handle_error(e)
247
+
248
+
249
+ @app.command("exports")
250
+ @click.argument("app_id")
251
+ @pass_context
252
+ def app_exports(ctx, app_id):
253
+ """List exports for an application."""
254
+ try:
255
+ session = ctx.get_session()
256
+ data = app_mod.get_exports(session, app_id)
257
+ ctx.output(data, lambda d: output_json(d))
258
+ except Exception as e:
259
+ ctx.handle_error(e)
260
+
261
+
262
+ @app.command("backup-logs")
263
+ @click.argument("app_id")
264
+ @click.option("--project-id", "-p", required=True, help="Project/org ID")
265
+ @click.option("--page-size", "-n", default=20, help="Items per page")
266
+ @click.option("--page", default=1, help="Page number")
267
+ @pass_context
268
+ def app_backup_logs(ctx, app_id, project_id, page_size, page):
269
+ """Get backup/restore operation logs."""
270
+ try:
271
+ session = ctx.get_session()
272
+ data = app_mod.get_backup_logs(session, app_id, project_id, page, page_size)
273
+ ctx.output(data, lambda d: output_json(d))
274
+ except Exception as e:
275
+ ctx.handle_error(e)
276
+
277
+
278
+ # ── Usage & Logs ─────────────────────────────────────────────────────────
279
+
280
+
281
+ @app.command("usage")
282
+ @click.option("--project-id", "-p", required=True, help="Project/org ID")
283
+ @click.option("--app-id", "-a", default="", help="Filter by app ID")
284
+ @click.option("--days", "-d", default=7, type=int, help="Day range (default 7)")
285
+ @pass_context
286
+ def app_usage(ctx, project_id, app_id, days):
287
+ """Get application usage statistics."""
288
+ try:
289
+ session = ctx.get_session()
290
+ data = app_mod.get_usage_stats(session, project_id, app_id=app_id, day_range=days)
291
+ ctx.output(data, lambda d: output_json(d))
292
+ except Exception as e:
293
+ ctx.handle_error(e)
294
+
295
+
296
+ @app.command("logs")
297
+ @click.argument("app_id")
298
+ @click.option("--page-size", "-n", default=50, help="Items per page")
299
+ @click.option("--page", default=1, help="Page number")
300
+ @pass_context
301
+ def app_logs(ctx, app_id, page_size, page):
302
+ """Get application operation logs."""
303
+ try:
304
+ session = ctx.get_session()
305
+ data = app_mod.get_app_logs(session, app_id, page, page_size)
306
+ ctx.output(data, lambda d: output_json(d))
307
+ except Exception as e:
308
+ ctx.handle_error(e)
@@ -0,0 +1,138 @@
1
+ """CLI commands for calendar/schedule management."""
2
+
3
+ import click
4
+
5
+ from hap_cli.context import pass_context
6
+ from hap_cli.core import calendar_mod as cal_mod
7
+ from hap_cli.utils.formatting import output_json, output_kv
8
+
9
+
10
+ @click.group()
11
+ def calendar():
12
+ """Calendar and schedule management."""
13
+ pass
14
+
15
+
16
+ @calendar.command("list")
17
+ @click.option("--start-date", "-s", required=True, help="Start date (YYYY-MM-DD)")
18
+ @click.option("--end-date", "-e", required=True, help="End date (YYYY-MM-DD)")
19
+ @click.option("--project-id", "-p", default="", help="Project/org ID")
20
+ @pass_context
21
+ def calendar_list(ctx, start_date, end_date, project_id):
22
+ """List calendar events in a date range."""
23
+ try:
24
+ session = ctx.get_session()
25
+ pid = project_id or session.default_project_id
26
+ data = cal_mod.get_calendars(session, start_date, end_date, project_id=pid)
27
+ ctx.output(data, lambda d: output_json(d))
28
+ except Exception as e:
29
+ ctx.handle_error(e)
30
+
31
+
32
+ @calendar.command("get")
33
+ @click.argument("calendar_id")
34
+ @pass_context
35
+ def calendar_get(ctx, calendar_id):
36
+ """Get calendar event detail."""
37
+ try:
38
+ session = ctx.get_session()
39
+ data = cal_mod.get_calendar_detail(session, calendar_id)
40
+ ctx.output(data, lambda d: output_kv(d))
41
+ except Exception as e:
42
+ ctx.handle_error(e)
43
+
44
+
45
+ @calendar.command("create")
46
+ @click.option("--name", "-n", required=True, help="Event title")
47
+ @click.option("--start-date", "-s", required=True, help="Start date/time")
48
+ @click.option("--end-date", "-e", required=True, help="End date/time")
49
+ @click.option("--desc", "-d", default="", help="Description")
50
+ @click.option("--private", is_flag=True, help="Make event private")
51
+ @pass_context
52
+ def calendar_create(ctx, name, start_date, end_date, desc, private):
53
+ """Create a new calendar event."""
54
+ try:
55
+ session = ctx.get_session()
56
+ data = cal_mod.insert_calendar(
57
+ session, name, start_date, end_date,
58
+ description=desc, is_private=private,
59
+ )
60
+ ctx.output(data, lambda d: click.echo(f"Event created: {d}"))
61
+ except Exception as e:
62
+ ctx.handle_error(e)
63
+
64
+
65
+ @calendar.command("update")
66
+ @click.argument("calendar_id")
67
+ @click.option("--name", "-n", default="", help="New title")
68
+ @click.option("--start-date", "-s", default="", help="New start date/time")
69
+ @click.option("--end-date", "-e", default="", help="New end date/time")
70
+ @click.option("--desc", "-d", default="", help="New description")
71
+ @pass_context
72
+ def calendar_update(ctx, calendar_id, name, start_date, end_date, desc):
73
+ """Update a calendar event."""
74
+ try:
75
+ session = ctx.get_session()
76
+ data = cal_mod.edit_calendar(
77
+ session, calendar_id, name=name,
78
+ start_date=start_date, end_date=end_date, description=desc,
79
+ )
80
+ ctx.output(data, lambda d: click.echo("Event updated."))
81
+ except Exception as e:
82
+ ctx.handle_error(e)
83
+
84
+
85
+ @calendar.command("delete")
86
+ @click.argument("calendar_id")
87
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
88
+ @pass_context
89
+ def calendar_delete(ctx, calendar_id, yes):
90
+ """Delete a calendar event."""
91
+ try:
92
+ if not yes:
93
+ click.confirm(f"Delete event {calendar_id}?", abort=True)
94
+ session = ctx.get_session()
95
+ data = cal_mod.delete_calendar(session, calendar_id)
96
+ ctx.output(data, lambda d: click.echo("Event deleted."))
97
+ except Exception as e:
98
+ ctx.handle_error(e)
99
+
100
+
101
+ @calendar.command("add-member")
102
+ @click.argument("calendar_id")
103
+ @click.option("--user", "-u", multiple=True, required=True, help="User account ID to invite")
104
+ @pass_context
105
+ def calendar_add_member(ctx, calendar_id, user):
106
+ """Add members to a calendar event."""
107
+ try:
108
+ session = ctx.get_session()
109
+ data = cal_mod.add_members(session, calendar_id, list(user))
110
+ ctx.output(data, lambda d: click.echo("Members added."))
111
+ except Exception as e:
112
+ ctx.handle_error(e)
113
+
114
+
115
+ @calendar.command("remove-member")
116
+ @click.argument("calendar_id")
117
+ @click.argument("account_id")
118
+ @pass_context
119
+ def calendar_remove_member(ctx, calendar_id, account_id):
120
+ """Remove a member from a calendar event."""
121
+ try:
122
+ session = ctx.get_session()
123
+ data = cal_mod.remove_member(session, calendar_id, account_id)
124
+ ctx.output(data, lambda d: click.echo("Member removed."))
125
+ except Exception as e:
126
+ ctx.handle_error(e)
127
+
128
+
129
+ @calendar.command("categories")
130
+ @pass_context
131
+ def calendar_categories(ctx):
132
+ """List calendar categories."""
133
+ try:
134
+ session = ctx.get_session()
135
+ data = cal_mod.get_categories(session)
136
+ ctx.output(data, lambda d: output_json(d))
137
+ except Exception as e:
138
+ ctx.handle_error(e)