@jskit-ai/agent-docs 0.1.154 → 0.1.156
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/guide/agent/app-setup/authentication.md +68 -0
- package/package.json +1 -1
- package/patterns/feature-package/example/booking-engine/package.json +1 -1
- package/patterns/minimal-foundation/example/package.json +4 -4
- package/patterns/shell-foundation/example/package.json +5 -5
- package/patterns/shell-foundation/example/packages/main/package.json +1 -1
- package/reference/autogen/PATTERN_INDEX.md +185 -20
- package/reference/autogen/README.md +8 -2
- package/reference/autogen/packages/auth-provider-local-core.md +3 -5
- package/reference/autogen/packages/connector-google-calendar.md +42 -0
- package/reference/autogen/packages/connectors-catalog.md +1320 -0
- package/reference/autogen/packages/connectors-core.md +85 -0
- package/reference/autogen/packages/connectors-web.md +31 -0
- package/reference/autogen/packages/payments-core.md +79 -0
- package/reference/autogen/packages/payments-web.md +37 -0
- package/reference/autogen/packages/{google-rewarded-core.md → rewarded-core.md} +36 -36
- package/reference/autogen/packages/rewarded-web.md +75 -0
- package/skills/jskit/references/pattern-index.md +185 -20
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
- package/skills/jskit/references/patterns/auth/supabase-auth/PATTERN.md +25 -0
- package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
- package/skills/jskit/references/patterns/connectors/ai-connections/PATTERN.md +84 -0
- package/skills/jskit/references/patterns/connectors/ai-connections/example/ai-model.js +11 -0
- package/skills/jskit/references/patterns/connectors/ai-connections/example/perplexity-answer.js +29 -0
- package/skills/jskit/references/patterns/connectors/api-key-connection/PATTERN.md +368 -0
- package/skills/jskit/references/patterns/connectors/api-key-connection/example/connections.js +18 -0
- package/skills/jskit/references/patterns/connectors/api-key-connection/example/integrations.json +19 -0
- package/skills/jskit/references/patterns/connectors/assistant-mcp/PATTERN.md +110 -0
- package/skills/jskit/references/patterns/connectors/assistant-mcp/example/integrations.json +13 -0
- package/skills/jskit/references/patterns/connectors/assistant-mcp-oauth/PATTERN.md +235 -0
- package/skills/jskit/references/patterns/connectors/assistant-mcp-oauth/example/integrations.json +102 -0
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/PATTERN.md +169 -0
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/data-formats.js +35 -0
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/package-lock.json +49 -0
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/package.json +12 -0
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/verify-formats.mjs +37 -0
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/integrations.json +38 -0
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/s3-transfer.js +34 -0
- package/skills/jskit/references/patterns/connectors/calendar-cli/PATTERN.md +106 -0
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/.env.example +6 -0
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/integrations.json +23 -0
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/knexfile.js +5 -0
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +16 -0
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/scripts/calendar.js +102 -0
- package/skills/jskit/references/patterns/connectors/event-delivery/PATTERN.md +151 -0
- package/skills/jskit/references/patterns/connectors/event-delivery/example/integrations.json +17 -0
- package/skills/jskit/references/patterns/connectors/firebase-messaging/PATTERN.md +156 -0
- package/skills/jskit/references/patterns/connectors/firebase-messaging/example/server/notifications.js +23 -0
- package/skills/jskit/references/patterns/connectors/google-ads-search/PATTERN.md +92 -0
- package/skills/jskit/references/patterns/connectors/google-ads-search/example/ads-setup.js +25 -0
- package/skills/jskit/references/patterns/connectors/oauth-connection/PATTERN.md +724 -0
- package/skills/jskit/references/patterns/connectors/oauth-connection/example/integrations.json +20 -0
- package/skills/jskit/references/patterns/connectors/paddle-catalogue/PATTERN.md +78 -0
- package/skills/jskit/references/patterns/connectors/paddle-catalogue/example/create-products.js +38 -0
- package/skills/jskit/references/patterns/connectors/public-image/PATTERN.md +84 -0
- package/skills/jskit/references/patterns/connectors/public-image/example/integrations.json +13 -0
- package/skills/jskit/references/patterns/connectors/public-image/example/logo-url.js +16 -0
- package/skills/jskit/references/patterns/connectors/redshift-queries/PATTERN.md +150 -0
- package/skills/jskit/references/patterns/connectors/redshift-queries/example/integrations.json +33 -0
- package/skills/jskit/references/patterns/connectors/source-scanning/PATTERN.md +83 -0
- package/skills/jskit/references/patterns/connectors/source-scanning/example/source-scanner.js +13 -0
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
- package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
- package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md +69 -0
- package/skills/jskit/references/patterns/rewards/google-rewarded/example/GoogleRewardedDeliveryProvider.js +12 -0
- package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js +142 -0
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
- package/reference/autogen/packages/google-rewarded-web.md +0 -65
package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/integrations.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"registrations": {},
|
|
4
|
+
"integrations": {
|
|
5
|
+
"files": {
|
|
6
|
+
"provider": "aws-s3",
|
|
7
|
+
"displayName": "Application files",
|
|
8
|
+
"accountMode": "shared",
|
|
9
|
+
"scopes": [
|
|
10
|
+
"read"
|
|
11
|
+
],
|
|
12
|
+
"authentication": {
|
|
13
|
+
"method": "api-key",
|
|
14
|
+
"secretRef": "env:AWS_SECRET_ACCESS_KEY"
|
|
15
|
+
},
|
|
16
|
+
"settings": {
|
|
17
|
+
"region": "ap-southeast-2",
|
|
18
|
+
"bucket": "my-app-files",
|
|
19
|
+
"accessKeyIdRef": "env:AWS_ACCESS_KEY_ID"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"queries": {
|
|
23
|
+
"provider": "aws-athena",
|
|
24
|
+
"accountMode": "shared",
|
|
25
|
+
"scopes": [],
|
|
26
|
+
"authentication": {
|
|
27
|
+
"method": "api-key",
|
|
28
|
+
"secretRef": "env:AWS_SECRET_ACCESS_KEY"
|
|
29
|
+
},
|
|
30
|
+
"settings": {
|
|
31
|
+
"region": "ap-southeast-2",
|
|
32
|
+
"accessKeyIdRef": "env:AWS_ACCESS_KEY_ID",
|
|
33
|
+
"workgroup": "reports",
|
|
34
|
+
"resultLocation": "s3://query-results/reports/"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/s3-transfer.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Application-owned composition: service.authorize must approve this exact key
|
|
2
|
+
// for context. The same function works in a Node CLI or application backend.
|
|
3
|
+
// Pass a bounded signal for both signing and the complete response-body read.
|
|
4
|
+
export async function transferS3Object({ service, context, integrationId, key,
|
|
5
|
+
direction, body, signal, fetchImpl = globalThis.fetch }) {
|
|
6
|
+
if (!["download", "upload"].includes(direction)) throw new TypeError("Choose download or upload.");
|
|
7
|
+
if (!signal) throw new TypeError("Supply a transfer cancellation/timeout signal.");
|
|
8
|
+
if (direction === "upload" && !(body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body))) {
|
|
9
|
+
throw new TypeError("Supply a Blob, ArrayBuffer or typed-array upload body.");
|
|
10
|
+
}
|
|
11
|
+
signal.throwIfAborted();
|
|
12
|
+
const signed = await service.invoke({ context, integrationId, signal,
|
|
13
|
+
operation: direction === "upload" ? "objects.uploadUrl" : "objects.downloadUrl",
|
|
14
|
+
input: { key } });
|
|
15
|
+
let response;
|
|
16
|
+
try {
|
|
17
|
+
response = await fetchImpl(signed.url, { method: signed.method,
|
|
18
|
+
...(direction === "upload" ? { body } : {}), signal,
|
|
19
|
+
credentials: "omit", redirect: "error" });
|
|
20
|
+
} catch {
|
|
21
|
+
// Do not expose the URL through a network error/cause or retry a PUT whose
|
|
22
|
+
// outcome is unknown. The app decides whether to inspect and retry later.
|
|
23
|
+
throw new Error(direction === "upload"
|
|
24
|
+
? "Upload interrupted; completion is unknown. Check the object before retrying."
|
|
25
|
+
: "Download interrupted. Request a fresh download when ready.");
|
|
26
|
+
}
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
await response.body?.cancel().catch(() => {});
|
|
29
|
+
throw new Error(`S3 ${direction} failed (HTTP ${response.status}). Check permissions and URL expiry.`);
|
|
30
|
+
}
|
|
31
|
+
// Keep binary downloads as a stream; the caller must await its consumption
|
|
32
|
+
// before announcing completion. Upload success means S3 acknowledged the PUT.
|
|
33
|
+
return response;
|
|
34
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: connectors/calendar-cli
|
|
3
|
+
title: Google Calendar from an application-owned CLI
|
|
4
|
+
summary: Compose reusable connection libraries with portable configuration, application ownership and durable storage.
|
|
5
|
+
keywords: connectors, integrations, google, calendar, oauth, cli, permissions
|
|
6
|
+
requires: @jskit-ai/connectors-core, @jskit-ai/connector-google-calendar, @jskit-ai/database-runtime-mysql
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Google Calendar from an application-owned CLI
|
|
10
|
+
|
|
11
|
+
## Use when
|
|
12
|
+
|
|
13
|
+
Use this pattern for a local operator command that connects an account and reads
|
|
14
|
+
Calendar pages. The example deliberately selects MySQL/MariaDB. A web app should
|
|
15
|
+
use its existing selected database client and authenticated callback routes.
|
|
16
|
+
|
|
17
|
+
## Do not use when
|
|
18
|
+
|
|
19
|
+
Do not use the local operator identity or loopback listener as a public web
|
|
20
|
+
application's authentication. Do not use this example for managed gateway
|
|
21
|
+
registrations, native public OAuth clients or service accounts.
|
|
22
|
+
|
|
23
|
+
## Product decisions
|
|
24
|
+
|
|
25
|
+
Adapt the example's `integrations.json`, environment bindings, command names,
|
|
26
|
+
ownership policy and output handling. Install its dependencies with ordinary
|
|
27
|
+
`npm install`; no JSKIT CLI, generator or template installation is involved.
|
|
28
|
+
The example's loopback HTTP callback is local command wiring. OAuth exchange,
|
|
29
|
+
PKCE, state checks, refresh, provider requests, encryption and storage remain
|
|
30
|
+
imports from the libraries.
|
|
31
|
+
|
|
32
|
+
The example trusts the local process owner. `CONNECTOR_APPLICATION_ID` and
|
|
33
|
+
`CONNECTOR_SUBJECT_ID` identify its persistent connection. Do not copy that
|
|
34
|
+
environment-based identity policy into an HTTP API: derive its owner from the
|
|
35
|
+
authenticated user and check membership/operation permissions there.
|
|
36
|
+
|
|
37
|
+
## Invariants
|
|
38
|
+
|
|
39
|
+
- One configuration file is read by CLI and UI.
|
|
40
|
+
- Credential values and connection grants stay outside source.
|
|
41
|
+
- The application authorizes owners and executes package-owned migrations.
|
|
42
|
+
- OAuth and persistence machinery is imported from the library.
|
|
43
|
+
- Provider consent and a successful account check precede Connected.
|
|
44
|
+
|
|
45
|
+
## Framework APIs
|
|
46
|
+
|
|
47
|
+
The script imports `parseIntegrationConfiguration`, `createConnectionService`,
|
|
48
|
+
`createEnvironmentReferenceResolver`, `createCredentialProtection`,
|
|
49
|
+
`createKnexConnectionStore` and `googleCalendarProvider`. `knexfile.js` uses
|
|
50
|
+
`createKnexMigrationConfigFromApp` with the selected MySQL dialect.
|
|
51
|
+
|
|
52
|
+
## Example files
|
|
53
|
+
|
|
54
|
+
`example/integrations.json` is portable source. `example/.env.example` lists
|
|
55
|
+
private runtime bindings. `example/scripts/calendar.js` owns command parsing
|
|
56
|
+
and local callback wiring. `example/package.json` and `example/knexfile.js`
|
|
57
|
+
provide ordinary npm and migration operations.
|
|
58
|
+
|
|
59
|
+
## Verification
|
|
60
|
+
|
|
61
|
+
1. Follow the package's provider setup guide. Register the exact loopback URL
|
|
62
|
+
from `.env.example` on your own Google OAuth web client.
|
|
63
|
+
2. Adapt `example/package.json`, `knexfile.js`, `integrations.json` and
|
|
64
|
+
`scripts/calendar.js` into your application. Make `.env` from `.env.example`,
|
|
65
|
+
supply database and provider credentials, and exclude it from Git. Create
|
|
66
|
+
the application's migration directories with `mkdir -p migrations/constraints`.
|
|
67
|
+
3. Generate a private storage key with
|
|
68
|
+
`node -e 'console.log(require("node:crypto").randomBytes(32).toString("base64"))'`.
|
|
69
|
+
Save it as `CONNECTOR_STORAGE_KEY`. Preserve it across restarts and backups.
|
|
70
|
+
4. Run `npm install`, then `npm run db:migrate` against your application's
|
|
71
|
+
database. Package migrations are discovered directly; do not copy them.
|
|
72
|
+
5. Run `npm run calendar -- validate`. This needs no database or provider
|
|
73
|
+
credentials and uses exactly the validator used by the UI.
|
|
74
|
+
6. Run `npm run calendar -- connect`, open the displayed URL in your normal
|
|
75
|
+
browser and approve the account permissions. A local callback completes the
|
|
76
|
+
command. Ctrl-C cancels the pending attempt.
|
|
77
|
+
7. Run `npm run calendar -- calendars`, then
|
|
78
|
+
`npm run calendar -- events '{"calendarId":"primary","maxResults":10}'`.
|
|
79
|
+
The output includes the provider's next-page token when present.
|
|
80
|
+
8. Restart the process and run `npm run calendar -- status`. Run
|
|
81
|
+
`npm run calendar -- disconnect` to remove this application's local grant.
|
|
82
|
+
|
|
83
|
+
For command output, apply your application's privacy requirements before
|
|
84
|
+
retaining or sharing calendar data. The example prints operation results, not
|
|
85
|
+
credentials.
|
|
86
|
+
|
|
87
|
+
## Variation points
|
|
88
|
+
|
|
89
|
+
Use `createConnectorsFeature()` with the ordinary JSKIT action runtime, or call
|
|
90
|
+
`createConnectionService()` from an existing Feature. Keep business operations
|
|
91
|
+
named, such as `calendar.events.list`. Use the app's authenticated context in
|
|
92
|
+
the authorization policy. A web callback recovers the initiating owner's
|
|
93
|
+
context and passes the full registered callback URL to `completeAuthorization`.
|
|
94
|
+
|
|
95
|
+
Use `IntegrationConfigurationFields` from `@jskit-ai/connectors-web/client` for
|
|
96
|
+
the form. The parent uses the normal resource/add-edit seam to save the same
|
|
97
|
+
file, detect concurrent edits and expose the existing secret-entry control.
|
|
98
|
+
Saving configuration and connecting an account are different operations.
|
|
99
|
+
|
|
100
|
+
## Avoid
|
|
101
|
+
|
|
102
|
+
The source pattern is validated locally; live Google consent still requires
|
|
103
|
+
your credentials and account approval. The initial runtime implements own web
|
|
104
|
+
client registrations. The editor configures the generated application's own registration. Its backend
|
|
105
|
+
resolves the secret from the application environment; never ship a client secret
|
|
106
|
+
inside a desktop binary or browser bundle.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"integrations": {
|
|
4
|
+
"calendar": {
|
|
5
|
+
"provider": "google-calendar",
|
|
6
|
+
"displayName": "My calendar",
|
|
7
|
+
"accountMode": "per-user",
|
|
8
|
+
"scopes": [
|
|
9
|
+
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
|
|
10
|
+
"https://www.googleapis.com/auth/calendar.events.readonly"
|
|
11
|
+
],
|
|
12
|
+
"authentication": { "method": "oauth2", "registrationRef": "google" }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"registrations": {
|
|
16
|
+
"google": {
|
|
17
|
+
"source": "own",
|
|
18
|
+
"clientId": "YOUR_GOOGLE_CLIENT_ID",
|
|
19
|
+
"clientSecretRef": "env:GOOGLE_CLIENT_SECRET",
|
|
20
|
+
"callbackUrlRef": "env:GOOGLE_CALLBACK_URL"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createKnexMigrationConfigFromApp } from "@jskit-ai/database-runtime/server/knexMigrationConfig";
|
|
3
|
+
|
|
4
|
+
if (existsSync(".env")) process.loadEnvFile(".env");
|
|
5
|
+
export default await createKnexMigrationConfigFromApp({ client: "mysql2" });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"private": true,
|
|
3
|
+
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"calendar": "node --env-file-if-exists=.env scripts/calendar.js",
|
|
6
|
+
"db:migrate": "knex --knexfile ./knexfile.js migrate:latest",
|
|
7
|
+
"db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@jskit-ai/connectors-core": "0.1.1",
|
|
11
|
+
"@jskit-ai/connector-google-calendar": "0.1.1",
|
|
12
|
+
"@jskit-ai/database-runtime": "0.1.184",
|
|
13
|
+
"@jskit-ai/database-runtime-mysql": "0.1.182",
|
|
14
|
+
"knex": "^3.1.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { once } from "node:events";
|
|
4
|
+
import createKnex from "knex";
|
|
5
|
+
import { parseIntegrationConfiguration } from "@jskit-ai/connectors-core/shared/configuration";
|
|
6
|
+
import { createConnectionService, createEnvironmentReferenceResolver } from "@jskit-ai/connectors-core/server";
|
|
7
|
+
import { createCredentialProtection, createKnexConnectionStore } from "@jskit-ai/connectors-core/server/storage";
|
|
8
|
+
import { googleCalendarProvider } from "@jskit-ai/connector-google-calendar/server";
|
|
9
|
+
|
|
10
|
+
// A local operator command owns this HTTP listener; web apps use authenticated routes.
|
|
11
|
+
async function connectInBrowser(service, context) {
|
|
12
|
+
const callback = new URL(process.env.GOOGLE_CALLBACK_URL);
|
|
13
|
+
if (callback.protocol !== "http:" || callback.hostname !== "127.0.0.1" || !callback.port) {
|
|
14
|
+
throw new Error("This CLI requires a registered http://127.0.0.1:PORT callback.");
|
|
15
|
+
}
|
|
16
|
+
let state;
|
|
17
|
+
let finish;
|
|
18
|
+
let fail;
|
|
19
|
+
let completed = false;
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const finished = new Promise((resolve, reject) => { finish = resolve; fail = reject; });
|
|
22
|
+
const outcome = finished.then((result) => ({ result }), (error) => ({ error }));
|
|
23
|
+
const server = createServer(async (request, response) => {
|
|
24
|
+
const url = new URL(request.url, callback.origin);
|
|
25
|
+
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
26
|
+
response.setHeader("Cache-Control", "no-store");
|
|
27
|
+
if (request.method !== "GET" || url.pathname !== callback.pathname || !state || url.searchParams.get("state") !== state) {
|
|
28
|
+
response.writeHead(400).end("This is not the pending authorization callback.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const result = await service.completeAuthorization({ context, integrationId: "calendar", callbackUrl: url.href, signal: controller.signal });
|
|
33
|
+
completed = true;
|
|
34
|
+
response.end("Connected. You can close this window.");
|
|
35
|
+
finish(result);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
response.writeHead(400).end("Connection failed. Return to the terminal.");
|
|
38
|
+
fail(error);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
server.listen(Number(callback.port), "127.0.0.1");
|
|
42
|
+
await once(server, "listening");
|
|
43
|
+
const cancel = () => { controller.abort(); fail(new Error("Authorization cancelled.")); };
|
|
44
|
+
process.once("SIGINT", cancel);
|
|
45
|
+
const timeout = setTimeout(cancel, 10 * 60 * 1000);
|
|
46
|
+
try {
|
|
47
|
+
const start = await service.beginAuthorization({ context, integrationId: "calendar", signal: controller.signal });
|
|
48
|
+
state = new URL(start.authorizationUrl).searchParams.get("state");
|
|
49
|
+
console.log(`Open this URL in your browser:\n${start.authorizationUrl}`);
|
|
50
|
+
const completed = await outcome;
|
|
51
|
+
if (completed.error) throw completed.error;
|
|
52
|
+
return completed.result;
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
process.off("SIGINT", cancel);
|
|
56
|
+
try {
|
|
57
|
+
if (state && !completed) await service.cancelAuthorization({ context, integrationId: "calendar", state });
|
|
58
|
+
} finally {
|
|
59
|
+
await new Promise((resolve) => server.close(resolve));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function main() {
|
|
65
|
+
const command = process.argv[2] || "validate";
|
|
66
|
+
const configuration = parseIntegrationConfiguration(await readFile("integrations.json", "utf8"), { providers: [googleCalendarProvider] });
|
|
67
|
+
if (command === "validate") {
|
|
68
|
+
console.log("Integration configuration is valid.");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!["connect", "status", "calendars", "events", "disconnect"].includes(command)) {
|
|
72
|
+
throw new Error("Use validate, connect, status, calendars, events or disconnect.");
|
|
73
|
+
}
|
|
74
|
+
for (const name of ["DATABASE_URL", "CONNECTOR_STORAGE_KEY", "CONNECTOR_APPLICATION_ID", "CONNECTOR_SUBJECT_ID"]) {
|
|
75
|
+
if (!process.env[name]) throw new Error(`Set ${name} before using connections.`);
|
|
76
|
+
}
|
|
77
|
+
const context = { applicationId: process.env.CONNECTOR_APPLICATION_ID, subjectId: process.env.CONNECTOR_SUBJECT_ID };
|
|
78
|
+
const protection = createCredentialProtection({ keys: { current: Buffer.from(process.env.CONNECTOR_STORAGE_KEY, "base64") }, activeKeyId: "current" });
|
|
79
|
+
const knex = createKnex({ client: "mysql2", connection: process.env.DATABASE_URL });
|
|
80
|
+
try {
|
|
81
|
+
const service = createConnectionService({
|
|
82
|
+
configuration, providers: [googleCalendarProvider],
|
|
83
|
+
store: createKnexConnectionStore({ knex, protection }),
|
|
84
|
+
resolveReference: createEnvironmentReferenceResolver(process.env),
|
|
85
|
+
authorize: async () => context
|
|
86
|
+
});
|
|
87
|
+
const request = { context, integrationId: "calendar" };
|
|
88
|
+
let result;
|
|
89
|
+
if (command === "connect") result = await connectInBrowser(service, context);
|
|
90
|
+
else if (command === "status") result = await service.status(request);
|
|
91
|
+
else if (command === "disconnect") result = await service.disconnect(request);
|
|
92
|
+
else result = await service.invoke({ ...request, operation: command === "events" ? "events.list" : "calendars.list", input: JSON.parse(process.argv[3] || "{}") });
|
|
93
|
+
console.log(JSON.stringify(result, null, 2));
|
|
94
|
+
} finally { await knex.destroy(); }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch((error) => {
|
|
98
|
+
console.error(error.code || "calendar_command_failed");
|
|
99
|
+
if (error.fieldErrors) console.error(JSON.stringify(error.fieldErrors));
|
|
100
|
+
else if (!error.code) console.error(error.message);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: connectors/event-delivery
|
|
3
|
+
title: Authorized Inngest event delivery with portable files
|
|
4
|
+
summary: Compose the shared connector runtime for event delivery while leaving workflow code and event policy with the application.
|
|
5
|
+
keywords: connectors, integrations, inngest, events, workflows, files, cli
|
|
6
|
+
requires: @jskit-ai/connectors-core, @jskit-ai/connectors-catalog
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Authorized Inngest event delivery with portable files
|
|
10
|
+
|
|
11
|
+
## Use when
|
|
12
|
+
|
|
13
|
+
An existing application or CLI must send an event to Inngest and inspect app
|
|
14
|
+
metadata using separately owned Signing and Event Keys.
|
|
15
|
+
|
|
16
|
+
## Do not use when
|
|
17
|
+
|
|
18
|
+
This fragment does not execute or register workflow functions, implement an
|
|
19
|
+
Inngest serve endpoint, configure schedules or provide application-user login.
|
|
20
|
+
Those concerns belong to the app and its chosen Inngest SDK integration.
|
|
21
|
+
|
|
22
|
+
## Product decisions
|
|
23
|
+
|
|
24
|
+
Choose the connection owner, allowed event names, payload schema and stable
|
|
25
|
+
business operation ID. Decide which authenticated users may trigger each event.
|
|
26
|
+
Determine how the app will reconcile delivery uncertainty and report workflow
|
|
27
|
+
progress independently of the connector's delivery receipt.
|
|
28
|
+
|
|
29
|
+
## Invariants
|
|
30
|
+
|
|
31
|
+
- Configuration and connection state remain text files.
|
|
32
|
+
- Signing and Event Key values remain environment bindings, outside source.
|
|
33
|
+
- Metadata verification never sends an event or verifies the Event Key.
|
|
34
|
+
- Authorization sees the requested event before its Event Key is resolved.
|
|
35
|
+
- An accepted event does not prove a function completed.
|
|
36
|
+
- A cancelled request must not automatically replay a possibly accepted event.
|
|
37
|
+
|
|
38
|
+
## Framework APIs
|
|
39
|
+
|
|
40
|
+
Use the `api-key-connection` pattern's existing composition with
|
|
41
|
+
`providers: [inngestProvider]`, imported from
|
|
42
|
+
`@jskit-ai/connectors-catalog/server/inngest`. The JSKIT library owns validated
|
|
43
|
+
requests, credential selection, responses, cancellation and file grants.
|
|
44
|
+
|
|
45
|
+
## Example files
|
|
46
|
+
|
|
47
|
+
`example/integrations.json` supplies the shared `workflows` slot. Provision
|
|
48
|
+
`INNGEST_SIGNING_KEY` and `INNGEST_EVENT_KEY` in the backend environment and
|
|
49
|
+
use the existing file-store protection and authenticated connection context.
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
async function applicationConnectionPolicy(context, request) {
|
|
53
|
+
const owner = await workflowAccess.requireConnectionOwner(context, request.integrationId);
|
|
54
|
+
if (request.operation === "events.send") {
|
|
55
|
+
await workflowAccess.requireEventPermission(context, request.input);
|
|
56
|
+
}
|
|
57
|
+
return owner;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`workflowAccess` is application-owned policy, not a JSKIT API. It must validate
|
|
62
|
+
allowed names and payload ownership against authenticated state. It must also
|
|
63
|
+
authorize connection management and metadata reads. Return a stable trusted
|
|
64
|
+
`applicationId` and `subjectId`; do not trust owner IDs supplied by a browser.
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
await connections.connectApiKey({ context, integrationId: "workflows" });
|
|
68
|
+
// In an authorized application action, after validating report ownership:
|
|
69
|
+
const receipt = await connections.invoke({
|
|
70
|
+
context, integrationId: "workflows", operation: "events.send", signal,
|
|
71
|
+
input: {
|
|
72
|
+
name: "app/report.requested",
|
|
73
|
+
id: `app/report.requested:${reportRequestId}`,
|
|
74
|
+
data: { reportRequestId }
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// Record receipt.ids[0] as delivered; track workflow completion separately.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The application supplies `reportRequestId` from its validated business request.
|
|
81
|
+
Inngest's deduplication window is finite; a stable ID is not a permanent
|
|
82
|
+
exactly-once guarantee. Within an Inngest function, use the SDK's documented
|
|
83
|
+
step event-sending primitive so delivery participates in durable execution.
|
|
84
|
+
Do not replace SDK function orchestration with connector calls.
|
|
85
|
+
|
|
86
|
+
## Variation points
|
|
87
|
+
|
|
88
|
+
`settings.branchEnvironment` routes event delivery to a branch and does not
|
|
89
|
+
change metadata query selection. `accountMode: "assistant"` requires the host's
|
|
90
|
+
delegated event authority, including any explicit approval. A trusted operator
|
|
91
|
+
CLI uses the same JSON and library without depending on editor storage.
|
|
92
|
+
|
|
93
|
+
## Verification
|
|
94
|
+
|
|
95
|
+
The provider's focused tests simulate metadata and event replies and use real
|
|
96
|
+
encrypted file storage. The public-editor test exercises the same reference
|
|
97
|
+
and branch settings. This source pattern does not generate or run a sample app;
|
|
98
|
+
live event delivery and SDK function execution remain separate acceptance work.
|
|
99
|
+
|
|
100
|
+
## Avoid
|
|
101
|
+
|
|
102
|
+
Do not expose either key in browser code, send events as a connection probe,
|
|
103
|
+
interpret delivery as completed work, permit arbitrary events through a shared
|
|
104
|
+
public route, or retry after an ambiguous network failure without reconciliation.
|
|
105
|
+
|
|
106
|
+
## Native functions and cron
|
|
107
|
+
|
|
108
|
+
App-owned composition, using the native SDK's current `triggers` option
|
|
109
|
+
([functions](https://www.inngest.com/docs/learn/inngest-functions),
|
|
110
|
+
[cron](https://www.inngest.com/docs/guides/scheduled-functions),
|
|
111
|
+
[serve adapters](https://www.inngest.com/docs/learn/serving-inngest-functions);
|
|
112
|
+
reviewed 2026-09-13). Add `inngest` to the consuming application, not to JSKIT's
|
|
113
|
+
connector package. Resolve the JSON slot's Env references before constructing
|
|
114
|
+
its SDK client; standard names below match the example configuration.
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
import { Inngest } from "inngest";
|
|
118
|
+
import { fastifyPlugin } from "inngest/fastify";
|
|
119
|
+
|
|
120
|
+
const workflows = new Inngest({ id: "reports", eventKey: process.env.INNGEST_EVENT_KEY });
|
|
121
|
+
const report = workflows.createFunction(
|
|
122
|
+
{ id: "requested-report", triggers: { event: "app/report.requested" } },
|
|
123
|
+
async ({ event, step }) => {
|
|
124
|
+
const id = event.data.reportRequestId;
|
|
125
|
+
if (typeof id !== "string" || !id) throw new Error("Missing report request ID");
|
|
126
|
+
return step.run("render-and-store", () => reports.renderRequestedReport(id));
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
const reconcile = workflows.createFunction(
|
|
130
|
+
{ id: "reconcile-reports", triggers: { cron: "TZ=UTC 0 * * * *" } },
|
|
131
|
+
async ({ step }) => step.run("reconcile-due", () => reports.reconcileDueRequests())
|
|
132
|
+
);
|
|
133
|
+
// `fastify` is the app's existing server. The SDK validates signed requests
|
|
134
|
+
// using INNGEST_SIGNING_KEY from the deployed application's environment.
|
|
135
|
+
await fastify.register(fastifyPlugin, { client: workflows, functions: [report, reconcile] });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`reports` is the application's business service, not a library API. It loads
|
|
139
|
+
persisted, previously authorized requests, enforces tenant boundaries and makes
|
|
140
|
+
writes safe to retry. Do not trust an event's arbitrary user/tenant identifier
|
|
141
|
+
as authorization. `step.run` may retry failed work; a durable step is not a
|
|
142
|
+
substitute for idempotent business writes. Cron timezone is explicit; choose
|
|
143
|
+
schedules with awareness of daylight-saving behavior.
|
|
144
|
+
|
|
145
|
+
Expose the SDK route through the app's normal server composition, then deploy
|
|
146
|
+
and sync its HTTPS `/api/inngest` URL in the selected Inngest environment.
|
|
147
|
+
Keep the Signing Key private and let the native adapter verify signatures.
|
|
148
|
+
JSKIT CLI users follow the same steps without Vibe64. Other stacks use native
|
|
149
|
+
SDKs where available and the same event name/payload/Env contract; this example
|
|
150
|
+
does not promise an SDK in every language. No function execution, cloud sync
|
|
151
|
+
or generated application is performed by this reference pattern.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"registrations": {},
|
|
4
|
+
"integrations": {
|
|
5
|
+
"workflows": {
|
|
6
|
+
"provider": "inngest",
|
|
7
|
+
"displayName": "Report workflows",
|
|
8
|
+
"accountMode": "shared",
|
|
9
|
+
"scopes": [],
|
|
10
|
+
"authentication": {
|
|
11
|
+
"method": "api-key",
|
|
12
|
+
"secretRef": "env:INNGEST_SIGNING_KEY"
|
|
13
|
+
},
|
|
14
|
+
"settings": { "eventKeyRef": "env:INNGEST_EVENT_KEY" }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: connectors/firebase-messaging
|
|
3
|
+
title: Firebase messaging from a CLI or application backend
|
|
4
|
+
summary: Wire service-account grants, file storage, explicit message actions and public browser settings without generating an application.
|
|
5
|
+
keywords: connectors, integrations, firebase, fcm, service-account, jwt, notifications, push, vapid, cli, files
|
|
6
|
+
requires: @jskit-ai/connectors-core, @jskit-ai/connectors-catalog
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Firebase messaging from a CLI or application backend
|
|
10
|
+
|
|
11
|
+
## Use when
|
|
12
|
+
|
|
13
|
+
Use this pattern for an application's Firebase sender. The provider package
|
|
14
|
+
implements the service-account protocol and messaging operations; the core
|
|
15
|
+
implements token persistence, renewal and ownership. The application supplies
|
|
16
|
+
its existing authenticated identity, authorization, runtime directory and Env
|
|
17
|
+
bindings. The CLI uses these same libraries. Do not copy their token manager
|
|
18
|
+
or introduce an editor database.
|
|
19
|
+
|
|
20
|
+
Read the packaged `docs/firebase-cloud-messaging.md` before provisioning. No
|
|
21
|
+
OAuth callback exists for this credential mode. The application owner supplies
|
|
22
|
+
the Firebase project and service account through its own Env bindings.
|
|
23
|
+
|
|
24
|
+
## Do not use when
|
|
25
|
+
|
|
26
|
+
Use another provider contract for Firebase Authentication, Firestore or Cloud
|
|
27
|
+
Storage. This fragment does not provide those services or managed provisioning.
|
|
28
|
+
|
|
29
|
+
## Product decisions
|
|
30
|
+
|
|
31
|
+
Choose the target project, who may send which notifications, and whether the
|
|
32
|
+
application needs browser enrollment or only existing targets. Decide where
|
|
33
|
+
recipient/device associations belong in the application's existing storage.
|
|
34
|
+
|
|
35
|
+
## Invariants
|
|
36
|
+
|
|
37
|
+
Private service-account JSON remains on the server. Configuration stores its
|
|
38
|
+
reference, stable project identity and public settings. Verify before reporting
|
|
39
|
+
Connected; authorize every audience before sending. Preserve the existing file
|
|
40
|
+
store and token lock instead of copying the grant implementation.
|
|
41
|
+
|
|
42
|
+
## Framework APIs
|
|
43
|
+
|
|
44
|
+
Use `parseIntegrationConfiguration`, `createConnectionService`,
|
|
45
|
+
`createEnvironmentReferenceResolver`, `createFileConnectionStore`,
|
|
46
|
+
`createCredentialProtection` and `firebaseCloudMessagingProvider` as shown below.
|
|
47
|
+
The public helper is `firebaseCloudMessagingWebConfiguration`; the standard
|
|
48
|
+
Feature action is `connectors.verifyServiceAccount`.
|
|
49
|
+
|
|
50
|
+
## Example files
|
|
51
|
+
|
|
52
|
+
The example module and inline fragments adapt to the application's existing
|
|
53
|
+
source files. They are source guidance, not a generated application or template
|
|
54
|
+
installation.
|
|
55
|
+
|
|
56
|
+
Adapt this source fragment to the application's existing startup owner:
|
|
57
|
+
|
|
58
|
+
[server/notifications.js](example/server/notifications.js) exports
|
|
59
|
+
`openNotificationConnections({ configurationPath, runtimeDirectory, credentialKey, environment, authorizeConnectionOperation })`.
|
|
60
|
+
It composes the existing libraries and returns the ordinary connection service.
|
|
61
|
+
|
|
62
|
+
`credentialKey` is a private 32-byte `Uint8Array`, loaded from the application's
|
|
63
|
+
secret owner, not a literal in generated source. `runtimeDirectory` is an
|
|
64
|
+
absolute private path outside the repository. `environment` contains the
|
|
65
|
+
service-account JSON string referenced by `integrations.json`; a path to that
|
|
66
|
+
JSON file is not the credential value. An application with its own secret store
|
|
67
|
+
can supply an equivalent `resolveReference` implementation.
|
|
68
|
+
|
|
69
|
+
`authorizeConnectionOperation(context, request)` must obtain the real owner
|
|
70
|
+
from trusted identity and return `{ applicationId, subjectId }` only after
|
|
71
|
+
checking access. Shared senders use an authorized shared subject. Every
|
|
72
|
+
`messages.send` requires checking `request.input.message` against the allowed
|
|
73
|
+
recipient and content. A signed-in user or assistant must not gain arbitrary
|
|
74
|
+
topic broadcasts merely by accessing this integration. Keep public and paid
|
|
75
|
+
project assignments in host policy.
|
|
76
|
+
|
|
77
|
+
## Explicit actions
|
|
78
|
+
|
|
79
|
+
From the existing CLI command or server action, verify the configured sender:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
await connections.connectServiceAccount({
|
|
83
|
+
context: trustedContext, integrationId: "push", signal
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This checks a fixed topic with `validate_only: true`. It does not send a test
|
|
88
|
+
notification. For an actual application message, build a bounded typed payload
|
|
89
|
+
and authorize it before invoking the send operation:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
const message = {
|
|
93
|
+
fid: recipientInstallationId,
|
|
94
|
+
notification: { title: "Order ready", body: "Your order is ready." },
|
|
95
|
+
data: { orderId: String(order.id) }
|
|
96
|
+
};
|
|
97
|
+
await connections.invoke({ context: trustedContext, integrationId: "push",
|
|
98
|
+
operation: "messages.validate", input: { message }, signal });
|
|
99
|
+
// Use the application's explicit send action and its authorization policy.
|
|
100
|
+
await connections.invoke({ context: trustedContext, integrationId: "push",
|
|
101
|
+
operation: "messages.send", input: { message }, signal });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Validation does not authorize the later send and is not a delivery guarantee.
|
|
105
|
+
Use exactly one target: `fid`, existing `token`, `topic` or `condition`. Prefer
|
|
106
|
+
installation IDs when using the current Firebase SDK. Keep the installed SDK's
|
|
107
|
+
enrollment contract consistent; do not mix registration-token and installation
|
|
108
|
+
APIs. No automatic retry should wrap an ambiguous timed-out send. If the product
|
|
109
|
+
needs retry scheduling, implement that behavior in its existing delivery owner.
|
|
110
|
+
|
|
111
|
+
The standard JSKIT Feature is also available through
|
|
112
|
+
`createConnectorsFeature(options)` and `connectors.verifyServiceAccount`.
|
|
113
|
+
Product message operations belong in application actions that call `invoke`;
|
|
114
|
+
never expose an arbitrary authenticated URL or unconstrained provider operation.
|
|
115
|
+
|
|
116
|
+
## Variation points
|
|
117
|
+
|
|
118
|
+
### Browser composition
|
|
119
|
+
|
|
120
|
+
For a configuration with `clientMode: "web"`, use the pure library projection:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import { firebaseCloudMessagingWebConfiguration } from "@jskit-ai/connectors-catalog/client/firebase-cloud-messaging";
|
|
124
|
+
const { firebaseConfig, vapidKey } = firebaseCloudMessagingWebConfiguration(publicPushSettings);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Supply `firebaseConfig` to the application's Firebase initialization and
|
|
128
|
+
`vapidKey` to its SDK registration call. Current documentation uses
|
|
129
|
+
`register(messaging, { vapidKey })` and `onRegistered(messaging, callback)`;
|
|
130
|
+
verify those exports against the version actually installed before wiring them.
|
|
131
|
+
The callback passes the installation ID to an authenticated app endpoint that
|
|
132
|
+
associates it with the correct user/device. Serve the service worker at the
|
|
133
|
+
app's origin and request notification permission through an explicit UI action.
|
|
134
|
+
The public configuration contains no private service-account data.
|
|
135
|
+
|
|
136
|
+
On a custom-domain change, register the browser on the new origin and replace
|
|
137
|
+
its installation association through the same authorized endpoint. Keep the
|
|
138
|
+
stable application identity and project assignment. The server token exchange
|
|
139
|
+
does not acquire a new OAuth callback just because an app uses another domain.
|
|
140
|
+
|
|
141
|
+
## Verification
|
|
142
|
+
|
|
143
|
+
Use a generated test key and an injected `fetchImpl` fixture. Verify JWT issuer,
|
|
144
|
+
audience, expiry, scope and signature; inspect `validate_only` and payload
|
|
145
|
+
targets; cover missing permissions, malformed receipts and cancellation. Use
|
|
146
|
+
the actual file store to test restart and rotation. Form tests must check
|
|
147
|
+
server/web mode changes, reference rejection, public values and file reload.
|
|
148
|
+
These checks do not prove live delivery or browser subscription. Do not create
|
|
149
|
+
a real project, key, subscription or notification as an incidental test.
|
|
150
|
+
|
|
151
|
+
## Avoid
|
|
152
|
+
|
|
153
|
+
Do not expose service-account JSON to the browser, confuse Firebase API keys
|
|
154
|
+
with private service credentials, or choose the paid project from caller input.
|
|
155
|
+
Do not treat an accepted message receipt as device delivery or automatically
|
|
156
|
+
retry a send whose outcome is unknown.
|