premanmcp 0.10.4 → 0.10.6
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 +60 -14
- package/bin/account.js +34 -0
- package/bin/api_tools.js +1 -1
- package/bin/cli.js +23 -5
- package/bin/connect.js +130 -15
- package/bin/hook.js +276 -22
- package/bin/integrations.js +86 -13
- package/bin/runner.js +51 -8
- package/bin/shared.js +66 -20
- package/dist/server.js +25 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -64,21 +64,28 @@ and then retries from your home directory, where the override cannot reach it
|
|
|
64
64
|
first, then your agent. Agents you start in the overriding directory keep using its
|
|
65
65
|
backend, which is the point of the file.
|
|
66
66
|
|
|
67
|
-
Once linked, `connect`
|
|
67
|
+
Once linked, `connect` installs the git pre-push hook — so `git push` checks the endpoints
|
|
68
|
+
you touched — prints what is left, and stops. It asks nothing. Everything else it used to
|
|
69
|
+
run is its own command, because each one can fail on its own and none of them should hold
|
|
70
|
+
up a link that already worked:
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
```bash
|
|
73
|
+
preman endpoints discover # map this repo's endpoints
|
|
74
|
+
preman runner start --background # let PreMan apply fixes on this machine
|
|
75
|
+
preman github # or connect it in the dashboard
|
|
76
|
+
preman status # which of those are done
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`preman onboard` (or `setup`) is the prompted walk through all of it — sign in, coding
|
|
80
|
+
agent, endpoints, runner, GitHub, AWS, Slack — one question per step, `b` to go back, and a
|
|
81
|
+
summary at the end. `connect --guide` runs the old full pass inside connect itself:
|
|
82
|
+
discovery, a first test, the runner, the desktop app and the integration prompts.
|
|
77
83
|
|
|
78
84
|
Useful flags: `--agent cursor|claude-code|codex` skips the picker, `--project` writes
|
|
79
|
-
project-local config, `--print` shows the config without writing it, `--
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
project-local config, `--print` shows the config without writing it, `--no-hook` leaves
|
|
86
|
+
push testing alone, and `--no-guide` connects and nothing else. With `--guide`, `--yes`
|
|
87
|
+
takes every step's default without asking and `--no-runner` / `--no-desktop` /
|
|
88
|
+
`--no-integrations` skip one each.
|
|
82
89
|
|
|
83
90
|
`--yes` deliberately does *not* install the desktop app: that step's default is no, because
|
|
84
91
|
it downloads a hundred-odd megabytes and writes to `/Applications`. Run `install-desktop`
|
|
@@ -126,6 +133,7 @@ npm exec -y premanmcp@latest -- connect --project
|
|
|
126
133
|
## What It Does
|
|
127
134
|
|
|
128
135
|
- Converts API endpoints into agent-callable MCP tools.
|
|
136
|
+
- Migrates a Postman collection in one call (`migrate_from_postman`): keeps the assertions your `pm.test` blocks declared, splits the environment into shared variables and encrypted secrets, and schedules a monitored test suite per request.
|
|
129
137
|
- Creates/connects a PreMan account from the terminal or IDE agent.
|
|
130
138
|
- Lets agents test real backend endpoints from the IDE.
|
|
131
139
|
- Syncs endpoint inventory across backend and frontend workflows.
|
|
@@ -159,6 +167,10 @@ Convert these endpoints into an MCP.
|
|
|
159
167
|
Show me the audit log for this hosted MCP.
|
|
160
168
|
```
|
|
161
169
|
|
|
170
|
+
```text
|
|
171
|
+
Move my Postman collection over to PreMan.
|
|
172
|
+
```
|
|
173
|
+
|
|
162
174
|
```text
|
|
163
175
|
Pull my pending PreMan fix tasks and fix the failing endpoint.
|
|
164
176
|
```
|
|
@@ -218,17 +230,51 @@ npm exec -y premanmcp@latest -- login # Create/login and generate a PreMan API
|
|
|
218
230
|
npm exec -y premanmcp@latest -- install # Cursor-only installer (legacy)
|
|
219
231
|
```
|
|
220
232
|
|
|
233
|
+
### Push testing
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
preman hook install # Write the pre-push hook (connect does this too)
|
|
237
|
+
preman hook status # Installed? And does the command in it still answer?
|
|
238
|
+
preman hook repair # Rewrite it if it stopped working (usually automatic)
|
|
239
|
+
preman hook uninstall # Remove it, restoring any hook it replaced
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The hook is generated shell that runs `preman verify --pre-push` and can only ever be
|
|
243
|
+
advisory: no backend, no credentials, a crash or a timeout all exit 0 with a notice. Before
|
|
244
|
+
writing it, `install` runs the command it is about to embed and requires an answer — a hook
|
|
245
|
+
holding a `preman` that belongs to another package prints `checks skipped` at every push and
|
|
246
|
+
looks installed forever. The embedded command is pinned to the version that wrote it rather
|
|
247
|
+
than `@latest`, so upgrading us never changes what your pushes run; re-run `hook install`
|
|
248
|
+
to move it. Set `PREMAN_HOOK_INVOCATION` to write a command of your own instead, and
|
|
249
|
+
`PREMAN_SKIP_HOOK=1` to silence the hook for a push.
|
|
250
|
+
|
|
251
|
+
You should not have to run `repair`. A hook of ours that stops answering is rewritten in the
|
|
252
|
+
background by anything that proves PreMan runs here — any command, or your agent starting the
|
|
253
|
+
MCP server — because nothing re-runs `connect` after an upgrade to notice, and the broken
|
|
254
|
+
state is silent. It is narrow on purpose: a hook that still works keeps the version it pins, a
|
|
255
|
+
hook we did not write is never touched, and no hook is installed where you never asked for
|
|
256
|
+
one. The answer is remembered for an hour per directory, and `PREMAN_NO_HOOK_REPAIR=1` turns
|
|
257
|
+
it off.
|
|
258
|
+
|
|
221
259
|
### Runner
|
|
222
260
|
|
|
223
|
-
`connect`
|
|
261
|
+
`preman onboard` and `connect --guide` set this up for you; these are for managing it
|
|
262
|
+
afterwards.
|
|
224
263
|
|
|
225
264
|
```bash
|
|
226
265
|
preman runner status # Paired? Running?
|
|
227
266
|
preman runner start --background # Hold the job stream, log to ~/.preman/runner.log
|
|
228
267
|
preman runner stop # Stop it and report offline
|
|
229
|
-
preman runner register --agent claude-code #
|
|
268
|
+
preman runner register --agent claude-code # Pair without starting
|
|
230
269
|
```
|
|
231
270
|
|
|
271
|
+
`start` pairs this machine itself when it is not paired yet, and re-pairs it when the
|
|
272
|
+
backend has revoked the token it held, so neither is a command you have to be told to run.
|
|
273
|
+
It works out which agent to pair as from the session it is running in, then from the agent
|
|
274
|
+
PreMan is already configured in, then from the only one installed — and asks only when
|
|
275
|
+
those disagree. Pass `--agent cursor|claude-code|codex` to settle it yourself, which is
|
|
276
|
+
also what a machine with no terminal to ask in needs.
|
|
277
|
+
|
|
232
278
|
The runner holds one outbound connection to PreMan and runs the work PreMan queues for
|
|
233
279
|
this machine — a failing endpoint becomes an agent run in your own repo instead of a
|
|
234
280
|
prompt you have to paste. It is bound to the agent and directory it was registered with
|
package/bin/account.js
CHANGED
|
@@ -11,6 +11,7 @@ import { existsSync, rmSync } from "node:fs";
|
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
|
|
13
13
|
import { detectCandidates } from "./detect.js";
|
|
14
|
+
import { hookStatus } from "./hook.js";
|
|
14
15
|
import {
|
|
15
16
|
CREDENTIALS_FILE,
|
|
16
17
|
backendUrl,
|
|
@@ -150,6 +151,39 @@ export async function doctorCommand(commandArgs = []) {
|
|
|
150
151
|
)
|
|
151
152
|
);
|
|
152
153
|
|
|
154
|
+
// An installed hook whose command no longer answers is the one failure here
|
|
155
|
+
// that reports itself as success everywhere else: `git push` prints a single
|
|
156
|
+
// skipped line and nothing else ever mentions it.
|
|
157
|
+
const hook = (() => {
|
|
158
|
+
try {
|
|
159
|
+
return hookStatus({ probe: true });
|
|
160
|
+
} catch {
|
|
161
|
+
return null; // not a git repository
|
|
162
|
+
}
|
|
163
|
+
})();
|
|
164
|
+
if (hook && hook.state === "installed") {
|
|
165
|
+
results.push(
|
|
166
|
+
line(
|
|
167
|
+
"push hook",
|
|
168
|
+
hook.works,
|
|
169
|
+
hook.works
|
|
170
|
+
? `${hook.invocation}${hook.current ? "" : ` — out of date, run \`${cliInvocation()} hook install --force\``}`
|
|
171
|
+
: `${hook.invocation || "unreadable"} does not answer — run \`${cliInvocation()} hook install --force\``
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
if (!hook.works) failures += 1;
|
|
175
|
+
} else if (hook) {
|
|
176
|
+
results.push(
|
|
177
|
+
line(
|
|
178
|
+
"push hook",
|
|
179
|
+
null,
|
|
180
|
+
hook.state === "foreign"
|
|
181
|
+
? "another tool owns pre-push here"
|
|
182
|
+
: `not installed — run \`${cliInvocation()} hook install\``
|
|
183
|
+
)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
153
187
|
if (status?.ok) {
|
|
154
188
|
const integrations = status.integrations || {};
|
|
155
189
|
for (const [name, section] of Object.entries(integrations)) {
|
package/bin/api_tools.js
CHANGED
package/bin/cli.js
CHANGED
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
CONNECT_HELP,
|
|
22
22
|
DISPATCH_HELP,
|
|
23
23
|
connectCommand,
|
|
24
|
+
discoverEndpoints,
|
|
24
25
|
dispatchCommand,
|
|
26
|
+
resolveAgentForPairing,
|
|
25
27
|
writeCursorConfig,
|
|
26
28
|
} from "./connect.js";
|
|
27
29
|
import {
|
|
@@ -33,7 +35,7 @@ import {
|
|
|
33
35
|
} from "./integrations.js";
|
|
34
36
|
import { HOSTED_HELP, linkCommand, runCommand, toolsCommand } from "./hosted.js";
|
|
35
37
|
import { STATUS_HELP, statusCommand } from "./status.js";
|
|
36
|
-
import { HOOK_HELP, hookCommand } from "./hook.js";
|
|
38
|
+
import { HOOK_HELP, hookCommand, scheduleHookRepair } from "./hook.js";
|
|
37
39
|
import { RUNNER_HELP, runnerCommand } from "./runner.js";
|
|
38
40
|
import { VERIFY_HELP, verifyCommand } from "./verify.js";
|
|
39
41
|
import { DESKTOP_HELP, installDesktopCommand } from "./desktop.js";
|
|
@@ -84,7 +86,7 @@ function printHelp() {
|
|
|
84
86
|
["runner start|status|stop", "Run PreMan's queued agent work on this machine"],
|
|
85
87
|
["doctor", "Diagnose credentials, backend, target, integrations"],
|
|
86
88
|
["install-desktop", "Download and install the PreMan desktop app"],
|
|
87
|
-
["onboard", "Sign in, then
|
|
89
|
+
["onboard", "Sign in, then agent, endpoints, runner, GitHub, AWS, Slack"],
|
|
88
90
|
["connect [options]", "Pick a coding agent and connect it"],
|
|
89
91
|
["dispatch [options]", "Let PreMan start agent runs for you"],
|
|
90
92
|
["aws | github | slack", "Connect one integration on its own"],
|
|
@@ -134,6 +136,7 @@ async function loginCommand() {
|
|
|
134
136
|
await loginBrowser(cliArgs);
|
|
135
137
|
return;
|
|
136
138
|
}
|
|
139
|
+
const cli = cliInvocation();
|
|
137
140
|
const creds = await authenticateTerminal(cliArgs);
|
|
138
141
|
process.stdout.write(`PreMan account ready.
|
|
139
142
|
|
|
@@ -152,6 +155,7 @@ You can now run:
|
|
|
152
155
|
* the same write plus agent choice, pairing, and dispatch setup.
|
|
153
156
|
*/
|
|
154
157
|
async function installCursorMcp() {
|
|
158
|
+
const cli = cliInvocation();
|
|
155
159
|
const serverName = argValue("--name", "preman");
|
|
156
160
|
const projectInstall = hasFlag("--project");
|
|
157
161
|
|
|
@@ -178,7 +182,7 @@ Server name: ${serverName}
|
|
|
178
182
|
Backend: ${serverConfig.env.PREMAN_BACKEND}
|
|
179
183
|
|
|
180
184
|
Next steps:
|
|
181
|
-
1. ${hasInlineKey ? "Your PreMan API key was written to the MCP config." : hasStoredKey ? `Your PreMan API key is saved in ${CREDENTIALS_FILE}; the MCP server will load it automatically.` :
|
|
185
|
+
1. ${hasInlineKey ? "Your PreMan API key was written to the MCP config." : hasStoredKey ? `Your PreMan API key is saved in ${CREDENTIALS_FILE}; the MCP server will load it automatically.` : `Run ${cli} login to create/connect your account and generate an API key.`}
|
|
182
186
|
2. Restart Cursor or toggle the PreMan MCP server off/on.
|
|
183
187
|
3. In your API repo, ask your coding agent:
|
|
184
188
|
"Use PreMan to convert the endpoints I choose into a hosted MCP server, then give me the Cursor/Claude install snippet."
|
|
@@ -215,6 +219,11 @@ function startServer() {
|
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
async function main() {
|
|
222
|
+
// Running at all is the signal. `hook` and `verify` own the hook themselves,
|
|
223
|
+
// and connect reinstalls it in the foreground, so a background rewrite there
|
|
224
|
+
// would only race with the one the user is watching.
|
|
225
|
+
if (!["hook", "verify", "connect"].includes(command)) scheduleHookRepair();
|
|
226
|
+
|
|
218
227
|
if (command === "login") {
|
|
219
228
|
await loginCommand();
|
|
220
229
|
} else if (command === "status") {
|
|
@@ -224,7 +233,10 @@ async function main() {
|
|
|
224
233
|
} else if (command === "hook") {
|
|
225
234
|
await hookCommand(commandArgs);
|
|
226
235
|
} else if (command === "runner") {
|
|
227
|
-
|
|
236
|
+
// Injected rather than imported inside runner.js, which connect.js already
|
|
237
|
+
// depends on: the agent picker lives there, and importing it back would
|
|
238
|
+
// close the cycle.
|
|
239
|
+
await runnerCommand(commandArgs, { resolveAgent: resolveAgentForPairing });
|
|
228
240
|
} else if (command === "logout") {
|
|
229
241
|
await logoutCommand();
|
|
230
242
|
} else if (command === "doctor") {
|
|
@@ -240,7 +252,13 @@ async function main() {
|
|
|
240
252
|
} else if (command === "onboard" || command === "setup") {
|
|
241
253
|
// makeArgs/authenticateTerminal/connectCommand are injected rather than
|
|
242
254
|
// imported there, so integrations.js stays free of a cycle back into the CLI.
|
|
243
|
-
await onboardCommand(commandArgs, {
|
|
255
|
+
await onboardCommand(commandArgs, {
|
|
256
|
+
makeArgs,
|
|
257
|
+
authenticateTerminal,
|
|
258
|
+
connectCommand,
|
|
259
|
+
discoverEndpoints,
|
|
260
|
+
runnerCommand,
|
|
261
|
+
});
|
|
244
262
|
} else if (command === "aws") {
|
|
245
263
|
await awsCommand(makeArgs(commandArgs));
|
|
246
264
|
} else if (command === "github") {
|
package/bin/connect.js
CHANGED
|
@@ -21,6 +21,7 @@ import { installHook, hookStatus } from "./hook.js";
|
|
|
21
21
|
import { MARK, awsCommand, githubCommand, slackCommand } from "./integrations.js";
|
|
22
22
|
import {
|
|
23
23
|
confirmRunnerOnline,
|
|
24
|
+
pairingIsLive,
|
|
24
25
|
readRunnerState,
|
|
25
26
|
registerRunner,
|
|
26
27
|
runnerIsAlive,
|
|
@@ -156,6 +157,52 @@ export function runningInside(env = process.env) {
|
|
|
156
157
|
return "";
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
/** Is PreMan's MCP server already written into this agent's config? */
|
|
161
|
+
function agentIsLinked(agentId, serverName = "preman") {
|
|
162
|
+
try {
|
|
163
|
+
const home = os.homedir();
|
|
164
|
+
if (agentId === "cursor") {
|
|
165
|
+
return Boolean(readJsonFile(path.join(home, ".cursor", "mcp.json")).mcpServers?.[serverName]);
|
|
166
|
+
}
|
|
167
|
+
if (agentId === "claude_code") {
|
|
168
|
+
return Boolean(readJsonFile(path.join(home, ".claude.json")).mcpServers?.[serverName]);
|
|
169
|
+
}
|
|
170
|
+
if (agentId === "codex") {
|
|
171
|
+
const file = path.join(process.env.CODEX_HOME || path.join(home, ".codex"), "config.toml");
|
|
172
|
+
return existsSync(file) && readFileSync(file, "utf8").includes(`[mcp_servers.${serverName}]`);
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
// An unreadable config is not an answer; fall through to the other signals.
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Which agent this machine should pair as, or "" when only a person can say.
|
|
182
|
+
*
|
|
183
|
+
* Pairing needs an agent id, and demanding `--agent` for it turned "start the
|
|
184
|
+
* runner" into a command someone had to be told twice. The signals are ordered
|
|
185
|
+
* by how much they actually know: the session we are inside, then the agent
|
|
186
|
+
* PreMan is already configured in, then the only one installed. Asking is the
|
|
187
|
+
* last resort rather than the first, and a machine with no terminal to ask in
|
|
188
|
+
* gets "" so the caller can fail with a sentence instead of hanging on a prompt.
|
|
189
|
+
*/
|
|
190
|
+
export async function resolveAgentForPairing({ interactive = Boolean(process.stdin.isTTY) } = {}) {
|
|
191
|
+
const inside = runningInside();
|
|
192
|
+
if (inside) return inside;
|
|
193
|
+
|
|
194
|
+
const linked = AGENTS.filter((agent) => agentIsLinked(agent.id));
|
|
195
|
+
if (linked.length === 1) return linked[0].id;
|
|
196
|
+
|
|
197
|
+
const detected = detectAgents();
|
|
198
|
+
const present = AGENTS.filter((agent) => detected[agent.id]);
|
|
199
|
+
if (present.length === 1) return present[0].id;
|
|
200
|
+
|
|
201
|
+
if (!interactive) return "";
|
|
202
|
+
const picked = await promptAgentChoice(detected);
|
|
203
|
+
return picked?.id || "";
|
|
204
|
+
}
|
|
205
|
+
|
|
159
206
|
async function promptAgentChoice(detected, insideId = runningInside()) {
|
|
160
207
|
process.stdout.write("Which coding agent?\n");
|
|
161
208
|
AGENTS.forEach((agent, index) => {
|
|
@@ -1060,6 +1107,26 @@ function nextStepsBlock(agent) {
|
|
|
1060
1107
|
);
|
|
1061
1108
|
}
|
|
1062
1109
|
|
|
1110
|
+
/**
|
|
1111
|
+
* What is left once the link is done, as commands rather than as questions.
|
|
1112
|
+
*
|
|
1113
|
+
* Connect used to run all of these itself, which bought a prompt per step and a
|
|
1114
|
+
* wait as long as the slowest broken one — and a link that had already succeeded
|
|
1115
|
+
* ended on a failed runner and a five-minute GitHub poll. Each is one command, so
|
|
1116
|
+
* this lists them and gets out of the way.
|
|
1117
|
+
*/
|
|
1118
|
+
function whatIsLeftBlock(args) {
|
|
1119
|
+
const cli = cliInvocation();
|
|
1120
|
+
return (
|
|
1121
|
+
"\nWhat is left:\n" +
|
|
1122
|
+
` ${cli} endpoints discover # map this repo's endpoints\n` +
|
|
1123
|
+
` ${cli} runner start --background # let PreMan apply fixes on this machine\n` +
|
|
1124
|
+
` ${cli} github # connect GitHub, or do it in the dashboard\n` +
|
|
1125
|
+
`\n${cli} status reports your endpoints, last push and integrations.\n` +
|
|
1126
|
+
`Watch runs at ${frontendUrl(args)}\n`
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1063
1130
|
/**
|
|
1064
1131
|
* Ask, or take the step's own default when nobody can answer.
|
|
1065
1132
|
*
|
|
@@ -1130,7 +1197,7 @@ export function headlessDiscovery(agent, serverName, instructions = []) {
|
|
|
1130
1197
|
*
|
|
1131
1198
|
* Never throws: a failed discovery must not fail the connect.
|
|
1132
1199
|
*/
|
|
1133
|
-
async function discoverEndpoints(args, agent, serverName, blockedHere = "") {
|
|
1200
|
+
export async function discoverEndpoints(args, agent, serverName, blockedHere = "") {
|
|
1134
1201
|
try {
|
|
1135
1202
|
const before = await endpointCounts(args);
|
|
1136
1203
|
if (before.registered) {
|
|
@@ -1241,6 +1308,24 @@ async function runFirstTest(args, runnable, { assumeYes }) {
|
|
|
1241
1308
|
}
|
|
1242
1309
|
}
|
|
1243
1310
|
|
|
1311
|
+
/**
|
|
1312
|
+
* Whether a stored pairing can still be used, rather than merely matching.
|
|
1313
|
+
*
|
|
1314
|
+
* Matching agent and path only prove the state was written for this project. The
|
|
1315
|
+
* token behind it can be revoked, replaced by another device, or left over from an
|
|
1316
|
+
* API key that no longer exists — and reusing one of those starts a daemon that
|
|
1317
|
+
* 401s on its first stream and exits, which reads as "the runner cannot start
|
|
1318
|
+
* here" when the pairing is the only thing that needed replacing.
|
|
1319
|
+
*
|
|
1320
|
+
* A backend we cannot reach answers "usable": re-pairing is for a token the
|
|
1321
|
+
* backend rejected, not for a network that was briefly down.
|
|
1322
|
+
*/
|
|
1323
|
+
export async function runnerPairingIsUsable(args, existing, agentId, { cwd = process.cwd() } = {}) {
|
|
1324
|
+
if (!existing || existing.agent !== agentId) return false;
|
|
1325
|
+
if (path.resolve(String(existing.project_path || "")) !== path.resolve(cwd)) return false;
|
|
1326
|
+
return pairingIsLive(args, existing);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1244
1329
|
/**
|
|
1245
1330
|
* Pair this machine as a runner and leave it running.
|
|
1246
1331
|
*
|
|
@@ -1267,7 +1352,7 @@ async function setUpRunner(args, agent, { assumeYes }) {
|
|
|
1267
1352
|
}
|
|
1268
1353
|
|
|
1269
1354
|
try {
|
|
1270
|
-
if (!
|
|
1355
|
+
if (!(await runnerPairingIsUsable(args, existing, agent.id))) {
|
|
1271
1356
|
await registerRunner(args, { agent: agent.id, projectPath: process.cwd() });
|
|
1272
1357
|
}
|
|
1273
1358
|
const started = startBackground([]);
|
|
@@ -1409,7 +1494,7 @@ async function setUpPushTesting(args) {
|
|
|
1409
1494
|
if (args.has("--no-hook")) return { state: "skipped" };
|
|
1410
1495
|
const current = (() => {
|
|
1411
1496
|
try {
|
|
1412
|
-
return hookStatus();
|
|
1497
|
+
return hookStatus({ probe: true });
|
|
1413
1498
|
} catch {
|
|
1414
1499
|
return null; // not a git repository
|
|
1415
1500
|
}
|
|
@@ -1418,9 +1503,10 @@ async function setUpPushTesting(args) {
|
|
|
1418
1503
|
process.stdout.write(`${MARK.skip()} Not a git repository — no push testing here.\n`);
|
|
1419
1504
|
return { state: "unavailable" };
|
|
1420
1505
|
}
|
|
1421
|
-
// A hook whose invocation went stale
|
|
1422
|
-
// it is the case where PreMan looks connected and
|
|
1423
|
-
|
|
1506
|
+
// A hook whose invocation went stale, or no longer answers, is reinstalled
|
|
1507
|
+
// rather than reported as on: it is the case where PreMan looks connected and
|
|
1508
|
+
// silently checks nothing.
|
|
1509
|
+
if (current.state === "installed" && current.current && current.works) {
|
|
1424
1510
|
process.stdout.write(`${MARK.ok()} Push testing already on.\n`);
|
|
1425
1511
|
return { state: "installed" };
|
|
1426
1512
|
}
|
|
@@ -1432,6 +1518,13 @@ async function setUpPushTesting(args) {
|
|
|
1432
1518
|
);
|
|
1433
1519
|
return result;
|
|
1434
1520
|
}
|
|
1521
|
+
if (result.action === "unproven") {
|
|
1522
|
+
process.stdout.write(
|
|
1523
|
+
`${MARK.skip()} No push testing: ${result.detail}\n` +
|
|
1524
|
+
` Then: ${cliInvocation()} hook install\n`
|
|
1525
|
+
);
|
|
1526
|
+
return result;
|
|
1527
|
+
}
|
|
1435
1528
|
process.stdout.write(
|
|
1436
1529
|
`${MARK.ok()} Push testing on — \`git push\` now checks the endpoints you touched.\n` +
|
|
1437
1530
|
" It never blocks a push; PREMAN_SKIP_HOOK=1 silences it.\n"
|
|
@@ -1440,10 +1533,12 @@ async function setUpPushTesting(args) {
|
|
|
1440
1533
|
}
|
|
1441
1534
|
|
|
1442
1535
|
/**
|
|
1443
|
-
* Everything after the link, in one pass,
|
|
1536
|
+
* Everything after the link, in one pass, for whoever asks for it with `--guide`.
|
|
1444
1537
|
*
|
|
1445
1538
|
* Order follows what a new account needs to see: what PreMan found, then where to
|
|
1446
|
-
* watch it, then who to tell, then when to run it.
|
|
1539
|
+
* watch it, then who to tell, then when to run it. It is opt-in because running
|
|
1540
|
+
* all of it unasked is what turned a finished connect into a screen of prompts,
|
|
1541
|
+
* a dead runner and a five-minute wait on an integration nobody had asked for.
|
|
1447
1542
|
*/
|
|
1448
1543
|
async function guidedFirstRun(args, agent, apiKey, serverName, { blockedHere = "" } = {}) {
|
|
1449
1544
|
const assumeYes = args.has("--yes");
|
|
@@ -1544,10 +1639,12 @@ Connect options:
|
|
|
1544
1639
|
--no-self-test Do not start the MCP server to finish the link
|
|
1545
1640
|
--no-auto-checkin Do not run the agent to finish the link
|
|
1546
1641
|
--no-wait Do not wait for the agent to check in
|
|
1547
|
-
--
|
|
1548
|
-
|
|
1549
|
-
--no-
|
|
1550
|
-
--no-
|
|
1642
|
+
--guide Also run discovery, the runner, the desktop app
|
|
1643
|
+
and the integration prompts after connecting
|
|
1644
|
+
--no-guide Connect only: no push hook, no closing summary
|
|
1645
|
+
--no-runner With --guide, do not pair this machine as a runner
|
|
1646
|
+
--no-desktop With --guide, do not offer the desktop app
|
|
1647
|
+
--no-integrations With --guide, do not offer GitHub / AWS / Slack
|
|
1551
1648
|
--no-hook Do not install the git pre-push hook
|
|
1552
1649
|
--yes Take every step's default without prompting
|
|
1553
1650
|
(the desktop app defaults to no; install-desktop)
|
|
@@ -1670,18 +1767,36 @@ export async function connectCommand(commandArgs) {
|
|
|
1670
1767
|
serverConfig,
|
|
1671
1768
|
projectInstall,
|
|
1672
1769
|
});
|
|
1770
|
+
// The agent goes back to the caller because `onboard` runs steps after this one
|
|
1771
|
+
// that need to know which agent to drive, and asking twice is a question we
|
|
1772
|
+
// already have the answer to.
|
|
1773
|
+
const connected = { agent, serverName, linked: checkIn.linked };
|
|
1673
1774
|
if (!checkIn.linked) {
|
|
1674
1775
|
// Still honour an explicitly-passed credential, but do not open a new prompt
|
|
1675
1776
|
// on top of a connect that just told the user something went wrong.
|
|
1676
1777
|
await captureDispatchCredential(args, agent, apiKey, { prompt: false });
|
|
1677
|
-
return;
|
|
1778
|
+
return connected;
|
|
1678
1779
|
}
|
|
1679
1780
|
|
|
1680
1781
|
process.stdout.write(`${MARK.ok()} Connected as ${agent.label}.\n`);
|
|
1681
|
-
|
|
1782
|
+
|
|
1783
|
+
// Connecting is one job. Running discovery, pairing a runner, offering the
|
|
1784
|
+
// desktop app and installing three integrations is five more, each with its
|
|
1785
|
+
// own prompt and its own way to fail -- and a connect that ends in a failed
|
|
1786
|
+
// runner and a five-minute GitHub poll reads as a broken product rather than a
|
|
1787
|
+
// finished link. The push hook stays because it is the only one that is not a
|
|
1788
|
+
// question: it is what makes PreMan run at all, and it costs a file write.
|
|
1789
|
+
if (args.has("--guide") && !args.has("--no-guide")) {
|
|
1682
1790
|
await guidedFirstRun(args, agent, apiKey, serverName, { blockedHere: checkIn.blockedHere });
|
|
1791
|
+
await captureDispatchCredential(args, agent, apiKey);
|
|
1792
|
+
return connected;
|
|
1683
1793
|
}
|
|
1684
|
-
|
|
1794
|
+
if (!args.has("--no-guide")) {
|
|
1795
|
+
await setUpPushTesting(args);
|
|
1796
|
+
process.stdout.write(whatIsLeftBlock(args));
|
|
1797
|
+
}
|
|
1798
|
+
await captureDispatchCredential(args, agent, apiKey, { prompt: false });
|
|
1799
|
+
return connected;
|
|
1685
1800
|
}
|
|
1686
1801
|
|
|
1687
1802
|
/**
|