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.
- hap_cli/README.md +194 -0
- hap_cli/README_CN.md +601 -0
- hap_cli/__init__.py +3 -0
- hap_cli/commands/__init__.py +1 -0
- hap_cli/commands/ai_cmd.py +224 -0
- hap_cli/commands/app_cmd.py +308 -0
- hap_cli/commands/calendar_cmd.py +138 -0
- hap_cli/commands/chat_cmd.py +101 -0
- hap_cli/commands/config_cmd.py +169 -0
- hap_cli/commands/contact_cmd.py +125 -0
- hap_cli/commands/department_cmd.py +168 -0
- hap_cli/commands/group_cmd.py +128 -0
- hap_cli/commands/instance_cmd.py +310 -0
- hap_cli/commands/node_cmd.py +538 -0
- hap_cli/commands/optionset_cmd.py +99 -0
- hap_cli/commands/page_cmd.py +102 -0
- hap_cli/commands/plugin_cmd.py +133 -0
- hap_cli/commands/post_cmd.py +155 -0
- hap_cli/commands/record_cmd.py +228 -0
- hap_cli/commands/role_cmd.py +221 -0
- hap_cli/commands/workflow_cmd.py +284 -0
- hap_cli/commands/worksheet_cmd.py +342 -0
- hap_cli/context.py +43 -0
- hap_cli/core/__init__.py +1 -0
- hap_cli/core/ai.py +133 -0
- hap_cli/core/app.py +307 -0
- hap_cli/core/auth.py +219 -0
- hap_cli/core/calendar_mod.py +114 -0
- hap_cli/core/chat.py +73 -0
- hap_cli/core/contact.py +85 -0
- hap_cli/core/department.py +131 -0
- hap_cli/core/flow_node.py +1001 -0
- hap_cli/core/group.py +99 -0
- hap_cli/core/instance.py +572 -0
- hap_cli/core/optionset.py +112 -0
- hap_cli/core/page.py +138 -0
- hap_cli/core/plugin.py +87 -0
- hap_cli/core/post.py +118 -0
- hap_cli/core/record.py +268 -0
- hap_cli/core/role.py +227 -0
- hap_cli/core/session.py +348 -0
- hap_cli/core/workflow.py +556 -0
- hap_cli/core/worksheet.py +403 -0
- hap_cli/hap_cli.py +105 -0
- hap_cli/skills/SKILL.md +383 -0
- hap_cli/skills/__init__.py +0 -0
- hap_cli/tests/__init__.py +1 -0
- hap_cli/tests/test_core.py +1824 -0
- hap_cli/tests/test_full_e2e.py +136 -0
- hap_cli/tests/test_integration.py +805 -0
- hap_cli/utils/__init__.py +1 -0
- hap_cli/utils/formatting.py +111 -0
- hap_cli/utils/options.py +10 -0
- hap_cli-0.5.0.dist-info/METADATA +223 -0
- hap_cli-0.5.0.dist-info/RECORD +58 -0
- hap_cli-0.5.0.dist-info/WHEEL +5 -0
- hap_cli-0.5.0.dist-info/entry_points.txt +2 -0
- hap_cli-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""CLI commands for chat and messaging management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from hap_cli.context import pass_context
|
|
8
|
+
from hap_cli.core import chat as chat_mod
|
|
9
|
+
from hap_cli.utils.formatting import output_json, output_kv
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
def chat():
|
|
14
|
+
"""Chat and messaging management."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@chat.command("list")
|
|
19
|
+
@click.option("--page-size", "-n", default=20, help="Items per page")
|
|
20
|
+
@click.option("--page", "-p", default=1, help="Page number")
|
|
21
|
+
@pass_context
|
|
22
|
+
def chat_list(ctx, page_size, page):
|
|
23
|
+
"""List recent chat sessions."""
|
|
24
|
+
try:
|
|
25
|
+
session = ctx.get_session()
|
|
26
|
+
data = chat_mod.get_chat_list(session, page_index=page, page_size=page_size)
|
|
27
|
+
ctx.output(data, lambda d: output_json(d))
|
|
28
|
+
except Exception as e:
|
|
29
|
+
ctx.handle_error(e)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@chat.command("send")
|
|
33
|
+
@click.option("--to", "-t", multiple=True, required=True, help="Target user account ID (can repeat)")
|
|
34
|
+
@click.option("--message", "-m", required=True, help="Message content")
|
|
35
|
+
@pass_context
|
|
36
|
+
def chat_send(ctx, to, message):
|
|
37
|
+
"""Send a message to one or more users."""
|
|
38
|
+
try:
|
|
39
|
+
session = ctx.get_session()
|
|
40
|
+
data = chat_mod.send_message(session, list(to), message)
|
|
41
|
+
ctx.output(data, lambda d: click.echo("Message sent."))
|
|
42
|
+
except Exception as e:
|
|
43
|
+
ctx.handle_error(e)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@chat.command("send-file")
|
|
47
|
+
@click.argument("chat_id")
|
|
48
|
+
@click.option("--file-url", required=True, help="File URL to send")
|
|
49
|
+
@pass_context
|
|
50
|
+
def chat_send_file(ctx, chat_id, file_url):
|
|
51
|
+
"""Send a file to a chat."""
|
|
52
|
+
try:
|
|
53
|
+
session = ctx.get_session()
|
|
54
|
+
data = chat_mod.send_file(session, chat_id, file_url)
|
|
55
|
+
ctx.output(data, lambda d: click.echo("File sent."))
|
|
56
|
+
except Exception as e:
|
|
57
|
+
ctx.handle_error(e)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@chat.command("send-card")
|
|
61
|
+
@click.argument("chat_id")
|
|
62
|
+
@click.option("--data", "card_data", required=True, help="Card data as JSON string")
|
|
63
|
+
@pass_context
|
|
64
|
+
def chat_send_card(ctx, chat_id, card_data):
|
|
65
|
+
"""Send a card to a chat."""
|
|
66
|
+
try:
|
|
67
|
+
session = ctx.get_session()
|
|
68
|
+
data = chat_mod.send_card(session, chat_id, json.loads(card_data))
|
|
69
|
+
ctx.output(data, lambda d: click.echo("Card sent."))
|
|
70
|
+
except json.JSONDecodeError as e:
|
|
71
|
+
ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
|
|
72
|
+
except Exception as e:
|
|
73
|
+
ctx.handle_error(e)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@chat.command("group-info")
|
|
77
|
+
@click.argument("group_id")
|
|
78
|
+
@pass_context
|
|
79
|
+
def chat_group_info(ctx, group_id):
|
|
80
|
+
"""Get chat group information."""
|
|
81
|
+
try:
|
|
82
|
+
session = ctx.get_session()
|
|
83
|
+
data = chat_mod.get_group_info(session, group_id)
|
|
84
|
+
ctx.output(data, lambda d: output_kv(d))
|
|
85
|
+
except Exception as e:
|
|
86
|
+
ctx.handle_error(e)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@chat.command("group-files")
|
|
90
|
+
@click.argument("group_id")
|
|
91
|
+
@click.option("--page-size", "-n", default=20, help="Items per page")
|
|
92
|
+
@click.option("--page", "-p", default=1, help="Page number")
|
|
93
|
+
@pass_context
|
|
94
|
+
def chat_group_files(ctx, group_id, page_size, page):
|
|
95
|
+
"""List files in a chat group."""
|
|
96
|
+
try:
|
|
97
|
+
session = ctx.get_session()
|
|
98
|
+
data = chat_mod.get_group_files(session, group_id, page_index=page, page_size=page_size)
|
|
99
|
+
ctx.output(data, lambda d: output_json(d))
|
|
100
|
+
except Exception as e:
|
|
101
|
+
ctx.handle_error(e)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""CLI commands for server configuration and authentication."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from hap_cli.context import pass_context
|
|
6
|
+
from hap_cli.core.session import Session, SessionError
|
|
7
|
+
from hap_cli.core import auth as auth_mod
|
|
8
|
+
from hap_cli.utils.formatting import output_json, output_kv
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
SERVER_HELP = (
|
|
12
|
+
"Server: 'mingdao', 'nocoly', or self-hosted URL "
|
|
13
|
+
"(e.g. https://hap.example.com)"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
def config():
|
|
19
|
+
"""Manage server connection and authentication."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@config.command("set")
|
|
24
|
+
@click.option("--server", "-s", required=True, help="MingDAO server URL")
|
|
25
|
+
@click.option("--token", "-t", required=True, help="Authentication token (md_pss_id)")
|
|
26
|
+
@click.option("--app-id", "-a", default="", help="Default application ID")
|
|
27
|
+
@click.option("--project-id", "-p", default="", help="Default project/org ID")
|
|
28
|
+
@pass_context
|
|
29
|
+
def config_set(ctx, server, token, app_id, project_id):
|
|
30
|
+
"""Set server URL and authentication token."""
|
|
31
|
+
session = Session(server_url=server, auth_token=token)
|
|
32
|
+
session.default_app_id = app_id
|
|
33
|
+
session.default_project_id = project_id
|
|
34
|
+
session.save()
|
|
35
|
+
ctx.output(
|
|
36
|
+
{"status": "ok", "server": server},
|
|
37
|
+
lambda d: click.echo(f"Config saved. Server: {server}"),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@config.command("show")
|
|
42
|
+
@pass_context
|
|
43
|
+
def config_show(ctx):
|
|
44
|
+
"""Show current configuration."""
|
|
45
|
+
session = ctx.get_session()
|
|
46
|
+
data = {
|
|
47
|
+
"server_url": session.server_url,
|
|
48
|
+
"auth_token": session.auth_token[:8] + "..." if session.auth_token else "",
|
|
49
|
+
"default_app_id": session.default_app_id,
|
|
50
|
+
"default_project_id": session.default_project_id,
|
|
51
|
+
"configured": session.is_configured(),
|
|
52
|
+
}
|
|
53
|
+
ctx.output(
|
|
54
|
+
data,
|
|
55
|
+
lambda d: output_kv(d, labels={
|
|
56
|
+
"server_url": "Server URL",
|
|
57
|
+
"auth_token": "Auth Token",
|
|
58
|
+
"default_app_id": "Default App ID",
|
|
59
|
+
"default_project_id": "Default Project ID",
|
|
60
|
+
"configured": "Configured",
|
|
61
|
+
}),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@config.command("login")
|
|
66
|
+
@click.argument("server", default="mingdao")
|
|
67
|
+
@click.option("--timeout", "-t", default=300, help="Login timeout in seconds")
|
|
68
|
+
@pass_context
|
|
69
|
+
def config_login(ctx, server, timeout):
|
|
70
|
+
"""Login via browser. Opens HAP login page and captures token.
|
|
71
|
+
|
|
72
|
+
SERVER can be 'mingdao', 'nocoly', or a self-hosted URL.
|
|
73
|
+
Default is 'mingdao'.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
api_host, webui = auth_mod.resolve_server(server)
|
|
77
|
+
port = auth_mod.get_available_port()
|
|
78
|
+
auth_url = auth_mod.build_auth_url(webui, port)
|
|
79
|
+
|
|
80
|
+
if not ctx.json_mode:
|
|
81
|
+
click.echo(f"Opening browser for login...")
|
|
82
|
+
click.echo(f" Server: {webui}")
|
|
83
|
+
click.echo(f" If browser doesn't open, visit:")
|
|
84
|
+
click.echo(f" {auth_url}")
|
|
85
|
+
click.echo(f" Waiting for login (timeout: {timeout}s)...")
|
|
86
|
+
|
|
87
|
+
token, api_host, user_info = auth_mod.login(server, timeout=timeout)
|
|
88
|
+
|
|
89
|
+
# Save to config
|
|
90
|
+
session = ctx.get_session()
|
|
91
|
+
session.server_url = api_host
|
|
92
|
+
session.auth_token = token
|
|
93
|
+
session.save()
|
|
94
|
+
|
|
95
|
+
data = {
|
|
96
|
+
"status": "ok",
|
|
97
|
+
"server_url": api_host,
|
|
98
|
+
"user": user_info,
|
|
99
|
+
}
|
|
100
|
+
ctx.output(
|
|
101
|
+
data,
|
|
102
|
+
lambda d: click.echo(
|
|
103
|
+
f"Logged in as: {user_info.get('name', '')} "
|
|
104
|
+
f"({user_info.get('email', '')})\n"
|
|
105
|
+
f"Server: {api_host}"
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
except TimeoutError:
|
|
109
|
+
ctx.handle_error(SessionError("Login timed out. Please try again."))
|
|
110
|
+
except ImportError as e:
|
|
111
|
+
ctx.handle_error(SessionError(str(e)))
|
|
112
|
+
except Exception as e:
|
|
113
|
+
ctx.handle_error(e)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@config.command("logout")
|
|
117
|
+
@pass_context
|
|
118
|
+
def config_logout(ctx):
|
|
119
|
+
"""Clear saved authentication token."""
|
|
120
|
+
session = ctx.get_session()
|
|
121
|
+
old_server = session.server_url
|
|
122
|
+
session.auth_token = ""
|
|
123
|
+
session.save()
|
|
124
|
+
ctx.output(
|
|
125
|
+
{"status": "ok", "server_url": old_server},
|
|
126
|
+
lambda d: click.echo(f"Logged out. Token cleared."),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@config.command("orgs")
|
|
131
|
+
@pass_context
|
|
132
|
+
def config_orgs(ctx):
|
|
133
|
+
"""List all organizations the current user belongs to."""
|
|
134
|
+
try:
|
|
135
|
+
session = ctx.get_session()
|
|
136
|
+
data = session.api_call("Account", "GetProjectList", {})
|
|
137
|
+
def _table(d):
|
|
138
|
+
projects = d if isinstance(d, list) else d.get("list", [d])
|
|
139
|
+
for p in projects:
|
|
140
|
+
pid = p.get("projectId", "")
|
|
141
|
+
name = p.get("companyName", p.get("name", ""))
|
|
142
|
+
role = p.get("roleName", "")
|
|
143
|
+
click.echo(f"{pid} {name} {role}")
|
|
144
|
+
ctx.output(data, _table)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
ctx.handle_error(e)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@config.command("whoami")
|
|
150
|
+
@pass_context
|
|
151
|
+
def config_whoami(ctx):
|
|
152
|
+
"""Show current logged-in user info."""
|
|
153
|
+
try:
|
|
154
|
+
session = ctx.get_session()
|
|
155
|
+
if not session.is_configured():
|
|
156
|
+
raise SessionError("Not logged in. Run 'config login' first.")
|
|
157
|
+
|
|
158
|
+
user_info = auth_mod.get_user_info(session.server_url, session.auth_token)
|
|
159
|
+
ctx.output(
|
|
160
|
+
user_info,
|
|
161
|
+
lambda d: output_kv(d, labels={
|
|
162
|
+
"id": "Account ID",
|
|
163
|
+
"name": "Name",
|
|
164
|
+
"email": "Email",
|
|
165
|
+
"lang": "Language",
|
|
166
|
+
}),
|
|
167
|
+
)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
ctx.handle_error(e)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""CLI commands for address book and contact management."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from hap_cli.context import pass_context
|
|
6
|
+
from hap_cli.core import contact as contact_mod
|
|
7
|
+
from hap_cli.utils.formatting import output_json, output_table, output_kv
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def contact():
|
|
12
|
+
"""Address book and contact management."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@contact.command("search")
|
|
17
|
+
@click.argument("keywords")
|
|
18
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
19
|
+
@pass_context
|
|
20
|
+
def contact_search(ctx, keywords, project_id):
|
|
21
|
+
"""Search contacts and departments."""
|
|
22
|
+
try:
|
|
23
|
+
session = ctx.get_session()
|
|
24
|
+
pid = project_id or session.default_project_id
|
|
25
|
+
data = contact_mod.search_contacts(session, keywords, project_id=pid)
|
|
26
|
+
ctx.output(data, lambda d: output_json(d))
|
|
27
|
+
except Exception as e:
|
|
28
|
+
ctx.handle_error(e)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@contact.command("get")
|
|
32
|
+
@click.argument("account_id")
|
|
33
|
+
@pass_context
|
|
34
|
+
def contact_get(ctx, account_id):
|
|
35
|
+
"""Get user account details."""
|
|
36
|
+
try:
|
|
37
|
+
session = ctx.get_session()
|
|
38
|
+
data = contact_mod.get_account_detail(session, account_id)
|
|
39
|
+
ctx.output(data, lambda d: output_kv(d))
|
|
40
|
+
except Exception as e:
|
|
41
|
+
ctx.handle_error(e)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@contact.command("friends")
|
|
45
|
+
@click.option("--page-size", "-n", default=20, help="Items per page")
|
|
46
|
+
@click.option("--page", "-p", default=1, help="Page number")
|
|
47
|
+
@pass_context
|
|
48
|
+
def contact_friends(ctx, page_size, page):
|
|
49
|
+
"""List friends/contacts in address book."""
|
|
50
|
+
try:
|
|
51
|
+
session = ctx.get_session()
|
|
52
|
+
data = contact_mod.get_friends(session, page_index=page, page_size=page_size)
|
|
53
|
+
ctx.output(data, lambda d: output_json(d))
|
|
54
|
+
except Exception as e:
|
|
55
|
+
ctx.handle_error(e)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@contact.command("add-friend")
|
|
59
|
+
@click.argument("account_id")
|
|
60
|
+
@click.option("--message", "-m", default="", help="Request message")
|
|
61
|
+
@pass_context
|
|
62
|
+
def contact_add_friend(ctx, account_id, message):
|
|
63
|
+
"""Send a friend request."""
|
|
64
|
+
try:
|
|
65
|
+
session = ctx.get_session()
|
|
66
|
+
data = contact_mod.add_friend(session, account_id, message=message)
|
|
67
|
+
ctx.output(data, lambda d: click.echo("Friend request sent."))
|
|
68
|
+
except Exception as e:
|
|
69
|
+
ctx.handle_error(e)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@contact.command("remove-friend")
|
|
73
|
+
@click.argument("account_id")
|
|
74
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
75
|
+
@pass_context
|
|
76
|
+
def contact_remove_friend(ctx, account_id, yes):
|
|
77
|
+
"""Remove a friend from address book."""
|
|
78
|
+
try:
|
|
79
|
+
if not yes:
|
|
80
|
+
click.confirm(f"Remove friend {account_id}?", abort=True)
|
|
81
|
+
session = ctx.get_session()
|
|
82
|
+
data = contact_mod.remove_friend(session, account_id)
|
|
83
|
+
ctx.output(data, lambda d: click.echo("Friend removed."))
|
|
84
|
+
except Exception as e:
|
|
85
|
+
ctx.handle_error(e)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@contact.command("friend-requests")
|
|
89
|
+
@click.option("--page-size", "-n", default=20, help="Items per page")
|
|
90
|
+
@click.option("--page", "-p", default=1, help="Page number")
|
|
91
|
+
@pass_context
|
|
92
|
+
def contact_friend_requests(ctx, page_size, page):
|
|
93
|
+
"""List pending friend requests."""
|
|
94
|
+
try:
|
|
95
|
+
session = ctx.get_session()
|
|
96
|
+
data = contact_mod.get_friend_requests(session, page_index=page, page_size=page_size)
|
|
97
|
+
ctx.output(data, lambda d: output_json(d))
|
|
98
|
+
except Exception as e:
|
|
99
|
+
ctx.handle_error(e)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@contact.command("accept-friend")
|
|
103
|
+
@click.argument("account_id")
|
|
104
|
+
@pass_context
|
|
105
|
+
def contact_accept_friend(ctx, account_id):
|
|
106
|
+
"""Accept a friend request."""
|
|
107
|
+
try:
|
|
108
|
+
session = ctx.get_session()
|
|
109
|
+
data = contact_mod.accept_friend(session, account_id)
|
|
110
|
+
ctx.output(data, lambda d: click.echo("Friend request accepted."))
|
|
111
|
+
except Exception as e:
|
|
112
|
+
ctx.handle_error(e)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@contact.command("reject-friend")
|
|
116
|
+
@click.argument("account_id")
|
|
117
|
+
@pass_context
|
|
118
|
+
def contact_reject_friend(ctx, account_id):
|
|
119
|
+
"""Reject a friend request."""
|
|
120
|
+
try:
|
|
121
|
+
session = ctx.get_session()
|
|
122
|
+
data = contact_mod.reject_friend(session, account_id)
|
|
123
|
+
ctx.output(data, lambda d: click.echo("Friend request rejected."))
|
|
124
|
+
except Exception as e:
|
|
125
|
+
ctx.handle_error(e)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""CLI commands for department management."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from hap_cli.context import pass_context
|
|
6
|
+
from hap_cli.core import department as dept_mod
|
|
7
|
+
from hap_cli.utils.formatting import output_json, output_table, output_kv
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def department():
|
|
12
|
+
"""Department management."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@department.command("list")
|
|
17
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
18
|
+
@click.option("--parent-id", default="", help="Parent department ID")
|
|
19
|
+
@click.option("--page-size", "-n", default=50, help="Items per page")
|
|
20
|
+
@click.option("--page", default=1, help="Page number")
|
|
21
|
+
@pass_context
|
|
22
|
+
def department_list(ctx, project_id, parent_id, page_size, page):
|
|
23
|
+
"""List sub-departments."""
|
|
24
|
+
try:
|
|
25
|
+
session = ctx.get_session()
|
|
26
|
+
pid = project_id or session.default_project_id
|
|
27
|
+
if not pid:
|
|
28
|
+
raise click.UsageError("Project ID required.")
|
|
29
|
+
data = dept_mod.list_departments(session, pid, parent_id=parent_id, page_index=page, page_size=page_size)
|
|
30
|
+
ctx.output(data, lambda d: output_json(d))
|
|
31
|
+
except Exception as e:
|
|
32
|
+
ctx.handle_error(e)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@department.command("tree")
|
|
36
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
37
|
+
@click.option("--department-id", "-d", default="", help="Root department ID")
|
|
38
|
+
@pass_context
|
|
39
|
+
def department_tree(ctx, project_id, department_id):
|
|
40
|
+
"""Get the full department tree."""
|
|
41
|
+
try:
|
|
42
|
+
session = ctx.get_session()
|
|
43
|
+
pid = project_id or session.default_project_id
|
|
44
|
+
if not pid:
|
|
45
|
+
raise click.UsageError("Project ID required.")
|
|
46
|
+
data = dept_mod.get_department_tree(session, pid, department_id=department_id)
|
|
47
|
+
ctx.output(data, lambda d: output_json(d))
|
|
48
|
+
except Exception as e:
|
|
49
|
+
ctx.handle_error(e)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@department.command("info")
|
|
53
|
+
@click.argument("department_id")
|
|
54
|
+
@pass_context
|
|
55
|
+
def department_info(ctx, department_id):
|
|
56
|
+
"""Get department details."""
|
|
57
|
+
try:
|
|
58
|
+
session = ctx.get_session()
|
|
59
|
+
data = dept_mod.get_department_info(session, department_id)
|
|
60
|
+
ctx.output(data, lambda d: output_kv(d))
|
|
61
|
+
except Exception as e:
|
|
62
|
+
ctx.handle_error(e)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@department.command("create")
|
|
66
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
67
|
+
@click.option("--name", "-n", required=True, help="Department name")
|
|
68
|
+
@click.option("--parent-id", default="", help="Parent department ID")
|
|
69
|
+
@pass_context
|
|
70
|
+
def department_create(ctx, project_id, name, parent_id):
|
|
71
|
+
"""Create a new department."""
|
|
72
|
+
try:
|
|
73
|
+
session = ctx.get_session()
|
|
74
|
+
pid = project_id or session.default_project_id
|
|
75
|
+
if not pid:
|
|
76
|
+
raise click.UsageError("Project ID required.")
|
|
77
|
+
data = dept_mod.create_department(session, pid, name, parent_id=parent_id)
|
|
78
|
+
ctx.output(data, lambda d: click.echo(f"Department created: {d}"))
|
|
79
|
+
except Exception as e:
|
|
80
|
+
ctx.handle_error(e)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@department.command("update")
|
|
84
|
+
@click.argument("department_id")
|
|
85
|
+
@click.option("--name", "-n", required=True, help="New department name")
|
|
86
|
+
@pass_context
|
|
87
|
+
def department_update(ctx, department_id, name):
|
|
88
|
+
"""Update department name."""
|
|
89
|
+
try:
|
|
90
|
+
session = ctx.get_session()
|
|
91
|
+
data = dept_mod.update_department(session, department_id, name)
|
|
92
|
+
ctx.output(data, lambda d: click.echo("Department updated."))
|
|
93
|
+
except Exception as e:
|
|
94
|
+
ctx.handle_error(e)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@department.command("delete")
|
|
98
|
+
@click.argument("department_ids", nargs=-1, required=True)
|
|
99
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
100
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
101
|
+
@pass_context
|
|
102
|
+
def department_delete(ctx, department_ids, project_id, yes):
|
|
103
|
+
"""Delete one or more departments."""
|
|
104
|
+
try:
|
|
105
|
+
if not yes:
|
|
106
|
+
click.confirm(f"Delete {len(department_ids)} department(s)?", abort=True)
|
|
107
|
+
session = ctx.get_session()
|
|
108
|
+
pid = project_id or session.default_project_id
|
|
109
|
+
if not pid:
|
|
110
|
+
raise click.UsageError("Project ID required.")
|
|
111
|
+
data = dept_mod.delete_departments(session, pid, list(department_ids))
|
|
112
|
+
ctx.output(data, lambda d: click.echo(f"Deleted {len(department_ids)} department(s)."))
|
|
113
|
+
except Exception as e:
|
|
114
|
+
ctx.handle_error(e)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@department.command("members")
|
|
118
|
+
@click.argument("department_id")
|
|
119
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
120
|
+
@click.option("--page-size", "-n", default=50, help="Items per page")
|
|
121
|
+
@click.option("--page", default=1, help="Page number")
|
|
122
|
+
@pass_context
|
|
123
|
+
def department_members(ctx, department_id, project_id, page_size, page):
|
|
124
|
+
"""Get members of a department."""
|
|
125
|
+
try:
|
|
126
|
+
session = ctx.get_session()
|
|
127
|
+
pid = project_id or session.default_project_id
|
|
128
|
+
if not pid:
|
|
129
|
+
raise click.UsageError("Project ID required.")
|
|
130
|
+
data = dept_mod.get_department_users(session, pid, department_id, page_index=page, page_size=page_size)
|
|
131
|
+
ctx.output(data, lambda d: output_json(d))
|
|
132
|
+
except Exception as e:
|
|
133
|
+
ctx.handle_error(e)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@department.command("search")
|
|
137
|
+
@click.argument("keywords")
|
|
138
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
139
|
+
@pass_context
|
|
140
|
+
def department_search(ctx, keywords, project_id):
|
|
141
|
+
"""Search departments and users."""
|
|
142
|
+
try:
|
|
143
|
+
session = ctx.get_session()
|
|
144
|
+
pid = project_id or session.default_project_id
|
|
145
|
+
if not pid:
|
|
146
|
+
raise click.UsageError("Project ID required.")
|
|
147
|
+
data = dept_mod.search_dept_and_users(session, pid, keywords)
|
|
148
|
+
ctx.output(data, lambda d: output_json(d))
|
|
149
|
+
except Exception as e:
|
|
150
|
+
ctx.handle_error(e)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@department.command("move")
|
|
154
|
+
@click.argument("department_id")
|
|
155
|
+
@click.argument("target_dept_id")
|
|
156
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
157
|
+
@pass_context
|
|
158
|
+
def department_move(ctx, department_id, target_dept_id, project_id):
|
|
159
|
+
"""Move a department under a new parent."""
|
|
160
|
+
try:
|
|
161
|
+
session = ctx.get_session()
|
|
162
|
+
pid = project_id or session.default_project_id
|
|
163
|
+
if not pid:
|
|
164
|
+
raise click.UsageError("Project ID required.")
|
|
165
|
+
data = dept_mod.move_department(session, pid, department_id, target_dept_id)
|
|
166
|
+
ctx.output(data, lambda d: click.echo("Department moved."))
|
|
167
|
+
except Exception as e:
|
|
168
|
+
ctx.handle_error(e)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""CLI commands for group management."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from hap_cli.context import pass_context
|
|
6
|
+
from hap_cli.core import group as group_mod
|
|
7
|
+
from hap_cli.utils.formatting import output_json, output_kv
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def group():
|
|
12
|
+
"""Group management."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@group.command("list")
|
|
17
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
18
|
+
@click.option("--page-size", "-n", default=50, help="Items per page")
|
|
19
|
+
@click.option("--page", default=1, help="Page number")
|
|
20
|
+
@pass_context
|
|
21
|
+
def group_list(ctx, project_id, page_size, page):
|
|
22
|
+
"""List groups."""
|
|
23
|
+
try:
|
|
24
|
+
session = ctx.get_session()
|
|
25
|
+
pid = project_id or session.default_project_id
|
|
26
|
+
if not pid:
|
|
27
|
+
raise click.UsageError("Project ID required.")
|
|
28
|
+
data = group_mod.get_groups(session, pid, page_index=page, page_size=page_size)
|
|
29
|
+
ctx.output(data, lambda d: output_json(d))
|
|
30
|
+
except Exception as e:
|
|
31
|
+
ctx.handle_error(e)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@group.command("info")
|
|
35
|
+
@click.argument("group_id")
|
|
36
|
+
@pass_context
|
|
37
|
+
def group_info(ctx, group_id):
|
|
38
|
+
"""Get group information."""
|
|
39
|
+
try:
|
|
40
|
+
session = ctx.get_session()
|
|
41
|
+
data = group_mod.get_group_info(session, group_id)
|
|
42
|
+
ctx.output(data, lambda d: output_kv(d))
|
|
43
|
+
except Exception as e:
|
|
44
|
+
ctx.handle_error(e)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@group.command("create")
|
|
48
|
+
@click.option("--project-id", "-p", default="", help="Project/org ID")
|
|
49
|
+
@click.option("--name", "-n", required=True, help="Group name")
|
|
50
|
+
@click.option("--about", "-a", default="", help="Group announcement")
|
|
51
|
+
@click.option("--member", "-m", multiple=True, help="Initial member account IDs")
|
|
52
|
+
@pass_context
|
|
53
|
+
def group_create(ctx, project_id, name, about, member):
|
|
54
|
+
"""Create a new group."""
|
|
55
|
+
try:
|
|
56
|
+
session = ctx.get_session()
|
|
57
|
+
pid = project_id or session.default_project_id
|
|
58
|
+
if not pid:
|
|
59
|
+
raise click.UsageError("Project ID required.")
|
|
60
|
+
data = group_mod.add_group(session, pid, name, about=about, member_ids=list(member) or None)
|
|
61
|
+
ctx.output(data, lambda d: click.echo(f"Group created: {d}"))
|
|
62
|
+
except Exception as e:
|
|
63
|
+
ctx.handle_error(e)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@group.command("delete")
|
|
67
|
+
@click.argument("group_id")
|
|
68
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
69
|
+
@pass_context
|
|
70
|
+
def group_delete(ctx, group_id, yes):
|
|
71
|
+
"""Delete/disband a group."""
|
|
72
|
+
try:
|
|
73
|
+
if not yes:
|
|
74
|
+
click.confirm(f"Delete group {group_id}?", abort=True)
|
|
75
|
+
session = ctx.get_session()
|
|
76
|
+
data = group_mod.remove_group(session, group_id)
|
|
77
|
+
ctx.output(data, lambda d: click.echo("Group deleted."))
|
|
78
|
+
except Exception as e:
|
|
79
|
+
ctx.handle_error(e)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@group.command("members")
|
|
83
|
+
@click.argument("group_id")
|
|
84
|
+
@click.option("--page-size", "-n", default=50, help="Items per page")
|
|
85
|
+
@click.option("--page", default=1, help="Page number")
|
|
86
|
+
@pass_context
|
|
87
|
+
def group_members(ctx, group_id, page_size, page):
|
|
88
|
+
"""List group members."""
|
|
89
|
+
try:
|
|
90
|
+
session = ctx.get_session()
|
|
91
|
+
data = group_mod.get_group_users(session, group_id, page_index=page, page_size=page_size)
|
|
92
|
+
ctx.output(data, lambda d: output_json(d))
|
|
93
|
+
except Exception as e:
|
|
94
|
+
ctx.handle_error(e)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@group.command("remove-member")
|
|
98
|
+
@click.argument("group_id")
|
|
99
|
+
@click.argument("account_id")
|
|
100
|
+
@pass_context
|
|
101
|
+
def group_remove_member(ctx, group_id, account_id):
|
|
102
|
+
"""Remove a member from a group."""
|
|
103
|
+
try:
|
|
104
|
+
session = ctx.get_session()
|
|
105
|
+
data = group_mod.remove_user(session, group_id, account_id)
|
|
106
|
+
ctx.output(data, lambda d: click.echo("Member removed."))
|
|
107
|
+
except Exception as e:
|
|
108
|
+
ctx.handle_error(e)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@group.command("update")
|
|
112
|
+
@click.argument("group_id")
|
|
113
|
+
@click.option("--name", "-n", default="", help="New group name")
|
|
114
|
+
@click.option("--about", "-a", default="", help="New group announcement")
|
|
115
|
+
@pass_context
|
|
116
|
+
def group_update(ctx, group_id, name, about):
|
|
117
|
+
"""Update group name and/or announcement."""
|
|
118
|
+
try:
|
|
119
|
+
session = ctx.get_session()
|
|
120
|
+
if name:
|
|
121
|
+
data = group_mod.update_group_name(session, group_id, name)
|
|
122
|
+
if about:
|
|
123
|
+
data = group_mod.update_group_about(session, group_id, about)
|
|
124
|
+
if not name and not about:
|
|
125
|
+
raise click.UsageError("Provide --name or --about to update.")
|
|
126
|
+
ctx.output(data, lambda d: click.echo("Group updated."))
|
|
127
|
+
except Exception as e:
|
|
128
|
+
ctx.handle_error(e)
|