@amerged/ohmyhost-mcp 0.1.15 → 0.1.18
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-LGXYRWBD.js → chunk-X6IS5LX7.js} +11 -11
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +313 -29
- package/package.json +1 -1
- package/src/apps/mcp/local-client.ts +70 -2
- package/src/apps/mcp/local-server.ts +122 -9
- package/src/apps/product-cli/cli.ts +97 -10
- package/src/apps/product-cli/command.ts +155 -10
- package/src/apps/product-cli/composition.ts +47 -3
- package/src/apps/product-cli/index.ts +1 -0
- package/src/apps/product-cli/product-api.ts +73 -2
- package/src/apps/product-cli/project-link-store.ts +45 -2
- package/src/apps/product-cli/repository-init.ts +6 -5
- package/src/apps/product-cli/runtime-contract.ts +60 -4
- package/src/packages/agent-skills/generated-skill-resources.ts +10 -10
- package/src/packages/contracts/feedback.ts +212 -32
- package/src/packages/sdk-ts/generated/index.ts +42 -0
- package/src/packages/sdk-ts/generated/sdk.gen.ts +282 -3
- package/src/packages/sdk-ts/generated/types.gen.ts +438 -10
- package/src/packages/sdk-ts/managed-mail-response.ts +7 -0
- package/src/packages/sdk-ts/managed-mail.ts +7 -1
|
@@ -79,6 +79,12 @@ export type ProductCliCommand =
|
|
|
79
79
|
readonly idempotencyKey: string;
|
|
80
80
|
readonly credentialStore: "native";
|
|
81
81
|
}
|
|
82
|
+
| {
|
|
83
|
+
readonly kind: "feedback-status";
|
|
84
|
+
readonly feedbackId: string;
|
|
85
|
+
readonly cursor?: string;
|
|
86
|
+
readonly credentialStore: "native";
|
|
87
|
+
}
|
|
82
88
|
| { kind: "billing-recharge-get"; organizationId: string; credentialStore: "native" }
|
|
83
89
|
| ({ kind: "billing-recharge-set"; credentialStore: "native" } & {
|
|
84
90
|
organizationId: string;
|
|
@@ -200,6 +206,7 @@ export type ProductCliCommand =
|
|
|
200
206
|
readonly organizationId: string;
|
|
201
207
|
readonly name: string;
|
|
202
208
|
readonly dataMode?: "shared" | "isolated";
|
|
209
|
+
readonly devAccessMode?: "protected" | "public";
|
|
203
210
|
readonly region?: "us" | "eu";
|
|
204
211
|
readonly idempotencyKey: string;
|
|
205
212
|
readonly credentialStore: "native";
|
|
@@ -215,6 +222,30 @@ export type ProductCliCommand =
|
|
|
215
222
|
readonly projectId: string;
|
|
216
223
|
readonly credentialStore: "native";
|
|
217
224
|
}
|
|
225
|
+
| {
|
|
226
|
+
readonly kind: "project-dev-share-link";
|
|
227
|
+
readonly projectId: string;
|
|
228
|
+
readonly credentialStore: "native";
|
|
229
|
+
}
|
|
230
|
+
| {
|
|
231
|
+
readonly kind: "project-dev-share-rotate";
|
|
232
|
+
readonly projectId: string;
|
|
233
|
+
readonly idempotencyKey: string;
|
|
234
|
+
readonly credentialStore: "native";
|
|
235
|
+
}
|
|
236
|
+
| {
|
|
237
|
+
readonly kind: "project-dev-share-revoke";
|
|
238
|
+
readonly projectId: string;
|
|
239
|
+
readonly idempotencyKey: string;
|
|
240
|
+
readonly credentialStore: "native";
|
|
241
|
+
}
|
|
242
|
+
| {
|
|
243
|
+
readonly kind: "project-dev-access-mode-set";
|
|
244
|
+
readonly projectId: string;
|
|
245
|
+
readonly mode: "protected" | "public";
|
|
246
|
+
readonly idempotencyKey: string;
|
|
247
|
+
readonly credentialStore: "native";
|
|
248
|
+
}
|
|
218
249
|
| {
|
|
219
250
|
readonly kind: "project-dev-access-create";
|
|
220
251
|
readonly projectId: string;
|
|
@@ -294,12 +325,16 @@ export type ProductCliCommand =
|
|
|
294
325
|
readonly kind: "plan";
|
|
295
326
|
readonly projectId: string;
|
|
296
327
|
readonly commitSha: string;
|
|
328
|
+
readonly environment: "dev" | "prod";
|
|
297
329
|
readonly credentialStore: "native";
|
|
298
330
|
}
|
|
299
331
|
| {
|
|
300
332
|
readonly kind: "deploy";
|
|
301
333
|
readonly projectId: string;
|
|
302
|
-
|
|
334
|
+
/** Exactly one of an existing plan or a commit that is planned first. */
|
|
335
|
+
readonly planId: string | null;
|
|
336
|
+
readonly commitSha: string | null;
|
|
337
|
+
readonly environment: "dev" | "prod";
|
|
303
338
|
readonly idempotencyKey: string;
|
|
304
339
|
readonly yes: true;
|
|
305
340
|
readonly wait: boolean;
|
|
@@ -403,6 +438,7 @@ export type ProductCliCommand =
|
|
|
403
438
|
readonly label:
|
|
404
439
|
| "mail domain set"
|
|
405
440
|
| "mail domain status"
|
|
441
|
+
| "mail domain delete"
|
|
406
442
|
| "mail setup"
|
|
407
443
|
| "mail status"
|
|
408
444
|
| "mail webhook set"
|
|
@@ -498,6 +534,7 @@ export type RecognizedCommandLabel =
|
|
|
498
534
|
| "token list"
|
|
499
535
|
| "token revoke"
|
|
500
536
|
| "feedback submit"
|
|
537
|
+
| "feedback status"
|
|
501
538
|
| "project create"
|
|
502
539
|
| "organization create"
|
|
503
540
|
| "organization list"
|
|
@@ -507,6 +544,10 @@ export type RecognizedCommandLabel =
|
|
|
507
544
|
| "project list"
|
|
508
545
|
| "project status"
|
|
509
546
|
| "project dev-access create"
|
|
547
|
+
| "project dev-share link"
|
|
548
|
+
| "project dev-share rotate"
|
|
549
|
+
| "project dev-share revoke"
|
|
550
|
+
| "project dev-access mode"
|
|
510
551
|
| "project handle check"
|
|
511
552
|
| "project handle set"
|
|
512
553
|
| "database write"
|
|
@@ -548,7 +589,8 @@ export type RecognizedCommandLabel =
|
|
|
548
589
|
| "mail messages get"
|
|
549
590
|
| "mail messages retry"
|
|
550
591
|
| "mail domain set"
|
|
551
|
-
| "mail domain status"
|
|
592
|
+
| "mail domain status"
|
|
593
|
+
| "mail domain delete";
|
|
552
594
|
|
|
553
595
|
export const recognizedCommandLabel = (argv: readonly string[]): RecognizedCommandLabel => {
|
|
554
596
|
const values = [...argv];
|
|
@@ -597,6 +639,7 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
597
639
|
if (value === "export" && (values[0] === "create" || values[0] === "get"))
|
|
598
640
|
return `export ${values[0]}`;
|
|
599
641
|
if (value === "feedback" && values[0] === "submit") return "feedback submit";
|
|
642
|
+
if (value === "feedback" && values[0] === "status") return "feedback status";
|
|
600
643
|
if (value === "project" && values[0] === "create") return "project create";
|
|
601
644
|
if (value === "organization" && values[0] === "create") return "organization create";
|
|
602
645
|
if (value === "organization" && values[0] === "list") return "organization list";
|
|
@@ -610,6 +653,18 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
610
653
|
return "project handle check";
|
|
611
654
|
if (value === "project" && values[0] === "handle" && values[1] === "set")
|
|
612
655
|
return "project handle set";
|
|
656
|
+
if (
|
|
657
|
+
value === "project" &&
|
|
658
|
+
values[0] === "dev-share" &&
|
|
659
|
+
["link", "rotate", "revoke"].includes(values[1] ?? "")
|
|
660
|
+
)
|
|
661
|
+
return values[1] === "link"
|
|
662
|
+
? "project dev-share link"
|
|
663
|
+
: values[1] === "rotate"
|
|
664
|
+
? "project dev-share rotate"
|
|
665
|
+
: "project dev-share revoke";
|
|
666
|
+
if (value === "project" && values[0] === "dev-access" && values[1] === "mode")
|
|
667
|
+
return "project dev-access mode";
|
|
613
668
|
if (value === "project" && values[0] === "dev-access" && values[1] === "create")
|
|
614
669
|
return "project dev-access create";
|
|
615
670
|
if (value === "database" && values[0] === "compute" && values[1] === "set")
|
|
@@ -669,6 +724,7 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
669
724
|
if (value === "mail" && values[0] === "domain") {
|
|
670
725
|
if (values[1] === "set") return "mail domain set";
|
|
671
726
|
if (values[1] === "status") return "mail domain status";
|
|
727
|
+
if (values[1] === "delete") return "mail domain delete";
|
|
672
728
|
}
|
|
673
729
|
return "unknown";
|
|
674
730
|
}
|
|
@@ -798,6 +854,18 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
798
854
|
return { kind: "token-revoke", organizationId, keyId: options["--key"], credentialStore };
|
|
799
855
|
throw new InvalidCommandError();
|
|
800
856
|
}
|
|
857
|
+
if (first === "feedback" && values[0] === "status") {
|
|
858
|
+
values.shift();
|
|
859
|
+
const feedbackId = values.shift();
|
|
860
|
+
if (yes || wait || follow || stdin || !isValue(feedbackId)) throw new InvalidCommandError();
|
|
861
|
+
const cursor = parseOptionalOptions(values, ["--cursor"])["--cursor"];
|
|
862
|
+
return {
|
|
863
|
+
kind: "feedback-status",
|
|
864
|
+
feedbackId,
|
|
865
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
866
|
+
credentialStore,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
801
869
|
if (first === "feedback" && values.shift() === "submit") {
|
|
802
870
|
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
803
871
|
const options = parseOptionalOptions(values, [
|
|
@@ -1116,6 +1184,38 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
1116
1184
|
const options = parseOptions(values, ["--handle"]);
|
|
1117
1185
|
return { kind: "project-handle-check", handle: options["--handle"], credentialStore };
|
|
1118
1186
|
}
|
|
1187
|
+
if (subcommand === "dev-share") {
|
|
1188
|
+
const action = values.shift();
|
|
1189
|
+
if (action === "link") {
|
|
1190
|
+
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1191
|
+
const options = parseOptions(values, ["--project"]);
|
|
1192
|
+
return { kind: "project-dev-share-link", projectId: options["--project"], credentialStore };
|
|
1193
|
+
}
|
|
1194
|
+
if ((action === "rotate" || action === "revoke") && yes && !wait && !follow && !stdin) {
|
|
1195
|
+
const options = parseOptions(values, ["--project", "--idempotency-key"]);
|
|
1196
|
+
return {
|
|
1197
|
+
kind: action === "rotate" ? "project-dev-share-rotate" : "project-dev-share-revoke",
|
|
1198
|
+
projectId: options["--project"],
|
|
1199
|
+
idempotencyKey: options["--idempotency-key"],
|
|
1200
|
+
credentialStore,
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
throw new InvalidCommandError();
|
|
1204
|
+
}
|
|
1205
|
+
if (subcommand === "dev-access" && values[0] === "mode") {
|
|
1206
|
+
values.shift();
|
|
1207
|
+
if (!yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1208
|
+
const options = parseOptions(values, ["--project", "--mode", "--idempotency-key"]);
|
|
1209
|
+
if (options["--mode"] !== "protected" && options["--mode"] !== "public")
|
|
1210
|
+
throw new InvalidCommandError();
|
|
1211
|
+
return {
|
|
1212
|
+
kind: "project-dev-access-mode-set",
|
|
1213
|
+
projectId: options["--project"],
|
|
1214
|
+
mode: options["--mode"],
|
|
1215
|
+
idempotencyKey: options["--idempotency-key"],
|
|
1216
|
+
credentialStore,
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1119
1219
|
if (subcommand === "dev-access" && values.shift() === "create") {
|
|
1120
1220
|
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1121
1221
|
const options = parseOptions(values, ["--project"]);
|
|
@@ -1177,15 +1277,20 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
1177
1277
|
"--name",
|
|
1178
1278
|
"--idempotency-key",
|
|
1179
1279
|
"--data-mode",
|
|
1280
|
+
"--dev-access-mode",
|
|
1180
1281
|
"--region",
|
|
1181
1282
|
]);
|
|
1182
1283
|
const dataMode = options["--data-mode"];
|
|
1284
|
+
const devAccessMode = options["--dev-access-mode"];
|
|
1183
1285
|
const region = options["--region"];
|
|
1184
1286
|
if (
|
|
1185
1287
|
!options["--organization"] ||
|
|
1186
1288
|
!options["--name"] ||
|
|
1187
1289
|
!options["--idempotency-key"] ||
|
|
1188
1290
|
(dataMode !== undefined && dataMode !== "shared" && dataMode !== "isolated") ||
|
|
1291
|
+
(devAccessMode !== undefined &&
|
|
1292
|
+
devAccessMode !== "protected" &&
|
|
1293
|
+
devAccessMode !== "public") ||
|
|
1189
1294
|
(region !== undefined && region !== "us" && region !== "eu")
|
|
1190
1295
|
)
|
|
1191
1296
|
throw new InvalidCommandError();
|
|
@@ -1194,6 +1299,7 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
1194
1299
|
organizationId: options["--organization"],
|
|
1195
1300
|
name: options["--name"],
|
|
1196
1301
|
...(dataMode === undefined ? {} : { dataMode }),
|
|
1302
|
+
...(devAccessMode === undefined ? {} : { devAccessMode }),
|
|
1197
1303
|
...(region === undefined ? {} : { region }),
|
|
1198
1304
|
idempotencyKey: options["--idempotency-key"],
|
|
1199
1305
|
credentialStore,
|
|
@@ -1465,22 +1571,46 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
1465
1571
|
}
|
|
1466
1572
|
if (first === "plan") {
|
|
1467
1573
|
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1468
|
-
const options =
|
|
1574
|
+
const options = parseOptionalOptions(values, ["--project", "--commit", "--environment"]);
|
|
1575
|
+
const projectId = options["--project"];
|
|
1576
|
+
const commitSha = options["--commit"];
|
|
1577
|
+
if (projectId === undefined || commitSha === undefined) throw new InvalidCommandError();
|
|
1469
1578
|
return {
|
|
1470
1579
|
kind: "plan",
|
|
1471
|
-
projectId
|
|
1472
|
-
commitSha
|
|
1580
|
+
projectId,
|
|
1581
|
+
commitSha,
|
|
1582
|
+
environment: deploymentEnvironment(options["--environment"]),
|
|
1473
1583
|
credentialStore,
|
|
1474
1584
|
};
|
|
1475
1585
|
}
|
|
1476
1586
|
if (first === "deploy") {
|
|
1477
1587
|
if (!yes || follow || stdin) throw new InvalidCommandError();
|
|
1478
|
-
const options =
|
|
1588
|
+
const options = parseOptionalOptions(values, [
|
|
1589
|
+
"--project",
|
|
1590
|
+
"--plan-id",
|
|
1591
|
+
"--commit",
|
|
1592
|
+
"--environment",
|
|
1593
|
+
"--idempotency-key",
|
|
1594
|
+
]);
|
|
1595
|
+
const projectId = options["--project"];
|
|
1596
|
+
const idempotencyKey = options["--idempotency-key"];
|
|
1597
|
+
const planId = options["--plan-id"] ?? null;
|
|
1598
|
+
const commitSha = options["--commit"] ?? null;
|
|
1599
|
+
// A plan already names its environment, so --environment only accompanies --commit.
|
|
1600
|
+
if (
|
|
1601
|
+
projectId === undefined ||
|
|
1602
|
+
idempotencyKey === undefined ||
|
|
1603
|
+
(planId === null) === (commitSha === null) ||
|
|
1604
|
+
(planId !== null && options["--environment"] !== undefined)
|
|
1605
|
+
)
|
|
1606
|
+
throw new InvalidCommandError();
|
|
1479
1607
|
return {
|
|
1480
1608
|
kind: "deploy",
|
|
1481
|
-
projectId
|
|
1482
|
-
planId
|
|
1483
|
-
|
|
1609
|
+
projectId,
|
|
1610
|
+
planId,
|
|
1611
|
+
commitSha,
|
|
1612
|
+
environment: deploymentEnvironment(options["--environment"]),
|
|
1613
|
+
idempotencyKey,
|
|
1484
1614
|
yes,
|
|
1485
1615
|
wait,
|
|
1486
1616
|
credentialStore,
|
|
@@ -1752,10 +1882,12 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
1752
1882
|
throw new InvalidCommandError();
|
|
1753
1883
|
}
|
|
1754
1884
|
if (first === "mail") {
|
|
1755
|
-
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1756
1885
|
const area = values.shift();
|
|
1757
1886
|
const sub =
|
|
1758
1887
|
area === "webhook" || area === "messages" || area === "domain" ? values.shift() : undefined;
|
|
1888
|
+
// Only retiring the mail domain is destructive, and it requires --yes.
|
|
1889
|
+
const retiring = area === "domain" && sub === "delete";
|
|
1890
|
+
if (yes !== retiring || wait || follow || stdin) throw new InvalidCommandError();
|
|
1759
1891
|
const options = parseOptionalOptions(values, [
|
|
1760
1892
|
"--project",
|
|
1761
1893
|
"--environment",
|
|
@@ -1792,6 +1924,13 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
1792
1924
|
} else if (area === "status" || (area === "domain" && sub === "status")) {
|
|
1793
1925
|
request = { ...scope, action: "status" };
|
|
1794
1926
|
label = area === "domain" ? "mail domain status" : "mail status";
|
|
1927
|
+
} else if (retiring) {
|
|
1928
|
+
request = {
|
|
1929
|
+
...scope,
|
|
1930
|
+
action: "domain_delete",
|
|
1931
|
+
idempotencyKey: required("--idempotency-key"),
|
|
1932
|
+
};
|
|
1933
|
+
label = "mail domain delete";
|
|
1795
1934
|
} else if (area === "webhook" && sub === "set") {
|
|
1796
1935
|
request = {
|
|
1797
1936
|
...scope,
|
|
@@ -1857,6 +1996,12 @@ const parseOptions = <T extends string>(
|
|
|
1857
1996
|
return result;
|
|
1858
1997
|
};
|
|
1859
1998
|
|
|
1999
|
+
const deploymentEnvironment = (value: string | undefined): "dev" | "prod" => {
|
|
2000
|
+
if (value === undefined || value === "dev") return "dev";
|
|
2001
|
+
if (value === "prod") return "prod";
|
|
2002
|
+
throw new InvalidCommandError();
|
|
2003
|
+
};
|
|
2004
|
+
|
|
1860
2005
|
const parseOptionalOptions = <T extends string>(
|
|
1861
2006
|
values: readonly string[],
|
|
1862
2007
|
allowed: readonly T[],
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
} from "./credential-store.js";
|
|
45
45
|
import { GeneratedSdkProductApi, type ProductApi } from "./product-api.js";
|
|
46
46
|
import { workspaceOf, type WorkspaceSession } from "./workspace.js";
|
|
47
|
-
import { OwnerOnlyProjectLinkStore } from "./project-link-store.js";
|
|
47
|
+
import { OwnerOnlyProjectLinkStore, readLinkedProjectId } from "./project-link-store.js";
|
|
48
48
|
import {
|
|
49
49
|
initializeRepository,
|
|
50
50
|
RepositoryInitBlockedError,
|
|
@@ -67,6 +67,8 @@ export interface ProductCliIo {
|
|
|
67
67
|
|
|
68
68
|
export interface ProductCliCompositionOverrides {
|
|
69
69
|
readonly credentialStore?: CredentialStore;
|
|
70
|
+
/** Directory whose project link supplies a missing --project; defaults to the process directory. */
|
|
71
|
+
readonly workingDirectory?: string;
|
|
70
72
|
readonly readSecretValue?: (signal: AbortSignal) => Promise<string>;
|
|
71
73
|
}
|
|
72
74
|
|
|
@@ -221,7 +223,16 @@ export const runProductionCli = async (
|
|
|
221
223
|
const diagnostics = new StderrDiagnosticSink(io);
|
|
222
224
|
let command: ProductCliCommand;
|
|
223
225
|
try {
|
|
224
|
-
|
|
226
|
+
const parsed = await parseWithLinkedProject(argv, async () => {
|
|
227
|
+
const profile = resolveProductCliEnvironment(environment);
|
|
228
|
+
return readLinkedProjectId(
|
|
229
|
+
join(overrides.workingDirectory ?? process.cwd(), profile.linkDirectory),
|
|
230
|
+
profile.apiOrigin,
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
command = parsed.command;
|
|
234
|
+
if (parsed.linkedProjectId !== null)
|
|
235
|
+
io.writeStderr(`ohmyhost: using linked project ${parsed.linkedProjectId}\n`);
|
|
225
236
|
} catch (error) {
|
|
226
237
|
const result =
|
|
227
238
|
error instanceof InvalidCommandError
|
|
@@ -344,7 +355,10 @@ export const runProductionCli = async (
|
|
|
344
355
|
signal,
|
|
345
356
|
},
|
|
346
357
|
),
|
|
347
|
-
projectLinkStore: new OwnerOnlyProjectLinkStore(
|
|
358
|
+
projectLinkStore: new OwnerOnlyProjectLinkStore(
|
|
359
|
+
join(process.cwd(), profile.linkDirectory),
|
|
360
|
+
process.cwd(),
|
|
361
|
+
),
|
|
348
362
|
productApiOrigin: profile.apiOrigin,
|
|
349
363
|
tokenRefresher: new PublicWorkosTokenRefresher(environment),
|
|
350
364
|
tokenRevoker: lifecycle,
|
|
@@ -588,6 +602,36 @@ export class GeneratedSdkSessionRevoker implements TokenRevoker {
|
|
|
588
602
|
}
|
|
589
603
|
}
|
|
590
604
|
|
|
605
|
+
/**
|
|
606
|
+
* A command run inside a linked directory may omit --project; the link supplies it. The
|
|
607
|
+
* link is read only when the command needs a project it did not name.
|
|
608
|
+
*/
|
|
609
|
+
export async function parseWithLinkedProject(
|
|
610
|
+
argv: readonly string[],
|
|
611
|
+
linkedProjectId: () => Promise<string | null>,
|
|
612
|
+
): Promise<{ readonly command: ProductCliCommand; readonly linkedProjectId: string | null }> {
|
|
613
|
+
try {
|
|
614
|
+
return { command: parseProductCliCommand(argv), linkedProjectId: null };
|
|
615
|
+
} catch (error) {
|
|
616
|
+
if (!(error instanceof InvalidCommandError) || argv.includes("--project")) throw error;
|
|
617
|
+
let projectId: string | null;
|
|
618
|
+
try {
|
|
619
|
+
projectId = await linkedProjectId();
|
|
620
|
+
} catch {
|
|
621
|
+
projectId = null;
|
|
622
|
+
}
|
|
623
|
+
if (projectId === null) throw error;
|
|
624
|
+
try {
|
|
625
|
+
return {
|
|
626
|
+
command: parseProductCliCommand([...argv, "--project", projectId]),
|
|
627
|
+
linkedProjectId: projectId,
|
|
628
|
+
};
|
|
629
|
+
} catch {
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
591
635
|
class StderrDiagnosticSink implements DiagnosticSink {
|
|
592
636
|
public constructor(private readonly io: ProductCliIo) {}
|
|
593
637
|
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
type UserApiKeyCreation,
|
|
25
25
|
type UserApiKeyPage,
|
|
26
26
|
} from "@ohmyhost/contracts/user-api-keys";
|
|
27
|
-
import { parseFeedbackReceipt } from "@ohmyhost/contracts/feedback";
|
|
27
|
+
import { parseFeedbackReceipt, parseFeedbackStatus } from "@ohmyhost/contracts/feedback";
|
|
28
28
|
import {
|
|
29
29
|
getProjectContext,
|
|
30
30
|
setProjectNotes,
|
|
@@ -57,9 +57,14 @@ import {
|
|
|
57
57
|
configureProjectSourceAutoDeploy,
|
|
58
58
|
createCloudflareDnsAuthorization,
|
|
59
59
|
createProjectDevAccessTicket,
|
|
60
|
+
ensureProjectDevShareLink,
|
|
61
|
+
rotateProjectDevShareLink,
|
|
62
|
+
revokeProjectDevShareLink,
|
|
63
|
+
setProjectDevAccessMode,
|
|
60
64
|
createDeployment,
|
|
61
65
|
createProject,
|
|
62
66
|
submitFeedback,
|
|
67
|
+
getFeedback,
|
|
63
68
|
createOrganization,
|
|
64
69
|
type Organization,
|
|
65
70
|
type GithubConnectionAuthorization,
|
|
@@ -131,6 +136,7 @@ export interface ProjectCreateInput {
|
|
|
131
136
|
readonly organizationId: string;
|
|
132
137
|
readonly name: string;
|
|
133
138
|
readonly dataMode?: "shared" | "isolated";
|
|
139
|
+
readonly devAccessMode?: "protected" | "public";
|
|
134
140
|
readonly region?: "us" | "eu";
|
|
135
141
|
readonly idempotencyKey: string;
|
|
136
142
|
}
|
|
@@ -257,10 +263,28 @@ export interface ProductApi {
|
|
|
257
263
|
readonly idempotencyKey: string;
|
|
258
264
|
}): Promise<PaidDomain>;
|
|
259
265
|
createDevAccessTicket(projectId: string): Promise<DevAccessTicket>;
|
|
266
|
+
ensureDevShareLink(projectId: string): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
|
|
267
|
+
rotateDevShareLink(
|
|
268
|
+
projectId: string,
|
|
269
|
+
idempotencyKey: string,
|
|
270
|
+
): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
|
|
271
|
+
revokeDevShareLink(
|
|
272
|
+
projectId: string,
|
|
273
|
+
idempotencyKey: string,
|
|
274
|
+
): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
|
|
275
|
+
setDevAccessMode(
|
|
276
|
+
projectId: string,
|
|
277
|
+
mode: "protected" | "public",
|
|
278
|
+
idempotencyKey: string,
|
|
279
|
+
): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
|
|
260
280
|
submitFeedback(input: {
|
|
261
281
|
report: import("@ohmyhost/sdk-ts").FeedbackSubmission;
|
|
262
282
|
idempotencyKey: string;
|
|
263
283
|
}): Promise<import("@ohmyhost/sdk-ts").FeedbackReceipt>;
|
|
284
|
+
getFeedback(
|
|
285
|
+
feedbackId: string,
|
|
286
|
+
cursor?: string,
|
|
287
|
+
): Promise<import("@ohmyhost/sdk-ts").FeedbackStatus>;
|
|
264
288
|
createProject(input: ProjectCreateInput): Promise<Operation>;
|
|
265
289
|
getOperation(operationId: string): Promise<Operation>;
|
|
266
290
|
reconcileOperation(input: {
|
|
@@ -298,6 +322,7 @@ export interface ProductApi {
|
|
|
298
322
|
planDeployment(input: {
|
|
299
323
|
readonly projectId: string;
|
|
300
324
|
readonly commitSha: string;
|
|
325
|
+
readonly environment: "dev" | "prod";
|
|
301
326
|
}): Promise<DeploymentPlan>;
|
|
302
327
|
createDeployment(input: {
|
|
303
328
|
readonly projectId: string;
|
|
@@ -736,6 +761,35 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
736
761
|
);
|
|
737
762
|
}
|
|
738
763
|
|
|
764
|
+
public async ensureDevShareLink(projectId: string) {
|
|
765
|
+
return ensureProjectDevShareLink({ project_id: projectId }, { client: this.#client });
|
|
766
|
+
}
|
|
767
|
+
public async rotateDevShareLink(projectId: string, idempotencyKey: string) {
|
|
768
|
+
return rotateProjectDevShareLink(
|
|
769
|
+
{ project_id: projectId, "Idempotency-Key": idempotencyKey },
|
|
770
|
+
{ client: this.#client },
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
public async revokeDevShareLink(projectId: string, idempotencyKey: string) {
|
|
774
|
+
return revokeProjectDevShareLink(
|
|
775
|
+
{ project_id: projectId, "Idempotency-Key": idempotencyKey },
|
|
776
|
+
{ client: this.#client },
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
public async setDevAccessMode(
|
|
780
|
+
projectId: string,
|
|
781
|
+
mode: "protected" | "public",
|
|
782
|
+
idempotencyKey: string,
|
|
783
|
+
) {
|
|
784
|
+
return setProjectDevAccessMode(
|
|
785
|
+
{
|
|
786
|
+
project_id: projectId,
|
|
787
|
+
"Idempotency-Key": idempotencyKey,
|
|
788
|
+
setDevAccessModeRequest: { mode },
|
|
789
|
+
},
|
|
790
|
+
{ client: this.#client },
|
|
791
|
+
);
|
|
792
|
+
}
|
|
739
793
|
public async createDevAccessTicket(projectId: string): Promise<DevAccessTicket> {
|
|
740
794
|
return createProjectDevAccessTicket({ project_id: projectId }, { client: this.#client });
|
|
741
795
|
}
|
|
@@ -752,6 +806,18 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
752
806
|
);
|
|
753
807
|
}
|
|
754
808
|
|
|
809
|
+
public async getFeedback(
|
|
810
|
+
feedbackId: string,
|
|
811
|
+
cursor?: string,
|
|
812
|
+
): Promise<import("@ohmyhost/sdk-ts").FeedbackStatus> {
|
|
813
|
+
return parseFeedbackStatus(
|
|
814
|
+
await getFeedback(
|
|
815
|
+
{ feedback_id: feedbackId, ...(cursor === undefined ? {} : { cursor }) },
|
|
816
|
+
{ client: this.#client },
|
|
817
|
+
),
|
|
818
|
+
) as import("@ohmyhost/sdk-ts").FeedbackStatus;
|
|
819
|
+
}
|
|
820
|
+
|
|
755
821
|
public async createProject(input: ProjectCreateInput): Promise<Operation> {
|
|
756
822
|
return createProject(
|
|
757
823
|
{
|
|
@@ -760,6 +826,7 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
760
826
|
organization_id: input.organizationId,
|
|
761
827
|
name: input.name,
|
|
762
828
|
...(input.dataMode === undefined ? {} : { data_mode: input.dataMode }),
|
|
829
|
+
...(input.devAccessMode === undefined ? {} : { dev_access_mode: input.devAccessMode }),
|
|
763
830
|
...(input.region === undefined ? {} : { region: input.region }),
|
|
764
831
|
},
|
|
765
832
|
},
|
|
@@ -880,9 +947,13 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
880
947
|
public async planDeployment(input: {
|
|
881
948
|
readonly projectId: string;
|
|
882
949
|
readonly commitSha: string;
|
|
950
|
+
readonly environment: "dev" | "prod";
|
|
883
951
|
}): Promise<DeploymentPlan> {
|
|
884
952
|
return planDeployment(
|
|
885
|
-
{
|
|
953
|
+
{
|
|
954
|
+
project_id: input.projectId,
|
|
955
|
+
planDeploymentRequest: { commit_sha: input.commitSha, environment: input.environment },
|
|
956
|
+
},
|
|
886
957
|
{ client: this.#client },
|
|
887
958
|
);
|
|
888
959
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmod, mkdir, rename, writeFile } from "node:fs/promises";
|
|
1
|
+
import { appendFile, chmod, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
export interface ProjectLinkMetadata {
|
|
@@ -14,8 +14,32 @@ export interface ProjectLinkStore {
|
|
|
14
14
|
save(metadata: ProjectLinkMetadata): Promise<void>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** The project this directory was linked to for this API, or null when there is no usable link. */
|
|
18
|
+
export async function readLinkedProjectId(
|
|
19
|
+
directory: string,
|
|
20
|
+
apiOrigin: string,
|
|
21
|
+
): Promise<string | null> {
|
|
22
|
+
try {
|
|
23
|
+
const link = JSON.parse(await readFile(join(directory, "link.json"), "utf8")) as unknown;
|
|
24
|
+
if (typeof link !== "object" || link === null) return null;
|
|
25
|
+
const { version, api_origin, project_id } = link as Record<string, unknown>;
|
|
26
|
+
return version === 1 &&
|
|
27
|
+
api_origin === apiOrigin &&
|
|
28
|
+
typeof project_id === "string" &&
|
|
29
|
+
/^[0-9A-HJKMNP-TV-Z]{26}$/u.test(project_id)
|
|
30
|
+
? project_id
|
|
31
|
+
: null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
export class OwnerOnlyProjectLinkStore implements ProjectLinkStore {
|
|
18
|
-
|
|
38
|
+
/** repositoryRoot: when it is a Git working tree, the link directory is kept out of Git. */
|
|
39
|
+
public constructor(
|
|
40
|
+
private readonly directory: string,
|
|
41
|
+
private readonly repositoryRoot?: string,
|
|
42
|
+
) {}
|
|
19
43
|
|
|
20
44
|
public async save(metadata: ProjectLinkMetadata): Promise<void> {
|
|
21
45
|
await mkdir(this.directory, { mode: 0o700, recursive: true });
|
|
@@ -26,5 +50,24 @@ export class OwnerOnlyProjectLinkStore implements ProjectLinkStore {
|
|
|
26
50
|
await chmod(temporary, 0o600);
|
|
27
51
|
await rename(temporary, target);
|
|
28
52
|
await chmod(target, 0o600);
|
|
53
|
+
if (this.repositoryRoot !== undefined) await ignoreLinkDirectory(this.repositoryRoot);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function ignoreLinkDirectory(root: string): Promise<void> {
|
|
58
|
+
try {
|
|
59
|
+
await stat(join(root, ".git"));
|
|
60
|
+
} catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const file = join(root, ".gitignore");
|
|
64
|
+
let current = "";
|
|
65
|
+
try {
|
|
66
|
+
current = await readFile(file, "utf8");
|
|
67
|
+
} catch {
|
|
68
|
+
current = "";
|
|
29
69
|
}
|
|
70
|
+
const lines = current.split(/\r?\n/u).map((line) => line.trim());
|
|
71
|
+
if (lines.some((line) => /^\/?\.ohmyhost\/?$/u.test(line))) return;
|
|
72
|
+
await appendFile(file, `${current === "" || current.endsWith("\n") ? "" : "\n"}/.ohmyhost/\n`);
|
|
30
73
|
}
|
|
@@ -73,9 +73,9 @@ const STORAGE_RUNTIME_BINDINGS = Object.freeze([
|
|
|
73
73
|
"OHMYHOST_STORAGE_GATEWAY_URL",
|
|
74
74
|
"OHMYHOST_STORAGE_KEY",
|
|
75
75
|
]);
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
const CUSTOMER_RUNTIME_SOURCE = `
|
|
76
|
+
// The runtime release is published unchanged as `@amerged/ohmyhost-runtime`; this pinned alias keeps
|
|
77
|
+
// its import name and, unlike the website archive each release removes, stays installable.
|
|
78
|
+
const CUSTOMER_RUNTIME_SOURCE = `npm:@amerged/ohmyhost-runtime@${cliPackage.version}`;
|
|
79
79
|
const SCHEDULED_HANDLER_PATTERN = /\bscheduled\s*(?:\(|:|,|\})/u;
|
|
80
80
|
// The platform imports the module's default export; handlers that are only named exports are
|
|
81
81
|
// never invoked, so init blocks them before a build or a runtime rejection would.
|
|
@@ -139,7 +139,7 @@ export interface RepositoryInitInventory {
|
|
|
139
139
|
|
|
140
140
|
export interface RepositoryInitRuntimePackages {
|
|
141
141
|
readonly betterAuth?: "1.7.1";
|
|
142
|
-
/** Exact install source:
|
|
142
|
+
/** Exact install source: an npm alias pinned to the published runtime of this client version. */
|
|
143
143
|
readonly customerRuntime?: string;
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -339,7 +339,8 @@ export async function initializeRepository(
|
|
|
339
339
|
}),
|
|
340
340
|
});
|
|
341
341
|
const targetBetterAuth = existingConfiguration?.auth?.provider === "better-auth";
|
|
342
|
-
|
|
342
|
+
// Mail stays an explicit choice: Better Auth or a detected mail package never enables it.
|
|
343
|
+
const targetMail = existingConfiguration?.mail?.enabled === true;
|
|
343
344
|
const targetStorage = r2 || supabaseStorage;
|
|
344
345
|
const configuredCompanionRequired =
|
|
345
346
|
existingConfiguration !== null &&
|