netizen-cli 0.13.0__tar.gz → 0.13.1__tar.gz

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 (309) hide show
  1. {netizen_cli-0.13.0/netizen_cli.egg-info → netizen_cli-0.13.1}/PKG-INFO +1 -1
  2. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/__init__.py +1 -1
  3. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/admin.css +42 -8
  4. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/admin.js +216 -15
  5. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/identity.js +18 -5
  6. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/index.html +108 -67
  7. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/session-access.js +6 -1
  8. netizen_cli-0.13.1/netizen_cli/admin/static/worktrees.js +365 -0
  9. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/controls.py +85 -49
  10. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel_app.py +5 -6
  11. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/domain.py +1 -0
  12. {netizen_cli-0.13.0 → netizen_cli-0.13.1/netizen_cli.egg-info}/PKG-INFO +1 -1
  13. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/pyproject.toml +1 -1
  14. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/admin.md +14 -2
  15. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/sessions.md +3 -2
  16. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_worktree_new.py +121 -18
  17. netizen_cli-0.13.0/netizen_cli/admin/static/worktrees.js +0 -227
  18. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/MANIFEST.in +0 -0
  19. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/README.md +0 -0
  20. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/_build.py +0 -0
  21. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/config.example.yaml +0 -0
  22. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/__main__.py +0 -0
  23. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/account_rate_limits.py +0 -0
  24. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/__init__.py +0 -0
  25. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/auth.py +0 -0
  26. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/credentials.py +0 -0
  27. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/errors.py +0 -0
  28. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/feishu.py +0 -0
  29. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/login.py +0 -0
  30. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/port_config.py +0 -0
  31. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/presentation.py +0 -0
  32. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/queries.py +0 -0
  33. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/chat-picker.css +0 -0
  34. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/chat-picker.js +0 -0
  35. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/login.html +0 -0
  36. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/static/login.js +0 -0
  37. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/transport.py +0 -0
  38. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/users.py +0 -0
  39. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/admin/web.py +0 -0
  40. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/bindings.py +0 -0
  41. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/builtin_skills.py +0 -0
  42. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/__init__.py +0 -0
  43. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/callbacks.py +0 -0
  44. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/chat_target.py +0 -0
  45. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/defaults.py +0 -0
  46. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/fork.py +0 -0
  47. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/model_info.py +0 -0
  48. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/pagination.py +0 -0
  49. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/questions.py +0 -0
  50. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/reply.py +0 -0
  51. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/scheduled.py +0 -0
  52. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/session_ids.py +0 -0
  53. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/status.py +0 -0
  54. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cards/usage.py +0 -0
  55. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/__init__.py +0 -0
  56. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/completion_mentions.py +0 -0
  57. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/input_preparation.py +0 -0
  58. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/messages.py +0 -0
  59. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/ports.py +0 -0
  60. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/question_inputs.py +0 -0
  61. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/reactions.py +0 -0
  62. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/reply_presenter.py +0 -0
  63. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/session_pages.py +0 -0
  64. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/channel/topics.py +0 -0
  65. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/chat_avatars.py +0 -0
  66. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/chat_targets.py +0 -0
  67. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cli.py +0 -0
  68. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cli_data.py +0 -0
  69. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cli_packages.py +0 -0
  70. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cli_services.py +0 -0
  71. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cli_setup.py +0 -0
  72. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cli_update.py +0 -0
  73. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cli_update_restore.py +0 -0
  74. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/cli_update_worker.py +0 -0
  75. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/codex_runtime.py +0 -0
  76. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/completion_mention.py +0 -0
  77. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/database_migrations.py +0 -0
  78. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/defaults/__init__.py +0 -0
  79. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/defaults/models.py +0 -0
  80. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/defaults/service.py +0 -0
  81. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/defaults/store.py +0 -0
  82. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/deployment/__init__.py +0 -0
  83. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/deployment/restart_worker.py +0 -0
  84. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/deployment/update_executor.py +0 -0
  85. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/deployment/update_protocol.py +0 -0
  86. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/error_messages.py +0 -0
  87. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/experience.py +0 -0
  88. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/feishu_app_onboarding.py +0 -0
  89. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/feishu_app_permissions.py +0 -0
  90. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/feishu_links.py +0 -0
  91. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/git_status.py +0 -0
  92. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/image_inputs.py +0 -0
  93. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/instance.py +0 -0
  94. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/lark_app.py +0 -0
  95. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/main.py +0 -0
  96. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/management/__init__.py +0 -0
  97. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/management/blocking_io.py +0 -0
  98. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/management/chat_directory.py +0 -0
  99. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/management/chat_labels.py +0 -0
  100. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/management/coordination.py +0 -0
  101. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/management/service.py +0 -0
  102. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/management/updates.py +0 -0
  103. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/markdown_images.py +0 -0
  104. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/mcp_transport.py +0 -0
  105. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/message_content.py +0 -0
  106. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/message_history.py +0 -0
  107. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/message_preparation.py +0 -0
  108. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/message_projection.py +0 -0
  109. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/migrations/__init__.py +0 -0
  110. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/migrations/schema.py +0 -0
  111. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/migrations/v14.py +0 -0
  112. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/migrations/v15.py +0 -0
  113. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/model_settings.py +0 -0
  114. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/package_resources.py +0 -0
  115. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/pagination.py +0 -0
  116. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/partial_answers.py +0 -0
  117. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/projects.py +0 -0
  118. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/prompt_projection.py +0 -0
  119. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/quoted_context.py +0 -0
  120. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/result_images.py +0 -0
  121. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/runtime/__init__.py +0 -0
  122. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/runtime/contracts.py +0 -0
  123. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/runtime/name_writes.py +0 -0
  124. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/runtime/thread_naming.py +0 -0
  125. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/schedules/__init__.py +0 -0
  126. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/schedules/mcp.py +0 -0
  127. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/schedules/models.py +0 -0
  128. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/schedules/scheduler.py +0 -0
  129. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/schedules/service.py +0 -0
  130. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/schedules/store.py +0 -0
  131. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sdk_gap_adapter.py +0 -0
  132. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/service_launcher.py +0 -0
  133. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/session_inventory.py +0 -0
  134. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/session_settings.py +0 -0
  135. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sessions/__init__.py +0 -0
  136. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sessions/delegation.py +0 -0
  137. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sessions/errors.py +0 -0
  138. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sessions/mcp.py +0 -0
  139. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sessions/policy.py +0 -0
  140. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sessions/presentation.py +0 -0
  141. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sessions/schema.py +0 -0
  142. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/sessions/service.py +0 -0
  143. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/settings.py +0 -0
  144. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/skill_references.py +0 -0
  145. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/task_text.py +0 -0
  146. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/terminal_cleanup.py +0 -0
  147. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/thread_history.py +0 -0
  148. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/turn_activity.py +0 -0
  149. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/turn_files.py +0 -0
  150. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/turn_patch_children.py +0 -0
  151. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/turn_plan_observer.py +0 -0
  152. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/user_questions.py +0 -0
  153. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/worktrees/errors.py +0 -0
  154. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/worktrees/git.py +0 -0
  155. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/worktrees/models.py +0 -0
  156. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/worktrees/service.py +0 -0
  157. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/worktrees/settings.py +0 -0
  158. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli/worktrees/store.py +0 -0
  159. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli.egg-info/SOURCES.txt +0 -0
  160. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli.egg-info/dependency_links.txt +0 -0
  161. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli.egg-info/entry_points.txt +0 -0
  162. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli.egg-info/requires.txt +0 -0
  163. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/netizen_cli.egg-info/top_level.txt +0 -0
  164. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/setup.cfg +0 -0
  165. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-lark/SKILL.md +0 -0
  166. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/SKILL.md +0 -0
  167. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/commands.md +0 -0
  168. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/concepts.md +0 -0
  169. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/faq.md +0 -0
  170. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/feedback-and-files.md +0 -0
  171. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/fork-side-goal.md +0 -0
  172. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/messages.md +0 -0
  173. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/native-capabilities.md +0 -0
  174. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/scheduled-tasks.md +0 -0
  175. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/session-collaboration.md +0 -0
  176. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/status-and-control.md +0 -0
  177. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/skills/netizen-user-guide/references/user-guide.md +0 -0
  178. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_account_rate_limits.py +0 -0
  179. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_admin_update_executor_probe.py +0 -0
  180. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_binding_scheduled_channel.py +0 -0
  181. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_bindings.py +0 -0
  182. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cards.py +0 -0
  183. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_channel_app.py +0 -0
  184. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_channel_fixtures.py +0 -0
  185. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_channel_input_preparation.py +0 -0
  186. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_chat_avatars.py +0 -0
  187. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_chat_target_cards.py +0 -0
  188. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_check_sdk.py +0 -0
  189. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli.py +0 -0
  190. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli_data.py +0 -0
  191. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli_distribution.py +0 -0
  192. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli_integration.py +0 -0
  193. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli_packages.py +0 -0
  194. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli_packaging.py +0 -0
  195. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli_services.py +0 -0
  196. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli_setup.py +0 -0
  197. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cli_update.py +0 -0
  198. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_codex_runtime.py +0 -0
  199. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_codex_sdk_capabilities.py +0 -0
  200. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_codex_sdk_upgrade_0161.py +0 -0
  201. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_codex_sdk_upgrade_0163.py +0 -0
  202. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_completion_mention_cards.py +0 -0
  203. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_completion_mentions.py +0 -0
  204. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_completion_topic_delivery.py +0 -0
  205. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_cron_chat_targets.py +0 -0
  206. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_database_migrations.py +0 -0
  207. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_defaults_cards.py +0 -0
  208. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_defaults_channel.py +0 -0
  209. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_defaults_runtime.py +0 -0
  210. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_deployment.py +0 -0
  211. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_documentation_links.py +0 -0
  212. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_domain_experience.py +0 -0
  213. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_error_messages.py +0 -0
  214. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_feishu_app_onboarding.py +0 -0
  215. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_feishu_app_permissions.py +0 -0
  216. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_file_pagination.py +0 -0
  217. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_fork_cards.py +0 -0
  218. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_git_status.py +0 -0
  219. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_goal_reply_separation.py +0 -0
  220. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_image_inputs.py +0 -0
  221. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_lark_app.py +0 -0
  222. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_lark_sdk_contract.py +0 -0
  223. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_main.py +0 -0
  224. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_markdown_images.py +0 -0
  225. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_message_content.py +0 -0
  226. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_message_history.py +0 -0
  227. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_message_material_inputs.py +0 -0
  228. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_message_preparation.py +0 -0
  229. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_migration_schema_validation.py +0 -0
  230. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_model_info_cards.py +0 -0
  231. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_model_settings.py +0 -0
  232. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_name_writes.py +0 -0
  233. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_pagination.py +0 -0
  234. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_partial_answer_delivery.py +0 -0
  235. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_partial_answers.py +0 -0
  236. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_persistent_fork_bindings.py +0 -0
  237. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_persistent_fork_channel.py +0 -0
  238. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_persistent_fork_runtime.py +0 -0
  239. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_child_files.py +0 -0
  240. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_feishu_completion_mention.py +0 -0
  241. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_feishu_partial_answers.py +0 -0
  242. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_feishu_turn_file_card.py +0 -0
  243. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_persistent_fork.py +0 -0
  244. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_python_sdk.py +0 -0
  245. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_scheduled_tasks.py +0 -0
  246. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_sdk_completion_race.py +0 -0
  247. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_sdk_response_recovery.py +0 -0
  248. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_skill_execution.py +0 -0
  249. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_probe_thread_naming.py +0 -0
  250. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_project_delete_channel.py +0 -0
  251. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_project_delete_runtime.py +0 -0
  252. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_project_deletion_storage.py +0 -0
  253. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_prompt_projection.py +0 -0
  254. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_question_cards.py +0 -0
  255. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_question_channel.py +0 -0
  256. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_question_target_parity.py +0 -0
  257. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_quoted_context.py +0 -0
  258. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_release_script.py +0 -0
  259. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_repository_hygiene.py +0 -0
  260. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_result_images.py +0 -0
  261. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_resume_channel.py +0 -0
  262. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_retired_deployment.py +0 -0
  263. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_runtime_resume_activation.py +0 -0
  264. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_runtime_thread_naming.py +0 -0
  265. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_schedule_card_retry.py +0 -0
  266. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_schedule_cards.py +0 -0
  267. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_schedule_mcp.py +0 -0
  268. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_schedule_run_now.py +0 -0
  269. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_schedule_scope_matrix.py +0 -0
  270. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_schedule_service.py +0 -0
  271. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_scheduled_channel.py +0 -0
  272. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_scheduled_runtime.py +0 -0
  273. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_scheduler.py +0 -0
  274. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_scheduler_presenter.py +0 -0
  275. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_schedules_storage.py +0 -0
  276. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_sdk_gap_adapter.py +0 -0
  277. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_service_launcher.py +0 -0
  278. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_session_card_pages.py +0 -0
  279. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_session_collaboration_app.py +0 -0
  280. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_session_defaults.py +0 -0
  281. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_session_external_runtime.py +0 -0
  282. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_session_id_cards.py +0 -0
  283. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_session_policy.py +0 -0
  284. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_session_settings.py +0 -0
  285. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_session_tools.py +0 -0
  286. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_settings_projects.py +0 -0
  287. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_status_cards.py +0 -0
  288. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_status_commands.py +0 -0
  289. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_task_text.py +0 -0
  290. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_terminal_cleanup.py +0 -0
  291. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_thread_history.py +0 -0
  292. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_thread_naming.py +0 -0
  293. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_turn_activity.py +0 -0
  294. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_turn_files.py +0 -0
  295. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_turn_patch_children.py +0 -0
  296. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_turn_patch_statistics.py +0 -0
  297. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_turn_plan_observer.py +0 -0
  298. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_update_executor.py +0 -0
  299. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_usage_card.py +0 -0
  300. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_usage_control.py +0 -0
  301. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_user_guide_skill.py +0 -0
  302. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_user_questions.py +0 -0
  303. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_worktree_channel_cwd.py +0 -0
  304. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_worktree_git.py +0 -0
  305. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_worktree_runtime_cwd.py +0 -0
  306. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_worktree_service.py +0 -0
  307. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_worktree_settings.py +0 -0
  308. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_worktree_storage.py +0 -0
  309. {netizen_cli-0.13.0 → netizen_cli-0.13.1}/tests/test_worktree_tools_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netizen-cli
3
- Version: 0.13.0
3
+ Version: 0.13.1
4
4
  Summary: Use native Codex threads through a Feishu Channel.
5
5
  Requires-Python: <3.15,>=3.11
6
6
  Requires-Dist: h11==0.16.0
@@ -1,3 +1,3 @@
1
1
  """Netizen: a thin Feishu Channel for native Codex Threads."""
2
2
 
3
- __version__ = "0.13.0"
3
+ __version__ = "0.13.1"
@@ -220,7 +220,7 @@ button:focus-visible, input:focus-visible, select:focus-visible { outline: 3px s
220
220
  .time-range-fields { grid-template-columns: 1fr; }
221
221
  }
222
222
  .schedule-help { margin-top: 18px; color: var(--muted); }
223
- body.schedule-drawer-open, body.defaults-drawer-open, body.session-settings-drawer-open { overflow: hidden; }
223
+ body.schedule-drawer-open, body.defaults-drawer-open, body.session-settings-drawer-open, body.workspace-drawer-open { overflow: hidden; }
224
224
  .editor-drawer { position: fixed; inset: 0 0 0 auto; margin: 0; padding: 0; width: min(720px, 100vw); max-width: 100vw; height: 100dvh; max-height: 100dvh; border: 0; color: var(--ink); background: var(--paper); box-shadow: -12px 0 40px rgb(23 39 68 / 16%); }
225
225
  .editor-drawer::backdrop { background: rgb(16 33 63 / 35%); }
226
226
  #schedule-editor { height: 100%; display: flex; flex-direction: column; border: 0; border-radius: 0; padding: 0; margin: 0; }
@@ -329,6 +329,19 @@ body.schedule-detail-open { overflow: hidden; }
329
329
  .drawer-footer { display: flex; justify-content: flex-end; gap: 10px; padding: 16px 24px; border-top: 1px solid var(--line); }
330
330
  .drawer-form .drawer-cancel { color: var(--ink); background: white; border: 1px solid var(--line); }
331
331
  .drawer-form input { margin: 0; }
332
+ .workspace-form[hidden], .workspace-form [hidden] { display: none; }
333
+ .workspace-fields { display: grid; gap: 18px; }
334
+ .workspace-fields .hint { margin: -10px 0 0; }
335
+ .workspace-field-label { margin: 0 0 8px; color: var(--muted); font-size: 13px; }
336
+ .workspace-modes { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px; border-radius: 9px; background: var(--wash); }
337
+ .workspace-form .workspace-modes button { flex: 1; background: transparent; color: var(--muted); }
338
+ .workspace-form .workspace-modes button[aria-pressed="true"] { background: var(--paper); color: var(--blue); box-shadow: 0 1px 4px rgb(23 39 68 / 12%); font-weight: 600; }
339
+ .workspace-form .workspace-modes button:focus-visible { outline: 3px solid #8cb4ff; outline-offset: 2px; }
340
+ .workspace-form .drawer-body > .hint { margin-top: 12px; }
341
+ .workspace-form .status { margin-top: 24px; white-space: pre-line; overflow-wrap: anywhere; }
342
+ .workspace-form .cascade-choice input { width: 16px; height: 16px; margin: 0; flex: 0 0 auto; }
343
+ .workspace-form .target-summary, #worktree-effective-root { overflow-wrap: anywhere; }
344
+ #projects .section-head, #worktrees .section-head { flex-wrap: wrap; }
332
345
  #defaults [hidden], #defaults-editor[hidden], #defaults-editor [hidden] { display: none; }
333
346
  .defaults-help, .defaults-order-help, .defaults-field-help { color: var(--muted); font-size: 13px; line-height: 1.6; }
334
347
  .defaults-help { margin: 12px 0 24px; }
@@ -382,15 +395,36 @@ body.schedule-detail-open { overflow: hidden; }
382
395
  .person-avatar[hidden], .person-placeholder[hidden] { display: none; }
383
396
  .auth-expired { display: flex; align-items: center; justify-content: center; gap: 18px; padding: 18px; background: #fff2df; color: #7b4400; }
384
397
  .auth-expired[hidden], .person[hidden], .identity-panel[hidden], .identity-panel [hidden], .login-card [hidden] { display: none; }
385
- .system-tabs { width: fit-content; margin: 22px 0; }
398
+ .system-tabs { overflow-x: auto; }
399
+ .system-tabs .session-tab { flex: 0 0 auto; min-height: 44px; }
400
+ .system-tab-panel { padding-top: 20px; }
401
+ .tab-unsaved { display: inline-block; margin-left: 6px; padding: 2px 6px; border-radius: 4px; background: #fff3d6; color: #875800; font-size: 12px; font-weight: 400; }
402
+ .tab-unsaved[hidden] { display: none; }
386
403
  .admin-users { padding: 0; margin: 0; list-style: none; }
387
404
  .admin-users > li { display: flex; justify-content: space-between; align-items: center; gap: 16px; border-top: 1px solid var(--line); padding: 16px 0; }
388
- .session-access { margin-top: 32px; padding-top: 18px; border-top: 1px solid var(--line); }
389
- .session-access-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: 24px; }
390
- .session-access-grid h4 { margin: 0 0 8px; }
391
- .session-access-grid p { min-height: 48px; color: var(--muted); line-height: 1.6; }
392
- .session-access-grid textarea { width: 100%; resize: vertical; font-family: monospace; }
393
- .session-access-grid [hidden] { display: none; }
405
+ .session-access-form { border: 0; padding: 0; }
406
+ #session-access-fields { min-width: 0; margin: 0; border: 0; padding: 0; }
407
+ .session-access-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); align-items: start; gap: 16px; }
408
+ .session-access-rule { min-width: 0; border: 1px solid var(--line); border-radius: 10px; padding: 20px; }
409
+ .session-access-rule h4 { margin: 0; font-size: 16px; }
410
+ .session-access-description { margin: 6px 0 20px; color: var(--muted); font-size: 13px; line-height: 1.6; }
411
+ .session-access-targets { display: grid; gap: 16px; margin-top: 16px; }
412
+ .session-access-rule-note { margin: 16px 0 0; color: var(--muted); font-size: 12px; line-height: 1.6; }
413
+ .session-access-rule textarea { width: 100%; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; line-height: 1.6; }
414
+ .session-access-rule .chat-picker-popup { width: 100%; max-width: 100%; }
415
+ .session-access-rule [hidden] { display: none; }
416
+ .session-access-help { margin-top: 16px; padding: 14px 16px; border-radius: 8px; background: var(--wash); color: var(--muted); font-size: 13px; line-height: 1.6; }
417
+ .session-access-help h4 { margin: 0 0 6px; color: var(--ink); font-size: inherit; font-weight: 600; }
418
+ .session-access-help p { margin: 4px 0 0; }
419
+ .session-access-footer { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line); }
420
+ .session-access-footer .defaults-field-help { flex: 1 1 240px; margin: 0; }
421
+ .session-access-footer .actions { flex: 0 0 auto; gap: 8px; margin-left: auto; }
422
+ .session-access-footer button { min-width: 96px; min-height: 40px; border: 1px solid var(--blue); padding: 9px 14px; font-size: var(--control-font-size); line-height: var(--control-line-height); font-weight: 500; }
423
+ .session-access-footer .refresh { border-color: var(--line); background: var(--paper); color: var(--ink); }
424
+ @media (max-width: 650px) {
425
+ .session-access-rule { padding: 16px; }
426
+ .session-access-footer button { min-height: 44px; }
427
+ }
394
428
  .identity-panel { max-width: 480px; border: 1px solid var(--line); border-radius: 14px; margin-bottom: 24px; padding: 20px; color: var(--ink); }
395
429
  .identity-panel h3 { margin: 0; }
396
430
  .identity-panel > p { margin: 20px 0; }
@@ -3,6 +3,7 @@
3
3
  const state = {
4
4
  tab: "projects",
5
5
  sessionTab: "ordinary",
6
+ systemTab: "admins",
6
7
  projects: null,
7
8
  sessions: null,
8
9
  sides: null,
@@ -56,6 +57,12 @@ function expireAdminSession() {
56
57
  state.authExpired = true;
57
58
  stopUpdatePolling();
58
59
  window.NetizenSelect?.close();
60
+ // Release these modal drawers so the page's login link remains reachable.
61
+ // Inputs stay in memory, but expired actions can never be submitted again.
62
+ for (const dialog of document.querySelectorAll("#project-drawer, #worktree-create-drawer, #worktree-settings-drawer")) {
63
+ if (dialog.open) dialog.close();
64
+ }
65
+ document.body.classList.toggle("workspace-drawer-open", false);
59
66
  for (const element of document.querySelectorAll?.(".panel, dialog, .tabs") || []) element.inert = true;
60
67
  for (const button of document.querySelectorAll?.("button") || []) button.disabled = true;
61
68
  const notice = document.querySelector("#auth-expired");
@@ -403,6 +410,163 @@ function actionsCell(row) {
403
410
  return wrap;
404
411
  }
405
412
 
413
+ let projectEditor = null;
414
+ const projectInput = (name) => document.querySelector(`#project-${name}`);
415
+
416
+ function projectEditorValues() {
417
+ if (!projectEditor) return null;
418
+ return { mode: projectEditor.mode, alias: projectInput("alias").value,
419
+ paths: { ...projectEditor.paths, [projectEditor.mode]: projectInput("path").value } };
420
+ }
421
+
422
+ function projectEditorHasChanges() {
423
+ return Boolean(projectEditor && JSON.stringify(projectEditorValues()) !== projectEditor.discardBase);
424
+ }
425
+
426
+ function renderProjectEditorControls() {
427
+ const busy = state.authExpired || projectEditor?.saving;
428
+ projectInput("editor").setAttribute("aria-busy", String(Boolean(projectEditor?.saving || projectEditor?.checking)));
429
+ projectInput("new").disabled = Boolean(busy
430
+ || !(state.projects?.actions?.register || state.projects?.actions?.createDirectory));
431
+ for (const button of document.querySelectorAll("[data-project-add]")) button.disabled = projectInput("new").disabled;
432
+ for (const field of projectInput("editor").querySelectorAll("input, button")) field.disabled = Boolean(busy);
433
+ if (!projectEditor) {
434
+ projectInput("submit").disabled = true;
435
+ return;
436
+ }
437
+ const registering = projectEditor.mode === "register";
438
+ projectInput("path").required = registering;
439
+ projectInput("path-label").textContent = registering ? "绝对路径" : "绝对路径(可留空)";
440
+ projectInput("path-help").textContent = registering
441
+ ? "登记一个已存在的目录作为 Project。"
442
+ : "留空时在实例项目根目录下按 Alias 创建空目录;自定义路径也须位于该根目录内。";
443
+ for (const button of document.querySelectorAll("[data-project-mode]")) {
444
+ button.setAttribute("aria-pressed", String(button.dataset.projectMode === projectEditor.mode));
445
+ button.disabled = Boolean(busy || projectEditor.attempted);
446
+ }
447
+ projectInput("submit").textContent = projectEditor.saving
448
+ ? registering ? "正在登记…" : "正在创建…" : registering ? "登记 Project" : "创建并登记";
449
+ projectInput("submit").disabled = Boolean(busy || projectEditor.attempted || !projectEditor.actions[projectEditor.mode]);
450
+ projectInput("check").disabled = Boolean(busy || projectEditor.checking);
451
+ }
452
+
453
+ function openProjectEditor() {
454
+ if (state.authExpired || projectEditor?.saving
455
+ || !(state.projects?.actions?.register || state.projects?.actions?.createDirectory)) return;
456
+ if (projectEditor && !closeProjectEditor()) return;
457
+ projectEditor = { mode: "register", paths: { register: "", createDirectory: "" },
458
+ actions: { ...state.projects.actions }, returnFocus: document.activeElement, saving: false, attempted: false };
459
+ projectInput("alias").value = "";
460
+ projectInput("path").value = "";
461
+ projectInput("note").textContent = "";
462
+ projectInput("note").classList.toggle("error", false);
463
+ projectInput("check").hidden = true;
464
+ projectInput("check").textContent = "刷新列表核对";
465
+ projectEditor.discardBase = JSON.stringify(projectEditorValues());
466
+ projectInput("editor").hidden = false;
467
+ renderProjectEditorControls();
468
+ projectInput("drawer").showModal();
469
+ document.body.classList.toggle("workspace-drawer-open", true);
470
+ projectInput("drawer").querySelector(".drawer-body").scrollTop = 0;
471
+ projectInput("alias").focus();
472
+ }
473
+
474
+ function closeProjectEditor({ saved = false, confirmed = false } = {}) {
475
+ if (!projectEditor) return true;
476
+ if (projectEditor.saving) return false;
477
+ if (!saved && !confirmed && !confirmDiscardChanges(projectEditorHasChanges())) return false;
478
+ const returnFocus = projectEditor.returnFocus;
479
+ projectEditor = null;
480
+ projectInput("editor").hidden = true;
481
+ projectInput("drawer").close();
482
+ document.body.classList.toggle("workspace-drawer-open", false);
483
+ renderProjectEditorControls();
484
+ (returnFocus?.isConnected === false ? projectInput("new") : returnFocus)?.focus();
485
+ return true;
486
+ }
487
+
488
+ function selectProjectMode(mode) {
489
+ if (!projectEditor || projectEditor.saving || projectEditor.attempted || state.authExpired) return;
490
+ projectEditor.paths[projectEditor.mode] = projectInput("path").value;
491
+ projectEditor.mode = mode;
492
+ projectInput("path").value = projectEditor.paths[mode];
493
+ renderProjectEditorControls();
494
+ }
495
+
496
+ async function checkProjectResult() {
497
+ const editor = projectEditor;
498
+ if (!editor || editor.saving || editor.checking || state.authExpired) return;
499
+ editor.checking = true;
500
+ renderProjectEditorControls();
501
+ try {
502
+ const refreshed = await loadProjects();
503
+ if (projectEditor !== editor || state.authExpired) return;
504
+ if (refreshed !== false && editor.attempted && !editor.uncertain) {
505
+ editor.actions = { ...state.projects.actions };
506
+ editor.attempted = false;
507
+ projectInput("check").hidden = true;
508
+ projectInput("note").classList.toggle("error", false);
509
+ projectInput("note").textContent = "已刷新,当前输入已保留。可修改后重新提交。";
510
+ } else {
511
+ projectInput("note").textContent = refreshed === false
512
+ ? "列表未更新,请再次刷新核对。"
513
+ : "已刷新 Projects 列表;请关闭表单核对 Alias 与路径。未重复提交操作。";
514
+ }
515
+ } catch (error) {
516
+ if (projectEditor === editor && !state.authExpired) projectInput("note").textContent = `列表读取失败:${error.message}`;
517
+ } finally {
518
+ editor.checking = false;
519
+ if (projectEditor === editor) renderProjectEditorControls();
520
+ }
521
+ }
522
+
523
+ async function saveProject(event) {
524
+ event.preventDefault();
525
+ const editor = projectEditor;
526
+ if (!editor || editor.saving || editor.attempted || state.authExpired || !editor.actions[editor.mode]) return;
527
+ if (!projectInput("editor").reportValidity()) return;
528
+ const envelope = editor.actions[editor.mode];
529
+ editor.actions[editor.mode] = null;
530
+ if (state.projects?.actions) state.projects.actions[editor.mode] = null;
531
+ editor.saving = true;
532
+ editor.attempted = true;
533
+ projectInput("note").textContent = editor.mode === "register" ? "正在登记 Project…" : "正在创建 Project…";
534
+ renderProjectEditorControls();
535
+ let result;
536
+ try {
537
+ result = await api(editor.mode === "register" ? "/api/v1/projects/register" : "/api/v1/projects/create-directory", {
538
+ method: "POST", headers: { "Content-Type": "application/json" },
539
+ body: JSON.stringify(actionPayload(envelope, { alias: projectInput("alias").value,
540
+ path: projectInput("path").value || null })),
541
+ });
542
+ } catch (error) {
543
+ if (state.authExpired) return;
544
+ editor.uncertain = !error.status || error.status >= 500;
545
+ projectInput("note").textContent = !editor.uncertain
546
+ ? `${error.message} 输入已保留,请刷新后修改并重新提交。`
547
+ : "操作结果未确认,请刷新 Projects 列表核对;不要重复提交。";
548
+ projectInput("note").classList.toggle("error", true);
549
+ projectInput("check").hidden = false;
550
+ projectInput("check").textContent = editor.uncertain ? "刷新列表核对" : "刷新后继续填写";
551
+ setStatus(projectInput("note").textContent, true);
552
+ return;
553
+ } finally {
554
+ editor.saving = false;
555
+ renderProjectEditorControls();
556
+ }
557
+ const alias = projectInput("alias").value;
558
+ closeProjectEditor({ saved: true });
559
+ const message = `Project「${result?.alias || alias}」已${editor.mode === "register" ? "登记" : "创建并登记"}。`
560
+ + (result?.cwd ? `目录:${result.cwd}` : "");
561
+ try {
562
+ const refreshed = await loadProjects();
563
+ if (!state.authExpired && state.tab === "projects") setStatus(
564
+ refreshed === false ? `${message} 列表未更新,请手动刷新。` : message);
565
+ } catch {
566
+ if (!state.authExpired && state.tab === "projects") setStatus(`${message} 列表刷新失败,请手动刷新查看。`, true);
567
+ }
568
+ }
569
+
406
570
  function confirmMaterializedDelete(session) {
407
571
  const name = session.nativeTitle || "未命名 Session";
408
572
  return window.confirm(
@@ -574,6 +738,19 @@ async function loadProjects(cursor = undefined) {
574
738
  }
575
739
  body.append(row);
576
740
  }
741
+ if (!data.items.length) {
742
+ const row = document.createElement("tr");
743
+ const empty = document.createElement("td");
744
+ empty.colSpan = 6;
745
+ const message = document.createElement("p");
746
+ message.textContent = "本页没有 Project。可登记已有目录,或创建空目录。";
747
+ const add = actionButton("添加 Project", openProjectEditor);
748
+ add.setAttribute("data-project-add", "");
749
+ empty.append(message, add);
750
+ row.append(empty);
751
+ body.append(row);
752
+ }
753
+ renderProjectEditorControls();
577
754
  renderCursorPagination("projects", state.projectPage, data.items.length);
578
755
  return true;
579
756
  }
@@ -2740,7 +2917,7 @@ function confirmDiscardChanges(changed) {
2740
2917
  }
2741
2918
 
2742
2919
  function hasUnsavedEditors() {
2743
- return sessionSettingsHasChanges() || defaultEditorHasChanges() || scheduleEditorHasChanges()
2920
+ return projectEditorHasChanges() || sessionSettingsHasChanges() || defaultEditorHasChanges() || scheduleEditorHasChanges()
2744
2921
  || Boolean(pendingScheduleDraft?.dirty) || Boolean(window.NetizenSessionAccess?.dirty())
2745
2922
  || Boolean(window.NetizenWorktrees?.dirty());
2746
2923
  }
@@ -2748,9 +2925,10 @@ function hasUnsavedEditors() {
2748
2925
  // Navigation asks once before closing any editor. Busy submissions retain the
2749
2926
  // existing fail-closed boundary; this helper never cancels or repeats a POST.
2750
2927
  function confirmDiscardEditors() {
2751
- if (sessionSettingsEditor?.saving || defaultsBusy || (scheduleEditor && scheduleInput("fields").disabled)
2928
+ if (projectEditor?.saving || sessionSettingsEditor?.saving || defaultsBusy || (scheduleEditor && scheduleInput("fields").disabled)
2752
2929
  || window.NetizenSessionAccess?.busy() || window.NetizenWorktrees?.busy()) return false;
2753
2930
  if (!confirmDiscardChanges(hasUnsavedEditors())) return false;
2931
+ closeProjectEditor({ confirmed: true });
2754
2932
  closeSessionSettingsEditor({ confirmed: true });
2755
2933
  closeDefaultEditor({ confirmed: true });
2756
2934
  if (scheduleEditor) closeScheduleEditor({ confirmed: true });
@@ -4089,6 +4267,20 @@ function selectSessionTab(name) {
4089
4267
  return refresh("sessions");
4090
4268
  }
4091
4269
 
4270
+ function selectSystemTab(name) {
4271
+ if (state.authExpired || state.systemTab === name) return;
4272
+ window.NetizenSelect?.close();
4273
+ if (state.systemTab === "admins") window.NetizenIdentity?.suspendSelector();
4274
+ else window.NetizenSessionAccess?.closePickers();
4275
+ state.systemTab = name;
4276
+ for (const button of document.querySelectorAll("[data-system-tab]")) {
4277
+ const selected = button.dataset.systemTab === name;
4278
+ button.setAttribute("aria-selected", String(selected));
4279
+ button.setAttribute("tabindex", selected ? "0" : "-1");
4280
+ document.querySelector(`#system-${button.dataset.systemTab}`).hidden = !selected;
4281
+ }
4282
+ }
4283
+
4092
4284
  function selectTab(name, sessionTab = state.sessionTab || "ordinary", { preserveEditors = false, replace = false } = {}) {
4093
4285
  if (state.authExpired) return;
4094
4286
  if (!preserveEditors && !confirmDiscardEditors()) return;
@@ -4141,6 +4333,18 @@ for (const tab of document.querySelectorAll("[data-session-tab]")) {
4141
4333
  });
4142
4334
  }
4143
4335
 
4336
+ for (const tab of document.querySelectorAll("[data-system-tab]")) {
4337
+ tab.addEventListener("click", () => selectSystemTab(tab.dataset.systemTab));
4338
+ tab.addEventListener("keydown", (event) => {
4339
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
4340
+ event.preventDefault();
4341
+ const name = event.key === "Home" ? "admins" : event.key === "End" ? "access"
4342
+ : tab.dataset.systemTab === "admins" ? "access" : "admins";
4343
+ selectSystemTab(name);
4344
+ document.querySelector(`#system-tab-${name}`).focus();
4345
+ });
4346
+ }
4347
+
4144
4348
  for (const button of document.querySelectorAll("[data-refresh]")) {
4145
4349
  button.addEventListener("click", () => refresh(button.dataset.refresh));
4146
4350
  }
@@ -4149,21 +4353,18 @@ for (const button of document.querySelectorAll("[data-session-sort]")) {
4149
4353
  button.addEventListener("click", () => sortSessionPage(button.dataset.sessionSort, button.dataset.sortBy));
4150
4354
  }
4151
4355
 
4152
- document.querySelector("#register-project").addEventListener("submit", async (event) => {
4356
+ projectInput("new").addEventListener("click", openProjectEditor);
4357
+ projectInput("cancel").addEventListener("click", closeProjectEditor);
4358
+ projectInput("close").addEventListener("click", closeProjectEditor);
4359
+ projectInput("drawer").addEventListener("cancel", (event) => {
4153
4360
  event.preventDefault();
4154
- const formElement = event.currentTarget;
4155
- const form = new FormData(formElement);
4156
- await mutate("/api/v1/projects/register", state.projects.actions.register, { alias: form.get("alias"), path: form.get("path") });
4157
- formElement.reset();
4158
- });
4159
-
4160
- document.querySelector("#create-project").addEventListener("submit", async (event) => {
4161
- event.preventDefault();
4162
- const formElement = event.currentTarget;
4163
- const form = new FormData(formElement);
4164
- await mutate("/api/v1/projects/create-directory", state.projects.actions.createDirectory, { alias: form.get("alias"), path: form.get("path") || null });
4165
- formElement.reset();
4361
+ closeProjectEditor();
4166
4362
  });
4363
+ projectInput("editor").addEventListener("submit", saveProject);
4364
+ projectInput("check").addEventListener("click", checkProjectResult);
4365
+ for (const button of document.querySelectorAll("[data-project-mode]")) {
4366
+ button.addEventListener("click", () => selectProjectMode(button.dataset.projectMode));
4367
+ }
4167
4368
 
4168
4369
  document.querySelector("#session-inventory-state").addEventListener("change", updateSessionDateFilters);
4169
4370
  document.querySelector("#session-filter").addEventListener("submit", (event) => {
@@ -88,14 +88,27 @@ window.NetizenIdentity = (() => {
88
88
  });
89
89
  }
90
90
 
91
- function clearSelector() {
91
+ function clearSelector({ preserveSelection = false } = {}) {
92
92
  generation += 1;
93
- selected = null;
93
+ if (!preserveSelection) selected = null;
94
94
  try { selector?.unmount?.(); } catch { /* Closing the local picker remains possible. */ }
95
95
  selector = null;
96
96
  node("admin-selector").replaceChildren();
97
- node("admin-user-selection").hidden = true;
98
- node("admin-user-confirm").disabled = true;
97
+ node("admin-user-selection").hidden = !selected;
98
+ node("admin-user-confirm").disabled = pending || !selected;
99
+ }
100
+
101
+ function suspendSelector() {
102
+ if (node("admin-user-panel").hidden) return;
103
+ // The SDK owns a body-mounted popup. Unmount it while preserving the
104
+ // selection and its original grant; switching tabs must not fetch new ones.
105
+ clearSelector({ preserveSelection: true });
106
+ node("admin-selector-status").classList.toggle("error", configStalled);
107
+ node("admin-selector-status").textContent = configStalled ? reloadMessage : selected
108
+ ? "已保留选中人员,可确认添加;如需更换,请重新加载选人组件。"
109
+ : "选人组件已收起,重新加载后可继续选择。";
110
+ node("admin-selector-retry").hidden = configStalled;
111
+ node("admin-selector-reload").hidden = !configStalled;
99
112
  }
100
113
 
101
114
  function closePanel() {
@@ -265,7 +278,7 @@ window.NetizenIdentity = (() => {
265
278
  node("admin-selector-retry").addEventListener("click", () => { void loadSelector(); });
266
279
  node("admin-selector-reload").addEventListener("click", () => { window.location.reload(); });
267
280
  return {
268
- loadProfile, loadUsers, closePanel,
281
+ loadProfile, loadUsers, closePanel, suspendSelector,
269
282
  expire() { expired = true; closePanel(); renderUsers(); },
270
283
  };
271
284
  })();