@hopping-dev/hub 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/assets/hopping-skill/SKILL.md +221 -0
  2. package/dist/approval/manager.d.ts +60 -0
  3. package/dist/approval/manager.d.ts.map +1 -0
  4. package/dist/approval/manager.js +101 -0
  5. package/dist/approval/manager.js.map +1 -0
  6. package/dist/approval/session-memory.d.ts +37 -0
  7. package/dist/approval/session-memory.d.ts.map +1 -0
  8. package/dist/approval/session-memory.js +63 -0
  9. package/dist/approval/session-memory.js.map +1 -0
  10. package/dist/cli/config-writer.d.ts +57 -0
  11. package/dist/cli/config-writer.d.ts.map +1 -0
  12. package/dist/cli/config-writer.js +318 -0
  13. package/dist/cli/config-writer.js.map +1 -0
  14. package/dist/cli/index.d.ts +3 -0
  15. package/dist/cli/index.d.ts.map +1 -0
  16. package/dist/cli/index.js +82 -0
  17. package/dist/cli/index.js.map +1 -0
  18. package/dist/cli/path-resolver.d.ts +48 -0
  19. package/dist/cli/path-resolver.d.ts.map +1 -0
  20. package/dist/cli/path-resolver.js +212 -0
  21. package/dist/cli/path-resolver.js.map +1 -0
  22. package/dist/cli/setup.d.ts +10 -0
  23. package/dist/cli/setup.d.ts.map +1 -0
  24. package/dist/cli/setup.js +268 -0
  25. package/dist/cli/setup.js.map +1 -0
  26. package/dist/cloud/connector.d.ts +74 -0
  27. package/dist/cloud/connector.d.ts.map +1 -0
  28. package/dist/cloud/connector.js +524 -0
  29. package/dist/cloud/connector.js.map +1 -0
  30. package/dist/cloud/index.d.ts +3 -0
  31. package/dist/cloud/index.d.ts.map +1 -0
  32. package/dist/cloud/index.js +6 -0
  33. package/dist/cloud/index.js.map +1 -0
  34. package/dist/config/manager.d.ts +76 -0
  35. package/dist/config/manager.d.ts.map +1 -0
  36. package/dist/config/manager.js +296 -0
  37. package/dist/config/manager.js.map +1 -0
  38. package/dist/dev-mode.d.ts +30 -0
  39. package/dist/dev-mode.d.ts.map +1 -0
  40. package/dist/dev-mode.js +53 -0
  41. package/dist/dev-mode.js.map +1 -0
  42. package/dist/index.d.ts +10 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +354 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/ipc/index.d.ts +12 -0
  47. package/dist/ipc/index.d.ts.map +1 -0
  48. package/dist/ipc/index.js +15 -0
  49. package/dist/ipc/index.js.map +1 -0
  50. package/dist/ipc/watcher.d.ts +226 -0
  51. package/dist/ipc/watcher.d.ts.map +1 -0
  52. package/dist/ipc/watcher.js +745 -0
  53. package/dist/ipc/watcher.js.map +1 -0
  54. package/dist/local/approval-dialog.d.ts +30 -0
  55. package/dist/local/approval-dialog.d.ts.map +1 -0
  56. package/dist/local/approval-dialog.js +214 -0
  57. package/dist/local/approval-dialog.js.map +1 -0
  58. package/dist/local/index.d.ts +8 -0
  59. package/dist/local/index.d.ts.map +1 -0
  60. package/dist/local/index.js +13 -0
  61. package/dist/local/index.js.map +1 -0
  62. package/dist/local/local-approval.d.ts +55 -0
  63. package/dist/local/local-approval.d.ts.map +1 -0
  64. package/dist/local/local-approval.js +125 -0
  65. package/dist/local/local-approval.js.map +1 -0
  66. package/dist/local/notifier.d.ts +19 -0
  67. package/dist/local/notifier.d.ts.map +1 -0
  68. package/dist/local/notifier.js +110 -0
  69. package/dist/local/notifier.js.map +1 -0
  70. package/dist/local/sanitize.d.ts +20 -0
  71. package/dist/local/sanitize.d.ts.map +1 -0
  72. package/dist/local/sanitize.js +28 -0
  73. package/dist/local/sanitize.js.map +1 -0
  74. package/dist/mcp/file-extractor.d.ts +11 -0
  75. package/dist/mcp/file-extractor.d.ts.map +1 -0
  76. package/dist/mcp/file-extractor.js +74 -0
  77. package/dist/mcp/file-extractor.js.map +1 -0
  78. package/dist/mcp/risk-level.d.ts +44 -0
  79. package/dist/mcp/risk-level.d.ts.map +1 -0
  80. package/dist/mcp/risk-level.js +127 -0
  81. package/dist/mcp/risk-level.js.map +1 -0
  82. package/dist/mcp/schemas.d.ts +83 -0
  83. package/dist/mcp/schemas.d.ts.map +1 -0
  84. package/dist/mcp/schemas.js +84 -0
  85. package/dist/mcp/schemas.js.map +1 -0
  86. package/dist/mcp/summary.d.ts +11 -0
  87. package/dist/mcp/summary.d.ts.map +1 -0
  88. package/dist/mcp/summary.js +150 -0
  89. package/dist/mcp/summary.js.map +1 -0
  90. package/dist/mcp/tools.d.ts +45 -0
  91. package/dist/mcp/tools.d.ts.map +1 -0
  92. package/dist/mcp/tools.js +1217 -0
  93. package/dist/mcp/tools.js.map +1 -0
  94. package/dist/pairing/auto-pairing.d.ts +37 -0
  95. package/dist/pairing/auto-pairing.d.ts.map +1 -0
  96. package/dist/pairing/auto-pairing.js +144 -0
  97. package/dist/pairing/auto-pairing.js.map +1 -0
  98. package/dist/pairing/binding-poller.d.ts +26 -0
  99. package/dist/pairing/binding-poller.d.ts.map +1 -0
  100. package/dist/pairing/binding-poller.js +108 -0
  101. package/dist/pairing/binding-poller.js.map +1 -0
  102. package/dist/pairing/pairing-server.d.ts +14 -0
  103. package/dist/pairing/pairing-server.d.ts.map +1 -0
  104. package/dist/pairing/pairing-server.js +277 -0
  105. package/dist/pairing/pairing-server.js.map +1 -0
  106. package/dist/pairing/qr-display.d.ts +14 -0
  107. package/dist/pairing/qr-display.d.ts.map +1 -0
  108. package/dist/pairing/qr-display.js +40 -0
  109. package/dist/pairing/qr-display.js.map +1 -0
  110. package/dist/policy/engine.d.ts +31 -0
  111. package/dist/policy/engine.d.ts.map +1 -0
  112. package/dist/policy/engine.js +187 -0
  113. package/dist/policy/engine.js.map +1 -0
  114. package/dist/policy/store.d.ts +26 -0
  115. package/dist/policy/store.d.ts.map +1 -0
  116. package/dist/policy/store.js +70 -0
  117. package/dist/policy/store.js.map +1 -0
  118. package/dist/policy/system-policies.d.ts +15 -0
  119. package/dist/policy/system-policies.d.ts.map +1 -0
  120. package/dist/policy/system-policies.js +265 -0
  121. package/dist/policy/system-policies.js.map +1 -0
  122. package/dist/policy/tool-mapping.d.ts +45 -0
  123. package/dist/policy/tool-mapping.d.ts.map +1 -0
  124. package/dist/policy/tool-mapping.js +88 -0
  125. package/dist/policy/tool-mapping.js.map +1 -0
  126. package/dist/policy/tool-registry.json +85 -0
  127. package/dist/store/db.d.ts +17 -0
  128. package/dist/store/db.d.ts.map +1 -0
  129. package/dist/store/db.js +193 -0
  130. package/dist/store/db.js.map +1 -0
  131. package/dist/store/index.d.ts +4 -0
  132. package/dist/store/index.d.ts.map +1 -0
  133. package/dist/store/index.js +7 -0
  134. package/dist/store/index.js.map +1 -0
  135. package/dist/store/metadata.d.ts +31 -0
  136. package/dist/store/metadata.d.ts.map +1 -0
  137. package/dist/store/metadata.js +178 -0
  138. package/dist/store/metadata.js.map +1 -0
  139. package/dist/store/operations.d.ts +26 -0
  140. package/dist/store/operations.d.ts.map +1 -0
  141. package/dist/store/operations.js +171 -0
  142. package/dist/store/operations.js.map +1 -0
  143. package/dist/utils/json.d.ts +7 -0
  144. package/dist/utils/json.d.ts.map +1 -0
  145. package/dist/utils/json.js +33 -0
  146. package/dist/utils/json.js.map +1 -0
  147. package/dist/utils/logger.d.ts +13 -0
  148. package/dist/utils/logger.d.ts.map +1 -0
  149. package/dist/utils/logger.js +58 -0
  150. package/dist/utils/logger.js.map +1 -0
  151. package/dist/utils/open-browser.d.ts +8 -0
  152. package/dist/utils/open-browser.d.ts.map +1 -0
  153. package/dist/utils/open-browser.js +38 -0
  154. package/dist/utils/open-browser.js.map +1 -0
  155. package/node_modules/@hopping/shared/dist/types.d.ts +649 -0
  156. package/node_modules/@hopping/shared/dist/types.js +48 -0
  157. package/node_modules/@hopping/shared/dist/types.js.map +1 -0
  158. package/node_modules/@hopping/shared/package.json +14 -0
  159. package/node_modules/@hopping/shared/tsconfig.json +16 -0
  160. package/node_modules/@hopping/shared/types.d.ts +650 -0
  161. package/node_modules/@hopping/shared/types.d.ts.map +1 -0
  162. package/node_modules/@hopping/shared/types.js +48 -0
  163. package/node_modules/@hopping/shared/types.js.map +1 -0
  164. package/node_modules/@hopping/shared/types.ts +895 -0
  165. package/package.json +52 -0
@@ -0,0 +1,221 @@
1
+ ---
2
+ name: hopping-skill
3
+ description: HopPing AI Agent Remote Control — call hopping.check before risky operations (shell commands, file writes, git pushes, database changes, .env access). For agents without Hook support, also call hopping.report after each significant operation.
4
+ user-invocable: false
5
+ ---
6
+
7
+ # HopPing — AI Agent Remote Control
8
+
9
+ HopPing lets users approve, control, and observe AI Agent operations from their phone. When `hopping.check` is available as an MCP tool, follow these guidelines to keep users in the loop.
10
+
11
+ ---
12
+
13
+ ## When to Call `hopping.check`
14
+
15
+ Call `hopping.check` **before** executing any of the following:
16
+
17
+ | Risk Level | Operations |
18
+ | ------------ | ---------------------------------------------------------------------------------------------------- |
19
+ | **Critical** | `rm -rf`, `rm -r`, `DROP TABLE`, `TRUNCATE`, accessing `.env` / credentials / secrets / private keys |
20
+ | **High** | `sudo`, `chmod 777`, `curl \| bash`, `wget \| bash`, production deployments |
21
+ | **Medium** | `git push --force`, `git reset --hard`, `git clean -f`, any `rm` command |
22
+ | **Audit** | All shell commands (`Bash` tool), all file writes (`Edit`, `Write` tools), all database mutations |
23
+
24
+ **Skip `hopping.check` for low-risk operations:**
25
+ `Read`, `Glob`, `Grep`, `WebSearch`, `WebFetch`, `TodoRead`, `TodoWrite`
26
+
27
+ ---
28
+
29
+ ## How to Call `hopping.check`
30
+
31
+ **Required fields:** `toolName`, `toolInput`
32
+ **Optional but recommended:** `sessionId`, `cwd`, `context`
33
+
34
+ ```json
35
+ {
36
+ "toolName": "Bash",
37
+ "toolInput": { "command": "rm -rf dist/" },
38
+ "sessionId": "session-abc123",
39
+ "cwd": "/Users/dev/my-project",
40
+ "context": {
41
+ "recentFiles": ["src/index.ts", "package.json"],
42
+ "branch": "main",
43
+ "project": "my-project"
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Handling the Response
49
+
50
+ ```json
51
+ {
52
+ "approvalId": "uuid",
53
+ "decision": "approved | denied | conditional | timeout | pending",
54
+ "riskLevel": "low | medium | high | critical",
55
+ "message": "human-readable explanation"
56
+ }
57
+ ```
58
+
59
+ | `decision` | Action |
60
+ | ------------- | ---------------------------------------------------------------------------------------------------------- |
61
+ | `approved` | Proceed with the operation |
62
+ | `denied` | **Stop.** Do not execute. Tell the user: _"Operation denied by HopPing: [message]"_ |
63
+ | `conditional` | Read `message` carefully. Only proceed if all stated conditions are met |
64
+ | `timeout` | User did not respond in time. For `high`/`critical` risk: do not proceed. For `low`/`medium`: use judgment |
65
+ | `pending` | **Ask the user for local approval.** See "Handling Pending Decisions" below |
66
+
67
+ ---
68
+
69
+ ## Handling Pending Decisions (`hopping.respond`)
70
+
71
+ When `hopping.check` returns `decision="pending"`, the remote approval timed out but the operation still requires the user's explicit approval (typically `high` or `critical` risk). **Do not proceed without user confirmation.**
72
+
73
+ ### Steps
74
+
75
+ 1. **Show the pending operation to the user.** Include the tool name, risk level, and the message from `hopping.check`.
76
+ 2. **Ask the user directly:** _"This operation requires your approval. Do you want to approve or deny it?"_
77
+ 3. **Wait for the user's response.** Do not auto-approve or guess.
78
+ 4. **Call `hopping.respond`** with the user's decision:
79
+
80
+ ```json
81
+ {
82
+ "approvalId": "the-approval-id-from-hopping-check",
83
+ "decision": "approved",
84
+ "reason": "User approved locally"
85
+ }
86
+ ```
87
+
88
+ 5. **Check the response from `hopping.respond`:**
89
+ - `{ "success": true, "source": "local" }` → User's decision was recorded. Proceed if approved, stop if denied.
90
+ - `{ "success": true, "decision": "approved", "source": "remote" }` → The user already approved on their phone. Proceed.
91
+ - `{ "success": true, "decision": "denied", "source": "remote" }` → The user already denied on their phone. Stop.
92
+ - `{ "success": false, "error": "..." }` → Something went wrong. Tell the user and do not proceed.
93
+
94
+ ### Important Rules
95
+
96
+ - **Never call `hopping.respond` without asking the user first.** The purpose of `pending` is to ensure a human makes the decision.
97
+ - If the user says "approve all" or similar blanket statements, still ask for confirmation for each individual `pending` operation.
98
+ - If you are unsure about the user's intent, default to `denied`.
99
+
100
+ ---
101
+
102
+ ## Using `hopping.report` (Agents Without Hook Support)
103
+
104
+ If you are running on **Codex, Gemini CLI, or any agent without Claude Code Hooks**, call `hopping.report` after each significant operation to enable observation:
105
+
106
+ **Required:** `toolName` only. All other fields are recommended.
107
+
108
+ ```json
109
+ {
110
+ "toolName": "Bash",
111
+ "toolParams": { "command": "pnpm install" },
112
+ "result": "Successfully installed 23 packages",
113
+ "duration": 4521,
114
+ "success": true,
115
+ "filePaths": ["package.json", "pnpm-lock.yaml"]
116
+ }
117
+ ```
118
+
119
+ **When to report:**
120
+
121
+ - After any shell command execution
122
+ - After file writes or edits
123
+ - After git operations
124
+ - After database queries
125
+
126
+ > **Agents with Claude Code Hooks (Claude Code, Cursor):** Hooks automatically observe all operations — `hopping.report` is not required unless you want to add extra context.
127
+
128
+ ---
129
+
130
+ ## Using `hopping.status`
131
+
132
+ Check Hub connectivity at session start or when debugging:
133
+
134
+ ```json
135
+ { "query": "all" }
136
+ ```
137
+
138
+ Returns: `activeSessions`, `pendingApprovals`, `todayOperations`, `hubUptime`, `connectedToCloud`.
139
+
140
+ Call `hopping.status` when:
141
+
142
+ - Starting a new session (verify Hub is running)
143
+ - Debugging why approvals are not arriving on phone
144
+ - Checking how many operations were recorded today
145
+
146
+ ---
147
+
148
+ ## Reporting Task Completion
149
+
150
+ When you have **finished all steps of the user's task**, call `hopping.report` with `context.taskCompleted: true`. This notifies the user's phone that the agent is now idle and ready for new instructions.
151
+
152
+ ```json
153
+ {
154
+ "toolName": "task_completed",
155
+ "result": "All requested tasks have been completed successfully.",
156
+ "success": true,
157
+ "context": {
158
+ "taskCompleted": true,
159
+ "project": "my-project"
160
+ }
161
+ }
162
+ ```
163
+
164
+ **When to send task completion:**
165
+
166
+ - After completing all steps in the user's current prompt/task
167
+ - When you have nothing more to do and are returning control to the user
168
+ - Do NOT send for intermediate milestones — only when the entire task is done
169
+
170
+ **Why this matters:** The user may be away from their computer. This notification lets them know they can return to give new instructions, or simply enjoy their coffee a bit longer.
171
+
172
+ ---
173
+
174
+ ## Examples
175
+
176
+ ### Before a destructive shell command
177
+
178
+ ```
179
+ 1. Call hopping.check: toolName="Bash", toolInput={"command": "rm -rf node_modules/"}
180
+ 2. If decision="approved" → run the Bash command
181
+ 3. If decision="denied" → tell user: "Hub denied: [message]"
182
+ ```
183
+
184
+ ### Before writing to a sensitive file
185
+
186
+ ```
187
+ 1. Call hopping.check: toolName="Write", toolInput={"file_path": ".env", "content": "..."}
188
+ 2. Wait for decision
189
+ 3. Only write if decision="approved"
190
+ ```
191
+
192
+ ### Reporting an operation (no Hook)
193
+
194
+ ```
195
+ 1. Execute: pnpm install
196
+ 2. Call hopping.report: toolName="Bash", toolParams={"command":"pnpm install"},
197
+ result="installed 23 packages", success=true, duration=4521
198
+ ```
199
+
200
+ ### Pending operation (user away, returns to computer)
201
+
202
+ ```
203
+ 1. Call hopping.check: toolName="Bash", toolInput={"command": "rm -rf /var/data/old-backups"}
204
+ 2. decision="pending" (remote approval timed out, critical risk)
205
+ 3. Tell user: "⏳ A critical operation is pending your approval:
206
+ - Tool: Bash
207
+ - Command: rm -rf /var/data/old-backups
208
+ Do you approve or deny?"
209
+ 4. User says "approve"
210
+ 5. Call hopping.respond: approvalId="abc-123", decision="approved", reason="User approved locally"
211
+ 6. Response: { success: true, source: "local" }
212
+ 7. Proceed with the Bash command
213
+ ```
214
+
215
+ ### Session start check
216
+
217
+ ```
218
+ 1. Call hopping.status: query="all"
219
+ 2. If connectedToCloud=false → note that phone approval is unavailable
220
+ 3. Proceed normally — local policies still apply
221
+ ```
@@ -0,0 +1,60 @@
1
+ import { Logger } from '../utils/logger';
2
+ /**
3
+ * waitForApproval() 回傳的結果
4
+ * - timedOut=true: 超時(未收到 Mobile 回應)
5
+ * - timedOut=false: 正常 resolve(收到 Mobile 回應或 clearAll)
6
+ */
7
+ export interface ApprovalResult {
8
+ approved: boolean;
9
+ reason?: string;
10
+ timedOut: boolean;
11
+ /** 決策來源(如 'mobile'、'local_dialog'、'local_respond') */
12
+ source?: string;
13
+ /** Rich response: 用戶選擇的選項 ID(responseType='choice' 時) */
14
+ selectedOption?: string;
15
+ /** Rich response: 用戶輸入的自由文字(responseType='freetext' 時) */
16
+ freetext?: string;
17
+ /** Rich response: isOther 選項的自訂文字 */
18
+ customText?: string;
19
+ }
20
+ /** Rich response 欄位(從 PermissionResponsePayload 或 Mobile 回傳) */
21
+ export interface RichResponseFields {
22
+ selectedOption?: string;
23
+ freetext?: string;
24
+ customText?: string;
25
+ }
26
+ /**
27
+ * Approval Manager
28
+ * 管理待審核請求、超時處理、決策回傳
29
+ */
30
+ export declare class ApprovalManager {
31
+ private pending;
32
+ private logger;
33
+ private defaultTimeoutMs;
34
+ constructor(logger: Logger, defaultTimeoutMs?: number);
35
+ /**
36
+ * 建立新的審核請求,回傳 Promise 等待用戶決策
37
+ */
38
+ waitForApproval(requestId: string, timeoutMs?: number): Promise<ApprovalResult>;
39
+ /**
40
+ * 處理用戶的審核決策
41
+ */
42
+ resolveApproval(requestId: string, approved: boolean, reason?: string, source?: string, richResponse?: RichResponseFields): boolean;
43
+ /**
44
+ * 檢查特定請求是否仍在 pending map 中
45
+ */
46
+ isPending(requestId: string): boolean;
47
+ /**
48
+ * 取得所有待審核的請求 ID
49
+ */
50
+ getPendingIds(): string[];
51
+ /**
52
+ * 待審核數量
53
+ */
54
+ get pendingCount(): number;
55
+ /**
56
+ * 清除所有待審核(例如關閉時)
57
+ */
58
+ clearAll(): void;
59
+ }
60
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/approval/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AASD;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAS;gBAErB,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAE,MAAc;IAK5D;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAyB/E;;OAEG;IACH,eAAe,CACb,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,kBAAkB,GAChC,OAAO;IAgCV;;OAEG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;IAIzB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACH,QAAQ,IAAI,IAAI;CAQjB"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApprovalManager = void 0;
4
+ /**
5
+ * Approval Manager
6
+ * 管理待審核請求、超時處理、決策回傳
7
+ */
8
+ class ApprovalManager {
9
+ pending = new Map();
10
+ logger;
11
+ defaultTimeoutMs;
12
+ constructor(logger, defaultTimeoutMs = 60000) {
13
+ this.logger = logger;
14
+ this.defaultTimeoutMs = defaultTimeoutMs;
15
+ }
16
+ /**
17
+ * 建立新的審核請求,回傳 Promise 等待用戶決策
18
+ */
19
+ waitForApproval(requestId, timeoutMs) {
20
+ const timeout = timeoutMs ?? this.defaultTimeoutMs;
21
+ return new Promise((resolve) => {
22
+ const timer = setTimeout(() => {
23
+ this.logger.warn('Approval timeout', { requestId, timeoutMs: timeout });
24
+ this.pending.delete(requestId);
25
+ resolve({ approved: false, reason: 'Approval timeout', timedOut: true });
26
+ }, timeout);
27
+ this.pending.set(requestId, {
28
+ id: requestId,
29
+ resolve,
30
+ timeout: timer,
31
+ createdAt: Date.now(),
32
+ });
33
+ this.logger.info('Approval request created', {
34
+ requestId,
35
+ timeoutMs: timeout,
36
+ pendingCount: this.pending.size,
37
+ });
38
+ });
39
+ }
40
+ /**
41
+ * 處理用戶的審核決策
42
+ */
43
+ resolveApproval(requestId, approved, reason, source, richResponse) {
44
+ const pending = this.pending.get(requestId);
45
+ if (!pending) {
46
+ this.logger.warn('No pending approval found', { requestId });
47
+ return false;
48
+ }
49
+ clearTimeout(pending.timeout);
50
+ this.pending.delete(requestId);
51
+ const waitTimeMs = Date.now() - pending.createdAt;
52
+ this.logger.info('Approval resolved', {
53
+ requestId,
54
+ approved,
55
+ reason,
56
+ source,
57
+ waitTimeMs,
58
+ });
59
+ pending.resolve({
60
+ approved,
61
+ reason,
62
+ timedOut: false,
63
+ source,
64
+ selectedOption: richResponse?.selectedOption,
65
+ freetext: richResponse?.freetext,
66
+ customText: richResponse?.customText,
67
+ });
68
+ return true;
69
+ }
70
+ /**
71
+ * 檢查特定請求是否仍在 pending map 中
72
+ */
73
+ isPending(requestId) {
74
+ return this.pending.has(requestId);
75
+ }
76
+ /**
77
+ * 取得所有待審核的請求 ID
78
+ */
79
+ getPendingIds() {
80
+ return Array.from(this.pending.keys());
81
+ }
82
+ /**
83
+ * 待審核數量
84
+ */
85
+ get pendingCount() {
86
+ return this.pending.size;
87
+ }
88
+ /**
89
+ * 清除所有待審核(例如關閉時)
90
+ */
91
+ clearAll() {
92
+ for (const [, pending] of this.pending.entries()) {
93
+ clearTimeout(pending.timeout);
94
+ pending.resolve({ approved: false, reason: 'System shutdown', timedOut: false });
95
+ }
96
+ this.pending.clear();
97
+ this.logger.info('All pending approvals cleared');
98
+ }
99
+ }
100
+ exports.ApprovalManager = ApprovalManager;
101
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/approval/manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { RiskLevel } from '@hopping/shared';
2
+ /**
3
+ * SessionMemory — Hub in-memory "don't ask again" store (S4-HUB-DONTASK-001).
4
+ *
5
+ * Records (toolName, riskLevel) pairs when Mobile approves with reason='dont_ask_again'.
6
+ * Subsequent hopping.check / IpcWatcher away-mode calls for the same tool auto-approve
7
+ * if the current riskLevel is less than or equal to the stored ceiling.
8
+ *
9
+ * Cleared on Hub process restart (session scope, intentionally not persisted).
10
+ */
11
+ export declare class SessionMemory {
12
+ private readonly allowedTools;
13
+ /**
14
+ * Add a tool to the don't-ask-again list.
15
+ * Trust expansion: if the tool already exists with a lower riskLevel, upgrade to the higher value.
16
+ * Trust does NOT shrink: adding a lower riskLevel than the existing entry is a no-op.
17
+ */
18
+ add(toolName: string, riskLevel: RiskLevel): void;
19
+ /**
20
+ * Check if a tool call should be auto-approved.
21
+ * Returns true only if the tool is in the list AND currentRiskLevel <= storedRiskLevel.
22
+ */
23
+ has(toolName: string, currentRiskLevel: RiskLevel): boolean;
24
+ /** Remove a specific tool from the list. Returns true if the entry existed. */
25
+ remove(toolName: string): boolean;
26
+ /** Clear all entries (simulates Hub restart for testing). */
27
+ clear(): void;
28
+ /** Number of tools currently in the list. */
29
+ get size(): number;
30
+ /** Return all entries as an array (used by hopping.status). */
31
+ getAll(): Array<{
32
+ toolName: string;
33
+ riskLevel: RiskLevel;
34
+ addedAt: number;
35
+ }>;
36
+ }
37
+ //# sourceMappingURL=session-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-memory.d.ts","sourceRoot":"","sources":["../../src/approval/session-memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQjD;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgE;IAE7F;;;;OAIG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI;IAMjD;;;OAGG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,GAAG,OAAO;IAM3D,+EAA+E;IAC/E,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIjC,6DAA6D;IAC7D,KAAK,IAAI,IAAI;IAIb,6CAA6C;IAC7C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,+DAA+D;IAC/D,MAAM,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAO7E"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionMemory = void 0;
4
+ /**
5
+ * Risk level ordering for ceiling comparison.
6
+ * Lower index = lower risk. has() returns true only if currentRisk <= storedRisk.
7
+ */
8
+ const RISK_ORDER = { low: 0, medium: 1, high: 2, critical: 3 };
9
+ /**
10
+ * SessionMemory — Hub in-memory "don't ask again" store (S4-HUB-DONTASK-001).
11
+ *
12
+ * Records (toolName, riskLevel) pairs when Mobile approves with reason='dont_ask_again'.
13
+ * Subsequent hopping.check / IpcWatcher away-mode calls for the same tool auto-approve
14
+ * if the current riskLevel is less than or equal to the stored ceiling.
15
+ *
16
+ * Cleared on Hub process restart (session scope, intentionally not persisted).
17
+ */
18
+ class SessionMemory {
19
+ allowedTools = new Map();
20
+ /**
21
+ * Add a tool to the don't-ask-again list.
22
+ * Trust expansion: if the tool already exists with a lower riskLevel, upgrade to the higher value.
23
+ * Trust does NOT shrink: adding a lower riskLevel than the existing entry is a no-op.
24
+ */
25
+ add(toolName, riskLevel) {
26
+ const existing = this.allowedTools.get(toolName);
27
+ if (existing && RISK_ORDER[existing.riskLevel] >= RISK_ORDER[riskLevel])
28
+ return;
29
+ this.allowedTools.set(toolName, { riskLevel, addedAt: Date.now() });
30
+ }
31
+ /**
32
+ * Check if a tool call should be auto-approved.
33
+ * Returns true only if the tool is in the list AND currentRiskLevel <= storedRiskLevel.
34
+ */
35
+ has(toolName, currentRiskLevel) {
36
+ const entry = this.allowedTools.get(toolName);
37
+ if (!entry)
38
+ return false;
39
+ return RISK_ORDER[currentRiskLevel] <= RISK_ORDER[entry.riskLevel];
40
+ }
41
+ /** Remove a specific tool from the list. Returns true if the entry existed. */
42
+ remove(toolName) {
43
+ return this.allowedTools.delete(toolName);
44
+ }
45
+ /** Clear all entries (simulates Hub restart for testing). */
46
+ clear() {
47
+ this.allowedTools.clear();
48
+ }
49
+ /** Number of tools currently in the list. */
50
+ get size() {
51
+ return this.allowedTools.size;
52
+ }
53
+ /** Return all entries as an array (used by hopping.status). */
54
+ getAll() {
55
+ return Array.from(this.allowedTools.entries()).map(([toolName, { riskLevel, addedAt }]) => ({
56
+ toolName,
57
+ riskLevel,
58
+ addedAt,
59
+ }));
60
+ }
61
+ }
62
+ exports.SessionMemory = SessionMemory;
63
+ //# sourceMappingURL=session-memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-memory.js","sourceRoot":"","sources":["../../src/approval/session-memory.ts"],"names":[],"mappings":";;;AAEA;;;GAGG;AACH,MAAM,UAAU,GAA8B,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AAE1F;;;;;;;;GAQG;AACH,MAAa,aAAa;IACP,YAAY,GAAG,IAAI,GAAG,EAAqD,CAAC;IAE7F;;;;OAIG;IACH,GAAG,CAAC,QAAgB,EAAE,SAAoB;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO;QAChF,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACtE,CAAC;IAED;;;OAGG;IACH,GAAG,CAAC,QAAgB,EAAE,gBAA2B;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,OAAO,UAAU,CAAC,gBAAgB,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACrE,CAAC;IAED,+EAA+E;IAC/E,MAAM,CAAC,QAAgB;QACrB,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED,6DAA6D;IAC7D,KAAK;QACH,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED,6CAA6C;IAC7C,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;IAChC,CAAC;IAED,+DAA+D;IAC/D,MAAM;QACJ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1F,QAAQ;YACR,SAAS;YACT,OAAO;SACR,CAAC,CAAC,CAAC;IACN,CAAC;CACF;AA/CD,sCA+CC"}
@@ -0,0 +1,57 @@
1
+ import type { DetectedAgent } from './path-resolver';
2
+ export interface McpServerEntry {
3
+ type?: string;
4
+ command: string;
5
+ args: string[];
6
+ env?: Record<string, string>;
7
+ }
8
+ export interface WriteResult {
9
+ path: string;
10
+ action: 'created' | 'updated';
11
+ backedUp: boolean;
12
+ }
13
+ export interface WriteOptions {
14
+ hubCommand: string;
15
+ hubArgs: string[];
16
+ devMode: string;
17
+ preHookPath: string;
18
+ postHookPath: string;
19
+ cursorAdapterPath: string;
20
+ scope: 'project' | 'user';
21
+ skipHooks: boolean;
22
+ dryRun: boolean;
23
+ }
24
+ /**
25
+ * 寫入 Claude Code / Cursor MCP config(JSON 格式)
26
+ * - Claude Code: .mcp.json(project) / ~/.claude.json(user)
27
+ * - Cursor: .cursor/mcp.json(project) / ~/.cursor/mcp.json(user)
28
+ */
29
+ export declare function writeMcpJson(agent: DetectedAgent, options: WriteOptions): Promise<WriteResult>;
30
+ /**
31
+ * 寫入 Codex MCP config(TOML 格式)
32
+ * - .codex/config.toml(project / user)
33
+ */
34
+ export declare function writeMcpToml(agent: DetectedAgent, options: WriteOptions): Promise<WriteResult>;
35
+ /**
36
+ * 寫入 MCP config(根據 agent 自動分派 JSON 或 TOML)
37
+ */
38
+ export declare function writeMcpConfig(agent: DetectedAgent, options: WriteOptions): Promise<WriteResult>;
39
+ /**
40
+ * 寫入 Claude Code Hooks config(.claude/settings.json,巢狀格式,PascalCase)
41
+ */
42
+ export declare function writeClaudeCodeHooks(hooksConfigPath: string, options: WriteOptions): Promise<WriteResult>;
43
+ /**
44
+ * 檢查 cursor-adapter.js 是否已實作(非空檔案)
45
+ */
46
+ export declare function isCursorAdapterReady(adapterPath: string): boolean;
47
+ /**
48
+ * 寫入 Cursor Hooks config(.cursor/hooks.json,扁平格式,camelCase,version: 1)
49
+ * 只有在 cursor-adapter.js 存在且已實作時才寫入
50
+ */
51
+ export declare function writeCursorHooks(hooksConfigPath: string, cursorAdapterPath: string, options: WriteOptions): Promise<WriteResult | null>;
52
+ /**
53
+ * 寫入 Hooks config(根據 agent 自動分派)
54
+ * 回傳 WriteResult(成功)、null(跳過,如 Cursor adapter 未實作)、或 'unsupported'(如 Codex)
55
+ */
56
+ export declare function writeHooksConfig(agent: DetectedAgent, options: WriteOptions): Promise<WriteResult | null | 'unsupported'>;
57
+ //# sourceMappingURL=config-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-writer.d.ts","sourceRoot":"","sources":["../../src/cli/config-writer.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAOrD,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB;AA6HD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CAStB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CAetB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CAKtB;AASD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CA+CtB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAWjE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,EACzB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAoC7B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,GAAG,IAAI,GAAG,aAAa,CAAC,CAuB7C"}