@getpaseo/cli 0.7.0-beta.2 → 0.7.0-beta.3
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/dist/commands/hub/connect.d.ts +2 -0
- package/dist/commands/hub/connect.js +12 -2
- package/dist/commands/hub/daemon-client.d.ts +8 -2
- package/dist/commands/hub/export.d.ts +28 -0
- package/dist/commands/hub/export.js +80 -0
- package/dist/commands/hub/hub-client/index.d.ts +3 -2
- package/dist/commands/hub/hub-client/index.js +13 -1
- package/dist/commands/hub/hub-client/internal/contracts.d.ts +23 -0
- package/dist/commands/hub/hub-client/internal/contracts.js +10 -0
- package/dist/commands/hub/index.js +13 -0
- package/dist/commands/hub/init.js +28 -28
- package/dist/commands/hub/permissions.d.ts +21 -0
- package/dist/commands/hub/permissions.js +61 -0
- package/dist/commands/hub/status-output.d.ts +1 -1
- package/dist/commands/hub/status-output.js +2 -2
- package/dist/commands/plugin/scaffold.js +11 -3
- package/package.json +4 -4
|
@@ -15,8 +15,14 @@ export async function runHubConnect(originInput, options, dependencies) {
|
|
|
15
15
|
reportHubProgress(dependencies.reporter, options, `Connecting this daemon to ${origin}`);
|
|
16
16
|
const credential = resolveHubCredential({ ...resolution, origin });
|
|
17
17
|
const token = await dependencies.hub.issueEnrollmentToken(origin, credential);
|
|
18
|
+
const permissions = options.permissions ?? options.permission ?? [];
|
|
18
19
|
return withHubDaemon(dependencies.daemon, options.host, async (daemon) => {
|
|
19
|
-
const response = await daemon.connectHub(origin, token);
|
|
20
|
+
const response = await daemon.connectHub(origin, token, permissions);
|
|
21
|
+
if (response.status.hubOrigin !== null &&
|
|
22
|
+
!samePermissions(response.status.permissions, permissions)) {
|
|
23
|
+
await daemon.disconnectHub(false).catch(() => undefined);
|
|
24
|
+
throw new Error("The daemon did not honor the requested Hub access. Update Paseo before connecting it.");
|
|
25
|
+
}
|
|
20
26
|
return hubStatusResult(response.status);
|
|
21
27
|
});
|
|
22
28
|
}
|
|
@@ -25,10 +31,14 @@ export function addHubConnectCommand(parent, dependencies) {
|
|
|
25
31
|
.command("connect")
|
|
26
32
|
.description("Enroll this daemon with a Paseo Hub")
|
|
27
33
|
.argument("[origin]", "Paseo Hub origin")
|
|
28
|
-
.option("--api-key <secret>", "Organization API key")
|
|
34
|
+
.option("--api-key <secret>", "Organization API key")
|
|
35
|
+
.option("--permission <permission...>", "Grant daemon permission during connection"))).action(withOutput(async (...args) => {
|
|
29
36
|
const origin = args[0];
|
|
30
37
|
const options = args.at(-2);
|
|
31
38
|
return runHubConnect(origin, options, dependencies);
|
|
32
39
|
}));
|
|
33
40
|
}
|
|
41
|
+
function samePermissions(actual, expected) {
|
|
42
|
+
return actual.length === expected.length && expected.every((scope) => actual.includes(scope));
|
|
43
|
+
}
|
|
34
44
|
//# sourceMappingURL=connect.js.map
|
|
@@ -3,7 +3,7 @@ export interface HubStatus {
|
|
|
3
3
|
state: string;
|
|
4
4
|
daemonId: string | null;
|
|
5
5
|
hubOrigin: string | null;
|
|
6
|
-
|
|
6
|
+
permissions: string[];
|
|
7
7
|
connectedAt: string | null;
|
|
8
8
|
lastError: string | null;
|
|
9
9
|
}
|
|
@@ -11,7 +11,13 @@ export interface HubProvidersSnapshotOptions {
|
|
|
11
11
|
cwd?: string;
|
|
12
12
|
}
|
|
13
13
|
export interface HubDaemonClient {
|
|
14
|
-
connectHub(url: string, token: string): Promise<{
|
|
14
|
+
connectHub(url: string, token: string, permissions?: readonly string[]): Promise<{
|
|
15
|
+
status: HubStatus;
|
|
16
|
+
}>;
|
|
17
|
+
updateHubPermissions(input: {
|
|
18
|
+
grant?: readonly string[];
|
|
19
|
+
revoke?: readonly string[];
|
|
20
|
+
}): Promise<{
|
|
15
21
|
status: HubStatus;
|
|
16
22
|
}>;
|
|
17
23
|
getHubStatus(): Promise<{
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type SingleResult } from "../../output/index.js";
|
|
3
|
+
import type { HubCredentialStore } from "./credentials.js";
|
|
4
|
+
import type { HubHttpClient } from "./hub-client/index.js";
|
|
5
|
+
import { type HubReporter } from "./reporter.js";
|
|
6
|
+
interface HubExportResult {
|
|
7
|
+
origin: string;
|
|
8
|
+
directory: string;
|
|
9
|
+
exported: number;
|
|
10
|
+
unchanged: number;
|
|
11
|
+
}
|
|
12
|
+
export interface HubExportOptions {
|
|
13
|
+
hub?: string;
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
force?: boolean;
|
|
16
|
+
json?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface HubExportDependencies {
|
|
19
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
20
|
+
credentials: HubCredentialStore;
|
|
21
|
+
hub: Pick<HubHttpClient, "listTriggers">;
|
|
22
|
+
reporter: HubReporter;
|
|
23
|
+
cwd(): string;
|
|
24
|
+
}
|
|
25
|
+
export declare function runHubExport(directoryInput: string | undefined, options: HubExportOptions, dependencies: HubExportDependencies): Promise<SingleResult<HubExportResult>>;
|
|
26
|
+
export declare function addHubExportCommand(parent: Command, dependencies: HubExportDependencies): void;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=export.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { withOutput } from "../../output/index.js";
|
|
4
|
+
import { addJsonOption } from "../../utils/command-options.js";
|
|
5
|
+
import { resolveHubCredential, resolveHubOrigin } from "./authority.js";
|
|
6
|
+
import { HubCommandError } from "./error.js";
|
|
7
|
+
import { addHubResolutionHelp } from "./help.js";
|
|
8
|
+
import { reportHubProgress } from "./reporter.js";
|
|
9
|
+
const schema = {
|
|
10
|
+
idField: "directory",
|
|
11
|
+
columns: [
|
|
12
|
+
{ header: "DIRECTORY", field: "directory" },
|
|
13
|
+
{ header: "EXPORTED", field: "exported" },
|
|
14
|
+
{ header: "UNCHANGED", field: "unchanged" },
|
|
15
|
+
{ header: "HUB", field: "origin" },
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
export async function runHubExport(directoryInput, options, dependencies) {
|
|
19
|
+
const resolution = {
|
|
20
|
+
options: { origin: options.hub, apiKey: options.apiKey },
|
|
21
|
+
env: dependencies.env,
|
|
22
|
+
credentials: dependencies.credentials,
|
|
23
|
+
};
|
|
24
|
+
const origin = resolveHubOrigin(resolution);
|
|
25
|
+
const credential = resolveHubCredential({ ...resolution, origin });
|
|
26
|
+
const directory = path.resolve(dependencies.cwd(), directoryInput ?? ".paseo/triggers");
|
|
27
|
+
reportHubProgress(dependencies.reporter, options, `Exporting triggers from ${origin}`);
|
|
28
|
+
const triggers = await dependencies.hub.listTriggers(origin, credential);
|
|
29
|
+
await mkdir(directory, { recursive: true });
|
|
30
|
+
const files = await Promise.all(triggers.map(async (trigger) => {
|
|
31
|
+
const destination = path.join(directory, `${trigger.name}.yml`);
|
|
32
|
+
return { destination, trigger, existing: await readOptionalFile(destination) };
|
|
33
|
+
}));
|
|
34
|
+
const conflict = files.find(({ existing, trigger }) => existing !== undefined && existing !== trigger.yaml && options.force !== true);
|
|
35
|
+
if (conflict !== undefined) {
|
|
36
|
+
throw new HubCommandError("HUB_EXPORT_CONFLICT", `${conflict.destination} already exists with different contents. Pass --force to replace it.`);
|
|
37
|
+
}
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
if (file.existing === file.trigger.yaml)
|
|
40
|
+
continue;
|
|
41
|
+
await writeFile(file.destination, file.trigger.yaml, file.existing === undefined ? { flag: "wx" } : undefined);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
type: "single",
|
|
45
|
+
data: {
|
|
46
|
+
origin,
|
|
47
|
+
directory,
|
|
48
|
+
exported: files.filter(({ existing, trigger }) => existing !== trigger.yaml).length,
|
|
49
|
+
unchanged: files.filter(({ existing, trigger }) => existing === trigger.yaml).length,
|
|
50
|
+
},
|
|
51
|
+
schema,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function addHubExportCommand(parent, dependencies) {
|
|
55
|
+
addJsonOption(addHubResolutionHelp(parent
|
|
56
|
+
.command("export")
|
|
57
|
+
.description("Export active Hub triggers as one YAML file per trigger")
|
|
58
|
+
.argument("[directory]", "Destination directory", ".paseo/triggers")
|
|
59
|
+
.option("--hub <origin>", "Paseo Hub origin")
|
|
60
|
+
.option("--api-key <secret>", "Organization API key")
|
|
61
|
+
.option("--force", "Replace trigger files with different contents"))).action(withOutput(async (...args) => {
|
|
62
|
+
const directory = args[0];
|
|
63
|
+
const options = args.at(-2);
|
|
64
|
+
return runHubExport(directory, options, dependencies);
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
async function readOptionalFile(filePath) {
|
|
68
|
+
try {
|
|
69
|
+
return await readFile(filePath, "utf8");
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (isMissingFile(error))
|
|
73
|
+
return undefined;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isMissingFile(error) {
|
|
78
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HubBundleFile } from "../deploy-bundle.js";
|
|
2
|
-
import { type CliAuthorization, type CliAuthorizationPoll, type HubInstallResult, type HubConfigurationResources, type HubSetupResources, type HubProject, type HubValidationResult } from "./internal/contracts.js";
|
|
3
|
-
export type { CliAuthorization, CliAuthorizationPoll, HubInstallResult, HubConfigurationResources, HubSetupResources, HubProject, HubValidationResult, } from "./internal/contracts.js";
|
|
2
|
+
import { type CliAuthorization, type CliAuthorizationPoll, type HubInstallResult, type HubConfigurationResources, type HubSetupResources, type HubProject, type HubTrigger, type HubValidationResult } from "./internal/contracts.js";
|
|
3
|
+
export type { CliAuthorization, CliAuthorizationPoll, HubInstallResult, HubConfigurationResources, HubSetupResources, HubProject, HubTrigger, HubValidationResult, } from "./internal/contracts.js";
|
|
4
4
|
interface HubConfigurationInput {
|
|
5
5
|
origin: string;
|
|
6
6
|
apiKey: string;
|
|
@@ -11,6 +11,7 @@ export declare class HubHttpClient {
|
|
|
11
11
|
startCliAuthorization(origin: string): Promise<CliAuthorization>;
|
|
12
12
|
pollCliAuthorization(origin: string, deviceCode: string, timeoutMilliseconds: number): Promise<CliAuthorizationPoll>;
|
|
13
13
|
listProjects(origin: string, apiKey: string): Promise<HubProject[]>;
|
|
14
|
+
listTriggers(origin: string, apiKey: string): Promise<HubTrigger[]>;
|
|
14
15
|
listConfigurationResources(origin: string, apiKey: string): Promise<HubConfigurationResources>;
|
|
15
16
|
listSetupResources(origin: string, apiKey: string): Promise<HubSetupResources>;
|
|
16
17
|
installConfiguration(input: HubConfigurationInput): Promise<HubInstallResult>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HubCommandError } from "../error.js";
|
|
2
|
-
import { authorizationPollSchema, authorizationSchema, configurationResourcesSchema, enrollmentTokenSchema, installResponseSchema, projectsResponseSchema, validationResponseSchema, setupResourcesSchema, } from "./internal/contracts.js";
|
|
2
|
+
import { authorizationPollSchema, authorizationSchema, configurationResourcesSchema, enrollmentTokenSchema, installResponseSchema, projectsResponseSchema, triggersResponseSchema, validationResponseSchema, setupResourcesSchema, } from "./internal/contracts.js";
|
|
3
3
|
import { requestHub } from "./internal/transport.js";
|
|
4
4
|
export class HubHttpClient {
|
|
5
5
|
startCliAuthorization(origin) {
|
|
@@ -45,6 +45,18 @@ export class HubHttpClient {
|
|
|
45
45
|
});
|
|
46
46
|
return response.projects;
|
|
47
47
|
}
|
|
48
|
+
async listTriggers(origin, apiKey) {
|
|
49
|
+
const response = await requestHub({
|
|
50
|
+
origin,
|
|
51
|
+
path: "/api/v1/triggers",
|
|
52
|
+
method: "GET",
|
|
53
|
+
apiKey,
|
|
54
|
+
successStatus: 200,
|
|
55
|
+
schema: triggersResponseSchema,
|
|
56
|
+
failureMessage: "Hub trigger export failed",
|
|
57
|
+
});
|
|
58
|
+
return response.triggers;
|
|
59
|
+
}
|
|
48
60
|
listConfigurationResources(origin, apiKey) {
|
|
49
61
|
return requestHub({
|
|
50
62
|
origin,
|
|
@@ -42,6 +42,28 @@ export declare const projectsResponseSchema: z.ZodObject<{
|
|
|
42
42
|
name: z.ZodString;
|
|
43
43
|
}, z.core.$strict>>;
|
|
44
44
|
}, z.core.$strict>;
|
|
45
|
+
declare const triggerSchema: z.ZodObject<{
|
|
46
|
+
id: z.ZodString;
|
|
47
|
+
name: z.ZodString;
|
|
48
|
+
enabled: z.ZodBoolean;
|
|
49
|
+
format: z.ZodEnum<{
|
|
50
|
+
single_run: "single_run";
|
|
51
|
+
legacy_multistep: "legacy_multistep";
|
|
52
|
+
}>;
|
|
53
|
+
yaml: z.ZodString;
|
|
54
|
+
}, z.core.$strict>;
|
|
55
|
+
export declare const triggersResponseSchema: z.ZodObject<{
|
|
56
|
+
triggers: z.ZodArray<z.ZodObject<{
|
|
57
|
+
id: z.ZodString;
|
|
58
|
+
name: z.ZodString;
|
|
59
|
+
enabled: z.ZodBoolean;
|
|
60
|
+
format: z.ZodEnum<{
|
|
61
|
+
single_run: "single_run";
|
|
62
|
+
legacy_multistep: "legacy_multistep";
|
|
63
|
+
}>;
|
|
64
|
+
yaml: z.ZodString;
|
|
65
|
+
}, z.core.$strict>>;
|
|
66
|
+
}, z.core.$strict>;
|
|
45
67
|
export declare const configurationResourcesSchema: z.ZodObject<{
|
|
46
68
|
daemons: z.ZodArray<z.ZodObject<{
|
|
47
69
|
id: z.ZodString;
|
|
@@ -95,6 +117,7 @@ export declare const enrollmentTokenSchema: z.ZodObject<{
|
|
|
95
117
|
export type CliAuthorization = z.infer<typeof authorizationSchema>;
|
|
96
118
|
export type CliAuthorizationPoll = z.infer<typeof authorizationPollSchema>;
|
|
97
119
|
export type HubProject = z.infer<typeof projectSchema>;
|
|
120
|
+
export type HubTrigger = z.infer<typeof triggerSchema>;
|
|
98
121
|
export type HubConfigurationResources = z.infer<typeof configurationResourcesSchema>;
|
|
99
122
|
export type HubSetupResources = z.infer<typeof setupResourcesSchema>;
|
|
100
123
|
export type HubInstallResult = z.infer<typeof installResponseSchema>;
|
|
@@ -30,6 +30,16 @@ const projectSchema = z
|
|
|
30
30
|
.object({ id: z.string().uuid(), slug: z.string().min(1), name: z.string().min(1) })
|
|
31
31
|
.strict();
|
|
32
32
|
export const projectsResponseSchema = z.object({ projects: z.array(projectSchema) }).strict();
|
|
33
|
+
const triggerSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
id: z.string().uuid(),
|
|
36
|
+
name: z.string().regex(/^[a-z][a-z0-9_-]*$/u),
|
|
37
|
+
enabled: z.boolean(),
|
|
38
|
+
format: z.enum(["single_run", "legacy_multistep"]),
|
|
39
|
+
yaml: z.string(),
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
export const triggersResponseSchema = z.object({ triggers: z.array(triggerSchema) }).strict();
|
|
33
43
|
export const configurationResourcesSchema = z
|
|
34
44
|
.object({
|
|
35
45
|
daemons: z.array(z.object({ id: z.string().uuid(), slug: z.string().min(1) }).strict()),
|
|
@@ -11,10 +11,12 @@ import { createCliLoginFlow } from "./login-flow.js";
|
|
|
11
11
|
import { addHubLoginCommand } from "./login.js";
|
|
12
12
|
import { addHubLogoutCommand, productionLogoutPrompt } from "./logout.js";
|
|
13
13
|
import { addHubProjectsCommand } from "./projects.js";
|
|
14
|
+
import { addHubExportCommand } from "./export.js";
|
|
14
15
|
import { processHubReporter } from "./reporter.js";
|
|
15
16
|
import { hubStatusResult } from "./status-output.js";
|
|
16
17
|
import { addHubResolutionHelp } from "./help.js";
|
|
17
18
|
import { addHubInitCommand, continueHubGuidedSetup } from "./init.js";
|
|
19
|
+
import { addHubPermissionsCommand } from "./permissions.js";
|
|
18
20
|
function productionEnvironment() {
|
|
19
21
|
const env = process.env;
|
|
20
22
|
const hub = new HubHttpClient();
|
|
@@ -56,12 +58,23 @@ export function createHubCommand(overrides = {}) {
|
|
|
56
58
|
daemon: environment.daemon,
|
|
57
59
|
reporter: environment.reporter,
|
|
58
60
|
});
|
|
61
|
+
addHubPermissionsCommand(hub, {
|
|
62
|
+
daemon: environment.daemon,
|
|
63
|
+
reporter: environment.reporter,
|
|
64
|
+
});
|
|
59
65
|
addHubProjectsCommand(hub, {
|
|
60
66
|
env: environment.env,
|
|
61
67
|
credentials: environment.credentials,
|
|
62
68
|
hub: environment.hub,
|
|
63
69
|
reporter: environment.reporter,
|
|
64
70
|
});
|
|
71
|
+
addHubExportCommand(hub, {
|
|
72
|
+
env: environment.env,
|
|
73
|
+
credentials: environment.credentials,
|
|
74
|
+
hub: environment.hub,
|
|
75
|
+
reporter: environment.reporter,
|
|
76
|
+
cwd: environment.cwd,
|
|
77
|
+
});
|
|
65
78
|
addHubDeployCommand(hub, {
|
|
66
79
|
env: environment.env,
|
|
67
80
|
credentials: environment.credentials,
|
|
@@ -112,31 +112,28 @@ export async function runHubGuidedSetup(environment, state = {}) {
|
|
|
112
112
|
outro(deploy ? "Hub is ready" : "Hub bundle is ready");
|
|
113
113
|
}
|
|
114
114
|
export async function continueHubGuidedSetup(origin, environment) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
outro("Connect an app to continue");
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
environment.prompts.message(error.message);
|
|
135
|
-
}
|
|
136
|
-
return;
|
|
115
|
+
const currentStatus = await withHubDaemon(environment.daemon, undefined, async (daemon) => daemon.getHubStatus().then((response) => response.status));
|
|
116
|
+
const current = resolveHubInitConnection(currentStatus, origin);
|
|
117
|
+
if (current.kind === "connected") {
|
|
118
|
+
reportMessage(environment, `This daemon is already connected to ${origin}. Permissions: ${currentStatus.permissions.join(", ") || "None"}.`);
|
|
119
|
+
}
|
|
120
|
+
else if (current.kind === "pending") {
|
|
121
|
+
await waitForDaemonReady(origin, environment.daemon);
|
|
122
|
+
}
|
|
123
|
+
else if (current.kind === "conflict") {
|
|
124
|
+
reportMessage(environment, `This daemon is connected to ${current.origin}. Disconnect it before connecting to ${origin}.`);
|
|
125
|
+
}
|
|
126
|
+
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)) {
|
|
127
|
+
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);
|
|
128
|
+
await ensureDaemonConnection(origin, environment, true, grantExecution ? ["hub.execute"] : []);
|
|
129
|
+
if (!grantExecution) {
|
|
130
|
+
reportMessage(environment, "Daemon connected with no permissions.\n\nEnable Hub automations later:\n paseo hub permissions grant hub.execute");
|
|
137
131
|
}
|
|
138
|
-
throw error;
|
|
139
132
|
}
|
|
133
|
+
else {
|
|
134
|
+
reportMessage(environment, `Skipped daemon connection. Connect later with: ${hubLoginResumeCommand("connect", origin)}`);
|
|
135
|
+
}
|
|
136
|
+
reportMessage(environment, `Configure triggers in Hub: ${new URL("/triggers", origin).toString()}\nOr scaffold triggers as code: ${hubLoginResumeCommand("init", origin)}`);
|
|
140
137
|
}
|
|
141
138
|
async function ensureLogin(activeOrigin, environment) {
|
|
142
139
|
const endpoint = await requiredSelect(environment, {
|
|
@@ -175,10 +172,13 @@ async function ensureLogin(activeOrigin, environment) {
|
|
|
175
172
|
log.success(`Logged in to ${normalizedOrigin}`);
|
|
176
173
|
return normalizedOrigin;
|
|
177
174
|
}
|
|
178
|
-
async function ensureDaemonConnection(origin, environment, confirmed = false) {
|
|
175
|
+
async function ensureDaemonConnection(origin, environment, confirmed = false, permissions = ["hub.execute"]) {
|
|
179
176
|
const status = await withHubDaemon(environment.daemon, undefined, async (daemon) => daemon.getHubStatus().then((response) => response.status));
|
|
180
177
|
const connection = resolveHubInitConnection(status, origin);
|
|
181
178
|
if (connection.kind === "connected") {
|
|
179
|
+
if (permissions.includes("hub.execute") && !status.permissions.includes("hub.execute")) {
|
|
180
|
+
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.");
|
|
181
|
+
}
|
|
182
182
|
return connection.daemonId;
|
|
183
183
|
}
|
|
184
184
|
if (connection.kind === "pending") {
|
|
@@ -188,13 +188,13 @@ async function ensureDaemonConnection(origin, environment, confirmed = false) {
|
|
|
188
188
|
throw new HubCommandError("HUB_DAEMON_ALREADY_CONNECTED", `This daemon is connected to ${connection.origin}. Disconnect it before running Hub init for ${origin}.`);
|
|
189
189
|
}
|
|
190
190
|
if (!confirmed &&
|
|
191
|
-
!(await requiredConfirm(environment, `Connect this daemon to ${origin}?`, true))) {
|
|
191
|
+
!(await requiredConfirm(environment, `Connect this daemon to ${origin} and allow Hub workflows to create and control workspaces and agents?`, true))) {
|
|
192
192
|
throw new HubInitCancelledError("A connected daemon is required to create the bundle.");
|
|
193
193
|
}
|
|
194
|
-
return connectDaemon(origin, environment);
|
|
194
|
+
return connectDaemon(origin, environment, permissions);
|
|
195
195
|
}
|
|
196
|
-
async function connectDaemon(origin, environment) {
|
|
197
|
-
await runHubConnect(origin, {}, {
|
|
196
|
+
async function connectDaemon(origin, environment, permissions = ["hub.execute"]) {
|
|
197
|
+
await runHubConnect(origin, { permissions }, {
|
|
198
198
|
env: environment.env,
|
|
199
199
|
credentials: environment.credentials,
|
|
200
200
|
hub: environment.hub,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type ListResult } from "../../output/index.js";
|
|
3
|
+
import type { HubDaemonConnection } from "./daemon-client.js";
|
|
4
|
+
import { type HubReporter } from "./reporter.js";
|
|
5
|
+
interface HubPermissionsOptions {
|
|
6
|
+
host?: string;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface HubPermissionsDependencies {
|
|
10
|
+
daemon: HubDaemonConnection;
|
|
11
|
+
reporter: HubReporter;
|
|
12
|
+
}
|
|
13
|
+
interface PermissionRow {
|
|
14
|
+
permission: string;
|
|
15
|
+
description: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function runHubPermissionsList(options: HubPermissionsOptions, dependencies: HubPermissionsDependencies): Promise<ListResult<PermissionRow>>;
|
|
18
|
+
export declare function runHubPermissionChange(operation: "grant" | "revoke", permission: string, options: HubPermissionsOptions, dependencies: HubPermissionsDependencies): Promise<ListResult<import("./status-output.js").HubRow>>;
|
|
19
|
+
export declare function addHubPermissionsCommand(parent: Command, dependencies: HubPermissionsDependencies): void;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { withOutput } from "../../output/index.js";
|
|
2
|
+
import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
|
|
3
|
+
import { withHubDaemon } from "./daemon-client.js";
|
|
4
|
+
import { reportHubProgress } from "./reporter.js";
|
|
5
|
+
import { hubStatusResult } from "./status-output.js";
|
|
6
|
+
const schema = {
|
|
7
|
+
idField: "permission",
|
|
8
|
+
columns: [
|
|
9
|
+
{ header: "PERMISSION", field: "permission" },
|
|
10
|
+
{ header: "DESCRIPTION", field: "description" },
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
export function runHubPermissionsList(options, dependencies) {
|
|
14
|
+
return withHubDaemon(dependencies.daemon, options.host, async (client) => {
|
|
15
|
+
const status = (await client.getHubStatus()).status;
|
|
16
|
+
requireConnectedHub(status);
|
|
17
|
+
return {
|
|
18
|
+
type: "list",
|
|
19
|
+
data: status.permissions.map((permission) => ({
|
|
20
|
+
permission,
|
|
21
|
+
description: describePermission(permission),
|
|
22
|
+
})),
|
|
23
|
+
schema,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export function runHubPermissionChange(operation, permission, options, dependencies) {
|
|
28
|
+
return withHubDaemon(dependencies.daemon, options.host, async (client) => {
|
|
29
|
+
const current = (await client.getHubStatus()).status;
|
|
30
|
+
requireConnectedHub(current);
|
|
31
|
+
const response = await client.updateHubPermissions(operation === "grant" ? { grant: [permission] } : { revoke: [permission] });
|
|
32
|
+
reportHubProgress(dependencies.reporter, options, `${operation === "grant" ? "Granted" : "Revoked"} ${permission} ${operation === "grant" ? "to" : "from"} ${response.status.hubOrigin}`);
|
|
33
|
+
return hubStatusResult(response.status);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function addHubPermissionsCommand(parent, dependencies) {
|
|
37
|
+
const permissions = parent
|
|
38
|
+
.command("permissions")
|
|
39
|
+
.description("Manage this Hub's daemon permissions");
|
|
40
|
+
addJsonAndDaemonHostOptions(permissions.command("list")).action(withOutput(async (...args) => {
|
|
41
|
+
const options = args.at(-2);
|
|
42
|
+
return runHubPermissionsList(options, dependencies);
|
|
43
|
+
}));
|
|
44
|
+
for (const operation of ["grant", "revoke"]) {
|
|
45
|
+
addJsonAndDaemonHostOptions(permissions.command(operation).argument("<permission>", "Daemon permission")).action(withOutput(async (...args) => {
|
|
46
|
+
const permission = args[0];
|
|
47
|
+
const options = args.at(-2);
|
|
48
|
+
return runHubPermissionChange(operation, permission, options, dependencies);
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function requireConnectedHub(status) {
|
|
53
|
+
if (status.hubOrigin === null ||
|
|
54
|
+
(status.state !== "connected" && status.state !== "reconnecting")) {
|
|
55
|
+
throw new Error("This daemon is not connected to a Hub");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function describePermission(permission) {
|
|
59
|
+
return permission === "hub.execute" ? "Run agents for Hub automations" : permission;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -4,7 +4,7 @@ const schema = {
|
|
|
4
4
|
{ header: "STATE", field: "state" },
|
|
5
5
|
{ header: "HUB", field: "hub" },
|
|
6
6
|
{ header: "DAEMON", field: "daemonId" },
|
|
7
|
-
{ header: "
|
|
7
|
+
{ header: "PERMISSIONS", field: "permissions" },
|
|
8
8
|
{ header: "CONNECTED", field: "connectedAt" },
|
|
9
9
|
{ header: "ERROR", field: "error" },
|
|
10
10
|
{ header: "WARNING", field: "warning" },
|
|
@@ -18,7 +18,7 @@ export function hubStatusResult(status, warning, reportedHubOrigin = status.hubO
|
|
|
18
18
|
state: status.state,
|
|
19
19
|
daemonId: status.daemonId,
|
|
20
20
|
hub: reportedHubOrigin,
|
|
21
|
-
|
|
21
|
+
permissions: status.permissions.join(", "),
|
|
22
22
|
connectedAt: status.connectedAt,
|
|
23
23
|
error: status.lastError,
|
|
24
24
|
warning,
|
|
@@ -140,7 +140,15 @@ declare module "@getpaseo/plugin" {
|
|
|
140
140
|
layout: { compact: boolean; platform: "ios" | "android" | "web" };
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
interface PluginNavigableHostProps extends PluginHostProps {
|
|
144
|
+
/** Client-owned navigation. Undefined on older hosts; hide dependent affordances when absent. */
|
|
145
|
+
readonly navigation?: {
|
|
146
|
+
readonly openAgent: (input: { readonly agentId: string }) => void;
|
|
147
|
+
readonly openWorkspace: (input: { readonly workspaceId: string }) => void;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface PluginSurfaceProps extends PluginNavigableHostProps {}
|
|
144
152
|
|
|
145
153
|
export interface PluginIconProps {
|
|
146
154
|
name: string;
|
|
@@ -183,12 +191,12 @@ declare module "@getpaseo/plugin" {
|
|
|
183
191
|
readonly labels: Readonly<Record<string, string>>;
|
|
184
192
|
}
|
|
185
193
|
|
|
186
|
-
export interface PluginWorkspacePanelProps extends
|
|
194
|
+
export interface PluginWorkspacePanelProps extends PluginNavigableHostProps {
|
|
187
195
|
context: "workspace";
|
|
188
196
|
workspaceId: string;
|
|
189
197
|
}
|
|
190
198
|
|
|
191
|
-
export interface PluginAgentPanelProps extends
|
|
199
|
+
export interface PluginAgentPanelProps extends PluginNavigableHostProps {
|
|
192
200
|
context: "agent";
|
|
193
201
|
workspaceId: string;
|
|
194
202
|
agentId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpaseo/cli",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.3",
|
|
4
4
|
"description": "Paseo CLI - control your AI coding agents from the command line",
|
|
5
5
|
"bin": {
|
|
6
6
|
"paseo": "bin/paseo"
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@clack/prompts": "^1.0.0",
|
|
31
|
-
"@getpaseo/client": "0.7.0-beta.
|
|
32
|
-
"@getpaseo/protocol": "0.7.0-beta.
|
|
33
|
-
"@getpaseo/server": "0.7.0-beta.
|
|
31
|
+
"@getpaseo/client": "0.7.0-beta.3",
|
|
32
|
+
"@getpaseo/protocol": "0.7.0-beta.3",
|
|
33
|
+
"@getpaseo/server": "0.7.0-beta.3",
|
|
34
34
|
"chalk": "^5.3.0",
|
|
35
35
|
"commander": "^12.0.0",
|
|
36
36
|
"mime-types": "^2.1.35",
|