@sprigr/apps-app-sdk 0.8.0 → 0.8.2
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 +2 -1
- package/bin/check-write-protection.mjs +14 -3
- package/dist/index.d.ts +71 -12
- package/dist/index.js +51 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,8 @@ Three platform tiers, and they are not substitutes for each other. Full guide: [
|
|
|
72
72
|
- `undoEnvelope({...})` throws on a blank field. The platform drops the whole envelope on a blank and mints nothing, silently.
|
|
73
73
|
- `refuseWithoutForce(name, handler, opts)` / `archiveOfferRefusal(...)`: a delete refuses once and offers the vendor's native archive unless `force: true`.
|
|
74
74
|
- `buildConfirmationPolicy({ irreversible, always, rules })` declares a dispatcher's policy beside its action registry; `checkConfirmationPolicy({ policy, registry, ungated, requiredInput })` returns every silent failure (dead rule, `(unset)` placeholder, money threshold, unclassified write) for a one-line test.
|
|
75
|
-
- `runUndoApply(args, { env, journal, specs, pin })` is the body of your `internal: true` reverse tool: load, re-pin to the journalled connection, restore, drop.
|
|
75
|
+
- `runUndoApply(args, { env, journal, specs, pin })` is the body of your `internal: true` reverse tool: load, re-pin to the journalled connection, restore, drop. `pin` may hand `restore` a different type from the env (a per-actor token state), may throw (`connection_unavailable`) or return null to refuse (`connection_mismatch`); a restore that throws is `restore_failed` and the copy is kept.
|
|
76
|
+
- `safeCapture(scope, name, id, read)` and `offerUndo({ journal, entity, id, before, connection, fidelity, resource, describe, warning })`: the capture-then-offer halves for a write that is not behind an approval card (a dispatcher `update_*` action). `requireApproval` uses the same two on its granted pass.
|
|
76
77
|
- `sprigr-check-write-protection` (bin): every destructive-by-name tool or enumerated dispatcher action must carry a policy, or a reason in `apps/<app>/write-protection.json`; ratchets on `tools/write-protection-baseline.json`.
|
|
77
78
|
|
|
78
79
|
**Never offer `_undo` for money or messages.** Refunds, captures, cancellations, sent mail: the approval gate is the only control there.
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
*
|
|
30
30
|
* sprigr-check-write-protection [--apps apps] [--warn] [--baseline f] [--write-baseline]
|
|
31
31
|
*/
|
|
32
|
-
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
32
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs';
|
|
33
33
|
import { join } from 'node:path';
|
|
34
|
+
import { pathToFileURL } from 'node:url';
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* A tool or action name that destroys, reverses money, or cannot be walked
|
|
@@ -182,8 +183,18 @@ export function run(argv, cwd = process.cwd()) {
|
|
|
182
183
|
return { code: 0, lines };
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
// npm installs the bin as a SYMLINK named `sprigr-check-write-protection`, so
|
|
187
|
+
// process.argv[1] does not end in `.mjs`; resolve it before comparing, or the
|
|
188
|
+
// CLI silently does nothing when run through npx / pnpm exec (0.8.1 shipped
|
|
189
|
+
// that way and printed nothing at all).
|
|
190
|
+
function invokedDirectly() {
|
|
191
|
+
try {
|
|
192
|
+
return !!process.argv[1] && pathToFileURL(realpathSync(process.argv[1])).href === import.meta.url;
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (invokedDirectly()) {
|
|
187
198
|
const { code, lines } = run(process.argv.slice(2));
|
|
188
199
|
for (const l of lines) (code ? console.error : console.log)(l);
|
|
189
200
|
process.exit(code);
|
package/dist/index.d.ts
CHANGED
|
@@ -1190,19 +1190,28 @@ declare function requireApproval<E>(handlers: Record<string, ToolHandler<E>>, sp
|
|
|
1190
1190
|
* `pin`, a replay lands on the install's CURRENT default and rebuilds the
|
|
1191
1191
|
* object in the wrong customer account, and nothing errors.
|
|
1192
1192
|
*
|
|
1193
|
+
* `E` is the env the tool receives; `P` is what `pin` hands to `restore`. For
|
|
1194
|
+
* a store-pinned env they are the same type (Shopify). For an app that
|
|
1195
|
+
* re-authenticates per actor and organisation, `P` is the fresh token state
|
|
1196
|
+
* (Xero), which is why they are separate parameters.
|
|
1197
|
+
*
|
|
1193
1198
|
* Extracted from the Shopify and Xero `undo-apply.ts` handlers, which were
|
|
1194
1199
|
* byte-similar apart from their restore call and their pin.
|
|
1195
1200
|
*/
|
|
1196
1201
|
|
|
1197
|
-
interface RestoreSpec<
|
|
1202
|
+
interface RestoreSpec<P, T = Record<string, unknown>> {
|
|
1198
1203
|
resource: string;
|
|
1199
1204
|
fidelity: UndoFidelity;
|
|
1200
|
-
/**
|
|
1201
|
-
|
|
1205
|
+
/**
|
|
1206
|
+
* Rebuild or write back, through the PINNED connection. May return
|
|
1207
|
+
* `{ ok: false, error }` or throw; both become `restore_failed` and the
|
|
1208
|
+
* before-image is kept.
|
|
1209
|
+
*/
|
|
1210
|
+
restore: (pinned: P, before: T, row: JournalRowLike<T>) => Promise<{
|
|
1202
1211
|
ok: boolean;
|
|
1203
1212
|
newId?: string;
|
|
1204
1213
|
error?: string;
|
|
1205
|
-
}>;
|
|
1214
|
+
} | void>;
|
|
1206
1215
|
/** What the restore does NOT bring back, folded into the note verbatim. */
|
|
1207
1216
|
notRestored?: string;
|
|
1208
1217
|
}
|
|
@@ -1212,19 +1221,20 @@ interface JournalRowLike<T = unknown> {
|
|
|
1212
1221
|
connection: string | null;
|
|
1213
1222
|
before: T;
|
|
1214
1223
|
}
|
|
1215
|
-
interface UndoApplyOptions<E> {
|
|
1224
|
+
interface UndoApplyOptions<E, P = E> {
|
|
1216
1225
|
env: E;
|
|
1217
1226
|
journal: {
|
|
1218
1227
|
loadBefore<T = unknown>(ref: string): Promise<JournalRowLike<T> | null>;
|
|
1219
1228
|
dropBefore(ref: string): Promise<void>;
|
|
1220
1229
|
};
|
|
1221
|
-
specs: Record<string, RestoreSpec<
|
|
1230
|
+
specs: Record<string, RestoreSpec<P>>;
|
|
1222
1231
|
/**
|
|
1223
|
-
* Re-pin
|
|
1224
|
-
*
|
|
1225
|
-
* session-stateful app whose
|
|
1232
|
+
* Re-pin to the journalled connection. May be async (re-auth per actor, as
|
|
1233
|
+
* Xero does). Throw to report `connection_unavailable`; return null to
|
|
1234
|
+
* REFUSE with `connection_mismatch`, e.g. a session-stateful app whose
|
|
1235
|
+
* current business differs from the row's.
|
|
1226
1236
|
*/
|
|
1227
|
-
pin: (env: E, connection: string | null, args: ToolArgs) => Promise<
|
|
1237
|
+
pin: (env: E, connection: string | null, args: ToolArgs) => Promise<P | null> | P | null;
|
|
1228
1238
|
}
|
|
1229
1239
|
type UndoApplyResult = {
|
|
1230
1240
|
ok: true;
|
|
@@ -1241,7 +1251,7 @@ type UndoApplyResult = {
|
|
|
1241
1251
|
note: string;
|
|
1242
1252
|
};
|
|
1243
1253
|
/** Run the reversal. Every failure names what it was and states that nothing changed. */
|
|
1244
|
-
declare function runUndoApply<E>(args: ToolArgs, opts: UndoApplyOptions<E>): Promise<UndoApplyResult>;
|
|
1254
|
+
declare function runUndoApply<E, P = E>(args: ToolArgs, opts: UndoApplyOptions<E, P>): Promise<UndoApplyResult>;
|
|
1245
1255
|
|
|
1246
1256
|
/**
|
|
1247
1257
|
* Declare a dispatcher tool's confirmation policy beside its action registry,
|
|
@@ -1300,6 +1310,55 @@ interface PolicyCheckInput {
|
|
|
1300
1310
|
*/
|
|
1301
1311
|
declare function checkConfirmationPolicy(input: PolicyCheckInput): string[];
|
|
1302
1312
|
|
|
1313
|
+
/**
|
|
1314
|
+
* The two halves of offering an undo, for handlers that are not behind an
|
|
1315
|
+
* approval card (a dispatcher's `update_*` actions, for instance). The order
|
|
1316
|
+
* is the whole contract:
|
|
1317
|
+
*
|
|
1318
|
+
* const before = await safeCapture(scope, action, id, () => read(pinned, id));
|
|
1319
|
+
* const result = await write(pinned, input); // happens regardless
|
|
1320
|
+
* const _undo = await offerUndo({ journal, entity: action, id, before, connection, ... });
|
|
1321
|
+
* return { ok: true, result, ...(_undo ? { _undo } : {}) };
|
|
1322
|
+
*
|
|
1323
|
+
* `requireApproval` uses the same two functions on its granted pass.
|
|
1324
|
+
*/
|
|
1325
|
+
/**
|
|
1326
|
+
* Read the before-image, degrading to null on any failure. A capture that
|
|
1327
|
+
* throws (404, scope denied, network) is not a reason to refuse a write the
|
|
1328
|
+
* user asked for; it is a reason to offer no undo, and to say so in the log.
|
|
1329
|
+
*/
|
|
1330
|
+
declare function safeCapture(scope: string, name: string, id: string, capture: () => Promise<unknown>): Promise<Record<string, unknown> | null>;
|
|
1331
|
+
interface OfferUndoArgs {
|
|
1332
|
+
journal: CaptureJournal;
|
|
1333
|
+
/** The tool or action that captured; the reverse tool looks the spec up by it. */
|
|
1334
|
+
entity: string;
|
|
1335
|
+
id: string;
|
|
1336
|
+
/** From `safeCapture`. Null means no undo is offered, deliberately. */
|
|
1337
|
+
before: Record<string, unknown> | null;
|
|
1338
|
+
/** The resolved store / organisation / business, or null for a single-connection app. */
|
|
1339
|
+
connection: string | null;
|
|
1340
|
+
fidelity: UndoFidelity;
|
|
1341
|
+
resource: string;
|
|
1342
|
+
/** How the change reads in `list_undoable_changes`; the connection is appended when present. */
|
|
1343
|
+
describe: (before: Record<string, unknown>, id: string) => string;
|
|
1344
|
+
/** Relayed verbatim; never empty. */
|
|
1345
|
+
warning: string;
|
|
1346
|
+
/** Word between the description and the connection: "on" for a store, "in" for an organisation. */
|
|
1347
|
+
connectionPreposition?: 'on' | 'in';
|
|
1348
|
+
/**
|
|
1349
|
+
* What `describes` calls the connection, when the journalled id is not
|
|
1350
|
+
* readable by a person (a Xero tenantId). Defaults to `connection`. The
|
|
1351
|
+
* journal row always stores `connection` itself, because that is the pin.
|
|
1352
|
+
*/
|
|
1353
|
+
connectionLabel?: string;
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Store the before-image and build the `_undo` envelope, or return undefined
|
|
1357
|
+
* when there is nothing to offer. Never returns an envelope for a null capture
|
|
1358
|
+
* or a journal that could not store the copy.
|
|
1359
|
+
*/
|
|
1360
|
+
declare function offerUndo(args: OfferUndoArgs): Promise<AppUndoEnvelope | undefined>;
|
|
1361
|
+
|
|
1303
1362
|
/**
|
|
1304
1363
|
* @sprigr/apps-app-sdk
|
|
1305
1364
|
*
|
|
@@ -1700,4 +1759,4 @@ interface ExecutionContextLike {
|
|
|
1700
1759
|
}
|
|
1701
1760
|
type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
|
|
1702
1761
|
|
|
1703
|
-
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppUndoEnvelope, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, InvalidUndoEnvelopeError, type JournalRowLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type RestoreSpec, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type ToolArgs, type ToolHandler, type ToolResult, type ToolWrapperOptions, UNIT_SEP, type UndoApplyOptions, type UndoApplyResult, type UndoCaptureSpec, type UndoFidelity, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, approvalHash, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, checkConfirmationPolicy, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, describeMissingBridge, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, runUndoApply, seq, set, tool, undoEnvelope, withSprigrEmitFallback };
|
|
1762
|
+
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppUndoEnvelope, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, InvalidUndoEnvelopeError, type JournalRowLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type RestoreSpec, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type ToolArgs, type ToolHandler, type ToolResult, type ToolWrapperOptions, UNIT_SEP, type UndoApplyOptions, type UndoApplyResult, type UndoCaptureSpec, type UndoFidelity, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, approvalHash, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, checkConfirmationPolicy, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, describeMissingBridge, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, offerUndo, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, runUndoApply, safeCapture, seq, set, tool, undoEnvelope, withSprigrEmitFallback };
|
package/dist/index.js
CHANGED
|
@@ -575,6 +575,37 @@ function refuseWithoutForce(deleteAction, inner, opts) {
|
|
|
575
575
|
};
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
+
// src/write-protection/undo-capture.ts
|
|
579
|
+
async function safeCapture(scope, name, id, capture) {
|
|
580
|
+
try {
|
|
581
|
+
const got = await capture();
|
|
582
|
+
return got && typeof got === "object" ? got : null;
|
|
583
|
+
} catch (err) {
|
|
584
|
+
console.warn(`[${scope}] ${name} ${id}: capture failed, writing without undo: ${String(err)}`);
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async function offerUndo(args) {
|
|
589
|
+
if (!args.before) return void 0;
|
|
590
|
+
const captured = await args.journal.captureBefore({
|
|
591
|
+
entity: args.entity,
|
|
592
|
+
originalId: args.id,
|
|
593
|
+
before: args.before,
|
|
594
|
+
connection: args.connection
|
|
595
|
+
});
|
|
596
|
+
if (!captured) return void 0;
|
|
597
|
+
const base = args.describe(args.before, args.id);
|
|
598
|
+
const prep = args.connectionPreposition ?? "on";
|
|
599
|
+
const label = args.connectionLabel ?? args.connection;
|
|
600
|
+
return undoEnvelope({
|
|
601
|
+
fidelity: args.fidelity,
|
|
602
|
+
warning: args.warning,
|
|
603
|
+
describes: label ? `${base} ${prep} ${label}` : base,
|
|
604
|
+
resource: args.resource,
|
|
605
|
+
ref: captured.ref
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
578
609
|
// src/write-protection/require-approval.ts
|
|
579
610
|
function rawIdOf(args, keys) {
|
|
580
611
|
for (const k of keys) {
|
|
@@ -635,33 +666,22 @@ async function granted(name, spec, args, env, ctx, inner, opts) {
|
|
|
635
666
|
const id = rawIdOf(args, spec.keys);
|
|
636
667
|
if (!undo || !id) return stamp(await inner(args, env, ctx));
|
|
637
668
|
const pinned = connection ? opts.pinEnv(env, connection) : env;
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
const got = await undo.capture(pinned, id, args);
|
|
641
|
-
before = got && typeof got === "object" ? got : null;
|
|
642
|
-
} catch (err) {
|
|
643
|
-
console.warn(`[${opts.scope}] ${name} ${id}: capture failed, writing without undo: ${String(err)}`);
|
|
644
|
-
}
|
|
669
|
+
const before = await safeCapture(opts.scope, name, id, () => undo.capture(pinned, id, args));
|
|
645
670
|
const result = await inner(args, env, ctx);
|
|
646
671
|
if (!isOk(result) || !before) return stamp(result);
|
|
647
|
-
const
|
|
672
|
+
const envelope = await offerUndo({
|
|
673
|
+
journal: opts.journal(env),
|
|
648
674
|
entity: name,
|
|
649
|
-
|
|
675
|
+
id,
|
|
650
676
|
before,
|
|
651
|
-
connection: connection || null
|
|
677
|
+
connection: connection || null,
|
|
678
|
+
fidelity: undo.fidelity,
|
|
679
|
+
resource: undo.resource,
|
|
680
|
+
describe: undo.describe,
|
|
681
|
+
warning: undo.warning(id, before)
|
|
652
682
|
});
|
|
653
|
-
if (!
|
|
654
|
-
|
|
655
|
-
return {
|
|
656
|
-
...stamp(result),
|
|
657
|
-
_undo: undoEnvelope({
|
|
658
|
-
fidelity: undo.fidelity,
|
|
659
|
-
warning: undo.warning(id, before),
|
|
660
|
-
describes,
|
|
661
|
-
resource: undo.resource,
|
|
662
|
-
ref: captured.ref
|
|
663
|
-
})
|
|
664
|
-
};
|
|
683
|
+
if (!envelope) return stamp(result);
|
|
684
|
+
return { ...stamp(result), _undo: envelope };
|
|
665
685
|
}
|
|
666
686
|
|
|
667
687
|
// src/write-protection/undo-apply.ts
|
|
@@ -698,14 +718,19 @@ async function runUndoApply(args, opts) {
|
|
|
698
718
|
note: `Could not reach the connection this change was made in${row.connection ? ` (${row.connection})` : ""}: ${String(err)}. Nothing was changed.`
|
|
699
719
|
};
|
|
700
720
|
}
|
|
701
|
-
if (
|
|
721
|
+
if (pinned === null || pinned === void 0) {
|
|
702
722
|
return {
|
|
703
723
|
ok: false,
|
|
704
724
|
error: "connection_mismatch",
|
|
705
725
|
note: `This change was made in ${row.connection ?? "a different connection"}, which is not the one currently selected. Switch to it and retry. Nothing was changed.`
|
|
706
726
|
};
|
|
707
727
|
}
|
|
708
|
-
|
|
728
|
+
let result;
|
|
729
|
+
try {
|
|
730
|
+
result = await spec.restore(pinned, before, row) ?? { ok: true };
|
|
731
|
+
} catch (err) {
|
|
732
|
+
result = { ok: false, error: String(err) };
|
|
733
|
+
}
|
|
709
734
|
if (!result.ok) {
|
|
710
735
|
return {
|
|
711
736
|
ok: false,
|
|
@@ -922,6 +947,7 @@ export {
|
|
|
922
947
|
isNotConnectedError,
|
|
923
948
|
isStagingHost,
|
|
924
949
|
listAppFiles,
|
|
950
|
+
offerUndo,
|
|
925
951
|
overlaySprigr,
|
|
926
952
|
ownerFromActorKey,
|
|
927
953
|
parseActor,
|
|
@@ -936,6 +962,7 @@ export {
|
|
|
936
962
|
resolveOAuthBouncerCallbackUrl,
|
|
937
963
|
resolvePlatformWebhookBase,
|
|
938
964
|
runUndoApply,
|
|
965
|
+
safeCapture,
|
|
939
966
|
seq,
|
|
940
967
|
set,
|
|
941
968
|
tool,
|