@amerged/ohmyhost-mcp 0.1.27 → 0.1.29

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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +1419 -0
  3. package/dist/{chunk-QF7MOXLY.js → chunk-BWVBJWRR.js} +69 -23
  4. package/dist/index.js +1 -1
  5. package/dist/stdio-main.js +24158 -2353
  6. package/package.json +1 -1
  7. package/src/apps/mcp/package.json +1 -1
  8. package/src/apps/mcp/src/local-client.ts +12 -2
  9. package/src/apps/mcp/src/local-server.ts +333 -4
  10. package/src/apps/product-cli/package.json +4 -2
  11. package/src/apps/product-cli/src/cli.ts +312 -2
  12. package/src/apps/product-cli/src/command.ts +261 -0
  13. package/src/apps/product-cli/src/composition.ts +74 -4
  14. package/src/apps/product-cli/src/index.ts +6 -0
  15. package/src/apps/product-cli/src/product-api.ts +5 -0
  16. package/src/apps/product-cli/src/project-link-store.ts +92 -1
  17. package/src/apps/product-cli/src/repository-init.ts +12 -0
  18. package/src/apps/product-cli/src/runtime-contract.ts +34 -1
  19. package/src/apps/product-cli/src/source-managed-client.ts +189 -0
  20. package/src/apps/product-cli/src/source-publication-store.ts +563 -0
  21. package/src/apps/product-cli/src/source-publisher.ts +195 -0
  22. package/src/apps/product-cli/src/source-workspace.ts +552 -0
  23. package/src/packages/agent-skills/src/generated-skill-resources.ts +20 -10
  24. package/src/packages/agent-skills/src/index.ts +1 -1
  25. package/src/packages/contracts/package.json +10 -0
  26. package/src/packages/contracts/src/index.ts +5 -0
  27. package/src/packages/contracts/src/managed-sources.ts +582 -0
  28. package/src/packages/contracts/src/mcp-source-codec.ts +150 -0
  29. package/src/packages/contracts/src/operation-failure.ts +22 -6
  30. package/src/packages/contracts/src/problem-policy.ts +68 -14
  31. package/src/packages/sdk-ts/package.json +1 -1
  32. package/src/packages/sdk-ts/src/generated/index.ts +92 -0
  33. package/src/packages/sdk-ts/src/generated/sdk.gen.ts +541 -0
  34. package/src/packages/sdk-ts/src/generated/types.gen.ts +912 -4
  35. package/src/packages/workos-auth-contracts/src/tokens.ts +61 -4
@@ -1,4 +1,24 @@
1
1
  import { open } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import {
4
+ assertManagedSourceResponse,
5
+ type ManagedProjectSource,
6
+ } from "@ohmyhost/contracts/managed-sources";
7
+ import {
8
+ createSourceSnapshot,
9
+ inspectSourceWorkspace,
10
+ SourceWorkspaceError,
11
+ type SourceWorkspace,
12
+ } from "./source-workspace.js";
13
+ import { publishSourceSnapshot } from "./source-publisher.js";
14
+ import {
15
+ prepareLocalSourcePublication,
16
+ completeLocalSourcePublication,
17
+ inspectLocalSourcePublications,
18
+ LocalSourcePublicationError,
19
+ type LocalSourceMetadataDirectory,
20
+ } from "./source-publication-store.js";
21
+ import { readProjectLinks } from "./project-link-store.js";
2
22
  import {
3
23
  parseProjectDatabaseWrite,
4
24
  assertProjectDatabaseWriteReceipt,
@@ -94,6 +114,7 @@ import {
94
114
  parseOperationEvent,
95
115
  parseProjectPage,
96
116
  parseProjectStatus,
117
+ parseProjectSourceBinding,
97
118
  parseProjectDatabaseQuery,
98
119
  parsePaidDomain,
99
120
  parsePaidDomainPlan,
@@ -212,6 +233,11 @@ export class PublicResponseTooLargeError extends Error {
212
233
  }
213
234
 
214
235
  export interface ProductCliDependencies {
236
+ readonly sourceDirectory?: string;
237
+ readonly sourceMetadataDirectory?: LocalSourceMetadataDirectory;
238
+ readonly sourceWorkspace?: () => Promise<SourceWorkspace>;
239
+ readonly linkedSourceProvider?: "github" | "managed";
240
+ readonly linkedProjectId?: string;
215
241
  readonly apiToken?: string;
216
242
  readonly commandPrefix: string;
217
243
  /** One credential store for a run without saved logins; a chosen saved login uses its own. */
@@ -234,11 +260,15 @@ export interface ProductCliDependencies {
234
260
  * keeping product mutation capabilities out of login.
235
261
  */
236
262
  readonly accountProfile: (accessToken: () => Promise<string>) => Promise<PublicAccount>;
237
- /** Optional onboarding guidance; production supplies only this one read-only SDK query. */
263
+ /** Optional onboarding guidance uses only these narrow read-only SDK queries. */
238
264
  readonly githubConnectionStatus?: (
239
265
  accessToken: () => Promise<string>,
240
266
  organizationId: string,
241
267
  ) => ReturnType<ProductApi["getGithubConnection"]>;
268
+ readonly projectSourceStatus?: (
269
+ accessToken: () => Promise<string>,
270
+ projectId: string,
271
+ ) => Promise<unknown>;
242
272
  readonly projectLinkStore: ProjectLinkStore;
243
273
  readonly productApiOrigin: string;
244
274
  readonly tokenRevoker: TokenRevoker;
@@ -389,6 +419,24 @@ const HELP_USAGE = Object.freeze({
389
419
  "source auto-deploy set":
390
420
  "ohmyhost source auto-deploy set --project ULID --branch BRANCH --enabled true|false --idempotency-key KEY --json",
391
421
  "source auto-deploy status": "ohmyhost source auto-deploy status --project ULID --json",
422
+ "source inspect":
423
+ "ohmyhost source inspect [--directory PATH] --json (read local source and sanitized GitHub context without sign-in)",
424
+ "source status": "ohmyhost source status --project ULID --json",
425
+ "source files": "ohmyhost source files --project ULID [--commit SHA] --json",
426
+ "source file": "ohmyhost source file --project ULID --path PATH [--commit SHA] --json",
427
+ "source versions":
428
+ "ohmyhost source versions --project ULID [--limit 1..100] [--before SHA] --json",
429
+ "source diff": "ohmyhost source diff --project ULID --from SHA --to SHA --json",
430
+ "source upload":
431
+ "ohmyhost source upload --project ULID --operation ULID --json (observe the same upload after uncertainty)",
432
+ "source publish":
433
+ "ohmyhost source publish --project ULID --mode initialize|commit|switch [--directory PATH] --expected-source-generation N [--expected-source-connection ULID] [--expected-commit SHA] --message TEXT --idempotency-key KEY [--wait] --json (current files, no history import; one managed commit)",
434
+ "source publish complete":
435
+ "ohmyhost source publish complete --project ULID [--directory PATH] [--operation ULID] --json (confirm the recorded publication and save this directory's own committed base)",
436
+ "source initialize":
437
+ "ohmyhost source initialize --project ULID --template empty|vite-react --expected-source-generation N --idempotency-key KEY [--wait] --json",
438
+ "source restore":
439
+ "ohmyhost source restore --project ULID --expected-source-generation N --expected-commit SHA --restore-commit SHA --message TEXT --idempotency-key KEY [--wait] --json (restore as a new version; publication is separate)",
392
440
  "domain cloudflare authorize":
393
441
  "ohmyhost domain cloudflare authorize --project ULID --zone ZONE --idempotency-key KEY --json",
394
442
  "domain cloudflare status": "ohmyhost domain cloudflare status --project ULID --json",
@@ -608,6 +656,24 @@ export class ProductCli {
608
656
  if (command.kind === "help" || command.kind === "version")
609
657
  return cliMetadataResult(command.kind, command.kind === "help" ? command.topic : undefined);
610
658
  if (command.kind === "init") throw new ProviderCapabilityBlockedError();
659
+ if (command.kind === "source-inspect")
660
+ return success(name, {
661
+ status: "inspected",
662
+ workspace: await inspectSourceWorkspace(
663
+ resolve(this.dependencies.sourceDirectory ?? process.cwd(), command.directory),
664
+ ),
665
+ pending_publications: await inspectLocalSourcePublications(
666
+ resolve(this.dependencies.sourceDirectory ?? process.cwd(), command.directory),
667
+ ),
668
+ });
669
+ if (
670
+ command.kind === "source-read" ||
671
+ command.kind === "source-publish" ||
672
+ command.kind === "source-initialize" ||
673
+ command.kind === "source-restore" ||
674
+ command.kind === "source-complete"
675
+ )
676
+ return await this.managedSource(command, signal);
611
677
  if (
612
678
  this.dependencies.apiToken !== undefined &&
613
679
  (command.kind === "organization-create" ||
@@ -1083,12 +1149,31 @@ export class ProductCli {
1083
1149
  }
1084
1150
 
1085
1151
  private async githubNextAction(
1086
- api: Pick<ProductApi, "getGithubConnection">,
1152
+ api: Pick<ProductApi, "getGithubConnection"> & {
1153
+ readonly managedSource?: Pick<NonNullable<ProductApi["managedSource"]>, "getSource">;
1154
+ },
1087
1155
  organizationId: string,
1088
1156
  ): Promise<Readonly<Record<string, string>>> {
1089
1157
  // Optional onboarding guidance must not turn a completed login into failure when
1090
1158
  // a scoped key cannot read source connections or the status endpoint is unavailable.
1091
1159
  try {
1160
+ let provider = this.dependencies.linkedSourceProvider;
1161
+ if (this.dependencies.linkedProjectId !== undefined) {
1162
+ if (api.managedSource === undefined) return {};
1163
+ const binding = await api.managedSource.getSource(this.dependencies.linkedProjectId);
1164
+ if (binding !== null) parseProjectSourceBinding(binding);
1165
+ provider =
1166
+ binding === null ? undefined : (binding as { provider: "github" | "managed" }).provider;
1167
+ }
1168
+ if (provider === "managed") return {};
1169
+ if (provider !== "github" && this.dependencies.sourceWorkspace !== undefined) {
1170
+ const workspace = await this.dependencies.sourceWorkspace();
1171
+ if (!workspace.git?.remotes.some((remote) => remote.githubRepository !== null))
1172
+ return {
1173
+ next_action:
1174
+ "Ask once whether to deploy directly with ohmyho.st storing the versions or set up GitHub for this project. Inspect current source first; a missing GitHub connection does not require GitHub.",
1175
+ };
1176
+ }
1092
1177
  const github = parseGithubOrganizationConnectionStatus(
1093
1178
  await api.getGithubConnection(organizationId),
1094
1179
  );
@@ -1347,6 +1432,7 @@ export class ProductCli {
1347
1432
  }
1348
1433
  this.#profile = profile;
1349
1434
  const readGithubConnection = this.dependencies.githubConnectionStatus;
1435
+ const readProjectSource = this.dependencies.projectSourceStatus;
1350
1436
  if (organization !== null)
1351
1437
  return success("login", {
1352
1438
  status: "authenticated",
@@ -1356,6 +1442,17 @@ export class ProductCli {
1356
1442
  ? {}
1357
1443
  : await this.githubNextAction(
1358
1444
  {
1445
+ ...(readProjectSource === undefined
1446
+ ? {}
1447
+ : {
1448
+ managedSource: {
1449
+ getSource: (selectedProjectId) =>
1450
+ readProjectSource(
1451
+ async () => credential.accessToken,
1452
+ selectedProjectId,
1453
+ ),
1454
+ },
1455
+ }),
1359
1456
  getGithubConnection: (selectedOrganizationId) =>
1360
1457
  readGithubConnection(
1361
1458
  async () => credential.accessToken,
@@ -1844,6 +1941,7 @@ export class ProductCli {
1844
1941
  if (
1845
1942
  project.project_id !== command.projectId ||
1846
1943
  source === null ||
1944
+ source.provider !== "github" ||
1847
1945
  source.status !== "ready" ||
1848
1946
  source.repository_full_name.toLowerCase() !==
1849
1947
  `${command.repositoryOwner}/${command.repositoryName}`.toLowerCase()
@@ -1866,6 +1964,201 @@ export class ProductCli {
1866
1964
  return success("link", { status: "completed", operation, organization_id: organizationId });
1867
1965
  }
1868
1966
 
1967
+ private async managedSource(
1968
+ command: Extract<
1969
+ ProductCliCommand,
1970
+ {
1971
+ kind:
1972
+ | "source-read"
1973
+ | "source-publish"
1974
+ | "source-initialize"
1975
+ | "source-restore"
1976
+ | "source-complete";
1977
+ }
1978
+ >,
1979
+ signal: AbortSignal,
1980
+ ): Promise<CliResult> {
1981
+ const api = await this.authenticatedApi(command.credentialStore, signal);
1982
+ const source = api.managedSource;
1983
+ if (source === undefined) throw new ProviderCapabilityBlockedError();
1984
+ const name = parsedCommandName(command);
1985
+ if (command.kind === "source-complete") {
1986
+ const completed = await completeLocalSourcePublication({
1987
+ api: source,
1988
+ getOperation: (id) => api.getOperation(id),
1989
+ directory: resolve(this.dependencies.sourceDirectory ?? process.cwd(), command.directory),
1990
+ metadataDirectory: this.dependencies.sourceMetadataDirectory,
1991
+ apiOrigin: this.dependencies.productApiOrigin,
1992
+ identity: await api.getCurrentIdentity(),
1993
+ projectId: command.projectId,
1994
+ operationId: command.operationId,
1995
+ });
1996
+ return success(name, completed);
1997
+ }
1998
+ if (command.kind === "source-read") {
1999
+ const result =
2000
+ command.action === "status"
2001
+ ? await source.getSource(command.projectId)
2002
+ : command.action === "files" || command.action === "file"
2003
+ ? await source.files({
2004
+ projectId: command.projectId,
2005
+ commitSha: command.commitSha,
2006
+ path: command.path,
2007
+ })
2008
+ : command.action === "versions"
2009
+ ? await source.versions({
2010
+ projectId: command.projectId,
2011
+ limit: command.limit,
2012
+ beforeCommitSha: command.beforeCommitSha,
2013
+ })
2014
+ : command.action === "diff"
2015
+ ? await source.diff({
2016
+ projectId: command.projectId,
2017
+ fromCommitSha: String(command.fromCommitSha),
2018
+ toCommitSha: String(command.toCommitSha),
2019
+ })
2020
+ : await source.getUpload({
2021
+ projectId: command.projectId,
2022
+ operationId: String(command.operationId),
2023
+ });
2024
+ if (command.action === "status") {
2025
+ if (result !== null) parseProjectSourceBinding(result);
2026
+ } else
2027
+ assertManagedSourceResponse(command.action === "file" ? "files" : command.action, result);
2028
+ return success(name, { status: "succeeded", source: result, context: this.context() });
2029
+ }
2030
+ let operation: ReturnType<typeof parseOperation>;
2031
+ let receipt: unknown;
2032
+ let snapshot: { sha256: string; total_bytes: number; files: number } | undefined;
2033
+ if (command.kind === "source-publish") {
2034
+ const directory = resolve(
2035
+ this.dependencies.sourceDirectory ?? process.cwd(),
2036
+ command.directory,
2037
+ );
2038
+ const captured = await createSourceSnapshot({
2039
+ directory,
2040
+ });
2041
+ const prepared = await prepareLocalSourcePublication({
2042
+ directory,
2043
+ metadataDirectory: this.dependencies.sourceMetadataDirectory,
2044
+ apiOrigin: this.dependencies.productApiOrigin,
2045
+ identity: await api.getCurrentIdentity(),
2046
+ projectId: command.projectId,
2047
+ request: command.request,
2048
+ explicitExpectedCommit: command.explicitExpectedCommit,
2049
+ snapshotSha256: captured.sha256,
2050
+ idempotencyKey: command.idempotencyKey,
2051
+ });
2052
+ const published = await publishSourceSnapshot({
2053
+ api: source,
2054
+ projectId: command.projectId,
2055
+ request: prepared.request,
2056
+ snapshot: captured,
2057
+ idempotencyKey: command.idempotencyKey,
2058
+ signal,
2059
+ accepted: prepared.accepted,
2060
+ rejected: prepared.rejected,
2061
+ });
2062
+ operation = parseOperation(published.operation);
2063
+ receipt = published.receipt;
2064
+ snapshot = {
2065
+ sha256: captured.sha256,
2066
+ total_bytes: captured.totalBytes,
2067
+ files: captured.files.length,
2068
+ };
2069
+ } else
2070
+ operation = parseOperation(
2071
+ command.kind === "source-initialize"
2072
+ ? await source.initialize({
2073
+ projectId: command.projectId,
2074
+ body: command.request,
2075
+ idempotencyKey: command.idempotencyKey,
2076
+ })
2077
+ : await source.restore({
2078
+ projectId: command.projectId,
2079
+ body: command.request,
2080
+ idempotencyKey: command.idempotencyKey,
2081
+ }),
2082
+ );
2083
+ if (command.wait) operation = await this.waitForOperation(api, operation, signal);
2084
+ if (operation.state === "succeeded" && command.kind === "source-publish") {
2085
+ const completed = await completeLocalSourcePublication({
2086
+ api: source,
2087
+ getOperation: (id) => api.getOperation(id),
2088
+ directory: resolve(this.dependencies.sourceDirectory ?? process.cwd(), command.directory),
2089
+ metadataDirectory: this.dependencies.sourceMetadataDirectory,
2090
+ apiOrigin: this.dependencies.productApiOrigin,
2091
+ identity: await api.getCurrentIdentity(),
2092
+ projectId: command.projectId,
2093
+ operationId: operation.id,
2094
+ });
2095
+ receipt = completed.receipt;
2096
+ } else if (operation.state === "succeeded") {
2097
+ const current = await source.getSource(command.projectId);
2098
+ parseProjectSourceBinding(current);
2099
+ if (
2100
+ current === null ||
2101
+ typeof current !== "object" ||
2102
+ !("provider" in current) ||
2103
+ current.provider !== "managed"
2104
+ )
2105
+ throw new ResponseContractError();
2106
+ const managed = current as ManagedProjectSource;
2107
+ const expectedGeneration =
2108
+ command.kind === "source-initialize" ||
2109
+ (command.kind === "source-publish" && command.request.mode !== "commit")
2110
+ ? command.request.expected_source_generation + 1
2111
+ : command.request.expected_source_generation;
2112
+ if (managed.source_generation !== expectedGeneration || managed.status !== "ready")
2113
+ throw new ResponseContractError();
2114
+ const [organizationId, ...others] = parseCurrentIdentity(
2115
+ await api.getCurrentIdentity(),
2116
+ ).organization_ids;
2117
+ if (organizationId === undefined || others.length > 0) throw new ResponseContractError();
2118
+ const prior = (
2119
+ await readProjectLinks(
2120
+ join(
2121
+ this.dependencies.sourceDirectory ?? process.cwd(),
2122
+ this.dependencies.sourceMetadataDirectory ?? ".ohmyhost",
2123
+ ),
2124
+ this.dependencies.productApiOrigin,
2125
+ )
2126
+ ).links.find(
2127
+ (link) => link.organization_id === organizationId && link.project_id === command.projectId,
2128
+ );
2129
+ const confirmed =
2130
+ prior?.version === 3 &&
2131
+ prior.source.provider === "managed" &&
2132
+ prior.source.source_connection_id === managed.source_connection_id &&
2133
+ prior.source.source_generation === managed.source_generation
2134
+ ? prior.source.last_confirmed_commit_sha
2135
+ : null;
2136
+ await this.dependencies.projectLinkStore.save({
2137
+ version: 3,
2138
+ api_origin: this.dependencies.productApiOrigin,
2139
+ organization_id: organizationId,
2140
+ project_id: command.projectId,
2141
+ source: {
2142
+ provider: "managed",
2143
+ source_connection_id: managed.source_connection_id,
2144
+ source_generation: managed.source_generation,
2145
+ namespace: managed.namespace,
2146
+ repository_name: managed.repository_name,
2147
+ branch: "main",
2148
+ last_confirmed_commit_sha: confirmed,
2149
+ },
2150
+ });
2151
+ }
2152
+ return success(name, {
2153
+ status: command.wait ? "completed" : "accepted",
2154
+ operation,
2155
+ ...(receipt === undefined ? {} : { receipt }),
2156
+ ...(snapshot === undefined ? {} : { snapshot }),
2157
+ next_action:
2158
+ "Read the resulting commit_sha, then plan and deploy that exact version. Publication is a separate action.",
2159
+ });
2160
+ }
2161
+
1869
2162
  private async configureAutoDeploy(
1870
2163
  command: Extract<ProductCliCommand, { kind: "source-auto-deploy-set" }>,
1871
2164
  signal: AbortSignal,
@@ -2512,6 +2805,17 @@ const classifyFailure = (
2512
2805
  signal: AbortSignal,
2513
2806
  actingAs?: Readonly<Record<string, unknown>>,
2514
2807
  ): CliResult => {
2808
+ if (error instanceof LocalSourcePublicationError)
2809
+ return failure(
2810
+ command,
2811
+ 2,
2812
+ error.code,
2813
+ error.code === "source_publication_pending",
2814
+ error.message,
2815
+ error.operation,
2816
+ );
2817
+ if (error instanceof SourceWorkspaceError)
2818
+ return failure(command, 2, error.code, error.code === "source_snapshot_changed", error.message);
2515
2819
  if (signal.aborted || isCancellation(error)) {
2516
2820
  return failure(
2517
2821
  command,
@@ -2743,6 +3047,12 @@ const failure = (
2743
3047
  };
2744
3048
 
2745
3049
  const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
3050
+ if (command.kind === "source-inspect") return "source inspect";
3051
+ if (command.kind === "source-publish") return "source publish";
3052
+ if (command.kind === "source-complete") return "source publish complete";
3053
+ if (command.kind === "source-initialize") return "source initialize";
3054
+ if (command.kind === "source-restore") return "source restore";
3055
+ if (command.kind === "source-read") return `source ${command.action}`;
2746
3056
  if (command.kind === "export-create") return "export create";
2747
3057
  if (command.kind === "export-get") return "export get";
2748
3058
  if (command.kind === "token-create") return "token create";