@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.
- package/README.md +103 -0
- package/dist/auth/forge-interface.d.ts +67 -0
- package/dist/auth/forge-interface.d.ts.map +1 -0
- package/dist/auth/forge-interface.js +69 -0
- package/dist/auth/github-app.d.ts +64 -0
- package/dist/auth/github-app.d.ts.map +1 -0
- package/dist/auth/github-app.js +141 -0
- package/dist/auth/github-oauth.d.ts +27 -0
- package/dist/auth/github-oauth.d.ts.map +1 -0
- package/dist/auth/github-oauth.js +89 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +14 -0
- package/dist/auth/jwt.d.ts +24 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +43 -0
- package/dist/auth/middleware.d.ts +22 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +36 -0
- package/dist/auth/ssh-keys.d.ts +21 -0
- package/dist/auth/ssh-keys.d.ts.map +1 -0
- package/dist/auth/ssh-keys.js +59 -0
- package/dist/auth/types.d.ts +165 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +53 -0
- package/dist/backend.d.ts +117 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +15 -0
- package/dist/backends/git-native.d.ts +51 -0
- package/dist/backends/git-native.d.ts.map +1 -0
- package/dist/backends/git-native.js +324 -0
- package/dist/backends/github-issues.d.ts +77 -0
- package/dist/backends/github-issues.d.ts.map +1 -0
- package/dist/backends/github-issues.js +796 -0
- package/dist/backends/index.d.ts +48 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/index.js +139 -0
- package/dist/backends/server.d.ts +41 -0
- package/dist/backends/server.d.ts.map +1 -0
- package/dist/backends/server.js +298 -0
- package/dist/cli/auth-store.d.ts +49 -0
- package/dist/cli/auth-store.d.ts.map +1 -0
- package/dist/cli/auth-store.js +150 -0
- package/dist/cli/client-config.d.ts +10 -0
- package/dist/cli/client-config.d.ts.map +1 -0
- package/dist/cli/client-config.js +87 -0
- package/dist/cli/commands/ask.d.ts +3 -0
- package/dist/cli/commands/ask.d.ts.map +1 -0
- package/dist/cli/commands/ask.js +171 -0
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +510 -0
- package/dist/cli/commands/breakpoints.d.ts +3 -0
- package/dist/cli/commands/breakpoints.d.ts.map +1 -0
- package/dist/cli/commands/breakpoints.js +152 -0
- package/dist/cli/commands/responder-loop.d.ts +3 -0
- package/dist/cli/commands/responder-loop.d.ts.map +1 -0
- package/dist/cli/commands/responder-loop.js +78 -0
- package/dist/cli/commands/responders.d.ts +3 -0
- package/dist/cli/commands/responders.d.ts.map +1 -0
- package/dist/cli/commands/responders.js +74 -0
- package/dist/cli/commands/server.d.ts +3 -0
- package/dist/cli/commands/server.d.ts.map +1 -0
- package/dist/cli/commands/server.js +34 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/output.d.ts +26 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +143 -0
- package/dist/cli/program.d.ts +6 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +32 -0
- package/dist/client/answer-poller.d.ts +52 -0
- package/dist/client/answer-poller.d.ts.map +1 -0
- package/dist/client/answer-poller.js +199 -0
- package/dist/client/auth-client.d.ts +200 -0
- package/dist/client/auth-client.d.ts.map +1 -0
- package/dist/client/auth-client.js +309 -0
- package/dist/client/breakpoint-router.d.ts +45 -0
- package/dist/client/breakpoint-router.d.ts.map +1 -0
- package/dist/client/breakpoint-router.js +45 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +16 -0
- package/dist/client/profile-validator.d.ts +34 -0
- package/dist/client/profile-validator.d.ts.map +1 -0
- package/dist/client/profile-validator.js +89 -0
- package/dist/client/responder-client.d.ts +39 -0
- package/dist/client/responder-client.d.ts.map +1 -0
- package/dist/client/responder-client.js +72 -0
- package/dist/client/responder-matcher.d.ts +49 -0
- package/dist/client/responder-matcher.d.ts.map +1 -0
- package/dist/client/responder-matcher.js +226 -0
- package/dist/client/server-client.d.ts +124 -0
- package/dist/client/server-client.d.ts.map +1 -0
- package/dist/client/server-client.js +266 -0
- package/dist/client/timeout-manager.d.ts +47 -0
- package/dist/client/timeout-manager.d.ts.map +1 -0
- package/dist/client/timeout-manager.js +77 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +93 -0
- package/dist/harness/index.d.ts +4 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +2 -0
- package/dist/harness/interaction-provider.d.ts +71 -0
- package/dist/harness/interaction-provider.d.ts.map +1 -0
- package/dist/harness/interaction-provider.js +124 -0
- package/dist/harness/routing-rules.d.ts +7 -0
- package/dist/harness/routing-rules.d.ts.map +1 -0
- package/dist/harness/routing-rules.js +37 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/mcp/backend-resolver.d.ts +43 -0
- package/dist/mcp/backend-resolver.d.ts.map +1 -0
- package/dist/mcp/backend-resolver.js +111 -0
- package/dist/mcp/http-transport.d.ts +37 -0
- package/dist/mcp/http-transport.d.ts.map +1 -0
- package/dist/mcp/http-transport.js +103 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/server.d.ts +20 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +121 -0
- package/dist/mcp/tools/answer-breakpoint.d.ts +32 -0
- package/dist/mcp/tools/answer-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/answer-breakpoint.js +45 -0
- package/dist/mcp/tools/ask-breakpoint.d.ts +58 -0
- package/dist/mcp/tools/ask-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/ask-breakpoint.js +78 -0
- package/dist/mcp/tools/check-status.d.ts +16 -0
- package/dist/mcp/tools/check-status.d.ts.map +1 -0
- package/dist/mcp/tools/check-status.js +18 -0
- package/dist/mcp/tools/claim-breakpoint.d.ts +18 -0
- package/dist/mcp/tools/claim-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/claim-breakpoint.js +28 -0
- package/dist/mcp/tools/list-breakpoints.d.ts +16 -0
- package/dist/mcp/tools/list-breakpoints.d.ts.map +1 -0
- package/dist/mcp/tools/list-breakpoints.js +14 -0
- package/dist/mcp/tools/list-responders.d.ts +18 -0
- package/dist/mcp/tools/list-responders.d.ts.map +1 -0
- package/dist/mcp/tools/list-responders.js +37 -0
- package/dist/mcp/tools/poll-breakpoints.d.ts +18 -0
- package/dist/mcp/tools/poll-breakpoints.d.ts.map +1 -0
- package/dist/mcp/tools/poll-breakpoints.js +36 -0
- package/dist/mcp/tools/verify-answer.d.ts +16 -0
- package/dist/mcp/tools/verify-answer.d.ts.map +1 -0
- package/dist/mcp/tools/verify-answer.js +38 -0
- package/dist/proven/index.d.ts +5 -0
- package/dist/proven/index.d.ts.map +1 -0
- package/dist/proven/index.js +3 -0
- package/dist/proven/keys.d.ts +33 -0
- package/dist/proven/keys.d.ts.map +1 -0
- package/dist/proven/keys.js +117 -0
- package/dist/proven/sign.d.ts +16 -0
- package/dist/proven/sign.d.ts.map +1 -0
- package/dist/proven/sign.js +60 -0
- package/dist/proven/types.d.ts +26 -0
- package/dist/proven/types.d.ts.map +1 -0
- package/dist/proven/types.js +5 -0
- package/dist/proven/verify.d.ts +6 -0
- package/dist/proven/verify.d.ts.map +1 -0
- package/dist/proven/verify.js +58 -0
- package/dist/types.d.ts +4034 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +244 -0
- package/package.json +86 -0
- package/responder/README.md +42 -0
- package/responder/backend-responder.json +9 -0
- package/responder/devops-responder.json +9 -0
- package/responder/frontend-responder.json +9 -0
- 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 @@
|
|
|
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 @@
|
|
|
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"}
|