@a5c-ai/tasks-mux 5.0.1-staging.a2e883985f51

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 (175) hide show
  1. package/README.md +103 -0
  2. package/dist/auth/forge-interface.d.ts +67 -0
  3. package/dist/auth/forge-interface.d.ts.map +1 -0
  4. package/dist/auth/forge-interface.js +69 -0
  5. package/dist/auth/github-app.d.ts +64 -0
  6. package/dist/auth/github-app.d.ts.map +1 -0
  7. package/dist/auth/github-app.js +141 -0
  8. package/dist/auth/github-oauth.d.ts +27 -0
  9. package/dist/auth/github-oauth.d.ts.map +1 -0
  10. package/dist/auth/github-oauth.js +89 -0
  11. package/dist/auth/index.d.ts +8 -0
  12. package/dist/auth/index.d.ts.map +1 -0
  13. package/dist/auth/index.js +14 -0
  14. package/dist/auth/jwt.d.ts +24 -0
  15. package/dist/auth/jwt.d.ts.map +1 -0
  16. package/dist/auth/jwt.js +43 -0
  17. package/dist/auth/middleware.d.ts +22 -0
  18. package/dist/auth/middleware.d.ts.map +1 -0
  19. package/dist/auth/middleware.js +36 -0
  20. package/dist/auth/ssh-keys.d.ts +21 -0
  21. package/dist/auth/ssh-keys.d.ts.map +1 -0
  22. package/dist/auth/ssh-keys.js +59 -0
  23. package/dist/auth/types.d.ts +165 -0
  24. package/dist/auth/types.d.ts.map +1 -0
  25. package/dist/auth/types.js +53 -0
  26. package/dist/backend.d.ts +117 -0
  27. package/dist/backend.d.ts.map +1 -0
  28. package/dist/backend.js +15 -0
  29. package/dist/backends/git-native.d.ts +51 -0
  30. package/dist/backends/git-native.d.ts.map +1 -0
  31. package/dist/backends/git-native.js +324 -0
  32. package/dist/backends/github-issues.d.ts +77 -0
  33. package/dist/backends/github-issues.d.ts.map +1 -0
  34. package/dist/backends/github-issues.js +796 -0
  35. package/dist/backends/index.d.ts +48 -0
  36. package/dist/backends/index.d.ts.map +1 -0
  37. package/dist/backends/index.js +139 -0
  38. package/dist/backends/server.d.ts +41 -0
  39. package/dist/backends/server.d.ts.map +1 -0
  40. package/dist/backends/server.js +298 -0
  41. package/dist/cli/auth-store.d.ts +49 -0
  42. package/dist/cli/auth-store.d.ts.map +1 -0
  43. package/dist/cli/auth-store.js +150 -0
  44. package/dist/cli/client-config.d.ts +10 -0
  45. package/dist/cli/client-config.d.ts.map +1 -0
  46. package/dist/cli/client-config.js +87 -0
  47. package/dist/cli/commands/ask.d.ts +3 -0
  48. package/dist/cli/commands/ask.d.ts.map +1 -0
  49. package/dist/cli/commands/ask.js +171 -0
  50. package/dist/cli/commands/auth.d.ts +3 -0
  51. package/dist/cli/commands/auth.d.ts.map +1 -0
  52. package/dist/cli/commands/auth.js +510 -0
  53. package/dist/cli/commands/breakpoints.d.ts +3 -0
  54. package/dist/cli/commands/breakpoints.d.ts.map +1 -0
  55. package/dist/cli/commands/breakpoints.js +152 -0
  56. package/dist/cli/commands/responder-loop.d.ts +3 -0
  57. package/dist/cli/commands/responder-loop.d.ts.map +1 -0
  58. package/dist/cli/commands/responder-loop.js +78 -0
  59. package/dist/cli/commands/responders.d.ts +3 -0
  60. package/dist/cli/commands/responders.d.ts.map +1 -0
  61. package/dist/cli/commands/responders.js +74 -0
  62. package/dist/cli/commands/server.d.ts +3 -0
  63. package/dist/cli/commands/server.d.ts.map +1 -0
  64. package/dist/cli/commands/server.js +34 -0
  65. package/dist/cli/index.d.ts +4 -0
  66. package/dist/cli/index.d.ts.map +1 -0
  67. package/dist/cli/index.js +9 -0
  68. package/dist/cli/output.d.ts +26 -0
  69. package/dist/cli/output.d.ts.map +1 -0
  70. package/dist/cli/output.js +143 -0
  71. package/dist/cli/program.d.ts +6 -0
  72. package/dist/cli/program.d.ts.map +1 -0
  73. package/dist/cli/program.js +32 -0
  74. package/dist/client/answer-poller.d.ts +52 -0
  75. package/dist/client/answer-poller.d.ts.map +1 -0
  76. package/dist/client/answer-poller.js +199 -0
  77. package/dist/client/auth-client.d.ts +200 -0
  78. package/dist/client/auth-client.d.ts.map +1 -0
  79. package/dist/client/auth-client.js +309 -0
  80. package/dist/client/breakpoint-router.d.ts +45 -0
  81. package/dist/client/breakpoint-router.d.ts.map +1 -0
  82. package/dist/client/breakpoint-router.js +45 -0
  83. package/dist/client/index.d.ts +17 -0
  84. package/dist/client/index.d.ts.map +1 -0
  85. package/dist/client/index.js +16 -0
  86. package/dist/client/profile-validator.d.ts +34 -0
  87. package/dist/client/profile-validator.d.ts.map +1 -0
  88. package/dist/client/profile-validator.js +89 -0
  89. package/dist/client/responder-client.d.ts +39 -0
  90. package/dist/client/responder-client.d.ts.map +1 -0
  91. package/dist/client/responder-client.js +72 -0
  92. package/dist/client/responder-matcher.d.ts +49 -0
  93. package/dist/client/responder-matcher.d.ts.map +1 -0
  94. package/dist/client/responder-matcher.js +226 -0
  95. package/dist/client/server-client.d.ts +124 -0
  96. package/dist/client/server-client.d.ts.map +1 -0
  97. package/dist/client/server-client.js +266 -0
  98. package/dist/client/timeout-manager.d.ts +47 -0
  99. package/dist/client/timeout-manager.d.ts.map +1 -0
  100. package/dist/client/timeout-manager.js +77 -0
  101. package/dist/config.d.ts +20 -0
  102. package/dist/config.d.ts.map +1 -0
  103. package/dist/config.js +93 -0
  104. package/dist/harness/index.d.ts +4 -0
  105. package/dist/harness/index.d.ts.map +1 -0
  106. package/dist/harness/index.js +2 -0
  107. package/dist/harness/interaction-provider.d.ts +71 -0
  108. package/dist/harness/interaction-provider.d.ts.map +1 -0
  109. package/dist/harness/interaction-provider.js +124 -0
  110. package/dist/harness/routing-rules.d.ts +7 -0
  111. package/dist/harness/routing-rules.d.ts.map +1 -0
  112. package/dist/harness/routing-rules.js +37 -0
  113. package/dist/index.d.ts +19 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/dist/index.js +26 -0
  116. package/dist/mcp/backend-resolver.d.ts +43 -0
  117. package/dist/mcp/backend-resolver.d.ts.map +1 -0
  118. package/dist/mcp/backend-resolver.js +111 -0
  119. package/dist/mcp/http-transport.d.ts +37 -0
  120. package/dist/mcp/http-transport.d.ts.map +1 -0
  121. package/dist/mcp/http-transport.js +103 -0
  122. package/dist/mcp/index.d.ts +14 -0
  123. package/dist/mcp/index.d.ts.map +1 -0
  124. package/dist/mcp/index.js +11 -0
  125. package/dist/mcp/server.d.ts +20 -0
  126. package/dist/mcp/server.d.ts.map +1 -0
  127. package/dist/mcp/server.js +121 -0
  128. package/dist/mcp/tools/answer-breakpoint.d.ts +32 -0
  129. package/dist/mcp/tools/answer-breakpoint.d.ts.map +1 -0
  130. package/dist/mcp/tools/answer-breakpoint.js +45 -0
  131. package/dist/mcp/tools/ask-breakpoint.d.ts +58 -0
  132. package/dist/mcp/tools/ask-breakpoint.d.ts.map +1 -0
  133. package/dist/mcp/tools/ask-breakpoint.js +78 -0
  134. package/dist/mcp/tools/check-status.d.ts +16 -0
  135. package/dist/mcp/tools/check-status.d.ts.map +1 -0
  136. package/dist/mcp/tools/check-status.js +18 -0
  137. package/dist/mcp/tools/claim-breakpoint.d.ts +18 -0
  138. package/dist/mcp/tools/claim-breakpoint.d.ts.map +1 -0
  139. package/dist/mcp/tools/claim-breakpoint.js +28 -0
  140. package/dist/mcp/tools/list-breakpoints.d.ts +16 -0
  141. package/dist/mcp/tools/list-breakpoints.d.ts.map +1 -0
  142. package/dist/mcp/tools/list-breakpoints.js +14 -0
  143. package/dist/mcp/tools/list-responders.d.ts +18 -0
  144. package/dist/mcp/tools/list-responders.d.ts.map +1 -0
  145. package/dist/mcp/tools/list-responders.js +37 -0
  146. package/dist/mcp/tools/poll-breakpoints.d.ts +18 -0
  147. package/dist/mcp/tools/poll-breakpoints.d.ts.map +1 -0
  148. package/dist/mcp/tools/poll-breakpoints.js +36 -0
  149. package/dist/mcp/tools/verify-answer.d.ts +16 -0
  150. package/dist/mcp/tools/verify-answer.d.ts.map +1 -0
  151. package/dist/mcp/tools/verify-answer.js +38 -0
  152. package/dist/proven/index.d.ts +5 -0
  153. package/dist/proven/index.d.ts.map +1 -0
  154. package/dist/proven/index.js +3 -0
  155. package/dist/proven/keys.d.ts +33 -0
  156. package/dist/proven/keys.d.ts.map +1 -0
  157. package/dist/proven/keys.js +117 -0
  158. package/dist/proven/sign.d.ts +16 -0
  159. package/dist/proven/sign.d.ts.map +1 -0
  160. package/dist/proven/sign.js +60 -0
  161. package/dist/proven/types.d.ts +26 -0
  162. package/dist/proven/types.d.ts.map +1 -0
  163. package/dist/proven/types.js +5 -0
  164. package/dist/proven/verify.d.ts +6 -0
  165. package/dist/proven/verify.d.ts.map +1 -0
  166. package/dist/proven/verify.js +58 -0
  167. package/dist/types.d.ts +4034 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/types.js +244 -0
  170. package/package.json +86 -0
  171. package/responder/README.md +42 -0
  172. package/responder/backend-responder.json +9 -0
  173. package/responder/devops-responder.json +9 -0
  174. package/responder/frontend-responder.json +9 -0
  175. package/responder/schema.json +52 -0
@@ -0,0 +1,510 @@
1
+ import { Command } from "commander";
2
+ import { createServer } from "node:http";
3
+ import { randomBytes } from "node:crypto";
4
+ import { mkdirSync, writeFileSync, readdirSync, statSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { GitHubOAuthClient, generateSSHKeyPair, } from "../../auth/index.js";
7
+ import { loadAuthState, saveAuthState, clearAuthState, isTokenExpired, getKeysDir, loadClientConfig, saveClientConfig, } from "../auth-store.js";
8
+ import { printError } from "../output.js";
9
+ import { createCliServerClient, resolveServerUrl, resolveAuthToken } from "../client-config.js";
10
+ // ── Constants ─────────────────────────────────────────────────────────────
11
+ const DEFAULT_OAUTH_CONFIG = {
12
+ clientId: process.env.BMUX_GITHUB_CLIENT_ID ?? "",
13
+ clientSecret: process.env.BMUX_GITHUB_CLIENT_SECRET ?? "",
14
+ callbackUrl: "http://localhost:0/callback",
15
+ scopes: ["read:user", "user:email", "read:org", "repo"],
16
+ };
17
+ // ── Command creation ─────────────────────────────────────────────────────
18
+ export function createAuthCommand() {
19
+ const cmd = new Command("auth").description("Authentication and key management");
20
+ cmd.addCommand(createLoginCommand());
21
+ cmd.addCommand(createServerConfigCommand());
22
+ cmd.addCommand(createTokenCommand());
23
+ cmd.addCommand(createLogoutCommand());
24
+ cmd.addCommand(createStatusCommand());
25
+ cmd.addCommand(createKeygenCommand());
26
+ cmd.addCommand(createKeyPushCommand());
27
+ cmd.addCommand(createKeysCommand());
28
+ return cmd;
29
+ }
30
+ // ── login ────────────────────────────────────────────────────────────────
31
+ function createLoginCommand() {
32
+ return new Command("login")
33
+ .description("Authenticate with GitHub OAuth")
34
+ .action(async (_opts, command) => {
35
+ const allOpts = command.optsWithGlobals();
36
+ const jsonMode = allOpts.json === true;
37
+ try {
38
+ const state = randomBytes(16).toString("hex");
39
+ // Create a one-shot local HTTP server to receive the OAuth callback
40
+ const { port, authCode } = await startCallbackServer(state);
41
+ const oauthConfig = {
42
+ ...DEFAULT_OAUTH_CONFIG,
43
+ callbackUrl: `http://localhost:${port}/callback`,
44
+ };
45
+ const oauthClient = new GitHubOAuthClient(oauthConfig);
46
+ const authUrl = oauthClient.getAuthorizationUrl(state);
47
+ // Try to open the browser; fall back to printing the URL
48
+ let browserOpened = false;
49
+ try {
50
+ const { default: open } = await import("open");
51
+ await open(authUrl);
52
+ browserOpened = true;
53
+ }
54
+ catch {
55
+ // open package not available or failed
56
+ }
57
+ if (!jsonMode) {
58
+ if (browserOpened) {
59
+ console.log("Opening browser for authentication...");
60
+ }
61
+ else {
62
+ console.log("Open the following URL in your browser to authenticate:");
63
+ }
64
+ console.log(`\n ${authUrl}\n`);
65
+ console.log("Waiting for callback...");
66
+ }
67
+ // Wait for the authorization code
68
+ const code = await authCode;
69
+ // Exchange code for token and fetch user profile
70
+ const tokenResult = await oauthClient.exchangeCode(code);
71
+ const user = await oauthClient.getUserProfile(tokenResult.accessToken);
72
+ const expiresAt = new Date(Date.now() + 3600 * 1000).toISOString();
73
+ saveAuthState({
74
+ accessToken: tokenResult.accessToken,
75
+ refreshToken: tokenResult.accessToken, // GitHub OAuth doesn't issue refresh tokens; store access token
76
+ expiresAt,
77
+ user,
78
+ });
79
+ if (allOpts.serverUrl) {
80
+ saveClientConfig({ serverUrl: resolveServerUrl(allOpts.serverUrl) });
81
+ }
82
+ if (jsonMode) {
83
+ console.log(JSON.stringify({ status: "logged_in", user }, null, 2));
84
+ }
85
+ else {
86
+ console.log(`Logged in as ${user.name} (${user.login})`);
87
+ }
88
+ }
89
+ catch (error) {
90
+ printError(error, jsonMode);
91
+ process.exitCode = 1;
92
+ }
93
+ });
94
+ }
95
+ function createServerConfigCommand() {
96
+ const cmd = new Command("server").description("Manage the saved tasks-mux server URL");
97
+ cmd
98
+ .command("set")
99
+ .argument("<url>", "Breakpoints-mux server URL")
100
+ .description("Save a default server URL in ~/.tasks-mux/config.json")
101
+ .action((url, _opts, command) => {
102
+ const allOpts = command.optsWithGlobals();
103
+ const jsonMode = allOpts.json === true;
104
+ try {
105
+ saveClientConfig({ serverUrl: resolveServerUrl(url) });
106
+ if (jsonMode) {
107
+ console.log(JSON.stringify({
108
+ status: "saved",
109
+ key: "serverUrl",
110
+ value: resolveServerUrl(url),
111
+ source: "~/.tasks-mux/config.json",
112
+ }));
113
+ }
114
+ else {
115
+ console.log(`Saved default server URL to ~/.tasks-mux/config.json: ${resolveServerUrl(url)}`);
116
+ }
117
+ }
118
+ catch (error) {
119
+ printError(error, jsonMode);
120
+ process.exitCode = 1;
121
+ }
122
+ });
123
+ cmd
124
+ .command("clear")
125
+ .description("Remove the saved default server URL from ~/.tasks-mux/config.json")
126
+ .action((_opts, command) => {
127
+ const allOpts = command.optsWithGlobals();
128
+ const jsonMode = allOpts.json === true;
129
+ try {
130
+ saveClientConfig({ serverUrl: "" });
131
+ if (jsonMode) {
132
+ console.log(JSON.stringify({ status: "cleared", key: "serverUrl", source: "~/.tasks-mux/config.json" }));
133
+ }
134
+ else {
135
+ console.log("Cleared saved default server URL from ~/.tasks-mux/config.json");
136
+ }
137
+ }
138
+ catch (error) {
139
+ printError(error, jsonMode);
140
+ process.exitCode = 1;
141
+ }
142
+ });
143
+ return cmd;
144
+ }
145
+ function createTokenCommand() {
146
+ const cmd = new Command("token").description("Manage a saved bearer token for CLI and MCP use");
147
+ cmd
148
+ .command("set")
149
+ .argument("<token>", "Bearer token value")
150
+ .description("Save a bearer token in ~/.tasks-mux/config.json")
151
+ .action((token, _opts, command) => {
152
+ const allOpts = command.optsWithGlobals();
153
+ const jsonMode = allOpts.json === true;
154
+ try {
155
+ saveClientConfig({
156
+ authToken: token,
157
+ serverUrl: allOpts.serverUrl ? resolveServerUrl(allOpts.serverUrl) : undefined,
158
+ });
159
+ if (jsonMode) {
160
+ console.log(JSON.stringify({ status: "saved", source: "~/.tasks-mux/config.json" }));
161
+ }
162
+ else {
163
+ console.log("Saved bearer token to ~/.tasks-mux/config.json");
164
+ }
165
+ }
166
+ catch (error) {
167
+ printError(error, jsonMode);
168
+ process.exitCode = 1;
169
+ }
170
+ });
171
+ cmd
172
+ .command("clear")
173
+ .description("Remove the saved bearer token from ~/.tasks-mux/config.json")
174
+ .action((_opts, command) => {
175
+ const allOpts = command.optsWithGlobals();
176
+ const jsonMode = allOpts.json === true;
177
+ try {
178
+ saveClientConfig({ authToken: "" });
179
+ if (jsonMode) {
180
+ console.log(JSON.stringify({ status: "cleared", source: "~/.tasks-mux/config.json" }));
181
+ }
182
+ else {
183
+ console.log("Cleared saved bearer token from ~/.tasks-mux/config.json");
184
+ }
185
+ }
186
+ catch (error) {
187
+ printError(error, jsonMode);
188
+ process.exitCode = 1;
189
+ }
190
+ });
191
+ return cmd;
192
+ }
193
+ // ── logout ───────────────────────────────────────────────────────────────
194
+ function createLogoutCommand() {
195
+ return new Command("logout")
196
+ .description("Clear stored authentication tokens")
197
+ .action((_opts, command) => {
198
+ const allOpts = command.optsWithGlobals();
199
+ const jsonMode = allOpts.json === true;
200
+ try {
201
+ clearAuthState();
202
+ saveClientConfig({ authToken: "" });
203
+ if (jsonMode) {
204
+ console.log(JSON.stringify({ status: "logged_out" }));
205
+ }
206
+ else {
207
+ console.log("Logged out. Authentication tokens cleared.");
208
+ }
209
+ }
210
+ catch (error) {
211
+ printError(error, jsonMode);
212
+ process.exitCode = 1;
213
+ }
214
+ });
215
+ }
216
+ // ── status ───────────────────────────────────────────────────────────────
217
+ function createStatusCommand() {
218
+ return new Command("status")
219
+ .description("Show current authentication status")
220
+ .action(async (_opts, command) => {
221
+ const allOpts = command.optsWithGlobals();
222
+ const jsonMode = allOpts.json === true;
223
+ try {
224
+ const auth = loadAuthState();
225
+ const config = loadClientConfig();
226
+ const token = await resolveAuthToken(allOpts.serverUrl, allOpts.authToken);
227
+ const serverUrl = resolveServerUrl(allOpts.serverUrl);
228
+ if (!token && !auth) {
229
+ if (jsonMode) {
230
+ console.log(JSON.stringify({ authenticated: false }));
231
+ }
232
+ else {
233
+ console.log("Not authenticated. Use `tasks-mux auth login`, `tasks-mux auth token set`, or BMUX_AUTH_TOKEN.");
234
+ }
235
+ return;
236
+ }
237
+ const client = await createCliServerClient({
238
+ serverUrl: allOpts.serverUrl,
239
+ authToken: allOpts.authToken,
240
+ });
241
+ const user = await client.get("/auth/me");
242
+ const expired = auth ? isTokenExpired(auth.expiresAt) : false;
243
+ const source = allOpts.authToken
244
+ ? "flag"
245
+ : process.env.BMUX_AUTH_TOKEN
246
+ ? "BMUX_AUTH_TOKEN"
247
+ : process.env.AUTH_TOKEN
248
+ ? "AUTH_TOKEN"
249
+ : config.authToken
250
+ ? "~/.tasks-mux/config.json"
251
+ : auth
252
+ ? "~/.tasks-mux/auth.json"
253
+ : "unknown";
254
+ if (jsonMode) {
255
+ console.log(JSON.stringify({
256
+ authenticated: true,
257
+ expired,
258
+ source,
259
+ user,
260
+ expiresAt: auth?.expiresAt,
261
+ serverUrl,
262
+ }, null, 2));
263
+ }
264
+ else {
265
+ console.log(`Authenticated as: ${user.name} (${user.login})`);
266
+ if (user.email) {
267
+ console.log(`Email: ${user.email}`);
268
+ }
269
+ console.log(`Server: ${serverUrl}`);
270
+ console.log(`Auth source: ${source}`);
271
+ if (auth?.expiresAt) {
272
+ console.log(`Token expires: ${auth.expiresAt}`);
273
+ }
274
+ console.log(`Status: ${expired ? "EXPIRED" : "Active"}`);
275
+ }
276
+ }
277
+ catch (error) {
278
+ printError(error, jsonMode);
279
+ process.exitCode = 1;
280
+ }
281
+ });
282
+ }
283
+ // ── keygen ───────────────────────────────────────────────────────────────
284
+ function createKeygenCommand() {
285
+ return new Command("keygen")
286
+ .description("Generate a new Ed25519 SSH key pair")
287
+ .action((_opts, command) => {
288
+ const allOpts = command.optsWithGlobals();
289
+ const jsonMode = allOpts.json === true;
290
+ try {
291
+ const keyPair = generateSSHKeyPair();
292
+ // Save private key to ~/.tasks-mux/keys/{fingerprint}.pem
293
+ const keysDir = getKeysDir();
294
+ mkdirSync(keysDir, { recursive: true });
295
+ const safeName = keyPair.fingerprint.replace(/[/:]/g, "_");
296
+ const privateKeyPath = join(keysDir, `${safeName}.pem`);
297
+ writeFileSync(privateKeyPath, keyPair.privateKey, {
298
+ encoding: "utf-8",
299
+ mode: 0o600,
300
+ });
301
+ if (jsonMode) {
302
+ console.log(JSON.stringify({
303
+ publicKey: keyPair.publicKey,
304
+ fingerprint: keyPair.fingerprint,
305
+ algorithm: keyPair.algorithm,
306
+ privateKeyPath,
307
+ createdAt: keyPair.createdAt,
308
+ }, null, 2));
309
+ }
310
+ else {
311
+ console.log(`Key pair generated successfully.`);
312
+ console.log(` Fingerprint: ${keyPair.fingerprint}`);
313
+ console.log(` Algorithm: ${keyPair.algorithm}`);
314
+ console.log(` Private key: ${privateKeyPath}`);
315
+ console.log(` Created: ${keyPair.createdAt}`);
316
+ console.log(``);
317
+ console.log(`Public key:`);
318
+ console.log(keyPair.publicKey);
319
+ }
320
+ }
321
+ catch (error) {
322
+ printError(error, jsonMode);
323
+ process.exitCode = 1;
324
+ }
325
+ });
326
+ }
327
+ // ── key-push ─────────────────────────────────────────────────────────────
328
+ function createKeyPushCommand() {
329
+ return new Command("key-push")
330
+ .description("Push public key to repository")
331
+ .option("--pr", "Create a pull request with the key", false)
332
+ .option("--key <fingerprint>", "Fingerprint of the key to push (defaults to most recent)")
333
+ .action(async (opts, command) => {
334
+ const allOpts = command.optsWithGlobals();
335
+ const localOpts = opts;
336
+ const jsonMode = allOpts.json === true;
337
+ const serverUrl = resolveServerUrl(allOpts.serverUrl);
338
+ try {
339
+ const keysDir = getKeysDir();
340
+ const keyFiles = listKeyFiles(keysDir);
341
+ if (keyFiles.length === 0) {
342
+ throw new Error("No keys found. Run `tasks-mux auth keygen` first.");
343
+ }
344
+ // Find the key to push
345
+ let keyFile;
346
+ if (localOpts.key) {
347
+ const safeName = localOpts.key.replace(/[/:]/g, "_");
348
+ const match = keyFiles.find((f) => f.includes(safeName));
349
+ if (!match) {
350
+ throw new Error(`Key not found for fingerprint: ${localOpts.key}`);
351
+ }
352
+ keyFile = match;
353
+ }
354
+ else {
355
+ // Use the most recently created key
356
+ keyFile = keyFiles[keyFiles.length - 1];
357
+ }
358
+ // For key-push, we need to communicate with the server
359
+ if (localOpts.pr) {
360
+ const response = await fetch(`${serverUrl}/api/v1/keys`, {
361
+ method: "POST",
362
+ headers: { "Content-Type": "application/json" },
363
+ body: JSON.stringify({
364
+ keyFile: keyFile.replace(".pem", ""),
365
+ createPR: true,
366
+ }),
367
+ });
368
+ if (!response.ok) {
369
+ throw new Error(`Server returned ${response.status}: ${response.statusText}`);
370
+ }
371
+ const result = (await response.json());
372
+ if (jsonMode) {
373
+ console.log(JSON.stringify(result, null, 2));
374
+ }
375
+ else {
376
+ if (result.prUrl) {
377
+ console.log(`Pull request created: ${result.prUrl}`);
378
+ }
379
+ else {
380
+ console.log(result.message ?? "Key pushed successfully.");
381
+ }
382
+ }
383
+ }
384
+ else {
385
+ if (jsonMode) {
386
+ console.log(JSON.stringify({ keyFile, status: "ready" }, null, 2));
387
+ }
388
+ else {
389
+ console.log(`Key ready to push: ${keyFile}`);
390
+ console.log(`Use --pr to create a pull request.`);
391
+ }
392
+ }
393
+ }
394
+ catch (error) {
395
+ printError(error, jsonMode);
396
+ process.exitCode = 1;
397
+ }
398
+ });
399
+ }
400
+ // ── keys ─────────────────────────────────────────────────────────────────
401
+ function createKeysCommand() {
402
+ return new Command("keys")
403
+ .description("List local SSH keys")
404
+ .action((_opts, command) => {
405
+ const allOpts = command.optsWithGlobals();
406
+ const jsonMode = allOpts.json === true;
407
+ try {
408
+ const keysDir = getKeysDir();
409
+ const keyFiles = listKeyFiles(keysDir);
410
+ if (keyFiles.length === 0) {
411
+ if (jsonMode) {
412
+ console.log(JSON.stringify([]));
413
+ }
414
+ else {
415
+ console.log("No keys found. Run `tasks-mux auth keygen` to generate a key pair.");
416
+ }
417
+ return;
418
+ }
419
+ const keys = keyFiles.map((file) => {
420
+ const filePath = join(keysDir, file);
421
+ const stats = statSync(filePath);
422
+ const fingerprint = file.replace(".pem", "").replace(/_/g, "/").replace(/SHA256\//, "SHA256:");
423
+ return {
424
+ fingerprint,
425
+ file,
426
+ createdAt: stats.birthtime.toISOString(),
427
+ };
428
+ });
429
+ if (jsonMode) {
430
+ console.log(JSON.stringify(keys, null, 2));
431
+ }
432
+ else {
433
+ console.log("Local SSH keys:\n");
434
+ for (const key of keys) {
435
+ console.log(` ${key.fingerprint}`);
436
+ console.log(` File: ${key.file}`);
437
+ console.log(` Created: ${key.createdAt}`);
438
+ console.log("");
439
+ }
440
+ }
441
+ }
442
+ catch (error) {
443
+ printError(error, jsonMode);
444
+ process.exitCode = 1;
445
+ }
446
+ });
447
+ }
448
+ // ── Internal helpers ─────────────────────────────────────────────────────
449
+ /**
450
+ * Start a temporary HTTP server to receive the OAuth callback.
451
+ * Returns the port and a promise that resolves with the authorization code.
452
+ */
453
+ function startCallbackServer(expectedState) {
454
+ return new Promise((resolveSetup) => {
455
+ let resolveCode;
456
+ let rejectCode;
457
+ const authCode = new Promise((resolve, reject) => {
458
+ resolveCode = resolve;
459
+ rejectCode = reject;
460
+ });
461
+ const server = createServer((req, res) => {
462
+ const url = new URL(req.url ?? "/", `http://localhost`);
463
+ if (url.pathname === "/callback") {
464
+ const code = url.searchParams.get("code");
465
+ const state = url.searchParams.get("state");
466
+ if (state !== expectedState) {
467
+ res.writeHead(400, { "Content-Type": "text/html" });
468
+ res.end("<h1>State mismatch. Authentication failed.</h1>");
469
+ rejectCode(new Error("OAuth state mismatch"));
470
+ server.close();
471
+ return;
472
+ }
473
+ if (!code) {
474
+ res.writeHead(400, { "Content-Type": "text/html" });
475
+ res.end("<h1>No authorization code received.</h1>");
476
+ rejectCode(new Error("No authorization code in callback"));
477
+ server.close();
478
+ return;
479
+ }
480
+ res.writeHead(200, { "Content-Type": "text/html" });
481
+ res.end("<h1>Authentication successful!</h1><p>You can close this window.</p>");
482
+ resolveCode(code);
483
+ // Close the server after a brief delay to let the response flush
484
+ setTimeout(() => server.close(), 500);
485
+ }
486
+ else {
487
+ res.writeHead(404);
488
+ res.end();
489
+ }
490
+ });
491
+ server.listen(0, () => {
492
+ const addr = server.address();
493
+ const port = typeof addr === "object" && addr !== null ? addr.port : 0;
494
+ resolveSetup({ port, authCode });
495
+ });
496
+ });
497
+ }
498
+ /**
499
+ * List .pem files in the keys directory.
500
+ */
501
+ function listKeyFiles(keysDir) {
502
+ try {
503
+ return readdirSync(keysDir)
504
+ .filter((f) => f.endsWith(".pem"))
505
+ .sort();
506
+ }
507
+ catch {
508
+ return [];
509
+ }
510
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createBreakpointsCommand(): Command;
3
+ //# sourceMappingURL=breakpoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breakpoints.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/breakpoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,wBAAgB,wBAAwB,IAAI,OAAO,CA+JlD"}
@@ -0,0 +1,152 @@
1
+ import { Command } from "commander";
2
+ import { ResponderClient, AnswerPoller, } from "../../client/index.js";
3
+ import { formatBreakpoint, formatAnswer, formatTable, printError } from "../output.js";
4
+ import { createCliServerClient } from "../client-config.js";
5
+ export function createBreakpointsCommand() {
6
+ const cmd = new Command("breakpoints").description("Manage breakpoints and answers");
7
+ cmd
8
+ .command("pending")
9
+ .description("List pending breakpoints for a responder")
10
+ .requiredOption("-e, --responder <responderId>", "Responder ID")
11
+ .action(async (opts, command) => {
12
+ const allOpts = command.optsWithGlobals();
13
+ const localOpts = opts;
14
+ const jsonMode = allOpts.json === true;
15
+ try {
16
+ const client = await createCliServerClient({
17
+ serverUrl: allOpts.serverUrl,
18
+ authToken: allOpts.authToken,
19
+ });
20
+ const responderClient = new ResponderClient(client, localOpts.responder);
21
+ const breakpoints = await responderClient.fetchPendingBreakpoints();
22
+ if (jsonMode) {
23
+ console.log(JSON.stringify(breakpoints, null, 2));
24
+ }
25
+ else if (breakpoints.length === 0) {
26
+ console.log("No pending breakpoints.");
27
+ }
28
+ else {
29
+ const rows = breakpoints.map((b) => [
30
+ b.id,
31
+ b.status,
32
+ b.text.length > 60 ? b.text.substring(0, 57) + "..." : b.text,
33
+ b.createdAt,
34
+ ]);
35
+ console.log(formatTable(rows, ["ID", "Status", "Breakpoint", "Created"]));
36
+ }
37
+ }
38
+ catch (error) {
39
+ printError(error, jsonMode);
40
+ process.exitCode = 1;
41
+ }
42
+ });
43
+ cmd
44
+ .command("answer")
45
+ .description("Submit an answer to a breakpoint")
46
+ .argument("<breakpointId>", "Breakpoint ID")
47
+ .requiredOption("-a, --answer <text>", "Answer text")
48
+ .requiredOption("-e, --responder <responderId>", "Responder ID")
49
+ .option("--confidence <number>", "Confidence level (0-100)", "80")
50
+ .action(async (breakpointId, opts, command) => {
51
+ const allOpts = command.optsWithGlobals();
52
+ const localOpts = opts;
53
+ const jsonMode = allOpts.json === true;
54
+ try {
55
+ const client = await createCliServerClient({
56
+ serverUrl: allOpts.serverUrl,
57
+ authToken: allOpts.authToken,
58
+ });
59
+ const responderClient = new ResponderClient(client, localOpts.responder);
60
+ const confidence = parseInt(localOpts.confidence ?? "80", 10);
61
+ const answer = await responderClient.submitAnswer(breakpointId, localOpts.answer, confidence);
62
+ if (jsonMode) {
63
+ console.log(JSON.stringify(answer, null, 2));
64
+ }
65
+ else {
66
+ console.log(formatAnswer(answer, false));
67
+ }
68
+ }
69
+ catch (error) {
70
+ printError(error, jsonMode);
71
+ process.exitCode = 1;
72
+ }
73
+ });
74
+ cmd
75
+ .command("status")
76
+ .description("Check breakpoint status")
77
+ .argument("<breakpointId>", "Breakpoint ID")
78
+ .action(async (breakpointId, _opts, command) => {
79
+ const allOpts = command.optsWithGlobals();
80
+ const jsonMode = allOpts.json === true;
81
+ try {
82
+ const client = await createCliServerClient({
83
+ serverUrl: allOpts.serverUrl,
84
+ authToken: allOpts.authToken,
85
+ });
86
+ const breakpoint = await client.getBreakpoint(breakpointId);
87
+ if (jsonMode) {
88
+ console.log(JSON.stringify(breakpoint, null, 2));
89
+ }
90
+ else {
91
+ console.log(formatBreakpoint(breakpoint, false));
92
+ if (breakpoint.answers.length > 0) {
93
+ console.log("\nAnswers:");
94
+ for (const answer of breakpoint.answers) {
95
+ console.log("");
96
+ console.log(formatAnswer(answer, false));
97
+ }
98
+ }
99
+ }
100
+ }
101
+ catch (error) {
102
+ printError(error, jsonMode);
103
+ process.exitCode = 1;
104
+ }
105
+ });
106
+ cmd
107
+ .command("poll")
108
+ .description("Poll for an answer to a breakpoint")
109
+ .argument("<breakpointId>", "Breakpoint ID")
110
+ .option("-t, --timeout <seconds>", "Timeout in seconds", "300")
111
+ .option("-i, --interval <seconds>", "Polling interval in seconds", "5")
112
+ .action(async (breakpointId, opts, command) => {
113
+ const allOpts = command.optsWithGlobals();
114
+ const localOpts = opts;
115
+ const jsonMode = allOpts.json === true;
116
+ try {
117
+ const client = await createCliServerClient({
118
+ serverUrl: allOpts.serverUrl,
119
+ authToken: allOpts.authToken,
120
+ });
121
+ const poller = new AnswerPoller(client);
122
+ const timeoutMs = parseInt(localOpts.timeout ?? "300", 10) * 1000;
123
+ const pollIntervalMs = parseInt(localOpts.interval ?? "5", 10) * 1000;
124
+ if (!jsonMode) {
125
+ console.log(`Polling for answer to ${breakpointId}...`);
126
+ }
127
+ const result = await poller.waitForAnswer(breakpointId, {
128
+ timeoutMs,
129
+ pollIntervalMs,
130
+ useSSE: true,
131
+ });
132
+ if (jsonMode) {
133
+ console.log(JSON.stringify(result, null, 2));
134
+ }
135
+ else {
136
+ console.log(formatBreakpoint(result.breakpoint, false));
137
+ if (result.answer) {
138
+ console.log("");
139
+ console.log(formatAnswer(result.answer, false));
140
+ }
141
+ else {
142
+ console.log("\nNo answer received within timeout.");
143
+ }
144
+ }
145
+ }
146
+ catch (error) {
147
+ printError(error, jsonMode);
148
+ process.exitCode = 1;
149
+ }
150
+ });
151
+ return cmd;
152
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createResponderLoopCommand(): Command;
3
+ //# sourceMappingURL=responder-loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responder-loop.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/responder-loop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,wBAAgB,0BAA0B,IAAI,OAAO,CAiEpD"}