@camerontaylor/paseo-cli 0.8.0-fork.1
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/bin/paseo +3 -0
- package/dist/classify.d.ts +19 -0
- package/dist/classify.js +49 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +135 -0
- package/dist/commands/agent/archive.d.ts +18 -0
- package/dist/commands/agent/archive.js +102 -0
- package/dist/commands/agent/attach.d.ts +11 -0
- package/dist/commands/agent/attach.js +161 -0
- package/dist/commands/agent/delete.d.ts +15 -0
- package/dist/commands/agent/delete.js +107 -0
- package/dist/commands/agent/detach.d.ts +9 -0
- package/dist/commands/agent/detach.js +38 -0
- package/dist/commands/agent/import.d.ts +14 -0
- package/dist/commands/agent/import.js +122 -0
- package/dist/commands/agent/index.d.ts +3 -0
- package/dist/commands/agent/index.js +56 -0
- package/dist/commands/agent/inspect.d.ts +15 -0
- package/dist/commands/agent/inspect.js +209 -0
- package/dist/commands/agent/logs.d.ts +19 -0
- package/dist/commands/agent/logs.js +180 -0
- package/dist/commands/agent/ls.d.ts +44 -0
- package/dist/commands/agent/ls.js +212 -0
- package/dist/commands/agent/mode.d.ts +18 -0
- package/dist/commands/agent/mode.js +100 -0
- package/dist/commands/agent/open.d.ts +11 -0
- package/dist/commands/agent/open.js +61 -0
- package/dist/commands/agent/reload.d.ts +15 -0
- package/dist/commands/agent/reload.js +75 -0
- package/dist/commands/agent/run.d.ts +78 -0
- package/dist/commands/agent/run.js +554 -0
- package/dist/commands/agent/send.d.ts +19 -0
- package/dist/commands/agent/send.js +187 -0
- package/dist/commands/agent/stop.d.ts +17 -0
- package/dist/commands/agent/stop.js +115 -0
- package/dist/commands/agent/update.d.ts +51 -0
- package/dist/commands/agent/update.js +186 -0
- package/dist/commands/agent/wait.d.ts +17 -0
- package/dist/commands/agent/wait.js +158 -0
- package/dist/commands/clone.d.ts +17 -0
- package/dist/commands/clone.js +65 -0
- package/dist/commands/daemon/index.d.ts +3 -0
- package/dist/commands/daemon/index.js +60 -0
- package/dist/commands/daemon/local-daemon.d.ts +78 -0
- package/dist/commands/daemon/local-daemon.js +547 -0
- package/dist/commands/daemon/pair.d.ts +35 -0
- package/dist/commands/daemon/pair.js +167 -0
- package/dist/commands/daemon/reload.d.ts +10 -0
- package/dist/commands/daemon/reload.js +36 -0
- package/dist/commands/daemon/restart.d.ts +12 -0
- package/dist/commands/daemon/restart.js +99 -0
- package/dist/commands/daemon/runtime-toolchain.d.ts +6 -0
- package/dist/commands/daemon/runtime-toolchain.js +87 -0
- package/dist/commands/daemon/set-password.d.ts +17 -0
- package/dist/commands/daemon/set-password.js +82 -0
- package/dist/commands/daemon/start.d.ts +6 -0
- package/dist/commands/daemon/start.js +57 -0
- package/dist/commands/daemon/status.d.ts +21 -0
- package/dist/commands/daemon/status.js +363 -0
- package/dist/commands/daemon/stop.d.ts +15 -0
- package/dist/commands/daemon/stop.js +60 -0
- package/dist/commands/heartbeat/index.d.ts +3 -0
- package/dist/commands/heartbeat/index.js +139 -0
- package/dist/commands/hooks.d.ts +19 -0
- package/dist/commands/hooks.js +89 -0
- package/dist/commands/hub/authority.d.ts +17 -0
- package/dist/commands/hub/authority.js +18 -0
- package/dist/commands/hub/connect.d.ts +23 -0
- package/dist/commands/hub/connect.js +44 -0
- package/dist/commands/hub/credentials.d.ts +21 -0
- package/dist/commands/hub/credentials.js +143 -0
- package/dist/commands/hub/daemon-client.d.ts +40 -0
- package/dist/commands/hub/daemon-client.js +14 -0
- package/dist/commands/hub/deploy-bundle.d.ts +16 -0
- package/dist/commands/hub/deploy-bundle.js +248 -0
- package/dist/commands/hub/deploy-triggers.d.ts +6 -0
- package/dist/commands/hub/deploy-triggers.js +116 -0
- package/dist/commands/hub/deploy.d.ts +50 -0
- package/dist/commands/hub/deploy.js +159 -0
- package/dist/commands/hub/disconnect.d.ts +16 -0
- package/dist/commands/hub/disconnect.js +24 -0
- package/dist/commands/hub/error.d.ts +6 -0
- package/dist/commands/hub/error.js +9 -0
- package/dist/commands/hub/export.d.ts +28 -0
- package/dist/commands/hub/export.js +80 -0
- package/dist/commands/hub/help.d.ts +3 -0
- package/dist/commands/hub/help.js +5 -0
- package/dist/commands/hub/hub-client/index.d.ts +22 -0
- package/dist/commands/hub/hub-client/index.js +135 -0
- package/dist/commands/hub/hub-client/internal/contracts.d.ts +126 -0
- package/dist/commands/hub/hub-client/internal/contracts.js +85 -0
- package/dist/commands/hub/hub-client/internal/problem.d.ts +3 -0
- package/dist/commands/hub/hub-client/internal/problem.js +85 -0
- package/dist/commands/hub/hub-client/internal/transport.d.ts +15 -0
- package/dist/commands/hub/hub-client/internal/transport.js +30 -0
- package/dist/commands/hub/index.d.ts +20 -0
- package/dist/commands/hub/index.js +94 -0
- package/dist/commands/hub/init-plan.d.ts +33 -0
- package/dist/commands/hub/init-plan.js +94 -0
- package/dist/commands/hub/init.d.ts +35 -0
- package/dist/commands/hub/init.js +524 -0
- package/dist/commands/hub/login-flow.d.ts +34 -0
- package/dist/commands/hub/login-flow.js +78 -0
- package/dist/commands/hub/login.d.ts +23 -0
- package/dist/commands/hub/login.js +40 -0
- package/dist/commands/hub/logout.d.ts +32 -0
- package/dist/commands/hub/logout.js +71 -0
- package/dist/commands/hub/origin.d.ts +2 -0
- package/dist/commands/hub/origin.js +27 -0
- package/dist/commands/hub/permissions.d.ts +21 -0
- package/dist/commands/hub/permissions.js +61 -0
- package/dist/commands/hub/projects.d.ts +24 -0
- package/dist/commands/hub/projects.js +49 -0
- package/dist/commands/hub/reporter.d.ts +10 -0
- package/dist/commands/hub/reporter.js +11 -0
- package/dist/commands/hub/starter-agent-runtime.d.ts +37 -0
- package/dist/commands/hub/starter-agent-runtime.js +49 -0
- package/dist/commands/hub/starter-trigger.d.ts +10 -0
- package/dist/commands/hub/starter-trigger.js +30 -0
- package/dist/commands/hub/status-output.d.ts +13 -0
- package/dist/commands/hub/status-output.js +30 -0
- package/dist/commands/onboard.d.ts +10 -0
- package/dist/commands/onboard.js +400 -0
- package/dist/commands/open.d.ts +4 -0
- package/dist/commands/open.js +89 -0
- package/dist/commands/permit/allow.d.ts +20 -0
- package/dist/commands/permit/allow.js +133 -0
- package/dist/commands/permit/deny.d.ts +12 -0
- package/dist/commands/permit/deny.js +105 -0
- package/dist/commands/permit/index.d.ts +3 -0
- package/dist/commands/permit/index.js +27 -0
- package/dist/commands/permit/ls.d.ts +18 -0
- package/dist/commands/permit/ls.js +66 -0
- package/dist/commands/plugin/index.d.ts +17 -0
- package/dist/commands/plugin/index.js +138 -0
- package/dist/commands/plugin/scaffold.d.ts +6 -0
- package/dist/commands/plugin/scaffold.js +162 -0
- package/dist/commands/plugin/shared.d.ts +5 -0
- package/dist/commands/plugin/shared.js +30 -0
- package/dist/commands/project/create.d.ts +10 -0
- package/dist/commands/project/create.js +41 -0
- package/dist/commands/project/delete.d.ts +9 -0
- package/dist/commands/project/delete.js +33 -0
- package/dist/commands/project/index.d.ts +3 -0
- package/dist/commands/project/index.js +28 -0
- package/dist/commands/project/ls.d.ts +5 -0
- package/dist/commands/project/ls.js +19 -0
- package/dist/commands/project/rename.d.ts +15 -0
- package/dist/commands/project/rename.js +50 -0
- package/dist/commands/project/shared.d.ts +11 -0
- package/dist/commands/project/shared.js +18 -0
- package/dist/commands/provider/add.d.ts +30 -0
- package/dist/commands/provider/add.js +123 -0
- package/dist/commands/provider/diagnostic.d.ts +12 -0
- package/dist/commands/provider/diagnostic.js +31 -0
- package/dist/commands/provider/index.d.ts +3 -0
- package/dist/commands/provider/index.js +38 -0
- package/dist/commands/provider/ls.d.ts +20 -0
- package/dist/commands/provider/ls.js +76 -0
- package/dist/commands/provider/models.d.ts +20 -0
- package/dist/commands/provider/models.js +53 -0
- package/dist/commands/provider/rm.d.ts +10 -0
- package/dist/commands/provider/rm.js +45 -0
- package/dist/commands/provider/shared.d.ts +7 -0
- package/dist/commands/provider/shared.js +10 -0
- package/dist/commands/schedule/create.d.ts +19 -0
- package/dist/commands/schedule/create.js +41 -0
- package/dist/commands/schedule/delete.d.ts +10 -0
- package/dist/commands/schedule/delete.js +32 -0
- package/dist/commands/schedule/index.d.ts +3 -0
- package/dist/commands/schedule/index.js +66 -0
- package/dist/commands/schedule/inspect.d.ts +6 -0
- package/dist/commands/schedule/inspect.js +27 -0
- package/dist/commands/schedule/logs.d.ts +6 -0
- package/dist/commands/schedule/logs.js +24 -0
- package/dist/commands/schedule/ls.d.ts +5 -0
- package/dist/commands/schedule/ls.js +25 -0
- package/dist/commands/schedule/pause.d.ts +5 -0
- package/dist/commands/schedule/pause.js +24 -0
- package/dist/commands/schedule/resume.d.ts +5 -0
- package/dist/commands/schedule/resume.js +24 -0
- package/dist/commands/schedule/run-once.d.ts +5 -0
- package/dist/commands/schedule/run-once.js +24 -0
- package/dist/commands/schedule/schema.d.ts +21 -0
- package/dist/commands/schedule/schema.js +67 -0
- package/dist/commands/schedule/shared.d.ts +59 -0
- package/dist/commands/schedule/shared.js +358 -0
- package/dist/commands/schedule/types.d.ts +165 -0
- package/dist/commands/schedule/types.js +2 -0
- package/dist/commands/schedule/update.d.ts +21 -0
- package/dist/commands/schedule/update.js +40 -0
- package/dist/commands/script/index.d.ts +3 -0
- package/dist/commands/script/index.js +19 -0
- package/dist/commands/script/ls.d.ts +6 -0
- package/dist/commands/script/ls.js +20 -0
- package/dist/commands/script/schema.d.ts +5 -0
- package/dist/commands/script/schema.js +13 -0
- package/dist/commands/script/shared.d.ts +11 -0
- package/dist/commands/script/shared.js +59 -0
- package/dist/commands/script/start.d.ts +6 -0
- package/dist/commands/script/start.js +23 -0
- package/dist/commands/script/stop.d.ts +6 -0
- package/dist/commands/script/stop.js +23 -0
- package/dist/commands/speech/index.d.ts +3 -0
- package/dist/commands/speech/index.js +5 -0
- package/dist/commands/terminal/capture.d.ts +10 -0
- package/dist/commands/terminal/capture.js +68 -0
- package/dist/commands/terminal/create.d.ts +11 -0
- package/dist/commands/terminal/create.js +29 -0
- package/dist/commands/terminal/index.d.ts +3 -0
- package/dist/commands/terminal/index.js +45 -0
- package/dist/commands/terminal/kill.d.ts +6 -0
- package/dist/commands/terminal/kill.js +36 -0
- package/dist/commands/terminal/ls.d.ts +11 -0
- package/dist/commands/terminal/ls.js +21 -0
- package/dist/commands/terminal/schema.d.ts +10 -0
- package/dist/commands/terminal/schema.js +17 -0
- package/dist/commands/terminal/send-keys.d.ts +7 -0
- package/dist/commands/terminal/send-keys.js +45 -0
- package/dist/commands/terminal/shared.d.ts +12 -0
- package/dist/commands/terminal/shared.js +62 -0
- package/dist/commands/workspace/archive.d.ts +12 -0
- package/dist/commands/workspace/archive.js +41 -0
- package/dist/commands/workspace/create.d.ts +49 -0
- package/dist/commands/workspace/create.js +114 -0
- package/dist/commands/workspace/index.d.ts +3 -0
- package/dist/commands/workspace/index.js +45 -0
- package/dist/commands/workspace/ls.d.ts +7 -0
- package/dist/commands/workspace/ls.js +28 -0
- package/dist/commands/workspace/rename.d.ts +22 -0
- package/dist/commands/workspace/rename.js +61 -0
- package/dist/commands/workspace/setup.d.ts +11 -0
- package/dist/commands/workspace/setup.js +41 -0
- package/dist/commands/workspace/shared.d.ts +12 -0
- package/dist/commands/workspace/shared.js +20 -0
- package/dist/commands/worktree/archive.d.ts +20 -0
- package/dist/commands/worktree/archive.js +104 -0
- package/dist/commands/worktree/create-input.d.ts +15 -0
- package/dist/commands/worktree/create-input.js +56 -0
- package/dist/commands/worktree/create.d.ts +11 -0
- package/dist/commands/worktree/create.js +58 -0
- package/dist/commands/worktree/index.d.ts +3 -0
- package/dist/commands/worktree/index.js +25 -0
- package/dist/commands/worktree/ls.d.ts +19 -0
- package/dist/commands/worktree/ls.js +97 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/output/index.d.ts +45 -0
- package/dist/output/index.js +47 -0
- package/dist/output/json.d.ts +11 -0
- package/dist/output/json.js +37 -0
- package/dist/output/pairing.d.ts +8 -0
- package/dist/output/pairing.js +24 -0
- package/dist/output/quiet.d.ts +9 -0
- package/dist/output/quiet.js +22 -0
- package/dist/output/render.d.ts +15 -0
- package/dist/output/render.js +84 -0
- package/dist/output/table.d.ts +13 -0
- package/dist/output/table.js +135 -0
- package/dist/output/types.d.ts +73 -0
- package/dist/output/types.js +8 -0
- package/dist/output/with-output.d.ts +41 -0
- package/dist/output/with-output.js +85 -0
- package/dist/output/yaml.d.ts +11 -0
- package/dist/output/yaml.js +38 -0
- package/dist/run.d.ts +14 -0
- package/dist/run.js +40 -0
- package/dist/ssh/ssh-tunnel.d.ts +9 -0
- package/dist/ssh/ssh-tunnel.js +79 -0
- package/dist/utils/client-id.d.ts +2 -0
- package/dist/utils/client-id.js +37 -0
- package/dist/utils/client.d.ts +54 -0
- package/dist/utils/client.js +363 -0
- package/dist/utils/command-options.d.ts +7 -0
- package/dist/utils/command-options.js +25 -0
- package/dist/utils/duration.d.ts +7 -0
- package/dist/utils/duration.js +39 -0
- package/dist/utils/errors.d.ts +5 -0
- package/dist/utils/errors.js +10 -0
- package/dist/utils/paths.d.ts +12 -0
- package/dist/utils/paths.js +22 -0
- package/dist/utils/provider-model.d.ts +11 -0
- package/dist/utils/provider-model.js +49 -0
- package/dist/utils/timeline.d.ts +11 -0
- package/dist/utils/timeline.js +11 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/package.json +50 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { cancel, confirm, intro, isCancel, log, note, outro, select, spinner, text, } from "@clack/prompts";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { link, lstat, mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { DEFAULT_HUB_ORIGIN, resolveHubCredential } from "./authority.js";
|
|
8
|
+
import { withHubDaemon } from "./daemon-client.js";
|
|
9
|
+
import { HubCommandError } from "./error.js";
|
|
10
|
+
import { createHubInitScaffold, hubLoginResumeCommand, resolveHubInitConnection, } from "./init-plan.js";
|
|
11
|
+
import { runHubConnect } from "./connect.js";
|
|
12
|
+
import { normalizeHubOrigin } from "./origin.js";
|
|
13
|
+
import { selectedStarterAgentRuntime, starterAgentProviderSnapshotState, suggestedStarterAgentChoice, } from "./starter-agent-runtime.js";
|
|
14
|
+
import { availableStarterTriggerConnections, } from "./starter-trigger.js";
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
const DAEMON_READY_TIMEOUT_MS = 60000;
|
|
17
|
+
const DAEMON_READY_POLL_MS = 250;
|
|
18
|
+
const PROVIDER_READY_TIMEOUT_MS = 60000;
|
|
19
|
+
class HubInitCancelledError extends Error {
|
|
20
|
+
}
|
|
21
|
+
function initErrorMessage(error) {
|
|
22
|
+
if (error instanceof HubCommandError && error.details) {
|
|
23
|
+
return `${error.message}\n${error.details}`;
|
|
24
|
+
}
|
|
25
|
+
return error instanceof Error ? error.message : String(error);
|
|
26
|
+
}
|
|
27
|
+
export function addHubInitCommand(parent, environment) {
|
|
28
|
+
parent
|
|
29
|
+
.command("init")
|
|
30
|
+
.description("Create and optionally deploy a safe starter Hub trigger")
|
|
31
|
+
.action(async () => {
|
|
32
|
+
try {
|
|
33
|
+
await runHubInit(environment);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof HubInitCancelledError) {
|
|
37
|
+
cancel(error.message);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
cancel(initErrorMessage(error));
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export async function runHubInit(environment) {
|
|
46
|
+
await runHubGuidedSetup(environment);
|
|
47
|
+
}
|
|
48
|
+
export async function runHubGuidedSetup(environment, state = {}) {
|
|
49
|
+
requireInteractiveTerminal(environment);
|
|
50
|
+
intro("Set up Paseo Hub");
|
|
51
|
+
const cwd = environment.cwd();
|
|
52
|
+
const activeLogin = environment.credentials.active();
|
|
53
|
+
const origin = state.origin ?? (await ensureLogin(activeLogin?.origin, environment));
|
|
54
|
+
const daemonId = state.daemonId ?? (await ensureDaemonConnection(origin, environment));
|
|
55
|
+
const resources = await loadConfigurationResources(origin, environment);
|
|
56
|
+
const triggerConnections = await resolveStarterTriggerConnections(resources, cwd);
|
|
57
|
+
reportStarterTriggerConnections(environment, triggerConnections);
|
|
58
|
+
const trigger = await chooseStarterTriggerConnection(environment, triggerConnections);
|
|
59
|
+
const daemon = resources.daemons.find(({ id }) => id === daemonId);
|
|
60
|
+
if (daemon === undefined) {
|
|
61
|
+
throw new HubCommandError("HUB_DAEMON_RESOURCE_MISSING", "The connected daemon is not available in this Hub organization. Reconnect it and try again.");
|
|
62
|
+
}
|
|
63
|
+
log.success(`Connected as ${daemon.slug}`);
|
|
64
|
+
const agent = await chooseStarterAgentRuntime(environment, cwd);
|
|
65
|
+
const providerFilters = await collectProviderIdentity(trigger, environment);
|
|
66
|
+
const scaffold = createHubInitScaffold({
|
|
67
|
+
cwd,
|
|
68
|
+
daemonSlug: daemon.slug,
|
|
69
|
+
agent,
|
|
70
|
+
provider: trigger.provider,
|
|
71
|
+
providerFilters,
|
|
72
|
+
});
|
|
73
|
+
const credential = resolveHubCredential({
|
|
74
|
+
options: { origin },
|
|
75
|
+
env: environment.env,
|
|
76
|
+
credentials: environment.credentials,
|
|
77
|
+
origin,
|
|
78
|
+
});
|
|
79
|
+
await withSpinner("Validating trigger", async () => {
|
|
80
|
+
await environment.hub.validateTrigger(origin, credential, scaffold.trigger);
|
|
81
|
+
});
|
|
82
|
+
log.success("Dry run passed");
|
|
83
|
+
const triggerExists = await prepareScaffoldDestination(cwd, scaffold.triggerPath);
|
|
84
|
+
if (triggerExists &&
|
|
85
|
+
!(await requiredConfirm(environment, `Replace the existing ${scaffold.triggerPath}?`, false))) {
|
|
86
|
+
throw new HubInitCancelledError(`${scaffold.triggerPath} left unchanged.`);
|
|
87
|
+
}
|
|
88
|
+
await writeScaffold(cwd, scaffold, triggerExists);
|
|
89
|
+
log.success(`Created ${scaffold.triggerPath}`);
|
|
90
|
+
const deploy = state.deploy ?? (await requiredConfirm(environment, "Deploy now?", true));
|
|
91
|
+
if (deploy) {
|
|
92
|
+
await withSpinner("Deploying trigger", async () => {
|
|
93
|
+
await environment.hub.installTrigger(origin, credential, scaffold.trigger);
|
|
94
|
+
});
|
|
95
|
+
log.success("Deployed");
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
reportMessage(environment, "Skipped deployment. Run `paseo hub deploy` when ready.");
|
|
99
|
+
}
|
|
100
|
+
const triggersUrl = new URL("/triggers", origin).toString();
|
|
101
|
+
note(`${scaffold.testAction}\nManage it at ${triggersUrl}`, "Test your trigger");
|
|
102
|
+
outro(deploy ? "Hub is ready" : "Hub trigger is ready");
|
|
103
|
+
}
|
|
104
|
+
export async function continueHubGuidedSetup(origin, environment) {
|
|
105
|
+
const currentStatus = await withHubDaemon(environment.daemon, undefined, async (daemon) => daemon.getHubStatus().then((response) => response.status));
|
|
106
|
+
const current = resolveHubInitConnection(currentStatus, origin);
|
|
107
|
+
if (current.kind === "connected") {
|
|
108
|
+
reportMessage(environment, `This daemon is already connected to ${origin}. Permissions: ${currentStatus.permissions.join(", ") || "None"}.`);
|
|
109
|
+
}
|
|
110
|
+
else if (current.kind === "pending") {
|
|
111
|
+
await waitForDaemonReady(origin, environment.daemon);
|
|
112
|
+
}
|
|
113
|
+
else if (current.kind === "conflict") {
|
|
114
|
+
reportMessage(environment, `This daemon is connected to ${current.origin}. Disconnect it before connecting to ${origin}.`);
|
|
115
|
+
}
|
|
116
|
+
else if (await requiredConfirm(environment, "Connect this daemon to Paseo Hub?\n\nConnecting lets Hub identify this daemon and show whether it is online.\nIt does not allow Hub to create workspaces or run agents.", true)) {
|
|
117
|
+
const grantExecution = await requiredConfirm(environment, "Allow Hub automations to run agents on this daemon?\n\nThis lets workflows triggered from GitHub, Slack, Discord, Linear, and other integrations create workspaces and run agents here.\n\nAgents can access files and run commands allowed by their workspace runtime.", false);
|
|
118
|
+
await ensureDaemonConnection(origin, environment, true, grantExecution ? ["hub.execute"] : []);
|
|
119
|
+
if (!grantExecution) {
|
|
120
|
+
reportMessage(environment, "Daemon connected with no permissions.\n\nEnable Hub automations later:\n paseo hub permissions grant hub.execute");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
reportMessage(environment, `Skipped daemon connection. Connect later with: ${hubLoginResumeCommand("connect", origin)}`);
|
|
125
|
+
}
|
|
126
|
+
reportMessage(environment, `Configure triggers in Hub: ${new URL("/triggers", origin).toString()}\nOr scaffold triggers as code: ${hubLoginResumeCommand("init", origin)}`);
|
|
127
|
+
}
|
|
128
|
+
async function ensureLogin(activeOrigin, environment) {
|
|
129
|
+
const endpoint = await requiredSelect(environment, {
|
|
130
|
+
message: "Hub endpoint",
|
|
131
|
+
initialValue: activeOrigin === undefined || activeOrigin === DEFAULT_HUB_ORIGIN ? "hosted" : "custom",
|
|
132
|
+
options: [
|
|
133
|
+
{ value: "hosted", label: "hub.paseo.sh" },
|
|
134
|
+
{ value: "custom", label: "Custom endpoint…" },
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
const origin = endpoint === "hosted"
|
|
138
|
+
? DEFAULT_HUB_ORIGIN
|
|
139
|
+
: await requiredText(environment, {
|
|
140
|
+
message: "Custom Hub URL",
|
|
141
|
+
initialValue: activeOrigin === undefined || activeOrigin === DEFAULT_HUB_ORIGIN
|
|
142
|
+
? environment.env.PASEO_HUB_URL
|
|
143
|
+
: activeOrigin,
|
|
144
|
+
validate(value) {
|
|
145
|
+
try {
|
|
146
|
+
normalizeHubOrigin(value ?? "");
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return "Enter a valid Hub URL";
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
const normalizedOrigin = normalizeHubOrigin(origin);
|
|
155
|
+
if (environment.credentials.get(normalizedOrigin) !== null) {
|
|
156
|
+
log.success(`Logged in to ${normalizedOrigin}`);
|
|
157
|
+
return normalizedOrigin;
|
|
158
|
+
}
|
|
159
|
+
environment.reporter.progress(`Logging in to ${normalizedOrigin}`);
|
|
160
|
+
const credential = await environment.login.authorize(normalizedOrigin);
|
|
161
|
+
environment.credentials.save({ origin: normalizedOrigin, credential });
|
|
162
|
+
log.success(`Logged in to ${normalizedOrigin}`);
|
|
163
|
+
return normalizedOrigin;
|
|
164
|
+
}
|
|
165
|
+
async function ensureDaemonConnection(origin, environment, confirmed = false, permissions = ["hub.execute"]) {
|
|
166
|
+
const status = await withHubDaemon(environment.daemon, undefined, async (daemon) => daemon.getHubStatus().then((response) => response.status));
|
|
167
|
+
const connection = resolveHubInitConnection(status, origin);
|
|
168
|
+
if (connection.kind === "connected") {
|
|
169
|
+
if (permissions.includes("hub.execute") && !status.permissions.includes("hub.execute")) {
|
|
170
|
+
throw new HubCommandError("HUB_DAEMON_EXECUTION_NOT_ALLOWED", "This daemon is connected to Hub but cannot run Hub automations. Run `paseo hub permissions grant hub.execute`, then run Hub init again.");
|
|
171
|
+
}
|
|
172
|
+
return connection.daemonId;
|
|
173
|
+
}
|
|
174
|
+
if (connection.kind === "pending") {
|
|
175
|
+
return waitForDaemonReady(origin, environment.daemon);
|
|
176
|
+
}
|
|
177
|
+
if (connection.kind === "conflict") {
|
|
178
|
+
throw new HubCommandError("HUB_DAEMON_ALREADY_CONNECTED", `This daemon is connected to ${connection.origin}. Disconnect it before running Hub init for ${origin}.`);
|
|
179
|
+
}
|
|
180
|
+
if (!confirmed &&
|
|
181
|
+
!(await requiredConfirm(environment, `Connect this daemon to ${origin} and allow Hub workflows to create and control workspaces and agents?`, true))) {
|
|
182
|
+
throw new HubInitCancelledError("A connected daemon is required to create the trigger.");
|
|
183
|
+
}
|
|
184
|
+
return connectDaemon(origin, environment, permissions);
|
|
185
|
+
}
|
|
186
|
+
async function connectDaemon(origin, environment, permissions = ["hub.execute"]) {
|
|
187
|
+
await runHubConnect(origin, { permissions }, {
|
|
188
|
+
env: environment.env,
|
|
189
|
+
credentials: environment.credentials,
|
|
190
|
+
hub: environment.hub,
|
|
191
|
+
daemon: environment.daemon,
|
|
192
|
+
reporter: environment.reporter,
|
|
193
|
+
});
|
|
194
|
+
return waitForDaemonReady(origin, environment.daemon);
|
|
195
|
+
}
|
|
196
|
+
async function waitForDaemonReady(origin, connection) {
|
|
197
|
+
return withSpinner("Waiting for the daemon to connect", async (reporter) => withHubDaemon(connection, undefined, async (daemon) => {
|
|
198
|
+
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
199
|
+
while (true) {
|
|
200
|
+
const status = (await daemon.getHubStatus()).status;
|
|
201
|
+
const resolution = resolveHubInitConnection(status, origin);
|
|
202
|
+
if (resolution.kind === "connected")
|
|
203
|
+
return resolution.daemonId;
|
|
204
|
+
if (resolution.kind === "conflict") {
|
|
205
|
+
throw new HubCommandError("HUB_DAEMON_ALREADY_CONNECTED", `This daemon connected to ${resolution.origin} while Hub init was waiting for ${origin}.`);
|
|
206
|
+
}
|
|
207
|
+
if (resolution.kind === "connect") {
|
|
208
|
+
throw new HubCommandError("HUB_DAEMON_CONNECTION_LOST", "The daemon lost its Hub relationship while Hub init was waiting for it.");
|
|
209
|
+
}
|
|
210
|
+
if (Date.now() >= deadline) {
|
|
211
|
+
throw new HubCommandError("HUB_DAEMON_CONNECTION_TIMEOUT", "The daemon did not connect within 60 seconds. Check `paseo hub status`, then run Hub init again.");
|
|
212
|
+
}
|
|
213
|
+
reporter.progress(`Daemon is ${resolution.state}`);
|
|
214
|
+
await delay(DAEMON_READY_POLL_MS);
|
|
215
|
+
}
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
async function resolveStarterTriggerConnections(resources, cwd) {
|
|
219
|
+
const remote = await readCommandValue("git", ["remote", "get-url", "origin"], cwd);
|
|
220
|
+
const repository = remote === undefined ? undefined : githubRepositoryFromRemote(remote);
|
|
221
|
+
const connections = availableStarterTriggerConnections(resources, repository);
|
|
222
|
+
if (connections.length === 0) {
|
|
223
|
+
throw new HubInitCancelledError("No Hub app connection is ready for this trigger.\nConnect GitHub, Slack, or Discord in Hub → Apps, then run `paseo hub init` again.");
|
|
224
|
+
}
|
|
225
|
+
return connections;
|
|
226
|
+
}
|
|
227
|
+
function reportStarterTriggerConnections(environment, connections) {
|
|
228
|
+
const details = `${connections.map(({ label }) => label).join("\n")}\n\nOnly configured connections are shown. To add another, open Hub → Apps, then run \`paseo hub init\` again.`;
|
|
229
|
+
if (environment.prompts === undefined) {
|
|
230
|
+
note(details, "Hub app connections ready for this trigger");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
environment.prompts.message(`Hub app connections ready for this trigger:\n${details}`);
|
|
234
|
+
}
|
|
235
|
+
async function chooseStarterTriggerConnection(environment, connections) {
|
|
236
|
+
if (connections.length === 1) {
|
|
237
|
+
const connection = connections[0];
|
|
238
|
+
reportMessage(environment, `Using ${connection.label}`);
|
|
239
|
+
return connection;
|
|
240
|
+
}
|
|
241
|
+
const id = await requiredSelect(environment, {
|
|
242
|
+
message: "Trigger connection",
|
|
243
|
+
options: connections.map((connection) => ({ value: connection.id, label: connection.label })),
|
|
244
|
+
});
|
|
245
|
+
const connection = connections.find((candidate) => candidate.id === id);
|
|
246
|
+
if (connection === undefined) {
|
|
247
|
+
throw new HubCommandError("HUB_PROVIDER_CONNECTION_INVALID", "The selected Hub app connection is no longer available. Run paseo hub init again.");
|
|
248
|
+
}
|
|
249
|
+
return connection;
|
|
250
|
+
}
|
|
251
|
+
async function chooseStarterAgentRuntime(environment, cwd) {
|
|
252
|
+
const providers = await waitForStarterAgentProviders(environment, cwd);
|
|
253
|
+
const provider = await chooseStarterAgentProvider(environment, providers);
|
|
254
|
+
const model = await chooseStarterAgentModel(environment, provider);
|
|
255
|
+
const mode = await chooseStarterAgentMode(environment, provider);
|
|
256
|
+
const runtime = selectedStarterAgentRuntime(provider, model, mode);
|
|
257
|
+
if (runtime?.mode === undefined) {
|
|
258
|
+
throw new HubCommandError("HUB_AGENT_RUNTIME_SELECTION_INVALID", "The selected starter agent runtime is no longer available. Run paseo hub init again.");
|
|
259
|
+
}
|
|
260
|
+
return { ...runtime, mode: runtime.mode };
|
|
261
|
+
}
|
|
262
|
+
async function waitForStarterAgentProviders(environment, cwd) {
|
|
263
|
+
return withSpinner("Discovering agent runtimes", async (reporter) => withHubDaemon(environment.daemon, undefined, async (daemon) => {
|
|
264
|
+
const deadline = Date.now() + PROVIDER_READY_TIMEOUT_MS;
|
|
265
|
+
while (true) {
|
|
266
|
+
const snapshot = await daemon.getProvidersSnapshot({ cwd });
|
|
267
|
+
const state = starterAgentProviderSnapshotState(snapshot.entries);
|
|
268
|
+
if (state.kind === "ready") {
|
|
269
|
+
const providers = state.providers.filter(({ modes }) => modes.length > 0);
|
|
270
|
+
if (providers.length > 0)
|
|
271
|
+
return providers;
|
|
272
|
+
throw new HubCommandError("HUB_AGENT_RUNTIME_REQUIRED", "No agent runtime with an execution mode is available from this daemon. Configure one, then run paseo hub init again.");
|
|
273
|
+
}
|
|
274
|
+
if (state.kind === "unavailable") {
|
|
275
|
+
throw new HubCommandError("HUB_AGENT_RUNTIME_REQUIRED", "No usable agent runtime is available from this daemon. Configure an enabled provider with a selectable model, then run paseo hub init again.");
|
|
276
|
+
}
|
|
277
|
+
if (Date.now() >= deadline) {
|
|
278
|
+
throw new HubCommandError("HUB_AGENT_RUNTIME_TIMEOUT", "Agent runtime discovery did not finish within 60 seconds. Check the daemon's provider configuration, then run paseo hub init again.");
|
|
279
|
+
}
|
|
280
|
+
reporter.progress("Waiting for agent runtime discovery");
|
|
281
|
+
await delay(DAEMON_READY_POLL_MS);
|
|
282
|
+
}
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
async function chooseStarterAgentProvider(environment, providers) {
|
|
286
|
+
const selected = await requiredSelect(environment, {
|
|
287
|
+
message: "Starter agent provider",
|
|
288
|
+
options: providers.map((provider) => ({
|
|
289
|
+
value: provider.id,
|
|
290
|
+
label: provider.label,
|
|
291
|
+
})),
|
|
292
|
+
});
|
|
293
|
+
const provider = providers.find((candidate) => candidate.id === selected);
|
|
294
|
+
if (provider === undefined)
|
|
295
|
+
throw invalidStarterAgentSelection();
|
|
296
|
+
return provider;
|
|
297
|
+
}
|
|
298
|
+
async function chooseStarterAgentModel(environment, provider) {
|
|
299
|
+
const suggested = suggestedStarterAgentChoice(provider.models);
|
|
300
|
+
const selected = await requiredSelect(environment, {
|
|
301
|
+
message: "Starter agent model",
|
|
302
|
+
...(suggested === undefined ? {} : { initialValue: suggested.id }),
|
|
303
|
+
options: provider.models.map((model) => ({
|
|
304
|
+
value: model.id,
|
|
305
|
+
label: model.label,
|
|
306
|
+
...(model.suggested ? { hint: "suggested" } : {}),
|
|
307
|
+
})),
|
|
308
|
+
});
|
|
309
|
+
if (!provider.models.some((model) => model.id === selected))
|
|
310
|
+
throw invalidStarterAgentSelection();
|
|
311
|
+
return selected;
|
|
312
|
+
}
|
|
313
|
+
async function chooseStarterAgentMode(environment, provider) {
|
|
314
|
+
if (provider.modes.length === 0)
|
|
315
|
+
return undefined;
|
|
316
|
+
const suggested = suggestedStarterAgentChoice(provider.modes);
|
|
317
|
+
const selected = await requiredSelect(environment, {
|
|
318
|
+
message: "Starter agent mode",
|
|
319
|
+
...(suggested === undefined ? {} : { initialValue: suggested.id }),
|
|
320
|
+
options: provider.modes.map((mode) => ({
|
|
321
|
+
value: mode.id,
|
|
322
|
+
label: mode.label,
|
|
323
|
+
...(mode.suggested ? { hint: "suggested" } : {}),
|
|
324
|
+
})),
|
|
325
|
+
});
|
|
326
|
+
if (!provider.modes.some((mode) => mode.id === selected))
|
|
327
|
+
throw invalidStarterAgentSelection();
|
|
328
|
+
return selected;
|
|
329
|
+
}
|
|
330
|
+
function invalidStarterAgentSelection() {
|
|
331
|
+
return new HubCommandError("HUB_AGENT_RUNTIME_SELECTION_INVALID", "The selected starter agent runtime is no longer available. Run paseo hub init again.");
|
|
332
|
+
}
|
|
333
|
+
async function collectProviderIdentity(trigger, environment) {
|
|
334
|
+
if (trigger.provider === "github") {
|
|
335
|
+
const login = await readGhValue(["api", "user", "--jq", ".login"]);
|
|
336
|
+
return {
|
|
337
|
+
...trigger.filters,
|
|
338
|
+
user: await requiredText(environment, {
|
|
339
|
+
message: "Your GitHub username (only this user can trigger the bot)",
|
|
340
|
+
initialValue: login,
|
|
341
|
+
}),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (trigger.provider === "slack") {
|
|
345
|
+
return {
|
|
346
|
+
...trigger.filters,
|
|
347
|
+
user: await requiredText(environment, {
|
|
348
|
+
message: "Your Slack member ID (only this user can trigger the bot)",
|
|
349
|
+
}),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
...trigger.filters,
|
|
354
|
+
user: await requiredText(environment, {
|
|
355
|
+
message: "Your Discord user ID (only this user can trigger the bot)",
|
|
356
|
+
}),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
async function loadConfigurationResources(origin, environment) {
|
|
360
|
+
try {
|
|
361
|
+
return await withSpinner("Loading Hub connections", () => environment.hub.listConfigurationResources(origin, resolveHubCredential({
|
|
362
|
+
options: { origin },
|
|
363
|
+
env: environment.env,
|
|
364
|
+
credentials: environment.credentials,
|
|
365
|
+
origin,
|
|
366
|
+
})));
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
if (error instanceof HubCommandError &&
|
|
370
|
+
(error.code === "HUB_NOT_FOUND" || error.code === "HUB_INVALID_RESPONSE")) {
|
|
371
|
+
throw new HubCommandError("HUB_UPDATE_REQUIRED", "This Hub needs an update before guided setup can use organization triggers. Update Hub and try again.");
|
|
372
|
+
}
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async function writeScaffold(cwd, scaffold, replaceExisting) {
|
|
377
|
+
const destination = path.join(cwd, scaffold.triggerPath);
|
|
378
|
+
const temporary = path.join(path.dirname(destination), `.${path.basename(destination)}.${process.pid}.${randomUUID()}.tmp`);
|
|
379
|
+
try {
|
|
380
|
+
await writeFile(temporary, scaffold.trigger, { flag: "wx" });
|
|
381
|
+
if (replaceExisting) {
|
|
382
|
+
await rename(temporary, destination);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
await link(temporary, destination);
|
|
386
|
+
await rm(temporary);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
await rm(temporary, { force: true });
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async function prepareScaffoldDestination(cwd, triggerPath) {
|
|
395
|
+
const root = path.resolve(cwd);
|
|
396
|
+
await requireSafeScaffoldDirectory(path.join(root, ".paseo"), ".paseo");
|
|
397
|
+
await requireSafeScaffoldDirectory(path.join(root, ".paseo", "triggers"), ".paseo/triggers");
|
|
398
|
+
const destination = path.join(root, triggerPath);
|
|
399
|
+
try {
|
|
400
|
+
const stats = await lstat(destination);
|
|
401
|
+
if (stats.isSymbolicLink())
|
|
402
|
+
throw unsafeScaffoldPath(triggerPath);
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
if (errorCode(error) === "ENOENT")
|
|
407
|
+
return false;
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function requireSafeScaffoldDirectory(target, displayPath) {
|
|
412
|
+
try {
|
|
413
|
+
const stats = await lstat(target);
|
|
414
|
+
if (stats.isSymbolicLink())
|
|
415
|
+
throw unsafeScaffoldPath(displayPath);
|
|
416
|
+
if (!stats.isDirectory()) {
|
|
417
|
+
throw new HubCommandError("HUB_TRIGGER_DIRECTORY_INVALID", `${displayPath} must be a directory.`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
if (errorCode(error) !== "ENOENT")
|
|
422
|
+
throw error;
|
|
423
|
+
await mkdir(target);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function unsafeScaffoldPath(triggerPath) {
|
|
427
|
+
return new HubCommandError("HUB_TRIGGER_UNSAFE_PATH", `${triggerPath} must not use a symlink.`);
|
|
428
|
+
}
|
|
429
|
+
function errorCode(error) {
|
|
430
|
+
return error instanceof Error && "code" in error && typeof error.code === "string"
|
|
431
|
+
? error.code
|
|
432
|
+
: undefined;
|
|
433
|
+
}
|
|
434
|
+
function delay(milliseconds) {
|
|
435
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
436
|
+
}
|
|
437
|
+
async function readGhValue(args) {
|
|
438
|
+
return readCommandValue("gh", args);
|
|
439
|
+
}
|
|
440
|
+
async function readCommandValue(command, args, cwd) {
|
|
441
|
+
try {
|
|
442
|
+
const { stdout } = await execFileAsync(command, [...args], { encoding: "utf8", cwd });
|
|
443
|
+
return stdout.trim() || undefined;
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
export function githubRepositoryFromRemote(remote) {
|
|
450
|
+
const trimmed = remote.trim().replace(/\.git$/u, "");
|
|
451
|
+
const ssh = /^(?:ssh:\/\/)?git@github\.com[:/]([^/]+\/[^/]+)$/u.exec(trimmed);
|
|
452
|
+
if (ssh?.[1] !== undefined)
|
|
453
|
+
return ssh[1];
|
|
454
|
+
try {
|
|
455
|
+
const url = new URL(trimmed);
|
|
456
|
+
if (url.hostname !== "github.com")
|
|
457
|
+
return undefined;
|
|
458
|
+
const repository = url.pathname.replace(/^\//u, "");
|
|
459
|
+
return /^[^/]+\/[^/]+$/u.test(repository) ? repository : undefined;
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async function withSpinner(message, action) {
|
|
466
|
+
const progress = spinner();
|
|
467
|
+
progress.start(message);
|
|
468
|
+
try {
|
|
469
|
+
const result = await action({ progress: (nextMessage) => progress.message(nextMessage) });
|
|
470
|
+
progress.stop(message);
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
progress.error(message);
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function requiredText(environment, options) {
|
|
479
|
+
const request = {
|
|
480
|
+
...options,
|
|
481
|
+
validate(value) {
|
|
482
|
+
const input = value ?? "";
|
|
483
|
+
const customError = options.validate?.(input);
|
|
484
|
+
if (customError !== undefined)
|
|
485
|
+
return customError;
|
|
486
|
+
return input.trim().length === 0 ? "A value is required" : undefined;
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
const answer = environment.prompts === undefined
|
|
490
|
+
? await text(request)
|
|
491
|
+
: await environment.prompts.text(request);
|
|
492
|
+
if (isCancel(answer))
|
|
493
|
+
throw new HubInitCancelledError("Hub init cancelled.");
|
|
494
|
+
return answer.trim();
|
|
495
|
+
}
|
|
496
|
+
async function requiredConfirm(environment, message, initialValue) {
|
|
497
|
+
const answer = environment.prompts === undefined
|
|
498
|
+
? await confirm({ message, initialValue })
|
|
499
|
+
: await environment.prompts.confirm(message, initialValue);
|
|
500
|
+
if (isCancel(answer))
|
|
501
|
+
throw new HubInitCancelledError("Hub init cancelled.");
|
|
502
|
+
return answer;
|
|
503
|
+
}
|
|
504
|
+
async function requiredSelect(environment, options) {
|
|
505
|
+
const answer = environment.prompts === undefined
|
|
506
|
+
? await select(options)
|
|
507
|
+
: (await environment.prompts.select(options));
|
|
508
|
+
if (isCancel(answer))
|
|
509
|
+
throw new HubInitCancelledError("Hub init cancelled.");
|
|
510
|
+
return answer;
|
|
511
|
+
}
|
|
512
|
+
function requireInteractiveTerminal(environment) {
|
|
513
|
+
if (!(environment.isInteractive?.() ?? (process.stdin.isTTY && process.stdout.isTTY))) {
|
|
514
|
+
throw new HubCommandError("HUB_INIT_INTERACTIVE_REQUIRED", "paseo hub init requires a TTY.");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function reportMessage(environment, message) {
|
|
518
|
+
if (environment.prompts === undefined) {
|
|
519
|
+
log.message(message);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
environment.prompts.message(message);
|
|
523
|
+
}
|
|
524
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { HubHttpClient } from "./hub-client/index.js";
|
|
2
|
+
export interface LoginWaiter {
|
|
3
|
+
wait(milliseconds: number): Promise<void>;
|
|
4
|
+
now(): number;
|
|
5
|
+
}
|
|
6
|
+
export interface BrowserOpener {
|
|
7
|
+
open(url: string): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
type BrowserLaunch = (command: string, args: string[]) => Promise<void>;
|
|
10
|
+
interface SystemBrowserOptions {
|
|
11
|
+
hostPlatform?: NodeJS.Platform;
|
|
12
|
+
launch?: BrowserLaunch;
|
|
13
|
+
}
|
|
14
|
+
export declare class SystemBrowser implements BrowserOpener {
|
|
15
|
+
private readonly hostPlatform;
|
|
16
|
+
private readonly launch;
|
|
17
|
+
constructor(options?: SystemBrowserOptions);
|
|
18
|
+
open(url: string): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
interface CliLoginFlowOptions {
|
|
21
|
+
hub: Pick<HubHttpClient, "startCliAuthorization" | "pollCliAuthorization">;
|
|
22
|
+
waiter: LoginWaiter;
|
|
23
|
+
browser: BrowserOpener;
|
|
24
|
+
instructions(verificationUri: string, userCode: string): void;
|
|
25
|
+
openBrowser: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare class CliLoginFlow {
|
|
28
|
+
private readonly options;
|
|
29
|
+
constructor(options: CliLoginFlowOptions);
|
|
30
|
+
authorize(origin: string): Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
export declare function createCliLoginFlow(hub: HubHttpClient): CliLoginFlow;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=login-flow.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
export class SystemBrowser {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.hostPlatform = options.hostPlatform ?? platform();
|
|
6
|
+
this.launch = options.launch ?? launchDetached;
|
|
7
|
+
}
|
|
8
|
+
async open(url) {
|
|
9
|
+
if (this.hostPlatform === "win32") {
|
|
10
|
+
await this.launch("rundll32.exe", ["url.dll,FileProtocolHandler", url]);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
await this.launch(this.hostPlatform === "darwin" ? "open" : "xdg-open", [url]);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class CliLoginFlow {
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.options = options;
|
|
19
|
+
}
|
|
20
|
+
async authorize(origin) {
|
|
21
|
+
const authorization = await this.options.hub.startCliAuthorization(origin);
|
|
22
|
+
this.options.instructions(authorization.verificationUri, authorization.userCode);
|
|
23
|
+
if (this.options.openBrowser) {
|
|
24
|
+
await this.options.browser.open(authorization.verificationUriComplete).catch(() => undefined);
|
|
25
|
+
}
|
|
26
|
+
let interval = authorization.interval;
|
|
27
|
+
const expiresAt = Date.parse(authorization.expiresAt);
|
|
28
|
+
while (true) {
|
|
29
|
+
const remaining = expiresAt - this.options.waiter.now();
|
|
30
|
+
if (remaining <= 0)
|
|
31
|
+
throw new Error("Hub CLI login expired");
|
|
32
|
+
await this.options.waiter.wait(Math.min(interval * 1000, remaining));
|
|
33
|
+
const pollLifetime = expiresAt - this.options.waiter.now();
|
|
34
|
+
if (pollLifetime <= 0)
|
|
35
|
+
throw new Error("Hub CLI login expired");
|
|
36
|
+
const outcome = await this.options.hub.pollCliAuthorization(origin, authorization.deviceCode, pollLifetime);
|
|
37
|
+
const credential = approvedCredential(outcome);
|
|
38
|
+
if (credential !== null)
|
|
39
|
+
return credential;
|
|
40
|
+
if (outcome.status === "denied")
|
|
41
|
+
throw new Error("Hub CLI login was denied");
|
|
42
|
+
if (outcome.status === "expired")
|
|
43
|
+
throw new Error("Hub CLI login expired");
|
|
44
|
+
if (outcome.status === "disclosed")
|
|
45
|
+
throw new Error("Hub CLI login was already completed");
|
|
46
|
+
if (outcome.status !== "retry_later")
|
|
47
|
+
interval = outcome.interval;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function createCliLoginFlow(hub) {
|
|
52
|
+
return new CliLoginFlow({
|
|
53
|
+
hub,
|
|
54
|
+
waiter: {
|
|
55
|
+
wait: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
56
|
+
now: Date.now,
|
|
57
|
+
},
|
|
58
|
+
browser: new SystemBrowser(),
|
|
59
|
+
instructions(verificationUri, userCode) {
|
|
60
|
+
process.stderr.write(`Open ${verificationUri} and enter code ${userCode}\n`);
|
|
61
|
+
},
|
|
62
|
+
openBrowser: process.stderr.isTTY === true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function approvedCredential(outcome) {
|
|
66
|
+
return outcome.status === "authorized" ? outcome.credential : null;
|
|
67
|
+
}
|
|
68
|
+
async function launchDetached(command, args) {
|
|
69
|
+
await new Promise((resolve, reject) => {
|
|
70
|
+
const child = spawn(command, args, { detached: true, shell: false, stdio: "ignore" });
|
|
71
|
+
child.once("spawn", () => {
|
|
72
|
+
child.unref();
|
|
73
|
+
resolve();
|
|
74
|
+
});
|
|
75
|
+
child.once("error", reject);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=login-flow.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type SingleResult } from "../../output/index.js";
|
|
3
|
+
import type { HubCredentialStore } from "./credentials.js";
|
|
4
|
+
import type { CliLoginFlow } from "./login-flow.js";
|
|
5
|
+
import { type HubReporter } from "./reporter.js";
|
|
6
|
+
interface HubLoginResult {
|
|
7
|
+
origin: string;
|
|
8
|
+
status: "logged_in";
|
|
9
|
+
}
|
|
10
|
+
interface HubLoginDependencies {
|
|
11
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
12
|
+
credentials: HubCredentialStore;
|
|
13
|
+
flow: Pick<CliLoginFlow, "authorize">;
|
|
14
|
+
reporter: HubReporter;
|
|
15
|
+
isInteractive?(): boolean;
|
|
16
|
+
continueGuidedSetup?(origin: string): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare function runHubLogin(originInput: string | undefined, options: {
|
|
19
|
+
json?: boolean;
|
|
20
|
+
}, dependencies: HubLoginDependencies): Promise<SingleResult<HubLoginResult>>;
|
|
21
|
+
export declare function addHubLoginCommand(parent: Command, dependencies: HubLoginDependencies): void;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=login.d.ts.map
|