mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 10:12:44 +00:00
feat(cli): simplify Hub login and export triggers (#4050)
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ Paseo Hub is an explicit opt-in connection from one Paseo daemon to one Hub. Run
|
||||
not register it with a Hub. The relationship begins only when a user runs
|
||||
`paseo hub connect [url]` from the daemon machine with an explicit API key or matching stored CLI login.
|
||||
|
||||
The human CLI login and daemon relationship are separate identities. `paseo hub login [url]` stores a durable organization-scoped CLI credential keyed by normalized Hub origin under `PASEO_HOME`. Origin resolution uses explicit command input, `PASEO_HUB_URL`, active login, then `https://hub.paseo.sh`. Connect uses exact-origin authority to request a one-time enrollment token, then passes only that token to the daemon. The daemon generates and persists its own relationship credential.
|
||||
The human CLI login and daemon relationship are separate identities. `paseo hub login [url]` stores a durable organization-scoped CLI credential keyed by normalized Hub origin under `PASEO_HOME`. Interactive login optionally connects the local daemon, then points to the Hub UI for trigger configuration; it does not scaffold or deploy configuration. `paseo hub init` remains the explicit triggers-as-code scaffold. `paseo hub export [directory]` writes the active organization's current triggers as one self-contained YAML file per trigger, using the active login unless another Hub or API key is selected. Origin resolution uses explicit command input, `PASEO_HUB_URL`, active login, then `https://hub.paseo.sh`. Connect uses exact-origin authority to request a one-time enrollment token, then passes only that token to the daemon. The daemon generates and persists its own relationship credential.
|
||||
|
||||
## Connection and authority
|
||||
|
||||
|
||||
@@ -91,6 +91,32 @@ describe("Hub HTTP client", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("reads self-contained trigger documents for export", async () => {
|
||||
const requests: Array<{ url: string | undefined; body: string }> = [];
|
||||
const origin = await startServer(
|
||||
() => ({
|
||||
status: 200,
|
||||
body: {
|
||||
triggers: [
|
||||
{
|
||||
id: "a50e05af-4f20-4c8f-8dcc-58e5ea360663",
|
||||
name: "slack-help",
|
||||
enabled: true,
|
||||
format: "single_run",
|
||||
yaml: "name: slack-help\n",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
requests,
|
||||
);
|
||||
|
||||
const triggers = await new HubHttpClient().listTriggers(origin, "secret");
|
||||
|
||||
assert.equal(triggers[0]?.yaml, "name: slack-help\n");
|
||||
assert.deepEqual(requests[0], { url: "/api/v1/triggers", body: "" });
|
||||
});
|
||||
|
||||
it("reads configuration resources in the Hub's slug vocabulary", async () => {
|
||||
const requests: Array<{ url: string | undefined; body: string }> = [];
|
||||
const origin = await startServer(
|
||||
|
||||
@@ -26,6 +26,7 @@ describe("Hub commands", () => {
|
||||
"status",
|
||||
"disconnect",
|
||||
"projects",
|
||||
"export",
|
||||
"deploy",
|
||||
"logout",
|
||||
]);
|
||||
@@ -92,7 +93,7 @@ describe("Hub commands", () => {
|
||||
assert.equal(result.data.origin, "https://hub.paseo.sh");
|
||||
});
|
||||
|
||||
it("interactive login continues through the injected guided setup coordinator without invoking a CLI command", async () => {
|
||||
it("interactive login continues through the injected daemon and Hub guidance coordinator", async () => {
|
||||
const credentials = new MemoryCredentials();
|
||||
const events: string[] = [];
|
||||
|
||||
@@ -111,8 +112,7 @@ describe("Hub commands", () => {
|
||||
isInteractive: () => true,
|
||||
continueGuidedSetup: async (origin) => {
|
||||
events.push(`connect:${origin}`);
|
||||
events.push("init");
|
||||
events.push("deploy");
|
||||
events.push("show-guidance");
|
||||
},
|
||||
reporter: { progress: (message) => events.push(`progress:${message}`) },
|
||||
},
|
||||
@@ -123,8 +123,7 @@ describe("Hub commands", () => {
|
||||
"login",
|
||||
"progress:Logged in",
|
||||
"connect:https://hub.test",
|
||||
"init",
|
||||
"deploy",
|
||||
"show-guidance",
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { strict as assert } from "node:assert";
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, it } from "vitest";
|
||||
import type { HubCredentialStore, StoredHubCredential } from "./credentials.js";
|
||||
import { HubCommandError } from "./error.js";
|
||||
import { runHubExport } from "./export.js";
|
||||
|
||||
const directories: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(directories.splice(0).map((directory) => rm(directory, { recursive: true })));
|
||||
});
|
||||
|
||||
describe("Hub trigger export", () => {
|
||||
it("uses the active login and writes one self-contained YAML file per trigger", async () => {
|
||||
const cwd = await temporaryDirectory();
|
||||
const credentials = new MemoryCredentials({
|
||||
origin: "https://hub.test",
|
||||
credential: "stored-secret",
|
||||
});
|
||||
const requests: Array<{ origin: string; credential: string }> = [];
|
||||
|
||||
const result = await runHubExport(
|
||||
undefined,
|
||||
{},
|
||||
{
|
||||
cwd: () => cwd,
|
||||
env: {},
|
||||
credentials,
|
||||
reporter: { progress() {} },
|
||||
hub: {
|
||||
async listTriggers(origin, credential) {
|
||||
requests.push({ origin, credential });
|
||||
return [
|
||||
{
|
||||
id: "a50e05af-4f20-4c8f-8dcc-58e5ea360663",
|
||||
name: "slack-help",
|
||||
enabled: true,
|
||||
format: "single_run",
|
||||
yaml: "name: slack-help\nenabled: true\n",
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(requests, [{ origin: "https://hub.test", credential: "stored-secret" }]);
|
||||
assert.equal(
|
||||
await readFile(path.join(cwd, ".paseo", "triggers", "slack-help.yml"), "utf8"),
|
||||
"name: slack-help\nenabled: true\n",
|
||||
);
|
||||
assert.deepEqual(result.data, {
|
||||
origin: "https://hub.test",
|
||||
directory: path.join(cwd, ".paseo", "triggers"),
|
||||
exported: 1,
|
||||
unchanged: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("leaves identical files alone and refuses to overwrite changed files without --force", async () => {
|
||||
const cwd = await temporaryDirectory();
|
||||
const destination = path.join(cwd, "trigger.yml");
|
||||
const credentials = new MemoryCredentials({ origin: "https://hub.test", credential: "secret" });
|
||||
const dependencies = {
|
||||
cwd: () => cwd,
|
||||
env: {},
|
||||
credentials,
|
||||
reporter: { progress() {} },
|
||||
hub: {
|
||||
listTriggers: async () => [
|
||||
{
|
||||
id: "a50e05af-4f20-4c8f-8dcc-58e5ea360663",
|
||||
name: "trigger",
|
||||
enabled: true,
|
||||
format: "single_run" as const,
|
||||
yaml: "name: trigger\n",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
await writeFile(destination, "name: trigger\n");
|
||||
assert.equal((await runHubExport(".", {}, dependencies)).data.unchanged, 1);
|
||||
|
||||
await writeFile(destination, "local changes\n");
|
||||
await assert.rejects(runHubExport(".", {}, dependencies), (error: unknown) => {
|
||||
assert.ok(error instanceof HubCommandError);
|
||||
assert.equal(error.code, "HUB_EXPORT_CONFLICT");
|
||||
return true;
|
||||
});
|
||||
await runHubExport(".", { force: true }, dependencies);
|
||||
assert.equal(await readFile(destination, "utf8"), "name: trigger\n");
|
||||
});
|
||||
});
|
||||
|
||||
class MemoryCredentials implements HubCredentialStore {
|
||||
constructor(private readonly record: StoredHubCredential) {}
|
||||
|
||||
active(): StoredHubCredential {
|
||||
return this.record;
|
||||
}
|
||||
|
||||
get(origin: string): StoredHubCredential | null {
|
||||
return origin === this.record.origin ? this.record : null;
|
||||
}
|
||||
|
||||
save(): void {}
|
||||
logoutActive(): StoredHubCredential {
|
||||
return this.record;
|
||||
}
|
||||
}
|
||||
|
||||
async function temporaryDirectory(): Promise<string> {
|
||||
const directory = await mkdtemp(path.join(tmpdir(), "paseo-hub-export-"));
|
||||
directories.push(directory);
|
||||
return directory;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { Command } from "commander";
|
||||
import { withOutput, type OutputSchema, type SingleResult } from "../../output/index.js";
|
||||
import { addJsonOption } from "../../utils/command-options.js";
|
||||
import { resolveHubCredential, resolveHubOrigin } from "./authority.js";
|
||||
import type { HubCredentialStore } from "./credentials.js";
|
||||
import { HubCommandError } from "./error.js";
|
||||
import { addHubResolutionHelp } from "./help.js";
|
||||
import type { HubHttpClient } from "./hub-client/index.js";
|
||||
import { reportHubProgress, type HubReporter } from "./reporter.js";
|
||||
|
||||
interface HubExportResult {
|
||||
origin: string;
|
||||
directory: string;
|
||||
exported: number;
|
||||
unchanged: number;
|
||||
}
|
||||
|
||||
const schema: OutputSchema<HubExportResult> = {
|
||||
idField: "directory",
|
||||
columns: [
|
||||
{ header: "DIRECTORY", field: "directory" },
|
||||
{ header: "EXPORTED", field: "exported" },
|
||||
{ header: "UNCHANGED", field: "unchanged" },
|
||||
{ header: "HUB", field: "origin" },
|
||||
],
|
||||
};
|
||||
|
||||
export interface HubExportOptions {
|
||||
hub?: string;
|
||||
apiKey?: string;
|
||||
force?: boolean;
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
interface HubExportDependencies {
|
||||
env: Readonly<Record<string, string | undefined>>;
|
||||
credentials: HubCredentialStore;
|
||||
hub: Pick<HubHttpClient, "listTriggers">;
|
||||
reporter: HubReporter;
|
||||
cwd(): string;
|
||||
}
|
||||
|
||||
export async function runHubExport(
|
||||
directoryInput: string | undefined,
|
||||
options: HubExportOptions,
|
||||
dependencies: HubExportDependencies,
|
||||
): Promise<SingleResult<HubExportResult>> {
|
||||
const resolution = {
|
||||
options: { origin: options.hub, apiKey: options.apiKey },
|
||||
env: dependencies.env,
|
||||
credentials: dependencies.credentials,
|
||||
};
|
||||
const origin = resolveHubOrigin(resolution);
|
||||
const credential = resolveHubCredential({ ...resolution, origin });
|
||||
const directory = path.resolve(dependencies.cwd(), directoryInput ?? ".paseo/triggers");
|
||||
reportHubProgress(dependencies.reporter, options, `Exporting triggers from ${origin}`);
|
||||
const triggers = await dependencies.hub.listTriggers(origin, credential);
|
||||
await mkdir(directory, { recursive: true });
|
||||
|
||||
const files = await Promise.all(
|
||||
triggers.map(async (trigger) => {
|
||||
const destination = path.join(directory, `${trigger.name}.yml`);
|
||||
return { destination, trigger, existing: await readOptionalFile(destination) };
|
||||
}),
|
||||
);
|
||||
const conflict = files.find(
|
||||
({ existing, trigger }) =>
|
||||
existing !== undefined && existing !== trigger.yaml && options.force !== true,
|
||||
);
|
||||
if (conflict !== undefined) {
|
||||
throw new HubCommandError(
|
||||
"HUB_EXPORT_CONFLICT",
|
||||
`${conflict.destination} already exists with different contents. Pass --force to replace it.`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
if (file.existing === file.trigger.yaml) continue;
|
||||
await writeFile(
|
||||
file.destination,
|
||||
file.trigger.yaml,
|
||||
file.existing === undefined ? { flag: "wx" } : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: "single",
|
||||
data: {
|
||||
origin,
|
||||
directory,
|
||||
exported: files.filter(({ existing, trigger }) => existing !== trigger.yaml).length,
|
||||
unchanged: files.filter(({ existing, trigger }) => existing === trigger.yaml).length,
|
||||
},
|
||||
schema,
|
||||
};
|
||||
}
|
||||
|
||||
export function addHubExportCommand(parent: Command, dependencies: HubExportDependencies): void {
|
||||
addJsonOption(
|
||||
addHubResolutionHelp(
|
||||
parent
|
||||
.command("export")
|
||||
.description("Export active Hub triggers as one YAML file per trigger")
|
||||
.argument("[directory]", "Destination directory", ".paseo/triggers")
|
||||
.option("--hub <origin>", "Paseo Hub origin")
|
||||
.option("--api-key <secret>", "Organization API key")
|
||||
.option("--force", "Replace trigger files with different contents"),
|
||||
),
|
||||
).action(
|
||||
withOutput(async (...args) => {
|
||||
const directory = args[0] as string | undefined;
|
||||
const options = args.at(-2) as HubExportOptions;
|
||||
return runHubExport(directory, options, dependencies);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function readOptionalFile(filePath: string): Promise<string | undefined> {
|
||||
try {
|
||||
return await readFile(filePath, "utf8");
|
||||
} catch (error) {
|
||||
if (isMissingFile(error)) return undefined;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function isMissingFile(error: unknown): error is NodeJS.ErrnoException {
|
||||
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
enrollmentTokenSchema,
|
||||
installResponseSchema,
|
||||
projectsResponseSchema,
|
||||
triggersResponseSchema,
|
||||
validationResponseSchema,
|
||||
type CliAuthorization,
|
||||
type CliAuthorizationPoll,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
setupResourcesSchema,
|
||||
type HubSetupResources,
|
||||
type HubProject,
|
||||
type HubTrigger,
|
||||
type HubValidationResult,
|
||||
} from "./internal/contracts.js";
|
||||
import { requestHub } from "./internal/transport.js";
|
||||
@@ -26,6 +28,7 @@ export type {
|
||||
HubConfigurationResources,
|
||||
HubSetupResources,
|
||||
HubProject,
|
||||
HubTrigger,
|
||||
HubValidationResult,
|
||||
} from "./internal/contracts.js";
|
||||
|
||||
@@ -86,6 +89,19 @@ export class HubHttpClient {
|
||||
return response.projects;
|
||||
}
|
||||
|
||||
async listTriggers(origin: string, apiKey: string): Promise<HubTrigger[]> {
|
||||
const response = await requestHub({
|
||||
origin,
|
||||
path: "/api/v1/triggers",
|
||||
method: "GET",
|
||||
apiKey,
|
||||
successStatus: 200,
|
||||
schema: triggersResponseSchema,
|
||||
failureMessage: "Hub trigger export failed",
|
||||
});
|
||||
return response.triggers;
|
||||
}
|
||||
|
||||
listConfigurationResources(origin: string, apiKey: string): Promise<HubConfigurationResources> {
|
||||
return requestHub({
|
||||
origin,
|
||||
|
||||
@@ -36,6 +36,18 @@ const projectSchema = z
|
||||
|
||||
export const projectsResponseSchema = z.object({ projects: z.array(projectSchema) }).strict();
|
||||
|
||||
const triggerSchema = z
|
||||
.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string().regex(/^[a-z][a-z0-9_-]*$/u),
|
||||
enabled: z.boolean(),
|
||||
format: z.enum(["single_run", "legacy_multistep"]),
|
||||
yaml: z.string(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const triggersResponseSchema = z.object({ triggers: z.array(triggerSchema) }).strict();
|
||||
|
||||
export const configurationResourcesSchema = z
|
||||
.object({
|
||||
daemons: z.array(z.object({ id: z.string().uuid(), slug: z.string().min(1) }).strict()),
|
||||
@@ -93,6 +105,7 @@ export const enrollmentTokenSchema = z
|
||||
export type CliAuthorization = z.infer<typeof authorizationSchema>;
|
||||
export type CliAuthorizationPoll = z.infer<typeof authorizationPollSchema>;
|
||||
export type HubProject = z.infer<typeof projectSchema>;
|
||||
export type HubTrigger = z.infer<typeof triggerSchema>;
|
||||
export type HubConfigurationResources = z.infer<typeof configurationResourcesSchema>;
|
||||
export type HubSetupResources = z.infer<typeof setupResourcesSchema>;
|
||||
export type HubInstallResult = z.infer<typeof installResponseSchema>;
|
||||
|
||||
@@ -15,6 +15,7 @@ import { createCliLoginFlow, type CliLoginFlow } from "./login-flow.js";
|
||||
import { addHubLoginCommand } from "./login.js";
|
||||
import { addHubLogoutCommand, productionLogoutPrompt } from "./logout.js";
|
||||
import { addHubProjectsCommand } from "./projects.js";
|
||||
import { addHubExportCommand } from "./export.js";
|
||||
import { processHubReporter, type HubReporter } from "./reporter.js";
|
||||
import { hubStatusResult } from "./status-output.js";
|
||||
import { addHubResolutionHelp } from "./help.js";
|
||||
@@ -85,6 +86,13 @@ export function createHubCommand(overrides: Partial<HubCommandEnvironment> = {})
|
||||
hub: environment.hub,
|
||||
reporter: environment.reporter,
|
||||
});
|
||||
addHubExportCommand(hub, {
|
||||
env: environment.env,
|
||||
credentials: environment.credentials,
|
||||
hub: environment.hub,
|
||||
reporter: environment.reporter,
|
||||
cwd: environment.cwd,
|
||||
});
|
||||
addHubDeployCommand(hub, {
|
||||
env: environment.env,
|
||||
credentials: environment.credentials,
|
||||
|
||||
@@ -21,11 +21,11 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe("Hub guided setup continuation", () => {
|
||||
it("uses the login origin and connected daemon to validate, write, and deploy without replaying setup prompts", async () => {
|
||||
it("offers daemon connection after login, then points to Hub without scaffolding files", async () => {
|
||||
const cwd = await temporaryDirectory();
|
||||
const credentials = new MemoryCredentials();
|
||||
const daemon = new SetupDaemon();
|
||||
const prompts = new PromptAnswers([true, true], ["codex", "gpt-5", "full-access"], ["U123"]);
|
||||
const prompts = new PromptAnswers([true], [], []);
|
||||
const calls: Array<{ operation: string; origin: string; files?: readonly string[] }> = [];
|
||||
const environment = setupEnvironment(cwd, credentials, daemon, prompts, calls);
|
||||
|
||||
@@ -42,45 +42,15 @@ describe("Hub guided setup continuation", () => {
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(prompts.confirmations, [
|
||||
"Connect this daemon to this Hub?",
|
||||
"Initialize and deploy a starter workflow?",
|
||||
]);
|
||||
assert.deepEqual(prompts.selections, [
|
||||
"Starter agent provider",
|
||||
"Starter agent model",
|
||||
"Starter agent mode",
|
||||
]);
|
||||
assert.deepEqual(prompts.selectionOptions, [
|
||||
["Codex"],
|
||||
["GPT-5 (suggested)", "GPT-5 mini"],
|
||||
["Read only", "Full access (suggested)"],
|
||||
]);
|
||||
assert.deepEqual(prompts.confirmations, ["Connect this daemon to this Hub?"]);
|
||||
assert.deepEqual(prompts.selections, []);
|
||||
assert.deepEqual(prompts.messages, [
|
||||
"Hub app connections ready for this workflow:\nSlack — Paseo\n\nOnly configured connections are shown. To add another, open Hub → Apps, then run `paseo hub init` again.",
|
||||
"Using Slack — Paseo",
|
||||
]);
|
||||
assert.deepEqual(calls, [
|
||||
{ operation: "token", origin: "https://hub.test" },
|
||||
{ operation: "projects", origin: "https://hub.test" },
|
||||
{ operation: "setup", origin: "https://hub.test" },
|
||||
{ operation: "resources", origin: "https://hub.test" },
|
||||
{
|
||||
operation: "validate",
|
||||
origin: "https://hub.test",
|
||||
files: [".paseo/hub.yml", ".paseo/workflows/slack-help.yml"],
|
||||
},
|
||||
{
|
||||
operation: "install",
|
||||
origin: "https://hub.test",
|
||||
files: [".paseo/hub.yml", ".paseo/workflows/slack-help.yml"],
|
||||
},
|
||||
"Configure triggers in Hub: https://hub.test/triggers\nOr scaffold triggers as code: paseo hub init",
|
||||
]);
|
||||
assert.deepEqual(calls, [{ operation: "token", origin: "https://hub.test" }]);
|
||||
assert.equal(daemon.connections, 1);
|
||||
assert.deepEqual(daemon.snapshotCwds, [cwd]);
|
||||
const hub = await readFile(path.join(cwd, ".paseo", "hub.yml"), "utf8");
|
||||
assert.match(hub, /daemon: macbook/u);
|
||||
assert.match(hub, /provider: codex\n model: gpt-5\n mode: full-access/u);
|
||||
assert.deepEqual(daemon.snapshotCwds, []);
|
||||
await assert.rejects(readFile(path.join(cwd, ".paseo", "hub.yml")), { code: "ENOENT" });
|
||||
});
|
||||
|
||||
it("prints exact actionable resume commands for login continuation declines", async () => {
|
||||
@@ -95,22 +65,16 @@ describe("Hub guided setup continuation", () => {
|
||||
setupEnvironment(cwd, credentials, daemon, connectDeclined, []),
|
||||
);
|
||||
assert.deepEqual(connectDeclined.messages, [
|
||||
"Skipped daemon connection. Run: paseo hub connect https://hub.test; then paseo hub init",
|
||||
"Skipped daemon connection. Connect later with: paseo hub connect https://hub.test",
|
||||
"Configure triggers in Hub: https://hub.test/triggers\nOr scaffold triggers as code: paseo hub init",
|
||||
]);
|
||||
|
||||
const initDeclined = new PromptAnswers([true, false], [], []);
|
||||
await continueHubGuidedSetup(
|
||||
"https://hub.test",
|
||||
setupEnvironment(cwd, credentials, daemon, initDeclined, []),
|
||||
);
|
||||
assert.deepEqual(initDeclined.messages, ["Skipped starter workflow. Run: paseo hub init"]);
|
||||
});
|
||||
|
||||
it("keeps login successful when no Hub app connection can initialize a workflow", async () => {
|
||||
it("does not require a Hub app connection during login", async () => {
|
||||
const cwd = await temporaryDirectory();
|
||||
const credentials = new MemoryCredentials();
|
||||
const daemon = new SetupDaemon();
|
||||
const prompts = new PromptAnswers([true, true], [], []);
|
||||
const prompts = new PromptAnswers([false], [], []);
|
||||
|
||||
await runHubLogin(
|
||||
"https://hub.test",
|
||||
@@ -132,7 +96,8 @@ describe("Hub guided setup continuation", () => {
|
||||
);
|
||||
|
||||
assert.deepEqual(prompts.messages, [
|
||||
"No Hub app connection is ready for this workflow.\nConnect GitHub, Slack, or Discord in Hub → Apps, then run `paseo hub init` again.",
|
||||
"Skipped daemon connection. Connect later with: paseo hub connect https://hub.test",
|
||||
"Configure triggers in Hub: https://hub.test/triggers\nOr scaffold triggers as code: paseo hub init",
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -191,35 +191,18 @@ export async function continueHubGuidedSetup(
|
||||
origin: string,
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
): Promise<void> {
|
||||
if (!(await requiredConfirm(environment, "Connect this daemon to this Hub?", true))) {
|
||||
if (await requiredConfirm(environment, "Connect this daemon to this Hub?", true)) {
|
||||
await ensureDaemonConnection(origin, environment, true);
|
||||
} else {
|
||||
reportMessage(
|
||||
environment,
|
||||
`Skipped daemon connection. Run: ${hubLoginResumeCommand("connect", origin)}; then paseo hub init`,
|
||||
`Skipped daemon connection. Connect later with: ${hubLoginResumeCommand("connect", origin)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const daemonId = await ensureDaemonConnection(origin, environment, true);
|
||||
if (!(await requiredConfirm(environment, "Initialize and deploy a starter workflow?", true))) {
|
||||
reportMessage(
|
||||
environment,
|
||||
`Skipped starter workflow. Run: ${hubLoginResumeCommand("init", origin)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await runHubGuidedSetup(environment, { origin, daemonId, deploy: true });
|
||||
} catch (error) {
|
||||
if (error instanceof HubInitCancelledError) {
|
||||
if (environment.prompts === undefined) {
|
||||
log.message(error.message);
|
||||
outro("Connect an app to continue");
|
||||
} else {
|
||||
environment.prompts.message(error.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
reportMessage(
|
||||
environment,
|
||||
`Configure triggers in Hub: ${new URL("/triggers", origin).toString()}\nOr scaffold triggers as code: ${hubLoginResumeCommand("init", origin)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function ensureLogin(
|
||||
|
||||
Reference in New Issue
Block a user