@amerged/ohmyhost-mcp 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-PXGLL6SR.js → chunk-4WY7AAPR.js} +11 -11
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +1575 -310
- package/package.json +1 -1
- package/src/apps/mcp/local-client.ts +119 -17
- package/src/apps/mcp/local-server.ts +321 -88
- package/src/apps/mcp/stdio-main.ts +11 -2
- package/src/apps/product-cli/cli.ts +370 -92
- package/src/apps/product-cli/command.ts +163 -6
- package/src/apps/product-cli/composition.ts +748 -120
- package/src/apps/product-cli/credential-store.ts +5 -3
- package/src/apps/product-cli/index.ts +4 -0
- package/src/apps/product-cli/product-api.ts +32 -4
- package/src/apps/product-cli/profile-store.ts +609 -0
- package/src/apps/product-cli/project-link-store.ts +169 -20
- package/src/apps/product-cli/runtime-contract.ts +63 -7
- package/src/apps/product-cli/workspace.ts +75 -13
- package/src/packages/agent-skills/generated-skill-resources.ts +10 -10
- package/src/packages/contracts/operation-failure.ts +3 -3
- package/src/packages/sdk-ts/generated/index.ts +21 -0
- package/src/packages/sdk-ts/generated/sdk.gen.ts +142 -0
- package/src/packages/sdk-ts/generated/types.gen.ts +200 -2
package/package.json
CHANGED
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
createProjectDevAccessTicket,
|
|
27
27
|
ensureProjectDevShareLink,
|
|
28
28
|
setProjectDevAccessMode,
|
|
29
|
+
getProjectPoweredByFlag,
|
|
30
|
+
setProjectPoweredByFlag,
|
|
29
31
|
rotateProjectDevShareLink,
|
|
30
32
|
revokeProjectDevShareLink,
|
|
31
33
|
createCloudflareDnsAuthorization,
|
|
@@ -52,6 +54,7 @@ import {
|
|
|
52
54
|
deletePaidProjectDomain,
|
|
53
55
|
getOrganizationCredits,
|
|
54
56
|
getOrganizationAccount,
|
|
57
|
+
getOrganizationReferral,
|
|
55
58
|
getOrganizationCreditUsage,
|
|
56
59
|
getProjectCreditBudget,
|
|
57
60
|
setProjectCreditBudget,
|
|
@@ -93,6 +96,8 @@ import {
|
|
|
93
96
|
streamOperationEvents,
|
|
94
97
|
type DevAccessTicket,
|
|
95
98
|
type DevAccessState,
|
|
99
|
+
type PoweredByFlag,
|
|
100
|
+
type OrganizationReferral,
|
|
96
101
|
type CloudflareDnsAuthorization,
|
|
97
102
|
type CloudflareDnsAuthorizationStatus,
|
|
98
103
|
type OhMyHostClient,
|
|
@@ -101,16 +106,20 @@ import {
|
|
|
101
106
|
} from "@ohmyhost/sdk-ts";
|
|
102
107
|
|
|
103
108
|
import type {
|
|
109
|
+
IdentityReport,
|
|
104
110
|
LocalMcpProductClient,
|
|
105
111
|
WorkspaceReport,
|
|
106
112
|
WorkspaceSelectionReport,
|
|
107
113
|
} from "./local-server.js";
|
|
108
114
|
import {
|
|
109
115
|
createAuthenticatedProductTransport,
|
|
116
|
+
resolveProductCliEnvironment,
|
|
110
117
|
prepareUserTokenFile,
|
|
111
118
|
ProductApiAuthenticationError,
|
|
112
119
|
parseDevAccessTicket,
|
|
113
120
|
parseDevAccessState,
|
|
121
|
+
parsePoweredByFlag,
|
|
122
|
+
parseOrganizationReferral,
|
|
114
123
|
parseCloudflareDnsAuthorization,
|
|
115
124
|
parseCloudflareDnsAuthorizationStatus,
|
|
116
125
|
parsePaidDomain,
|
|
@@ -119,11 +128,13 @@ import {
|
|
|
119
128
|
parseAccountProfile,
|
|
120
129
|
parseCurrentIdentity,
|
|
121
130
|
chooseWorkspace,
|
|
131
|
+
publicProfile,
|
|
122
132
|
publicWorkspace,
|
|
123
133
|
selectWorkspace,
|
|
124
|
-
selectedWorkspaceId,
|
|
125
134
|
InteractiveSessionRequiredError,
|
|
126
135
|
type AuthenticatedProductApiOverrides,
|
|
136
|
+
type EffectiveContext,
|
|
137
|
+
type LocalProfile,
|
|
127
138
|
type WorkspaceSession,
|
|
128
139
|
} from "@ohmyhost/product-cli";
|
|
129
140
|
|
|
@@ -134,17 +145,26 @@ export class OperationEventsUnavailableError extends Error {
|
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
|
|
148
|
+
/**
|
|
149
|
+
* One client per tool call, for the login that call asked for. The process binding and the call's
|
|
150
|
+
* own arguments choose it, so another agent's choice never changes which account a call uses.
|
|
151
|
+
*/
|
|
137
152
|
export async function createAuthenticatedLocalMcpClient(
|
|
138
153
|
signal: AbortSignal,
|
|
139
|
-
overrides: Omit<AuthenticatedProductApiOverrides, "productApiOrigin"> = {},
|
|
154
|
+
overrides: Omit<AuthenticatedProductApiOverrides, "productApiOrigin" | "selector"> = {},
|
|
140
155
|
): Promise<LocalMcpProductClient> {
|
|
141
156
|
try {
|
|
142
|
-
const transport = await createAuthenticatedProductTransport(signal,
|
|
157
|
+
const transport = await createAuthenticatedProductTransport(signal, {
|
|
158
|
+
...overrides,
|
|
159
|
+
selector: "mcp",
|
|
160
|
+
});
|
|
143
161
|
return new GeneratedSdkLocalMcpClient(
|
|
144
162
|
transport.baseUrl,
|
|
145
163
|
transport.accessToken,
|
|
146
164
|
transport.fetch,
|
|
147
165
|
transport.workspace,
|
|
166
|
+
transport.context,
|
|
167
|
+
resolveProductCliEnvironment(overrides.environment).commandPrefix,
|
|
148
168
|
);
|
|
149
169
|
} catch (error) {
|
|
150
170
|
if (!(error instanceof ProductApiAuthenticationError)) throw error;
|
|
@@ -160,9 +180,13 @@ export async function createAuthenticatedLocalMcpClient(
|
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
|
|
163
|
-
|
|
183
|
+
#client: OhMyHostClient;
|
|
164
184
|
|
|
165
|
-
|
|
185
|
+
#workspace: WorkspaceSession | undefined;
|
|
186
|
+
|
|
187
|
+
readonly #context: EffectiveContext;
|
|
188
|
+
|
|
189
|
+
readonly #commandPrefix: string;
|
|
166
190
|
|
|
167
191
|
readonly #rescope: (accessToken: string) => OhMyHostClient;
|
|
168
192
|
|
|
@@ -171,10 +195,50 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
|
|
|
171
195
|
accessToken: string,
|
|
172
196
|
fetch: typeof globalThis.fetch,
|
|
173
197
|
workspace?: WorkspaceSession,
|
|
198
|
+
context?: EffectiveContext,
|
|
199
|
+
commandPrefix = "OHMYHOST_ENVIRONMENT=production ohmyhost",
|
|
174
200
|
) {
|
|
175
201
|
this.#rescope = (token) => createClient({ auth: token, baseUrl, fetch, throwOnError: true });
|
|
176
202
|
this.#client = this.#rescope(accessToken);
|
|
177
203
|
this.#workspace = workspace;
|
|
204
|
+
this.#context = context ?? {
|
|
205
|
+
credential: workspace === undefined ? "environment_token" : "profile",
|
|
206
|
+
profile: workspace?.profile ?? null,
|
|
207
|
+
};
|
|
208
|
+
this.#commandPrefix = commandPrefix;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public actingAs(): IdentityReport["context"] {
|
|
212
|
+
return this.#contextReport();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Which account this call acts as, without any credential. */
|
|
216
|
+
#contextReport(): IdentityReport["context"] {
|
|
217
|
+
const profile = this.#workspace?.profile ?? this.#context.profile;
|
|
218
|
+
return {
|
|
219
|
+
credential: this.#context.credential,
|
|
220
|
+
profile: profile === null ? null : publicProfile(profile),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Bind this call's login, which has no organization yet, and continue with its new credential.
|
|
226
|
+
* A login in another organization keeps it; the provider verifies the account first.
|
|
227
|
+
*/
|
|
228
|
+
async #bind(
|
|
229
|
+
organization: Parameters<typeof selectWorkspace>[1],
|
|
230
|
+
signal: AbortSignal,
|
|
231
|
+
): Promise<LocalProfile> {
|
|
232
|
+
const session = this.#interactiveSession();
|
|
233
|
+
const bound = await selectWorkspace(session, organization, signal, (token) =>
|
|
234
|
+
getCurrentIdentity({}, { client: this.#rescope(token) }),
|
|
235
|
+
);
|
|
236
|
+
const store = session.profiles.credential(bound);
|
|
237
|
+
const credential = await store.read();
|
|
238
|
+
if (credential === undefined) throw new InteractiveSessionRequiredError();
|
|
239
|
+
this.#workspace = { ...session, store, profile: bound };
|
|
240
|
+
this.#client = this.#rescope(credential.accessToken);
|
|
241
|
+
return bound;
|
|
178
242
|
}
|
|
179
243
|
|
|
180
244
|
public async changeDatabaseCompute(input: {
|
|
@@ -275,20 +339,20 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
|
|
|
275
339
|
{ client: this.#client },
|
|
276
340
|
);
|
|
277
341
|
}
|
|
278
|
-
public async getCurrentIdentity(signal: AbortSignal) {
|
|
342
|
+
public async getCurrentIdentity(signal: AbortSignal): Promise<IdentityReport> {
|
|
279
343
|
const identity = await getCurrentIdentity({}, { client: this.#client });
|
|
280
344
|
// A session signed in before a workspace could be selected carries none, and then every
|
|
281
345
|
// project call answers with an empty page that reads like an empty account.
|
|
282
346
|
if (this.#workspace === undefined || parseCurrentIdentity(identity).organization_ids.length > 0)
|
|
283
|
-
return identity;
|
|
347
|
+
return { ...identity, context: this.#contextReport() };
|
|
284
348
|
const selection = chooseWorkspace(await this.#readWorkspaces(), undefined);
|
|
285
|
-
if (selection.outcome !== "selected") return identity;
|
|
286
|
-
await selectWorkspace(this.#workspace, selection.organization, signal);
|
|
349
|
+
if (selection.outcome !== "selected") return { ...identity, context: this.#contextReport() };
|
|
287
350
|
// The workspace lives in the access token, so the repaired identity needs the new one.
|
|
288
|
-
|
|
289
|
-
return
|
|
290
|
-
|
|
291
|
-
:
|
|
351
|
+
await this.#bind(selection.organization, signal);
|
|
352
|
+
return {
|
|
353
|
+
...(await getCurrentIdentity({}, { client: this.#client })),
|
|
354
|
+
context: this.#contextReport(),
|
|
355
|
+
};
|
|
292
356
|
}
|
|
293
357
|
public async changeProjectHandle(input: {
|
|
294
358
|
projectId: string;
|
|
@@ -362,6 +426,11 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
|
|
|
362
426
|
assertOrganizationAccount(account);
|
|
363
427
|
return account;
|
|
364
428
|
}
|
|
429
|
+
public async getOrganizationReferral(organizationId: string): Promise<OrganizationReferral> {
|
|
430
|
+
return parseOrganizationReferral(
|
|
431
|
+
await getOrganizationReferral({ organization_id: organizationId }, { client: this.#client }),
|
|
432
|
+
);
|
|
433
|
+
}
|
|
365
434
|
public getOrganizationCredits(organizationId: string) {
|
|
366
435
|
return getOrganizationCredits({ organization_id: organizationId }, { client: this.#client });
|
|
367
436
|
}
|
|
@@ -466,6 +535,16 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
|
|
|
466
535
|
},
|
|
467
536
|
{ client: this.#client },
|
|
468
537
|
);
|
|
538
|
+
const current = this.#interactiveSession().profile;
|
|
539
|
+
// A login in another organization keeps it, so no other agent using it moves along; the new
|
|
540
|
+
// workspace gets its own login instead.
|
|
541
|
+
if (current.organizationId !== null && current.organizationId !== created.id)
|
|
542
|
+
return {
|
|
543
|
+
organization: { id: created.id, name: created.name },
|
|
544
|
+
selected: current.organizationId,
|
|
545
|
+
profile: publicProfile(current),
|
|
546
|
+
next_action: `Run '${this.#commandPrefix} login --organization ${created.id} --user ${current.userId} --json' to add a login for the new workspace as the same user, then pass its profile_name.`,
|
|
547
|
+
};
|
|
469
548
|
// A created workspace only becomes usable once the access token carries it.
|
|
470
549
|
return this.selectOrganization(created.id, signal);
|
|
471
550
|
}
|
|
@@ -474,7 +553,8 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
|
|
|
474
553
|
const session = this.#interactiveSession();
|
|
475
554
|
return {
|
|
476
555
|
organizations: (await this.#readWorkspaces()).map(publicWorkspace),
|
|
477
|
-
selected:
|
|
556
|
+
selected: session.profile.organizationId,
|
|
557
|
+
profile: publicProfile(session.profile),
|
|
478
558
|
};
|
|
479
559
|
}
|
|
480
560
|
|
|
@@ -482,20 +562,21 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
|
|
|
482
562
|
organizationId: string,
|
|
483
563
|
signal: AbortSignal,
|
|
484
564
|
): Promise<WorkspaceSelectionReport> {
|
|
485
|
-
|
|
565
|
+
this.#interactiveSession();
|
|
486
566
|
const selection = chooseWorkspace(await this.#readWorkspaces(), organizationId);
|
|
487
567
|
if (selection.outcome !== "selected")
|
|
488
568
|
throw new Error(`ORGANIZATION_NOT_FOUND:${organizationId}`);
|
|
489
|
-
await
|
|
569
|
+
const profile = await this.#bind(selection.organization, signal);
|
|
490
570
|
return {
|
|
491
571
|
organization: publicWorkspace(selection.organization),
|
|
492
572
|
selected: selection.organization.id,
|
|
573
|
+
profile: publicProfile(profile),
|
|
493
574
|
};
|
|
494
575
|
}
|
|
495
576
|
|
|
496
577
|
async #readWorkspaces() {
|
|
497
578
|
this.#interactiveSession();
|
|
498
|
-
return parseAccountProfile(await getAccountProfile({ client: this.#client }));
|
|
579
|
+
return parseAccountProfile(await getAccountProfile({ client: this.#client })).organizations;
|
|
499
580
|
}
|
|
500
581
|
|
|
501
582
|
#interactiveSession(): WorkspaceSession {
|
|
@@ -537,6 +618,27 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
|
|
|
537
618
|
),
|
|
538
619
|
);
|
|
539
620
|
}
|
|
621
|
+
public async getPoweredByFlag(projectId: string): Promise<PoweredByFlag> {
|
|
622
|
+
return parsePoweredByFlag(
|
|
623
|
+
await getProjectPoweredByFlag({ project_id: projectId }, { client: this.#client }),
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
public async setPoweredByFlag(input: {
|
|
627
|
+
projectId: string;
|
|
628
|
+
enabled: boolean;
|
|
629
|
+
idempotencyKey: string;
|
|
630
|
+
}): Promise<PoweredByFlag> {
|
|
631
|
+
return parsePoweredByFlag(
|
|
632
|
+
await setProjectPoweredByFlag(
|
|
633
|
+
{
|
|
634
|
+
project_id: input.projectId,
|
|
635
|
+
"Idempotency-Key": input.idempotencyKey,
|
|
636
|
+
setPoweredByFlagRequest: { enabled: input.enabled },
|
|
637
|
+
},
|
|
638
|
+
{ client: this.#client },
|
|
639
|
+
),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
540
642
|
public async rotateDevShareLink(input: {
|
|
541
643
|
projectId: string;
|
|
542
644
|
idempotencyKey: string;
|