@eide/foir-cli 0.6.1 → 0.7.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/dist/cli.js +64 -38
- package/dist/lib/config-helpers.d.ts +39 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2949,12 +2949,14 @@ function createOperationsMethods(client) {
|
|
|
2949
2949
|
timeoutMs: params.timeoutMs,
|
|
2950
2950
|
inputSchema: params.inputSchema,
|
|
2951
2951
|
outputSchema: params.outputSchema,
|
|
2952
|
-
streamConfig: params.streamConfig,
|
|
2953
2952
|
quotas: params.quotas,
|
|
2954
2953
|
retryPolicy: params.retryPolicy,
|
|
2954
|
+
callbackTimeoutRetryPolicy: params.callbackTimeoutRetryPolicy,
|
|
2955
2955
|
allowedRoles: params.allowedRoles ?? [],
|
|
2956
2956
|
precondition: params.precondition,
|
|
2957
|
-
configId: params.configId
|
|
2957
|
+
configId: params.configId,
|
|
2958
|
+
supportsAsyncCallback: params.supportsAsyncCallback,
|
|
2959
|
+
callbackTtlSeconds: params.callbackTtlSeconds
|
|
2958
2960
|
})
|
|
2959
2961
|
);
|
|
2960
2962
|
return resp.operation ?? null;
|
|
@@ -2969,11 +2971,13 @@ function createOperationsMethods(client) {
|
|
|
2969
2971
|
timeoutMs: params.timeoutMs,
|
|
2970
2972
|
inputSchema: params.inputSchema,
|
|
2971
2973
|
outputSchema: params.outputSchema,
|
|
2972
|
-
streamConfig: params.streamConfig,
|
|
2973
2974
|
quotas: params.quotas,
|
|
2974
2975
|
retryPolicy: params.retryPolicy,
|
|
2976
|
+
callbackTimeoutRetryPolicy: params.callbackTimeoutRetryPolicy,
|
|
2975
2977
|
precondition: params.precondition,
|
|
2976
|
-
isActive: params.isActive
|
|
2978
|
+
isActive: params.isActive,
|
|
2979
|
+
supportsAsyncCallback: params.supportsAsyncCallback,
|
|
2980
|
+
callbackTtlSeconds: params.callbackTtlSeconds
|
|
2977
2981
|
})
|
|
2978
2982
|
);
|
|
2979
2983
|
return resp.operation ?? null;
|
|
@@ -4969,6 +4973,12 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
|
|
|
4969
4973
|
}
|
|
4970
4974
|
const ex = existingByKey.get(op.key);
|
|
4971
4975
|
const endpoint = resolveEndpoint(op.endpoint, operationBaseUrl);
|
|
4976
|
+
const supportsAsyncCallback = op.mode === "async";
|
|
4977
|
+
if (supportsAsyncCallback && op.timeoutMs && op.timeoutMs > 1e4) {
|
|
4978
|
+
console.warn(
|
|
4979
|
+
`\u26A0 operation "${op.key}": mode=async but timeoutMs=${op.timeoutMs} \u2014 ack should return in <10s; long timeouts mask slow extensions`
|
|
4980
|
+
);
|
|
4981
|
+
}
|
|
4972
4982
|
if (ex) {
|
|
4973
4983
|
const empty = {};
|
|
4974
4984
|
await client.operations.updateOperation({
|
|
@@ -4979,11 +4989,13 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
|
|
|
4979
4989
|
timeoutMs: op.timeoutMs,
|
|
4980
4990
|
inputSchema: op.inputSchema ?? empty,
|
|
4981
4991
|
outputSchema: op.outputSchema ?? empty,
|
|
4982
|
-
streamConfig: op.streamConfig ?? empty,
|
|
4983
4992
|
quotas: op.quotas ?? empty,
|
|
4984
4993
|
retryPolicy: op.retryPolicy ?? empty,
|
|
4994
|
+
callbackTimeoutRetryPolicy: op.callbackTimeoutRetryPolicy ?? empty,
|
|
4985
4995
|
precondition: op.precondition ?? empty,
|
|
4986
|
-
isActive: op.isActive
|
|
4996
|
+
isActive: op.isActive,
|
|
4997
|
+
supportsAsyncCallback,
|
|
4998
|
+
callbackTtlSeconds: op.callbackTtlSeconds
|
|
4987
4999
|
});
|
|
4988
5000
|
summary.operations.updated++;
|
|
4989
5001
|
} else {
|
|
@@ -4997,12 +5009,14 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
|
|
|
4997
5009
|
timeoutMs: op.timeoutMs,
|
|
4998
5010
|
inputSchema: op.inputSchema,
|
|
4999
5011
|
outputSchema: op.outputSchema,
|
|
5000
|
-
streamConfig: op.streamConfig,
|
|
5001
5012
|
quotas: op.quotas,
|
|
5002
5013
|
retryPolicy: op.retryPolicy,
|
|
5014
|
+
callbackTimeoutRetryPolicy: op.callbackTimeoutRetryPolicy,
|
|
5003
5015
|
allowedRoles: op.allowedRoles,
|
|
5004
5016
|
precondition: op.precondition,
|
|
5005
|
-
configId
|
|
5017
|
+
configId,
|
|
5018
|
+
supportsAsyncCallback,
|
|
5019
|
+
callbackTtlSeconds: op.callbackTtlSeconds
|
|
5006
5020
|
});
|
|
5007
5021
|
summary.operations.created++;
|
|
5008
5022
|
}
|
|
@@ -5238,6 +5252,7 @@ async function reconcileApiKeys(client, configKey, apiKeys, summary) {
|
|
|
5238
5252
|
for (const key of apiKeys) {
|
|
5239
5253
|
if (!key.name || !key.keyType || !key.envVar) continue;
|
|
5240
5254
|
const existingKey = existingByName.get(key.name);
|
|
5255
|
+
let rawKey;
|
|
5241
5256
|
if (existingKey) {
|
|
5242
5257
|
const wantScopes = (key.scopes ?? []).slice().sort();
|
|
5243
5258
|
const haveScopes = (existingKey.scopes ?? []).slice().sort();
|
|
@@ -5249,16 +5264,18 @@ async function reconcileApiKeys(client, configKey, apiKeys, summary) {
|
|
|
5249
5264
|
updateScopes: true
|
|
5250
5265
|
});
|
|
5251
5266
|
}
|
|
5252
|
-
|
|
5267
|
+
const rotated = await client.identity.rotateApiKey(existingKey.id);
|
|
5268
|
+
rawKey = rotated?.apiKey?.rawKey;
|
|
5269
|
+
} else {
|
|
5270
|
+
const result = await client.identity.createApiKey({
|
|
5271
|
+
name: key.name,
|
|
5272
|
+
keyType: key.keyType === "secret" ? 2 : 1,
|
|
5273
|
+
allowedModels: key.allowedModels,
|
|
5274
|
+
allowedFileTypes: key.allowedFileTypes,
|
|
5275
|
+
scopes: key.scopes
|
|
5276
|
+
});
|
|
5277
|
+
rawKey = result?.apiKey?.rawKey;
|
|
5253
5278
|
}
|
|
5254
|
-
const result = await client.identity.createApiKey({
|
|
5255
|
-
name: key.name,
|
|
5256
|
-
keyType: key.keyType === "secret" ? 2 : 1,
|
|
5257
|
-
allowedModels: key.allowedModels,
|
|
5258
|
-
allowedFileTypes: key.allowedFileTypes,
|
|
5259
|
-
scopes: key.scopes
|
|
5260
|
-
});
|
|
5261
|
-
const rawKey = result?.apiKey?.rawKey;
|
|
5262
5279
|
if (rawKey) {
|
|
5263
5280
|
summary.apiKeys.push({
|
|
5264
5281
|
name: key.name,
|
|
@@ -5568,6 +5585,24 @@ function discoverConfigFile() {
|
|
|
5568
5585
|
}
|
|
5569
5586
|
return null;
|
|
5570
5587
|
}
|
|
5588
|
+
function syncEnvVar(envPath, key, value) {
|
|
5589
|
+
const envContent = existsSync4(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
5590
|
+
const pattern = new RegExp(`^${key}=(.*)$`, "m");
|
|
5591
|
+
const currentMatch = envContent.match(pattern);
|
|
5592
|
+
const currentValue = currentMatch?.[1];
|
|
5593
|
+
if (currentValue === value) {
|
|
5594
|
+
return "unchanged";
|
|
5595
|
+
}
|
|
5596
|
+
if (currentMatch) {
|
|
5597
|
+
const updated = envContent.replace(new RegExp(`^${key}=.*$`, "m"), `${key}=${value}`);
|
|
5598
|
+
writeFileSync2(envPath, updated, "utf-8");
|
|
5599
|
+
return "replaced";
|
|
5600
|
+
}
|
|
5601
|
+
const needsNewline = envContent && !envContent.endsWith("\n");
|
|
5602
|
+
writeFileSync2(envPath, (needsNewline ? envContent + "\n" : envContent) + `${key}=${value}
|
|
5603
|
+
`, "utf-8");
|
|
5604
|
+
return "written";
|
|
5605
|
+
}
|
|
5571
5606
|
function writeEnvVar(envPath, key, value) {
|
|
5572
5607
|
let content = "";
|
|
5573
5608
|
if (existsSync4(envPath)) {
|
|
@@ -5669,31 +5704,22 @@ function registerPushCommand(program2, globalOpts) {
|
|
|
5669
5704
|
const { secret: signingSecret } = await client.operations.getSigningSecret();
|
|
5670
5705
|
if (signingSecret) {
|
|
5671
5706
|
const envPath2 = resolve4(opts.env ?? ".env");
|
|
5672
|
-
const
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
if (
|
|
5677
|
-
const updated = envContent.replace(
|
|
5678
|
-
/^FOIR_WEBHOOK_SECRET=.*$/m,
|
|
5679
|
-
`FOIR_WEBHOOK_SECRET=${signingSecret}`
|
|
5680
|
-
);
|
|
5681
|
-
writeFileSync2(envPath2, updated, "utf-8");
|
|
5707
|
+
const syncAndLog = (key, label) => {
|
|
5708
|
+
const result = syncEnvVar(envPath2, key, signingSecret);
|
|
5709
|
+
if (result === "unchanged") {
|
|
5710
|
+
console.log(chalk6.dim(` ${label}: ${key} already up to date, skipped`));
|
|
5711
|
+
} else if (result === "replaced") {
|
|
5682
5712
|
console.log(
|
|
5683
|
-
chalk6.yellow(
|
|
5713
|
+
chalk6.yellow(`\u27F3 ${label}`) + chalk6.dim(` \u2192 ${key} updated in ${envPath2}`)
|
|
5684
5714
|
);
|
|
5685
5715
|
} else {
|
|
5686
|
-
|
|
5687
|
-
key
|
|
5688
|
-
|
|
5689
|
-
label: "Webhook signing secret"
|
|
5690
|
-
});
|
|
5716
|
+
console.log(
|
|
5717
|
+
chalk6.green(`\u2713 ${label}`) + chalk6.dim(` \u2192 ${key} written to ${envPath2}`)
|
|
5718
|
+
);
|
|
5691
5719
|
}
|
|
5692
|
-
}
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
);
|
|
5696
|
-
}
|
|
5720
|
+
};
|
|
5721
|
+
syncAndLog("FOIR_WEBHOOK_SECRET", "Webhook signing secret");
|
|
5722
|
+
syncAndLog("FOIR_SIGNING_SECRET", "Callback signing secret");
|
|
5697
5723
|
}
|
|
5698
5724
|
if (envWrites.length > 0) {
|
|
5699
5725
|
console.log();
|
|
@@ -90,8 +90,6 @@ interface ApplyConfigOperationInput {
|
|
|
90
90
|
timeoutMs?: number;
|
|
91
91
|
inputSchema?: Record<string, unknown>;
|
|
92
92
|
outputSchema?: Record<string, unknown>;
|
|
93
|
-
/** Streaming configuration for SSE/chunked responses. */
|
|
94
|
-
streamConfig?: Record<string, unknown>;
|
|
95
93
|
/** Usage quota rules (rate limits per customer/user/tenant). */
|
|
96
94
|
quotas?: {
|
|
97
95
|
rules: QuotaRule[];
|
|
@@ -102,6 +100,45 @@ interface ApplyConfigOperationInput {
|
|
|
102
100
|
allowedRoles?: string[];
|
|
103
101
|
/** Precondition that must be met before execution (e.g., segment membership). */
|
|
104
102
|
precondition?: Precondition;
|
|
103
|
+
/**
|
|
104
|
+
* Execution mode the operation supports. `sync` blocks the platform worker
|
|
105
|
+
* on the HTTP call (≤30s realistically). `async` dispatches with
|
|
106
|
+
* 202-ack semantics — the extension returns 202 immediately and calls
|
|
107
|
+
* back via the typed GraphQL mutation when work completes. Use `async`
|
|
108
|
+
* for long-running work that exceeds the 30s wall clock.
|
|
109
|
+
*
|
|
110
|
+
* There is only one flavour of async on the platform: the callback-based
|
|
111
|
+
* handshake. The prior "fire-and-forget" meaning of async was retired
|
|
112
|
+
* in 2026-04 — every ASYNC dispatch now carries a scoped callback token
|
|
113
|
+
* and the platform tracks the execution to terminal state.
|
|
114
|
+
*
|
|
115
|
+
* Default: `sync`. The CLI maps this to `supportsAsyncCallback` on the
|
|
116
|
+
* platform Operation row — the operation can still be dispatched in SYNC
|
|
117
|
+
* mode at runtime when callers want a blocking call.
|
|
118
|
+
*/
|
|
119
|
+
mode?: 'sync' | 'async';
|
|
120
|
+
/**
|
|
121
|
+
* How long the platform waits for a callback before marking the execution
|
|
122
|
+
* `timed_out` and (per retry policy) re-queueing or sending to DLQ. Falls
|
|
123
|
+
* back to project setting `operations.callback_default_ttl`, then 24h.
|
|
124
|
+
* Clamped to [5m, 7d]. Only meaningful when `mode === 'async'`.
|
|
125
|
+
*/
|
|
126
|
+
callbackTtlSeconds?: number;
|
|
127
|
+
/**
|
|
128
|
+
* Retry policy applied specifically when the callback times out. Distinct
|
|
129
|
+
* from `retryPolicy`, which governs HTTP dispatch failures (network, 5xx).
|
|
130
|
+
* Splitting these lets operators retry dispatch failures aggressively while
|
|
131
|
+
* being conservative about callback timeouts — re-running an hour-long job
|
|
132
|
+
* that timed out because the upstream was slow tends to burn another hour
|
|
133
|
+
* and hit the same slowness.
|
|
134
|
+
*
|
|
135
|
+
* Shape mirrors `retryPolicy` (`{ maxRetries }`). Falls back to
|
|
136
|
+
* `retryPolicy` when unset, then to the watchdog default (3).
|
|
137
|
+
* Set `{ maxRetries: 0 }` to opt out of retry on timeout entirely.
|
|
138
|
+
*/
|
|
139
|
+
callbackTimeoutRetryPolicy?: {
|
|
140
|
+
maxRetries?: number;
|
|
141
|
+
};
|
|
105
142
|
}
|
|
106
143
|
interface ApplyConfigSegmentInput {
|
|
107
144
|
key: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eide/foir-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Universal platform CLI for Foir platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@bufbuild/protovalidate": "^1.1.1",
|
|
51
51
|
"@connectrpc/connect": "^2.0.0",
|
|
52
52
|
"@connectrpc/connect-node": "^2.0.0",
|
|
53
|
-
"@eide/foir-proto-ts": "^0.
|
|
53
|
+
"@eide/foir-proto-ts": "^0.16.0",
|
|
54
54
|
"chalk": "^5.3.0",
|
|
55
55
|
"commander": "^12.1.0",
|
|
56
56
|
"dotenv": "^16.4.5",
|