netizen-cli 0.10.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 (112) hide show
  1. netizen_cli/__init__.py +3 -0
  2. netizen_cli/__main__.py +4 -0
  3. netizen_cli/admin/__init__.py +1 -0
  4. netizen_cli/admin/auth.py +928 -0
  5. netizen_cli/admin/errors.py +9 -0
  6. netizen_cli/admin/port_config.py +115 -0
  7. netizen_cli/admin/presentation.py +257 -0
  8. netizen_cli/admin/queries.py +337 -0
  9. netizen_cli/admin/static/admin.css +260 -0
  10. netizen_cli/admin/static/admin.js +2898 -0
  11. netizen_cli/admin/static/index.html +327 -0
  12. netizen_cli/admin/transport.py +935 -0
  13. netizen_cli/admin/web.py +2717 -0
  14. netizen_cli/bindings.py +3215 -0
  15. netizen_cli/builtin_skills.py +93 -0
  16. netizen_cli/cards/__init__.py +105 -0
  17. netizen_cli/cards/callbacks.py +565 -0
  18. netizen_cli/cards/controls.py +2273 -0
  19. netizen_cli/cards/defaults.py +213 -0
  20. netizen_cli/cards/model_info.py +80 -0
  21. netizen_cli/cards/questions.py +220 -0
  22. netizen_cli/cards/reply.py +2247 -0
  23. netizen_cli/cards/scheduled.py +836 -0
  24. netizen_cli/channel/__init__.py +1 -0
  25. netizen_cli/channel/completion_mentions.py +60 -0
  26. netizen_cli/channel/input_preparation.py +644 -0
  27. netizen_cli/channel/messages.py +57 -0
  28. netizen_cli/channel/ports.py +52 -0
  29. netizen_cli/channel/question_inputs.py +51 -0
  30. netizen_cli/channel/reactions.py +293 -0
  31. netizen_cli/channel/reply_presenter.py +1505 -0
  32. netizen_cli/channel/topics.py +70 -0
  33. netizen_cli/channel_app.py +6593 -0
  34. netizen_cli/cli.py +287 -0
  35. netizen_cli/cli_data.py +536 -0
  36. netizen_cli/cli_packages.py +526 -0
  37. netizen_cli/cli_services.py +651 -0
  38. netizen_cli/cli_setup.py +242 -0
  39. netizen_cli/cli_update.py +303 -0
  40. netizen_cli/cli_update_restore.py +53 -0
  41. netizen_cli/cli_update_worker.py +333 -0
  42. netizen_cli/codex_runtime.py +7125 -0
  43. netizen_cli/completion_mention.py +16 -0
  44. netizen_cli/database_migrations.py +218 -0
  45. netizen_cli/defaults/__init__.py +5 -0
  46. netizen_cli/defaults/models.py +39 -0
  47. netizen_cli/defaults/service.py +232 -0
  48. netizen_cli/defaults/store.py +260 -0
  49. netizen_cli/deployment/__init__.py +1 -0
  50. netizen_cli/deployment/restart_worker.py +134 -0
  51. netizen_cli/deployment/update_executor.py +258 -0
  52. netizen_cli/deployment/update_protocol.py +281 -0
  53. netizen_cli/domain.py +416 -0
  54. netizen_cli/error_messages.py +124 -0
  55. netizen_cli/experience.py +531 -0
  56. netizen_cli/feishu_app_onboarding.py +187 -0
  57. netizen_cli/feishu_app_permissions.py +123 -0
  58. netizen_cli/git_status.py +63 -0
  59. netizen_cli/image_inputs.py +579 -0
  60. netizen_cli/instance.py +84 -0
  61. netizen_cli/lark_app.py +125 -0
  62. netizen_cli/main.py +903 -0
  63. netizen_cli/management/__init__.py +83 -0
  64. netizen_cli/management/blocking_io.py +352 -0
  65. netizen_cli/management/chat_labels.py +266 -0
  66. netizen_cli/management/coordination.py +32 -0
  67. netizen_cli/management/service.py +2187 -0
  68. netizen_cli/management/updates.py +214 -0
  69. netizen_cli/markdown_images.py +78 -0
  70. netizen_cli/message_content.py +786 -0
  71. netizen_cli/message_history.py +643 -0
  72. netizen_cli/message_preparation.py +60 -0
  73. netizen_cli/message_projection.py +923 -0
  74. netizen_cli/migrations/__init__.py +1 -0
  75. netizen_cli/migrations/schema.py +103 -0
  76. netizen_cli/migrations/v14.py +438 -0
  77. netizen_cli/model_settings.py +269 -0
  78. netizen_cli/package_resources.py +22 -0
  79. netizen_cli/projects.py +327 -0
  80. netizen_cli/prompt_projection.py +327 -0
  81. netizen_cli/quoted_context.py +312 -0
  82. netizen_cli/resources/config.example.yaml +35 -0
  83. netizen_cli/resources/skills/netizen-lark/SKILL.md +64 -0
  84. netizen_cli/resources/skills/netizen-user-guide/SKILL.md +37 -0
  85. netizen_cli/resources/skills/netizen-user-guide/references/user-guide.md +842 -0
  86. netizen_cli/result_images.py +123 -0
  87. netizen_cli/runtime/__init__.py +1 -0
  88. netizen_cli/runtime/contracts.py +792 -0
  89. netizen_cli/runtime/name_writes.py +67 -0
  90. netizen_cli/runtime/thread_naming.py +451 -0
  91. netizen_cli/schedules/__init__.py +1 -0
  92. netizen_cli/schedules/mcp.py +535 -0
  93. netizen_cli/schedules/models.py +394 -0
  94. netizen_cli/schedules/scheduler.py +374 -0
  95. netizen_cli/schedules/service.py +766 -0
  96. netizen_cli/schedules/store.py +771 -0
  97. netizen_cli/sdk_gap_adapter.py +1151 -0
  98. netizen_cli/service_launcher.py +583 -0
  99. netizen_cli/session_settings.py +126 -0
  100. netizen_cli/settings.py +216 -0
  101. netizen_cli/skill_references.py +40 -0
  102. netizen_cli/terminal_cleanup.py +155 -0
  103. netizen_cli/turn_activity.py +688 -0
  104. netizen_cli/turn_files.py +812 -0
  105. netizen_cli/turn_patch_children.py +254 -0
  106. netizen_cli/turn_plan_observer.py +315 -0
  107. netizen_cli/user_questions.py +106 -0
  108. netizen_cli-0.10.0.dist-info/METADATA +18 -0
  109. netizen_cli-0.10.0.dist-info/RECORD +112 -0
  110. netizen_cli-0.10.0.dist-info/WHEEL +5 -0
  111. netizen_cli-0.10.0.dist-info/entry_points.txt +2 -0
  112. netizen_cli-0.10.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,125 @@
1
+ """The instance's protected Lark application credentials, in CLI profile format.
2
+
3
+ This module uses only the standard library so installation and the service share
4
+ the same file contract without depending on the optional Lark CLI.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import re
12
+ import stat
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+
18
+ LARK_APP_PROFILE = "netizen"
19
+ LARK_APP_RELATIVE_PATH = Path("lark-app/config.json")
20
+ _MAX_CONFIG_BYTES = 65_536
21
+ _APP_ID = re.compile(r"cli_[A-Za-z0-9_-]+")
22
+
23
+
24
+ class LarkAppConfigError(ValueError):
25
+ """A credential configuration failure that is safe to display."""
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class LarkAppCredentials:
30
+ app_id: str
31
+ app_secret: str = field(repr=False)
32
+
33
+
34
+ def _unique_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
35
+ result: dict[str, Any] = {}
36
+ for key, value in pairs:
37
+ if key in result:
38
+ raise LarkAppConfigError("Lark app configuration has duplicate JSON fields")
39
+ result[key] = value
40
+ return result
41
+
42
+
43
+ def _validate_credentials(
44
+ app_id: Any, app_secret: Any, *, allow_incomplete: bool,
45
+ ) -> LarkAppCredentials:
46
+ if not isinstance(app_id, str) or (
47
+ not _APP_ID.fullmatch(app_id) and not (allow_incomplete and app_id == "")
48
+ ):
49
+ raise LarkAppConfigError("Lark app profile netizen requires a valid cli_ App ID")
50
+ if (
51
+ not isinstance(app_secret, str)
52
+ or app_secret.strip() != app_secret
53
+ or any(ord(character) < 0x20 for character in app_secret)
54
+ or (not app_secret and not allow_incomplete)
55
+ ):
56
+ raise LarkAppConfigError("Lark app profile netizen requires a non-empty App Secret string")
57
+ return LarkAppCredentials(app_id=app_id, app_secret=app_secret)
58
+
59
+
60
+ def parse_lark_app(
61
+ content: bytes, *, allow_incomplete: bool = False,
62
+ ) -> LarkAppCredentials:
63
+ if len(content) > _MAX_CONFIG_BYTES:
64
+ raise LarkAppConfigError("Lark app configuration exceeds the size limit")
65
+ try:
66
+ config = json.loads(content, object_pairs_hook=_unique_object)
67
+ except (ValueError, UnicodeError):
68
+ raise LarkAppConfigError("Lark app configuration must contain valid, unambiguous JSON") from None
69
+ if not isinstance(config, dict) or not isinstance(config.get("apps"), list):
70
+ raise LarkAppConfigError("Lark app configuration requires an apps array")
71
+ profiles = [
72
+ app for app in config["apps"]
73
+ if isinstance(app, dict) and app.get("name") == LARK_APP_PROFILE
74
+ ]
75
+ if len(profiles) != 1:
76
+ raise LarkAppConfigError("Lark app configuration requires exactly one profile named netizen")
77
+ profile = profiles[0]
78
+ if profile.get("brand") != "feishu":
79
+ raise LarkAppConfigError("Lark app profile netizen requires brand feishu")
80
+ return _validate_credentials(
81
+ profile.get("appId"), profile.get("appSecret"), allow_incomplete=allow_incomplete,
82
+ )
83
+
84
+
85
+ def load_lark_app(
86
+ path: str | Path, *, allow_incomplete: bool = False,
87
+ ) -> LarkAppCredentials:
88
+ path = Path(path)
89
+ if not path.is_absolute():
90
+ raise LarkAppConfigError("Lark app configuration path must be absolute")
91
+ try:
92
+ descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
93
+ with os.fdopen(descriptor, "rb") as stream:
94
+ metadata = os.fstat(stream.fileno())
95
+ if (
96
+ not stat.S_ISREG(metadata.st_mode)
97
+ or metadata.st_uid != os.geteuid()
98
+ or metadata.st_mode & 0o077
99
+ ):
100
+ raise LarkAppConfigError(
101
+ "Lark app configuration must be an owned regular file with permissions 0600 or stricter"
102
+ )
103
+ content = stream.read(_MAX_CONFIG_BYTES + 1)
104
+ except OSError:
105
+ raise LarkAppConfigError("Lark app configuration cannot be read; check its path and file permissions") from None
106
+ return parse_lark_app(content, allow_incomplete=allow_incomplete)
107
+
108
+
109
+ def encode_lark_app(app_id: str, app_secret: str) -> bytes:
110
+ credentials = _validate_credentials(app_id, app_secret, allow_incomplete=True)
111
+ config = {
112
+ "currentApp": LARK_APP_PROFILE,
113
+ "apps": [{
114
+ "name": LARK_APP_PROFILE,
115
+ "appId": credentials.app_id,
116
+ "appSecret": credentials.app_secret,
117
+ "brand": "feishu",
118
+ "defaultAs": "bot",
119
+ "users": [],
120
+ }],
121
+ }
122
+ content = (json.dumps(config, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
123
+ if len(content) > _MAX_CONFIG_BYTES:
124
+ raise LarkAppConfigError("Lark app configuration exceeds the size limit")
125
+ return content