run402 4.7.0 → 4.9.0
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/cli.mjs +6 -6
- package/lib/admin.mjs +17 -24
- package/lib/apps.mjs +68 -50
- package/lib/argparse.mjs +68 -1
- package/lib/assets.mjs +6 -8
- package/lib/billing.mjs +15 -11
- package/lib/branches.mjs +20 -17
- package/lib/ci.mjs +2 -0
- package/lib/cloud.mjs +25 -18
- package/lib/command-manifest.mjs +352 -0
- package/lib/contracts.mjs +47 -10
- package/lib/deploy-v2.mjs +21 -8
- package/lib/functions.mjs +99 -54
- package/lib/grants.mjs +39 -20
- package/lib/jobs.mjs +15 -9
- package/lib/notifications.mjs +431 -11
- package/lib/org.mjs +86 -48
- package/lib/projects.mjs +67 -38
- package/lib/secrets.mjs +98 -40
- package/lib/snapshots.mjs +20 -18
- package/lib/wallets.mjs +4 -3
- package/lib/webhook-secret.mjs +6 -0
- package/package.json +1 -1
- package/sdk/dist/namespaces/admin.d.ts +254 -4
- package/sdk/dist/namespaces/admin.d.ts.map +1 -1
- package/sdk/dist/namespaces/admin.js +134 -4
- package/sdk/dist/namespaces/admin.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts +2 -1
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +8 -12
- package/sdk/dist/namespaces/projects.js.map +1 -1
package/lib/secrets.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import { getSdk } from "./sdk.mjs";
|
|
3
3
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
4
|
-
import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs, validateRegularFile } from "./argparse.mjs";
|
|
4
|
+
import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs, resolveProjectSelector, validateRegularFile } from "./argparse.mjs";
|
|
5
5
|
import { editRequestAction } from "./next-actions.mjs";
|
|
6
6
|
|
|
7
7
|
const HELP = `run402 secrets — Manage project secrets
|
|
@@ -10,18 +10,25 @@ Usage:
|
|
|
10
10
|
run402 secrets <subcommand> [args...]
|
|
11
11
|
|
|
12
12
|
Subcommands:
|
|
13
|
-
set <
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
set <key> (--value <v> | --file <path> | --stdin) [--project <id>]
|
|
14
|
+
Set a secret on a project
|
|
15
|
+
list [--project <id>] List all secrets for a project
|
|
16
|
+
delete <key> [--project <id>] Delete a secret from a project
|
|
17
|
+
|
|
18
|
+
Legacy (still supported):
|
|
19
|
+
run402 secrets set <prj_id> <key> <value>
|
|
20
|
+
run402 secrets list <prj_id>
|
|
21
|
+
run402 secrets delete <prj_id> <key>
|
|
16
22
|
|
|
17
23
|
Examples:
|
|
18
|
-
printf %s "$STRIPE_KEY" | run402 secrets set
|
|
19
|
-
run402 secrets set
|
|
20
|
-
run402 secrets set
|
|
21
|
-
run402 secrets list prj_abc123
|
|
22
|
-
run402 secrets delete prj_abc123
|
|
24
|
+
printf %s "$STRIPE_KEY" | run402 secrets set STRIPE_KEY --stdin --project prj_abc123
|
|
25
|
+
run402 secrets set STRIPE_KEY --file ./.secrets/stripe-key --project prj_abc123
|
|
26
|
+
run402 secrets set TLS_CERT --file cert.pem
|
|
27
|
+
run402 secrets list --project prj_abc123
|
|
28
|
+
run402 secrets delete STRIPE_KEY --project prj_abc123
|
|
23
29
|
|
|
24
30
|
Notes:
|
|
31
|
+
- --project defaults to the active project ('run402 projects use')
|
|
25
32
|
- Secrets are injected as process.env in serverless functions
|
|
26
33
|
- Values are write-only — list returns keys and timestamps only
|
|
27
34
|
- Deploy manifests should declare existing keys with secrets.require; never put values in deploy specs
|
|
@@ -31,68 +38,86 @@ const SUB_HELP = {
|
|
|
31
38
|
set: `run402 secrets set — Set a secret on a project
|
|
32
39
|
|
|
33
40
|
Usage:
|
|
34
|
-
run402 secrets set <
|
|
35
|
-
run402 secrets set <
|
|
36
|
-
run402 secrets set <
|
|
41
|
+
run402 secrets set <key> --value <v> [--project <id>]
|
|
42
|
+
run402 secrets set <key> --file <path> [--project <id>]
|
|
43
|
+
run402 secrets set <key> --stdin [--project <id>]
|
|
44
|
+
|
|
45
|
+
Legacy (still supported):
|
|
46
|
+
run402 secrets set <prj_id> <key> <value>
|
|
47
|
+
run402 secrets set <key> <value>
|
|
37
48
|
|
|
38
49
|
Arguments:
|
|
39
|
-
<id> Project ID (from 'run402 projects list')
|
|
40
50
|
<key> Secret key name (exposed as process.env.<key>)
|
|
41
|
-
<value> Inline secret value (omit if using --file or --stdin)
|
|
42
51
|
|
|
43
52
|
Options:
|
|
53
|
+
--project <id> Project ID (defaults to the active project)
|
|
54
|
+
--value <v> Inline secret value (alternative to the legacy positional)
|
|
44
55
|
--file <path> Read the secret value from a file instead of inline
|
|
45
56
|
Use --file - or --file /dev/stdin to read from stdin
|
|
46
57
|
--stdin Read the secret value from stdin until EOF
|
|
47
58
|
|
|
48
59
|
Notes:
|
|
60
|
+
- Provide exactly one value source: --value, --file, --stdin, or the legacy inline positional
|
|
49
61
|
- Secrets are injected as process.env in serverless functions
|
|
50
62
|
- Values are write-only; 'list' cannot verify values by hash
|
|
51
63
|
- Prefer --stdin or --file for real secrets so values do not land in shell history
|
|
52
64
|
|
|
53
65
|
Examples:
|
|
54
|
-
printf %s "$STRIPE_KEY" | run402 secrets set
|
|
55
|
-
cat ./.secrets/stripe-key | run402 secrets set
|
|
56
|
-
run402 secrets set
|
|
57
|
-
run402 secrets set
|
|
66
|
+
printf %s "$STRIPE_KEY" | run402 secrets set STRIPE_KEY --stdin --project prj_abc123
|
|
67
|
+
cat ./.secrets/stripe-key | run402 secrets set STRIPE_KEY --file - --project prj_abc123
|
|
68
|
+
run402 secrets set STRIPE_KEY --file ./.secrets/stripe-key
|
|
69
|
+
run402 secrets set TLS_CERT --file cert.pem --project prj_abc123
|
|
58
70
|
`,
|
|
59
71
|
list: `run402 secrets list — List all secrets for a project
|
|
60
72
|
|
|
61
73
|
Usage:
|
|
62
|
-
run402 secrets list <id>
|
|
74
|
+
run402 secrets list [--project <id>]
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
Legacy (still supported):
|
|
77
|
+
run402 secrets list <prj_id>
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
--project <id> Project ID (defaults to the active project)
|
|
66
81
|
|
|
67
82
|
Notes:
|
|
68
83
|
- Returns secret keys and timestamps only; raw values and value-derived hashes are never returned
|
|
69
84
|
|
|
70
85
|
Examples:
|
|
71
|
-
run402 secrets list prj_abc123
|
|
86
|
+
run402 secrets list --project prj_abc123
|
|
72
87
|
`,
|
|
73
88
|
delete: `run402 secrets delete — Delete a secret from a project
|
|
74
89
|
|
|
75
90
|
Usage:
|
|
76
|
-
run402 secrets delete <
|
|
91
|
+
run402 secrets delete <key> [--project <id>]
|
|
92
|
+
|
|
93
|
+
Legacy (still supported):
|
|
94
|
+
run402 secrets delete <prj_id> <key>
|
|
77
95
|
|
|
78
96
|
Arguments:
|
|
79
|
-
<id> Project ID (from 'run402 projects list')
|
|
80
97
|
<key> Secret key name to remove
|
|
81
98
|
|
|
99
|
+
Options:
|
|
100
|
+
--project <id> Project ID (defaults to the active project)
|
|
101
|
+
|
|
82
102
|
Examples:
|
|
83
|
-
run402 secrets delete prj_abc123
|
|
103
|
+
run402 secrets delete STRIPE_KEY --project prj_abc123
|
|
84
104
|
`,
|
|
85
105
|
};
|
|
86
106
|
|
|
107
|
+
const SET_VALUE_FLAGS = ["--file", "--value", "--project"];
|
|
108
|
+
|
|
87
109
|
export function readSecretValueForSet(parsedArgs, values, readers = {}) {
|
|
88
110
|
const readStdin = readers.readStdin ?? (() => readFileSync(0, "utf-8"));
|
|
89
111
|
const readFile = readers.readFile ?? ((path) => readFileSync(path, "utf-8"));
|
|
90
112
|
const validateFile = readers.validateFile ?? validateRegularFile;
|
|
91
113
|
const file = flagValue(parsedArgs, "--file");
|
|
114
|
+
const valueFlagPresent = parsedArgs.includes("--value");
|
|
115
|
+
const valueFlag = valueFlagPresent ? flagValue(parsedArgs, "--value") : null;
|
|
92
116
|
const stdinRequested = parsedArgs.includes("--stdin");
|
|
93
117
|
const stdinFile = isStdinAlias(file);
|
|
94
118
|
const sources = [];
|
|
95
119
|
if (values.length === 1) sources.push("inline");
|
|
120
|
+
if (valueFlagPresent) sources.push("--value");
|
|
96
121
|
if (file) sources.push(stdinFile ? "--file stdin" : "--file");
|
|
97
122
|
if (stdinRequested) sources.push("--stdin");
|
|
98
123
|
|
|
@@ -101,22 +126,31 @@ export function readSecretValueForSet(parsedArgs, values, readers = {}) {
|
|
|
101
126
|
code: "BAD_USAGE",
|
|
102
127
|
message: "Provide exactly one secret value source.",
|
|
103
128
|
details: { sources },
|
|
104
|
-
hint: "Use one of:
|
|
129
|
+
hint: "Use one of: --value <v>, --file <path>, --stdin, or an inline value.",
|
|
105
130
|
});
|
|
106
131
|
}
|
|
107
132
|
if (file && !stdinFile) validateFile(file, "--file");
|
|
108
133
|
|
|
109
134
|
if (stdinRequested || stdinFile) return readStdin();
|
|
135
|
+
if (valueFlagPresent) return valueFlag;
|
|
110
136
|
if (file) return readFile(file);
|
|
111
137
|
if (values.length === 1) return values[0];
|
|
112
138
|
return undefined;
|
|
113
139
|
}
|
|
114
140
|
|
|
115
|
-
async function set(projectId,
|
|
141
|
+
async function set(projectId, args = []) {
|
|
116
142
|
const parsedArgs = normalizeArgv(args);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
143
|
+
assertKnownFlags(parsedArgs, [...SET_VALUE_FLAGS, "--stdin", "--help", "-h"], SET_VALUE_FLAGS);
|
|
144
|
+
const positionals = positionalArgs(parsedArgs, SET_VALUE_FLAGS);
|
|
145
|
+
const key = positionals[0];
|
|
146
|
+
if (!key) {
|
|
147
|
+
fail({
|
|
148
|
+
code: "BAD_USAGE",
|
|
149
|
+
message: "Missing <key>.",
|
|
150
|
+
hint: "run402 secrets set <key> --value <v> [--project <id>]",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const values = positionals.slice(1);
|
|
120
154
|
if (values.length > 1) {
|
|
121
155
|
fail({ code: "BAD_USAGE", message: `Unexpected argument for secrets set: ${values[1]}` });
|
|
122
156
|
}
|
|
@@ -172,8 +206,8 @@ function failMissingStdin() {
|
|
|
172
206
|
|
|
173
207
|
async function list(projectId, args = []) {
|
|
174
208
|
const parsedArgs = normalizeArgv(args);
|
|
175
|
-
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
176
|
-
const extra = positionalArgs(parsedArgs);
|
|
209
|
+
assertKnownFlags(parsedArgs, ["--project", "--help", "-h"], ["--project"]);
|
|
210
|
+
const extra = positionalArgs(parsedArgs, ["--project"]);
|
|
177
211
|
if (extra.length > 0) {
|
|
178
212
|
fail({ code: "BAD_USAGE", message: `Unexpected argument for secrets list: ${extra[0]}` });
|
|
179
213
|
}
|
|
@@ -192,12 +226,20 @@ async function list(projectId, args = []) {
|
|
|
192
226
|
}
|
|
193
227
|
}
|
|
194
228
|
|
|
195
|
-
async function deleteSecret(projectId,
|
|
229
|
+
async function deleteSecret(projectId, args = []) {
|
|
196
230
|
const parsedArgs = normalizeArgv(args);
|
|
197
|
-
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
231
|
+
assertKnownFlags(parsedArgs, ["--project", "--help", "-h"], ["--project"]);
|
|
232
|
+
const positionals = positionalArgs(parsedArgs, ["--project"]);
|
|
233
|
+
const key = positionals[0];
|
|
234
|
+
if (!key) {
|
|
235
|
+
fail({
|
|
236
|
+
code: "BAD_USAGE",
|
|
237
|
+
message: "Missing <key>.",
|
|
238
|
+
hint: "run402 secrets delete <key> [--project <id>]",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (positionals.length > 1) {
|
|
242
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for secrets delete: ${positionals[1]}` });
|
|
201
243
|
}
|
|
202
244
|
try {
|
|
203
245
|
await getSdk().secrets.delete(projectId, key);
|
|
@@ -213,10 +255,26 @@ export async function run(sub, args) {
|
|
|
213
255
|
console.log(SUB_HELP[sub] || HELP);
|
|
214
256
|
process.exit(0);
|
|
215
257
|
}
|
|
258
|
+
if (!["set", "list", "delete"].includes(sub)) {
|
|
259
|
+
fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown secrets subcommand: ${sub}`, hint: "Run `run402 secrets --help` for usage.", details: { command: "secrets", subcommand: sub } });
|
|
260
|
+
}
|
|
261
|
+
const parsed = normalizeArgv(Array.isArray(args) ? args : []);
|
|
216
262
|
switch (sub) {
|
|
217
|
-
case "set":
|
|
218
|
-
|
|
219
|
-
|
|
263
|
+
case "set": {
|
|
264
|
+
const { projectId, rest } = resolveProjectSelector(parsed, { valueFlags: SET_VALUE_FLAGS });
|
|
265
|
+
await set(projectId, rest);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case "list": {
|
|
269
|
+
const { projectId, rest } = resolveProjectSelector(parsed, { valueFlags: ["--project"] });
|
|
270
|
+
await list(projectId, rest);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "delete": {
|
|
274
|
+
const { projectId, rest } = resolveProjectSelector(parsed, { valueFlags: ["--project"] });
|
|
275
|
+
await deleteSecret(projectId, rest);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
220
278
|
default:
|
|
221
279
|
fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown secrets subcommand: ${sub}`, hint: "Run `run402 secrets --help` for usage.", details: { command: "secrets", subcommand: sub } });
|
|
222
280
|
}
|
package/lib/snapshots.mjs
CHANGED
|
@@ -8,24 +8,28 @@ import {
|
|
|
8
8
|
normalizeArgv,
|
|
9
9
|
parseIntegerFlag,
|
|
10
10
|
positionalArgs,
|
|
11
|
+
resolveProjectSelector,
|
|
11
12
|
} from "./argparse.mjs";
|
|
12
|
-
import { resolveProjectId } from "./config.mjs";
|
|
13
13
|
|
|
14
14
|
const HELP = `run402 snapshots — Project database restore points
|
|
15
15
|
|
|
16
16
|
Usage:
|
|
17
|
-
run402 snapshots create [project
|
|
18
|
-
run402 snapshots list [project
|
|
19
|
-
run402 snapshots get
|
|
20
|
-
run402 snapshots restore
|
|
21
|
-
run402 snapshots delete
|
|
17
|
+
run402 snapshots create [--project <id>] [--json]
|
|
18
|
+
run402 snapshots list [--project <id>] [--kind <kind>] [--limit <n>] [--after <cursor>] [--json]
|
|
19
|
+
run402 snapshots get <snapshot-id> [--project <id>] [--json]
|
|
20
|
+
run402 snapshots restore <snapshot-id> [--project <id>] [--include-auth] [--confirm <token>] [--json]
|
|
21
|
+
run402 snapshots delete <snapshot-id> [--project <id>] [--json]
|
|
22
|
+
|
|
23
|
+
Legacy (still supported): a leading prj_... positional selects the project,
|
|
24
|
+
e.g. run402 snapshots restore prj_abc123 <snapshot-id>. --project defaults to
|
|
25
|
+
the active project.
|
|
22
26
|
|
|
23
27
|
Restore is a two-step handshake. First call without --confirm to get a
|
|
24
28
|
restore_plan.confirm.token, then re-run with --confirm after reviewing the
|
|
25
29
|
data-loss statement.
|
|
26
30
|
`;
|
|
27
31
|
|
|
28
|
-
const FLAG_VALUES = ["--kind", "--limit", "--after", "--confirm"];
|
|
32
|
+
const FLAG_VALUES = ["--project", "--kind", "--limit", "--after", "--confirm"];
|
|
29
33
|
const FLAGS = new Set([...FLAG_VALUES, "--include-auth", "--json", "--help", "-h"]);
|
|
30
34
|
const SNAPSHOT_KINDS = ["manual", "pre_migration", "pre_restore", "scheduled"];
|
|
31
35
|
|
|
@@ -54,7 +58,7 @@ export async function run(sub, args = []) {
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
async function create(args) {
|
|
57
|
-
const projectId =
|
|
61
|
+
const { projectId } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES });
|
|
58
62
|
try {
|
|
59
63
|
const snapshot = await getSdk().snapshots.create(projectId);
|
|
60
64
|
console.log(JSON.stringify({ ok: snapshot.status === "ready", snapshot }, null, 2));
|
|
@@ -64,7 +68,7 @@ async function create(args) {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
async function list(args) {
|
|
67
|
-
const projectId =
|
|
71
|
+
const { projectId } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES });
|
|
68
72
|
const kind = flagValue(args, "--kind") ?? undefined;
|
|
69
73
|
if (kind !== undefined) assertAllowedValue(kind, SNAPSHOT_KINDS, "--kind");
|
|
70
74
|
const limitFlag = flagValue(args, "--limit");
|
|
@@ -124,14 +128,12 @@ async function deleteSnapshot(args) {
|
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
function resolveOptionalProject(value) {
|
|
128
|
-
if (value && String(value).startsWith("prj_")) return value;
|
|
129
|
-
return resolveProjectId(null);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
131
|
function resolveProjectAndSnapshot(args, usage) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
// Canonical: `<snapshot-id> [--project <id>]`; legacy `<prj_id> <snapshot-id>`
|
|
133
|
+
// still works (requireRestPositional keeps a lone snapshot id that happens to
|
|
134
|
+
// start with prj_ from being eaten as the project selector).
|
|
135
|
+
const { projectId, rest } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES, requireRestPositional: true });
|
|
136
|
+
const pos = positionalArgs(rest, FLAG_VALUES);
|
|
137
|
+
if (pos.length !== 1) fail({ code: "BAD_USAGE", message: `Usage: ${usage}` });
|
|
138
|
+
return { projectId, snapshotId: pos[0] };
|
|
137
139
|
}
|
package/lib/wallets.mjs
CHANGED
|
@@ -42,7 +42,7 @@ Usage:
|
|
|
42
42
|
run402 wallets current Show the resolved active wallet + how it was selected
|
|
43
43
|
run402 wallets new <name> Create a new named wallet (key stays local)
|
|
44
44
|
run402 wallets use <name> Set the global default wallet
|
|
45
|
-
run402 wallets rename <old> <new> Rename a wallet (
|
|
45
|
+
run402 wallets rename <old> --to <new> Rename a wallet (legacy: rename <old> <new>)
|
|
46
46
|
run402 wallets bind [<name>] Write ./.run402.json binding this directory to a wallet
|
|
47
47
|
run402 wallets unbind Remove ./.run402.json
|
|
48
48
|
run402 wallets import <name> --key <path|-> Adopt an existing private key as a named wallet
|
|
@@ -174,9 +174,10 @@ function cmdUse(args) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
async function cmdRename(args) {
|
|
177
|
-
const
|
|
177
|
+
const toFlag = flagVal(args, "--to");
|
|
178
|
+
const positionals = args.filter((a, i) => a && !a.startsWith("-") && args[i - 1] !== "--to");
|
|
178
179
|
const oldName = requireName(positionals[0], "old wallet name");
|
|
179
|
-
const newName = requireName(positionals[1], "new wallet name");
|
|
180
|
+
const newName = requireName(toFlag ?? positionals[1], "new wallet name");
|
|
180
181
|
if (newName === DEFAULT) {
|
|
181
182
|
fail({ code: "BAD_WALLET_NAME", message: "Cannot rename a wallet to the reserved name 'default'.", details: { name: newName } });
|
|
182
183
|
}
|
package/lib/webhook-secret.mjs
CHANGED
|
@@ -27,6 +27,12 @@ export async function run(sub, args = []) {
|
|
|
27
27
|
console.log(HELP);
|
|
28
28
|
process.exit(0);
|
|
29
29
|
}
|
|
30
|
+
// --help/-h anywhere in the argv must short-circuit BEFORE auth/config side
|
|
31
|
+
// effects (the cli-help.test.mjs contract) — same check as notifications.mjs.
|
|
32
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
33
|
+
console.log(HELP);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
30
36
|
if (sub !== "rotate") {
|
|
31
37
|
fail({
|
|
32
38
|
code: "UNKNOWN_SUBCOMMAND",
|
package/package.json
CHANGED
|
@@ -126,9 +126,50 @@ export type NotificationPreferencesPatch = Partial<Omit<NotificationPreferences,
|
|
|
126
126
|
/** Cannot be changed away from "always" — server returns 400. */
|
|
127
127
|
security_events?: "always";
|
|
128
128
|
};
|
|
129
|
+
export interface TestNotificationOptions {
|
|
130
|
+
/**
|
|
131
|
+
* Route the synthetic test event as if it came from the app lane
|
|
132
|
+
* (`project_events.source = 'app'`) or the platform. Defaults to
|
|
133
|
+
* `"platform"` on the gateway when omitted.
|
|
134
|
+
*/
|
|
135
|
+
source?: "app" | "platform";
|
|
136
|
+
/**
|
|
137
|
+
* Synthetic `event_type` override (flat snake_case,
|
|
138
|
+
* `^[a-z][a-z0-9_]{2,63}$`). Use this to exercise a specific routing rule's
|
|
139
|
+
* `event_types` filter precisely. Defaults to the gateway's built-in
|
|
140
|
+
* sample event when omitted.
|
|
141
|
+
*/
|
|
142
|
+
eventType?: string;
|
|
143
|
+
}
|
|
144
|
+
/** One Telegram destination's outcome from a `testNotification()` call —
|
|
145
|
+
* present only when the operator has a routing rule matching the synthetic
|
|
146
|
+
* event. Empty `telegram.destinations` is Faithful (no matching rule), not
|
|
147
|
+
* an error. */
|
|
148
|
+
export interface TestNotificationDestination {
|
|
149
|
+
binding_id: string;
|
|
150
|
+
label: string | null;
|
|
151
|
+
delivered: boolean;
|
|
152
|
+
/** Present on failures. `true` = retryable (429/5xx/timeout/rate-limited);
|
|
153
|
+
* `false` = permanent (bad chat, bot blocked/removed). */
|
|
154
|
+
transient?: boolean;
|
|
155
|
+
description?: string;
|
|
156
|
+
}
|
|
129
157
|
export interface TestNotificationResult {
|
|
130
|
-
status: "queued";
|
|
158
|
+
status: "delivered" | "skipped" | "queued";
|
|
131
159
|
source_event_id: string;
|
|
160
|
+
drained: {
|
|
161
|
+
claimed: number;
|
|
162
|
+
delivered: number;
|
|
163
|
+
skipped: number;
|
|
164
|
+
failed_transient: number;
|
|
165
|
+
failed_permanent: number;
|
|
166
|
+
};
|
|
167
|
+
/** Telegram delivery report for the synthetic event, routed through the
|
|
168
|
+
* operator's normal rules — the full binding + rule + render + send
|
|
169
|
+
* chain, not just email/webhook. */
|
|
170
|
+
telegram: {
|
|
171
|
+
destinations: TestNotificationDestination[];
|
|
172
|
+
};
|
|
132
173
|
note: string;
|
|
133
174
|
}
|
|
134
175
|
export interface RotateWebhookSecretResult {
|
|
@@ -137,6 +178,200 @@ export interface RotateWebhookSecretResult {
|
|
|
137
178
|
grace_window_hours: number;
|
|
138
179
|
note: string;
|
|
139
180
|
}
|
|
181
|
+
export type TelegramBindingStatus = "pending" | "active" | "revoked";
|
|
182
|
+
/**
|
|
183
|
+
* One Telegram binding as returned by `GET /agent/v1/notifications/channels`
|
|
184
|
+
* (`telegram[]`) and `r.admin.channels.list()`. Never carries the raw
|
|
185
|
+
* connect code — codes are single-use, hashed at rest, and returned only
|
|
186
|
+
* once, inline in {@link ConnectTelegramResult}.
|
|
187
|
+
*/
|
|
188
|
+
export interface TelegramChannelBinding {
|
|
189
|
+
id: string;
|
|
190
|
+
recipient_email: string;
|
|
191
|
+
status: TelegramBindingStatus;
|
|
192
|
+
chat_id: number | null;
|
|
193
|
+
chat_type: string | null;
|
|
194
|
+
chat_title: string | null;
|
|
195
|
+
label: string | null;
|
|
196
|
+
consecutive_failures: number;
|
|
197
|
+
/** Set once auto-disabled after 10 consecutive hard delivery failures. */
|
|
198
|
+
disabled_at: string | null;
|
|
199
|
+
/** Only set while `status === "pending"` — the connect code's 15-min TTL. */
|
|
200
|
+
code_expires_at: string | null;
|
|
201
|
+
created_at: string;
|
|
202
|
+
activated_at: string | null;
|
|
203
|
+
}
|
|
204
|
+
export interface ConnectTelegramOptions {
|
|
205
|
+
/** Human-readable label for the chat (e.g. `"kychon alerts"`), 1-64 chars. */
|
|
206
|
+
label?: string;
|
|
207
|
+
}
|
|
208
|
+
export interface ConnectTelegramNextAction {
|
|
209
|
+
type: string;
|
|
210
|
+
method?: string;
|
|
211
|
+
path?: string;
|
|
212
|
+
why?: string;
|
|
213
|
+
}
|
|
214
|
+
export interface ConnectTelegramResult {
|
|
215
|
+
binding_id: string;
|
|
216
|
+
status: "pending";
|
|
217
|
+
/** `t.me/<bot>?start=<code>` — tap to bind a PRIVATE chat. Single-use, 15-min TTL. */
|
|
218
|
+
connect_url: string;
|
|
219
|
+
/** `t.me/<bot>?startgroup=<code>` — tap to bind a GROUP chat. Same code/TTL as {@link connect_url} (whichever is tapped first consumes it). */
|
|
220
|
+
connect_group_url: string;
|
|
221
|
+
code_expires_at: string;
|
|
222
|
+
label: string | null;
|
|
223
|
+
next_actions: ConnectTelegramNextAction[];
|
|
224
|
+
}
|
|
225
|
+
export interface NotificationChannelsResult {
|
|
226
|
+
email: {
|
|
227
|
+
address: string | null;
|
|
228
|
+
verified: boolean;
|
|
229
|
+
};
|
|
230
|
+
webhook: {
|
|
231
|
+
configured: boolean;
|
|
232
|
+
url: string | null;
|
|
233
|
+
secret_configured: boolean;
|
|
234
|
+
};
|
|
235
|
+
/** Every live (non-revoked) Telegram binding for this operator, newest first. */
|
|
236
|
+
telegram: TelegramChannelBinding[];
|
|
237
|
+
}
|
|
238
|
+
export interface RevokeTelegramResult {
|
|
239
|
+
status: "revoked";
|
|
240
|
+
binding_id: string;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* `"app"` = app-emitted business events (`project_events.source = 'app'`,
|
|
244
|
+
* e.g. `events.emit(...)` from `@run402/functions`); `"platform"` = every
|
|
245
|
+
* non-app platform event (deploys, lifecycle, verification, ...). Absent /
|
|
246
|
+
* `null` on a rule is a wildcard — matches both.
|
|
247
|
+
*/
|
|
248
|
+
export type RoutingRuleSource = "app" | "platform";
|
|
249
|
+
/**
|
|
250
|
+
* Wire-shaped routing rule. Every match dimension (`project_id`, `source`,
|
|
251
|
+
* `event_types`, `classes`) is ANDed; `null` is a wildcard for that
|
|
252
|
+
* dimension. An explicit empty array (`event_types: []` / `classes: []`)
|
|
253
|
+
* matches NOTHING — Postgres `TEXT[]` semantics, deliberately different from
|
|
254
|
+
* the "`[]` means unfiltered" convention used by some read-filter query
|
|
255
|
+
* params elsewhere in this SDK. One rule always targets exactly one Telegram
|
|
256
|
+
* binding; overlapping rules that resolve to the same binding are deduped by
|
|
257
|
+
* the gateway at delivery time (one message, not one per matching rule).
|
|
258
|
+
*/
|
|
259
|
+
export interface RoutingRule {
|
|
260
|
+
id: string;
|
|
261
|
+
recipient_email: string;
|
|
262
|
+
project_id: string | null;
|
|
263
|
+
source: RoutingRuleSource | null;
|
|
264
|
+
event_types: string[] | null;
|
|
265
|
+
classes: string[] | null;
|
|
266
|
+
channel: "telegram";
|
|
267
|
+
telegram_binding_id: string;
|
|
268
|
+
enabled: boolean;
|
|
269
|
+
created_at: string;
|
|
270
|
+
updated_at: string;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* `r.admin.rules.create(...)` input. Every match dimension is optional
|
|
274
|
+
* (absent = wildcard); `telegramBindingId` is the only required field. An
|
|
275
|
+
* all-wildcard rule (every event on every project routes to one chat) is
|
|
276
|
+
* legal.
|
|
277
|
+
*/
|
|
278
|
+
export interface CreateRoutingRuleInput {
|
|
279
|
+
telegramBindingId: string;
|
|
280
|
+
projectId?: string | null;
|
|
281
|
+
source?: RoutingRuleSource | null;
|
|
282
|
+
eventTypes?: string[] | null;
|
|
283
|
+
classes?: string[] | null;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* `r.admin.rules.update(...)` patch. PATCH semantics: a field OMITTED from
|
|
287
|
+
* this object leaves the stored value unchanged; a field explicitly set to
|
|
288
|
+
* `null` CLEARS that dimension back to wildcard. There is no wire difference
|
|
289
|
+
* between "omitted" and "set to `undefined`" — both drop the key from the
|
|
290
|
+
* JSON request body, so the gateway sees no instruction to change it.
|
|
291
|
+
*/
|
|
292
|
+
export interface UpdateRoutingRulePatch {
|
|
293
|
+
projectId?: string | null;
|
|
294
|
+
source?: RoutingRuleSource | null;
|
|
295
|
+
eventTypes?: string[] | null;
|
|
296
|
+
classes?: string[] | null;
|
|
297
|
+
telegramBindingId?: string;
|
|
298
|
+
enabled?: boolean;
|
|
299
|
+
}
|
|
300
|
+
export interface ListRoutingRulesResult {
|
|
301
|
+
rules: RoutingRule[];
|
|
302
|
+
}
|
|
303
|
+
export interface CreateRoutingRuleResult extends RoutingRule {
|
|
304
|
+
next_actions: ConnectTelegramNextAction[];
|
|
305
|
+
}
|
|
306
|
+
export interface DeleteRoutingRuleResult {
|
|
307
|
+
deleted: true;
|
|
308
|
+
rule_id: string;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* `r.admin.channels` — the Telegram notification-channel binding lifecycle
|
|
312
|
+
* (connect / list / revoke). Mutations (`connectTelegram`, `revokeTelegram`)
|
|
313
|
+
* require `operator_passkey` assurance; `connectTelegram` additionally
|
|
314
|
+
* requires a VERIFIED operator email (bindings are addressed to it). See
|
|
315
|
+
* `r.admin.setAgentContact` / `r.admin.verifyAgentContactEmail` and
|
|
316
|
+
* `r.admin.startOperatorPasskeyEnrollment` to reach that assurance level —
|
|
317
|
+
* same ladder as {@link Admin.rotateWebhookSecret}.
|
|
318
|
+
*/
|
|
319
|
+
export declare class Channels {
|
|
320
|
+
private readonly client;
|
|
321
|
+
constructor(client: Client);
|
|
322
|
+
/**
|
|
323
|
+
* Start binding a Telegram chat. Returns two single-use, 15-minute deep
|
|
324
|
+
* links — `connect_url` for a private chat, `connect_group_url` for a
|
|
325
|
+
* group — plus a `pending` binding id. A human taps ONE of the links and
|
|
326
|
+
* starts the bot; poll {@link Channels.list} until the binding's `status`
|
|
327
|
+
* flips to `"active"` (or `code_expires_at` passes and it's swept back to
|
|
328
|
+
* `"revoked"`).
|
|
329
|
+
*
|
|
330
|
+
* Throws (via the generic SDK error hierarchy — check `err.code`) HTTP 503
|
|
331
|
+
* `TELEGRAM_CHANNEL_NOT_CONFIGURED` until the platform's dedicated
|
|
332
|
+
* notification bot is provisioned, and HTTP 412
|
|
333
|
+
* `OPERATOR_EMAIL_NOT_VERIFIED` when the caller has no verified email yet.
|
|
334
|
+
*/
|
|
335
|
+
connectTelegram(opts?: ConnectTelegramOptions): Promise<ConnectTelegramResult>;
|
|
336
|
+
/** List every notification channel — email, webhook, and every live
|
|
337
|
+
* (non-revoked) Telegram binding — for the authenticated wallet. */
|
|
338
|
+
list(): Promise<NotificationChannelsResult>;
|
|
339
|
+
/**
|
|
340
|
+
* Revoke a Telegram binding. Missing / already-revoked / another
|
|
341
|
+
* operator's binding id all return the SAME not-found error
|
|
342
|
+
* (authorize-before-reveal) — no existence oracle.
|
|
343
|
+
*/
|
|
344
|
+
revokeTelegram(bindingId: string): Promise<RevokeTelegramResult>;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* `r.admin.rules` — Telegram routing rules (design D4): one match (ANDed
|
|
348
|
+
* dimensions; an omitted dimension is a wildcard) → one Telegram binding.
|
|
349
|
+
* Rules govern the Telegram channel ONLY in v1 — email/webhook keep their
|
|
350
|
+
* existing preference-toggle semantics untouched. Mutations require
|
|
351
|
+
* `operator_passkey` assurance.
|
|
352
|
+
*/
|
|
353
|
+
export declare class Rules {
|
|
354
|
+
private readonly client;
|
|
355
|
+
constructor(client: Client);
|
|
356
|
+
/** List the operator's routing rules, newest first. */
|
|
357
|
+
list(): Promise<ListRoutingRulesResult>;
|
|
358
|
+
/**
|
|
359
|
+
* Create a routing rule. `telegramBindingId` must reference a binding this
|
|
360
|
+
* operator owns and that is currently usable (`status: "active"`); an
|
|
361
|
+
* unusable or foreign binding id returns the same 404 as a nonexistent one
|
|
362
|
+
* (authorize-before-reveal).
|
|
363
|
+
*/
|
|
364
|
+
create(input: CreateRoutingRuleInput): Promise<CreateRoutingRuleResult>;
|
|
365
|
+
/**
|
|
366
|
+
* Patch a routing rule. PATCH semantics: only fields PRESENT on `patch`
|
|
367
|
+
* are sent — `{ projectId: null }` clears that dimension back to
|
|
368
|
+
* wildcard; omitting a field leaves it unchanged (see
|
|
369
|
+
* {@link UpdateRoutingRulePatch}).
|
|
370
|
+
*/
|
|
371
|
+
update(ruleId: string, patch: UpdateRoutingRulePatch): Promise<RoutingRule>;
|
|
372
|
+
/** Delete a routing rule. */
|
|
373
|
+
delete(ruleId: string): Promise<DeleteRoutingRuleResult>;
|
|
374
|
+
}
|
|
140
375
|
export interface SetLeasePerpetualResult {
|
|
141
376
|
status: "ok";
|
|
142
377
|
org_id: string;
|
|
@@ -246,6 +481,16 @@ export declare class Admin {
|
|
|
246
481
|
* preview, accept, claim, cancel, listIncoming, listOutgoing}`.
|
|
247
482
|
*/
|
|
248
483
|
readonly transfers: Transfers;
|
|
484
|
+
/**
|
|
485
|
+
* Telegram notification-channel binding lifecycle. Access via
|
|
486
|
+
* `r.admin.channels.{connectTelegram, list, revokeTelegram}`.
|
|
487
|
+
*/
|
|
488
|
+
readonly channels: Channels;
|
|
489
|
+
/**
|
|
490
|
+
* Telegram routing rules — one match (ANDed dimensions) to one binding.
|
|
491
|
+
* Access via `r.admin.rules.{list, create, update, delete}`.
|
|
492
|
+
*/
|
|
493
|
+
readonly rules: Rules;
|
|
249
494
|
constructor(client: Client);
|
|
250
495
|
/**
|
|
251
496
|
* Operator-scoped sub-client for an org id — the operator analog of
|
|
@@ -280,10 +525,15 @@ export declare class Admin {
|
|
|
280
525
|
setNotificationPreferences(patch: NotificationPreferencesPatch): Promise<NotificationPreferences>;
|
|
281
526
|
/**
|
|
282
527
|
* Trigger a real test notification. Sends a sample `project_past_due`
|
|
283
|
-
* event through the normal worker pipeline; the audit row
|
|
284
|
-
* `is_test: true`.
|
|
528
|
+
* event through the normal worker pipeline (email/webhook); the audit row
|
|
529
|
+
* is marked `is_test: true`. ALSO delivers a synthetic event through the
|
|
530
|
+
* operator's Telegram routing rules end-to-end (binding + rule + render +
|
|
531
|
+
* send) and reports a per-destination outcome in `telegram.destinations`
|
|
532
|
+
* — pass `opts.source` / `opts.eventType` to target a specific rule's
|
|
533
|
+
* filters instead of the default sample event. Rate-limited per wallet at
|
|
534
|
+
* 1/min.
|
|
285
535
|
*/
|
|
286
|
-
testNotification(): Promise<TestNotificationResult>;
|
|
536
|
+
testNotification(opts?: TestNotificationOptions): Promise<TestNotificationResult>;
|
|
287
537
|
/**
|
|
288
538
|
* Rotate the operator's webhook signing secret. The new plaintext secret
|
|
289
539
|
* is returned EXACTLY once. The previous secret remains valid for 24
|