@withone/connect 0.18.0 → 0.19.1
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 +44 -1
- package/dist/config.cjs.js +71 -0
- package/dist/config.d.ts +102 -0
- package/dist/config.esm.js +69 -0
- package/dist/types.d.ts +3 -3
- package/package.json +6 -1
- package/skills/one-connect/SKILL.md +63 -4
- package/src/config.ts +120 -0
- package/src/types.ts +3 -3
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ The button, the routes and the calls are the same either way. Only what your ser
|
|
|
57
57
|
| Expires | Nothing | The access token per your app's Token lifetime; the refresh token after 30 days |
|
|
58
58
|
| You also run | Nothing | A daily refresh job |
|
|
59
59
|
| When a user revokes | The next call throws `reconnect_required` | The next refresh throws `refresh_failed` |
|
|
60
|
-
| A call outside the grant | `403` with `blockedByGrant: true` |
|
|
60
|
+
| A call outside the grant | `403` with `blockedByGrant: true` | The same |
|
|
61
61
|
| Pick it when | You are starting out | You need the bearer token itself, or your app already stores tokens |
|
|
62
62
|
|
|
63
63
|
The mode is the credential you pass to `createOneConnect`: `connectKey` and `userStore` for key mode, `tokenStore` for token mode. Sections 1 to 6 use key mode; [section 7](#7--token-mode) shows what changes for token mode.
|
|
@@ -84,6 +84,41 @@ ONE_REDIRECT_URI=https://yourapp.com/api/one/callback # exactly the registered
|
|
|
84
84
|
ONE_PERMISSION_SET=… # optional: the Permission set ID
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### Or from code
|
|
88
|
+
|
|
89
|
+
Describe the app in `one.connect.ts` and create it with the [One CLI](https://www.npmjs.com/package/@withone/cli) (2.10.0 or later). Running it again updates the same app.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// one.connect.ts
|
|
93
|
+
import { defineConnectApp } from "@withone/connect/config";
|
|
94
|
+
|
|
95
|
+
export default defineConnectApp({
|
|
96
|
+
name: "Mastra",
|
|
97
|
+
logoUrl: "https://mastra.ai/logo.png",
|
|
98
|
+
redirectUris: ["http://localhost:3000/api/one/callback"],
|
|
99
|
+
ask: {
|
|
100
|
+
gmail: { access: "custom", actions: ["sendEmail", "listAndViewEmailThreads"] },
|
|
101
|
+
"google-calendar": { access: "read-only" },
|
|
102
|
+
linear: { access: "read-write" },
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
one connect types # optional: tool slugs and action names autocomplete
|
|
109
|
+
one connect create # opens your browser to approve it, then writes .env
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The CLI opens a page in your signed-in browser showing the app as your users will see it. You approve it there; a new app's client secret and connect key are made on that page and shown once, so they never pass through the terminal. The CLI then writes `ONE_CLIENT_ID`, `ONE_PERMISSION_SET` and `ONE_REDIRECT_URI` to `.env`. `access` is `read-only`, `read-write`, `full-access`, or `custom` with `actions`, named as `one connect types` wrote them or by id. On Node older than 22.18, name the file `one.connect.mjs` and type it with `/** @type {import("@withone/connect/config").ConnectAppConfig} */`.
|
|
113
|
+
|
|
114
|
+
| Prop | |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `name`, `redirectUris` | Required. Every callback URL, character for character. |
|
|
117
|
+
| `reason` | One sentence on the card telling users why, at most 200 characters. |
|
|
118
|
+
| `description`, `logoUrl`, `homepageUrl` | Optional. |
|
|
119
|
+
| `branding` | `panelColor`, `buttonColor` (`#RRGGBB`), `cobrandEmail`. |
|
|
120
|
+
| `ask` | Per tool slug: `access` is `read-only`, `read-write`, `full-access`, or `custom` with `actions`. Omit it to let users choose from everything they have connected. |
|
|
121
|
+
|
|
87
122
|
## 2 · The server client
|
|
88
123
|
|
|
89
124
|
Your server holds one **connect key** for the app and saves one id per user who connects. Nothing expires and nothing is refreshed: One checks the user's consent on every call.
|
|
@@ -484,6 +519,14 @@ The job renews both tokens when either is within 3 days of expiring. Without it,
|
|
|
484
519
|
|
|
485
520
|
`useOneConnect` and `createConnectFlow` take `authorizeUrl`, `connectTheme`, `connector`, `returnTo`, `onSuccess`, `onError` and `onCancel`.
|
|
486
521
|
|
|
522
|
+
### Config · `@withone/connect/config`
|
|
523
|
+
|
|
524
|
+
| Export | |
|
|
525
|
+
|---|---|
|
|
526
|
+
| `defineConnectApp(config)` | Returns `config` unchanged; types `one.connect.ts`. |
|
|
527
|
+
| `ConnectAppConfig`, `ConnectToolAsk`, `ConnectAccess`, `ConnectAppBranding` | The config's types. |
|
|
528
|
+
| `ConnectRegistry` | Filled in by `one connect types` so slugs and action names autocomplete. |
|
|
529
|
+
|
|
487
530
|
## Support
|
|
488
531
|
|
|
489
532
|
- Bugs and questions: [GitHub issues](https://github.com/withoneai/connect/issues).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A Connect app described as code, for `one connect create`.
|
|
5
|
+
*
|
|
6
|
+
* // one.connect.ts
|
|
7
|
+
* import { defineConnectApp } from "@withone/connect/config";
|
|
8
|
+
*
|
|
9
|
+
* export default defineConnectApp({
|
|
10
|
+
* name: "Mastra",
|
|
11
|
+
* redirectUris: ["http://localhost:3000/api/one/callback"],
|
|
12
|
+
* ask: {
|
|
13
|
+
* gmail: { access: "custom", actions: ["sendEmail"] },
|
|
14
|
+
* "google-calendar": { access: "read-only" },
|
|
15
|
+
* linear: { access: "read-write" },
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* Types and one identity helper; nothing here calls One. `one connect
|
|
20
|
+
* create` reads the file and opens a page in your signed-in browser, where
|
|
21
|
+
* you approve the app (or, on a re-run, its new version). A new app's
|
|
22
|
+
* client secret and connect key are made and shown there once; neither is
|
|
23
|
+
* ever part of this file or passes through the CLI.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Filled in by `one connect types`, which writes a declaration file naming
|
|
28
|
+
* every tool and, for the tools your `ask` names, every action:
|
|
29
|
+
*
|
|
30
|
+
* declare module "@withone/connect/config" {
|
|
31
|
+
* interface ConnectRegistry {
|
|
32
|
+
* tools: "gmail" | "google-calendar" | "linear" | …;
|
|
33
|
+
* actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* Until it runs, any tool slug and any action name type-check.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
|
|
41
|
+
|
|
42
|
+
/** An action named for the config: its title in camel case ("sendEmail"),
|
|
43
|
+
* from the names `one connect types` wrote for that tool. */
|
|
44
|
+
|
|
45
|
+
/** An action as `actions` takes it: by name, or by its id from One's
|
|
46
|
+
* catalog (`one actions search` prints it). */
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* How much of a tool the app asks for, in the dashboard's words.
|
|
50
|
+
* - `read-only`: reads only.
|
|
51
|
+
* - `read-write`: reads, creates and updates; never deletes.
|
|
52
|
+
* - `full-access`: everything the tool offers.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/** One tool's ask: a level, or a hand-picked list of actions. */
|
|
56
|
+
|
|
57
|
+
/** A colour as `#RRGGBB`. */
|
|
58
|
+
|
|
59
|
+
/** How the connect card and its sign-in email look. Omit it to keep
|
|
60
|
+
* One's own look. */
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns `config` unchanged. It exists for the editor: the argument is
|
|
64
|
+
* checked against {@link ConnectAppConfig}, and after `one connect types`
|
|
65
|
+
* tool slugs and action names autocomplete.
|
|
66
|
+
*/
|
|
67
|
+
function defineConnectApp(config) {
|
|
68
|
+
return config;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
exports.defineConnectApp = defineConnectApp;
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Connect app described as code, for `one connect create`.
|
|
3
|
+
*
|
|
4
|
+
* // one.connect.ts
|
|
5
|
+
* import { defineConnectApp } from "@withone/connect/config";
|
|
6
|
+
*
|
|
7
|
+
* export default defineConnectApp({
|
|
8
|
+
* name: "Mastra",
|
|
9
|
+
* redirectUris: ["http://localhost:3000/api/one/callback"],
|
|
10
|
+
* ask: {
|
|
11
|
+
* gmail: { access: "custom", actions: ["sendEmail"] },
|
|
12
|
+
* "google-calendar": { access: "read-only" },
|
|
13
|
+
* linear: { access: "read-write" },
|
|
14
|
+
* },
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* Types and one identity helper; nothing here calls One. `one connect
|
|
18
|
+
* create` reads the file and opens a page in your signed-in browser, where
|
|
19
|
+
* you approve the app (or, on a re-run, its new version). A new app's
|
|
20
|
+
* client secret and connect key are made and shown there once; neither is
|
|
21
|
+
* ever part of this file or passes through the CLI.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Filled in by `one connect types`, which writes a declaration file naming
|
|
25
|
+
* every tool and, for the tools your `ask` names, every action:
|
|
26
|
+
*
|
|
27
|
+
* declare module "@withone/connect/config" {
|
|
28
|
+
* interface ConnectRegistry {
|
|
29
|
+
* tools: "gmail" | "google-calendar" | "linear" | …;
|
|
30
|
+
* actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* Until it runs, any tool slug and any action name type-check.
|
|
35
|
+
*/
|
|
36
|
+
export interface ConnectRegistry {
|
|
37
|
+
}
|
|
38
|
+
/** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
|
|
39
|
+
export type ConnectToolSlug = ConnectRegistry extends {
|
|
40
|
+
tools: infer T extends string;
|
|
41
|
+
} ? T : string;
|
|
42
|
+
/** An action named for the config: its title in camel case ("sendEmail"),
|
|
43
|
+
* from the names `one connect types` wrote for that tool. */
|
|
44
|
+
export type ConnectActionName<T extends string> = ConnectRegistry extends {
|
|
45
|
+
actions: infer A;
|
|
46
|
+
} ? T extends keyof A ? A[T] & string : string : string;
|
|
47
|
+
/** An action as `actions` takes it: by name, or by its id from One's
|
|
48
|
+
* catalog (`one actions search` prints it). */
|
|
49
|
+
export type ConnectActionRef<T extends string> = ConnectActionName<T> | `conn_mod_def::${string}`;
|
|
50
|
+
/**
|
|
51
|
+
* How much of a tool the app asks for, in the dashboard's words.
|
|
52
|
+
* - `read-only`: reads only.
|
|
53
|
+
* - `read-write`: reads, creates and updates; never deletes.
|
|
54
|
+
* - `full-access`: everything the tool offers.
|
|
55
|
+
*/
|
|
56
|
+
export type ConnectAccess = "read-only" | "read-write" | "full-access";
|
|
57
|
+
/** One tool's ask: a level, or a hand-picked list of actions. */
|
|
58
|
+
export type ConnectToolAsk<T extends string = string> = {
|
|
59
|
+
access: ConnectAccess;
|
|
60
|
+
} | {
|
|
61
|
+
access: "custom";
|
|
62
|
+
actions: readonly [ConnectActionRef<T>, ...ConnectActionRef<T>[]];
|
|
63
|
+
};
|
|
64
|
+
/** A colour as `#RRGGBB`. */
|
|
65
|
+
export type ConnectHexColor = `#${string}`;
|
|
66
|
+
/** How the connect card and its sign-in email look. Omit it to keep
|
|
67
|
+
* One's own look. */
|
|
68
|
+
export interface ConnectAppBranding {
|
|
69
|
+
/** The card's side panel. */
|
|
70
|
+
panelColor: ConnectHexColor;
|
|
71
|
+
/** Authorize, Continue and the other primary buttons. */
|
|
72
|
+
buttonColor: ConnectHexColor;
|
|
73
|
+
/** Whether the sign-in email carries the app's name, logo and colours.
|
|
74
|
+
* `false` when omitted. */
|
|
75
|
+
cobrandEmail?: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface ConnectAppConfig {
|
|
78
|
+
/** Shown on the connect card and in the user's list of apps. */
|
|
79
|
+
name: string;
|
|
80
|
+
/** Every callback URL the app's routes run on, character for character:
|
|
81
|
+
* local first is fine, then production. */
|
|
82
|
+
redirectUris: readonly [string, ...string[]];
|
|
83
|
+
description?: string;
|
|
84
|
+
/** A public https URL to a square image. */
|
|
85
|
+
logoUrl?: string;
|
|
86
|
+
homepageUrl?: string;
|
|
87
|
+
/** One sentence on the card telling users why the app asks, at most
|
|
88
|
+
* 200 characters. */
|
|
89
|
+
reason?: string;
|
|
90
|
+
branding?: ConnectAppBranding;
|
|
91
|
+
/** The tools the app asks for, keyed by slug. Omit it to let users
|
|
92
|
+
* choose from everything they have connected. */
|
|
93
|
+
ask?: {
|
|
94
|
+
[T in ConnectToolSlug]?: ConnectToolAsk<T>;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns `config` unchanged. It exists for the editor: the argument is
|
|
99
|
+
* checked against {@link ConnectAppConfig}, and after `one connect types`
|
|
100
|
+
* tool slugs and action names autocomplete.
|
|
101
|
+
*/
|
|
102
|
+
export declare function defineConnectApp<const C extends ConnectAppConfig>(config: C): C;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Connect app described as code, for `one connect create`.
|
|
3
|
+
*
|
|
4
|
+
* // one.connect.ts
|
|
5
|
+
* import { defineConnectApp } from "@withone/connect/config";
|
|
6
|
+
*
|
|
7
|
+
* export default defineConnectApp({
|
|
8
|
+
* name: "Mastra",
|
|
9
|
+
* redirectUris: ["http://localhost:3000/api/one/callback"],
|
|
10
|
+
* ask: {
|
|
11
|
+
* gmail: { access: "custom", actions: ["sendEmail"] },
|
|
12
|
+
* "google-calendar": { access: "read-only" },
|
|
13
|
+
* linear: { access: "read-write" },
|
|
14
|
+
* },
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* Types and one identity helper; nothing here calls One. `one connect
|
|
18
|
+
* create` reads the file and opens a page in your signed-in browser, where
|
|
19
|
+
* you approve the app (or, on a re-run, its new version). A new app's
|
|
20
|
+
* client secret and connect key are made and shown there once; neither is
|
|
21
|
+
* ever part of this file or passes through the CLI.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Filled in by `one connect types`, which writes a declaration file naming
|
|
26
|
+
* every tool and, for the tools your `ask` names, every action:
|
|
27
|
+
*
|
|
28
|
+
* declare module "@withone/connect/config" {
|
|
29
|
+
* interface ConnectRegistry {
|
|
30
|
+
* tools: "gmail" | "google-calendar" | "linear" | …;
|
|
31
|
+
* actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* Until it runs, any tool slug and any action name type-check.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
|
|
39
|
+
|
|
40
|
+
/** An action named for the config: its title in camel case ("sendEmail"),
|
|
41
|
+
* from the names `one connect types` wrote for that tool. */
|
|
42
|
+
|
|
43
|
+
/** An action as `actions` takes it: by name, or by its id from One's
|
|
44
|
+
* catalog (`one actions search` prints it). */
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* How much of a tool the app asks for, in the dashboard's words.
|
|
48
|
+
* - `read-only`: reads only.
|
|
49
|
+
* - `read-write`: reads, creates and updates; never deletes.
|
|
50
|
+
* - `full-access`: everything the tool offers.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/** One tool's ask: a level, or a hand-picked list of actions. */
|
|
54
|
+
|
|
55
|
+
/** A colour as `#RRGGBB`. */
|
|
56
|
+
|
|
57
|
+
/** How the connect card and its sign-in email look. Omit it to keep
|
|
58
|
+
* One's own look. */
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns `config` unchanged. It exists for the editor: the argument is
|
|
62
|
+
* checked against {@link ConnectAppConfig}, and after `one connect types`
|
|
63
|
+
* tool slugs and action names autocomplete.
|
|
64
|
+
*/
|
|
65
|
+
function defineConnectApp(config) {
|
|
66
|
+
return config;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { defineConnectApp };
|
package/dist/types.d.ts
CHANGED
|
@@ -77,9 +77,9 @@ export interface ConnectButtonLogo {
|
|
|
77
77
|
name: string;
|
|
78
78
|
imageUrl: string;
|
|
79
79
|
}
|
|
80
|
-
/** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.
|
|
80
|
+
/** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.20.0. */
|
|
81
81
|
export type ConnectButtonPlatformInput = ConnectButtonLogoInput;
|
|
82
|
-
/** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.
|
|
82
|
+
/** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.20.0. */
|
|
83
83
|
export type ConnectButtonPlatform = ConnectButtonLogo;
|
|
84
84
|
export type ConnectButtonVariant = "default" | "accent" | "block";
|
|
85
85
|
export type ConnectButtonSize = "sm" | "md" | "lg";
|
|
@@ -94,7 +94,7 @@ export interface ConnectButtonProps {
|
|
|
94
94
|
* into a "+N" chip. Decoration only; the permission set decides what
|
|
95
95
|
* One asks for. */
|
|
96
96
|
logos?: ConnectButtonLogoInput[];
|
|
97
|
-
/** @deprecated Renamed to `logos`; removed in 0.
|
|
97
|
+
/** @deprecated Renamed to `logos`; removed in 0.20.0. */
|
|
98
98
|
platforms?: ConnectButtonLogoInput[];
|
|
99
99
|
/** Whether this user has a live grant, from your server. When set, it
|
|
100
100
|
* decides the Connected state. When omitted, the button shows
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withone/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "One Connect for your app: the button your users press, the two backend routes as one import, and a server client that calls One with the grant. Users keep their connections in One; your app holds only what they granted.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -105,6 +105,11 @@
|
|
|
105
105
|
"import": "./dist/node.esm.js",
|
|
106
106
|
"require": "./dist/node.cjs.js"
|
|
107
107
|
},
|
|
108
|
+
"./config": {
|
|
109
|
+
"types": "./dist/config.d.ts",
|
|
110
|
+
"import": "./dist/config.esm.js",
|
|
111
|
+
"require": "./dist/config.cjs.js"
|
|
112
|
+
},
|
|
108
113
|
"./package.json": "./package.json"
|
|
109
114
|
},
|
|
110
115
|
"peerDependencies": {
|
|
@@ -30,7 +30,7 @@ the server keeps differs.
|
|
|
30
30
|
| Server keeps | one connect key for the app, one id per user | an access and a refresh token per user |
|
|
31
31
|
| Expires | nothing | yes; the app runs a daily refresh job |
|
|
32
32
|
| User revoked | next call throws `reconnect_required` | next refresh throws `refresh_failed` |
|
|
33
|
-
| Call outside the grant | `403`, `blockedByGrant: true` |
|
|
33
|
+
| Call outside the grant | `403`, `blockedByGrant: true` | the same |
|
|
34
34
|
|
|
35
35
|
Use **key mode** unless the human asks for token mode, the app needs the
|
|
36
36
|
bearer token itself, or the app already passes a `tokenStore`. Do not switch
|
|
@@ -39,7 +39,66 @@ key mode; section 10 lists what changes for token mode.
|
|
|
39
39
|
|
|
40
40
|
## 2 - Ask the human for these
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
If the human has the One CLI (2.10.0 or later), create the app from code
|
|
43
|
+
instead of asking for its values.
|
|
44
|
+
|
|
45
|
+
**Before you start.** The CLI must be logged in: `one login` (or
|
|
46
|
+
`one init`) gives the CLI its own One API key, kept in `~/.one`. Never put
|
|
47
|
+
that key in the app's `.env`; the app never needs it. Install the SDK in the
|
|
48
|
+
project first (`npm i @withone/connect`, 0.19.0 or later): the config file
|
|
49
|
+
imports it.
|
|
50
|
+
|
|
51
|
+
**1. Write `one.connect.ts`** at the project root:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { defineConnectApp } from "@withone/connect/config";
|
|
55
|
+
|
|
56
|
+
export default defineConnectApp({
|
|
57
|
+
name: "Acme Assistant", // shown on the connect card
|
|
58
|
+
redirectUris: ["http://localhost:3000/api/one/callback"], // every callback URL, exact
|
|
59
|
+
reason: "Acme Assistant reads your mail and plans your week", // optional, at most 200 characters
|
|
60
|
+
description: "Your inbox and calendar, planned", // optional
|
|
61
|
+
logoUrl: "https://acme.app/logo.png", // optional, square, public https
|
|
62
|
+
homepageUrl: "https://acme.app", // optional
|
|
63
|
+
branding: { panelColor: "#0A0C0B", buttonColor: "#0A0C0B", cobrandEmail: false }, // optional
|
|
64
|
+
ask: { // optional: omit to let users pick from everything they have
|
|
65
|
+
gmail: { access: "custom", actions: ["sendEmail", "listAndViewEmailThreads"] },
|
|
66
|
+
"google-calendar": { access: "full-access" },
|
|
67
|
+
notion: { access: "read-write" },
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Each tool in `ask` is keyed by its slug and takes `access`: `read-only`,
|
|
73
|
+
`read-write` (no deletes), `full-access`, or `custom` with `actions`.
|
|
74
|
+
|
|
75
|
+
**2. Find slugs and action names** with the CLI, which reads One's catalog
|
|
76
|
+
with its own key:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
one connect types # writes one-connect-env.d.ts: every slug, and action names for the tools in ask
|
|
80
|
+
one --agent platforms # every tool slug
|
|
81
|
+
one --agent actions find gmail "send an email" --no-knowledge # an action's id and title
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Use the names `one connect types` wrote, or an action's id
|
|
85
|
+
(`conn_mod_def::…`). A wrong name stops `connect create` before anything
|
|
86
|
+
is made.
|
|
87
|
+
|
|
88
|
+
**3. Run `one --agent connect create`.** It opens a page in the human's
|
|
89
|
+
browser and waits up to 10 minutes, so run it in the background or with a
|
|
90
|
+
long timeout; the link goes to stderr at once, so pass it on if no browser
|
|
91
|
+
opened. The human sees the app as their users will, and approves it. For a
|
|
92
|
+
new app that page makes `ONE_CLIENT_SECRET` and `ONE_CONNECT_KEY` and shows
|
|
93
|
+
them once: tell the human to copy both into `.env`. Once approved, the CLI
|
|
94
|
+
writes `ONE_CLIENT_ID`, `ONE_PERMISSION_SET` and `ONE_REDIRECT_URI` to
|
|
95
|
+
`.env` and prints `"outcome": "created"`.
|
|
96
|
+
|
|
97
|
+
**To change the app later,** edit `one.connect.ts` and run
|
|
98
|
+
`one --agent connect create` again. The human approves the new version in
|
|
99
|
+
the browser (`"outcome": "updated"`); the secrets stay as they are.
|
|
100
|
+
|
|
101
|
+
Otherwise they create the app in the One dashboard: Developers -> Connect -> New app.
|
|
43
102
|
|
|
44
103
|
| Value | Notes |
|
|
45
104
|
|---|---|
|
|
@@ -337,8 +396,8 @@ const accessToken = await oneConnect.getAccessToken(userId); // when the app nee
|
|
|
337
396
|
- When the user revokes access, the next refresh throws `refresh_failed`
|
|
338
397
|
(not `reconnect_required`) and the tokens are cleared. Ask them to connect
|
|
339
398
|
again.
|
|
340
|
-
-
|
|
341
|
-
|
|
399
|
+
- A call outside the grant comes back as in key mode: `403`,
|
|
400
|
+
`blockedByGrant: true`, `refusal: "grant"`.
|
|
342
401
|
|
|
343
402
|
Tokens expire, and two things keep them fresh. Only the first is automatic:
|
|
344
403
|
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Connect app described as code, for `one connect create`.
|
|
3
|
+
*
|
|
4
|
+
* // one.connect.ts
|
|
5
|
+
* import { defineConnectApp } from "@withone/connect/config";
|
|
6
|
+
*
|
|
7
|
+
* export default defineConnectApp({
|
|
8
|
+
* name: "Mastra",
|
|
9
|
+
* redirectUris: ["http://localhost:3000/api/one/callback"],
|
|
10
|
+
* ask: {
|
|
11
|
+
* gmail: { access: "custom", actions: ["sendEmail"] },
|
|
12
|
+
* "google-calendar": { access: "read-only" },
|
|
13
|
+
* linear: { access: "read-write" },
|
|
14
|
+
* },
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* Types and one identity helper; nothing here calls One. `one connect
|
|
18
|
+
* create` reads the file and opens a page in your signed-in browser, where
|
|
19
|
+
* you approve the app (or, on a re-run, its new version). A new app's
|
|
20
|
+
* client secret and connect key are made and shown there once; neither is
|
|
21
|
+
* ever part of this file or passes through the CLI.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Filled in by `one connect types`, which writes a declaration file naming
|
|
26
|
+
* every tool and, for the tools your `ask` names, every action:
|
|
27
|
+
*
|
|
28
|
+
* declare module "@withone/connect/config" {
|
|
29
|
+
* interface ConnectRegistry {
|
|
30
|
+
* tools: "gmail" | "google-calendar" | "linear" | …;
|
|
31
|
+
* actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* Until it runs, any tool slug and any action name type-check.
|
|
36
|
+
*/
|
|
37
|
+
export interface ConnectRegistry {}
|
|
38
|
+
|
|
39
|
+
/** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
|
|
40
|
+
export type ConnectToolSlug = ConnectRegistry extends {
|
|
41
|
+
tools: infer T extends string;
|
|
42
|
+
}
|
|
43
|
+
? T
|
|
44
|
+
: string;
|
|
45
|
+
|
|
46
|
+
/** An action named for the config: its title in camel case ("sendEmail"),
|
|
47
|
+
* from the names `one connect types` wrote for that tool. */
|
|
48
|
+
export type ConnectActionName<T extends string> = ConnectRegistry extends {
|
|
49
|
+
actions: infer A;
|
|
50
|
+
}
|
|
51
|
+
? T extends keyof A
|
|
52
|
+
? A[T] & string
|
|
53
|
+
: string
|
|
54
|
+
: string;
|
|
55
|
+
|
|
56
|
+
/** An action as `actions` takes it: by name, or by its id from One's
|
|
57
|
+
* catalog (`one actions search` prints it). */
|
|
58
|
+
export type ConnectActionRef<T extends string> =
|
|
59
|
+
ConnectActionName<T> | `conn_mod_def::${string}`;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* How much of a tool the app asks for, in the dashboard's words.
|
|
63
|
+
* - `read-only`: reads only.
|
|
64
|
+
* - `read-write`: reads, creates and updates; never deletes.
|
|
65
|
+
* - `full-access`: everything the tool offers.
|
|
66
|
+
*/
|
|
67
|
+
export type ConnectAccess = "read-only" | "read-write" | "full-access";
|
|
68
|
+
|
|
69
|
+
/** One tool's ask: a level, or a hand-picked list of actions. */
|
|
70
|
+
export type ConnectToolAsk<T extends string = string> =
|
|
71
|
+
| { access: ConnectAccess }
|
|
72
|
+
| {
|
|
73
|
+
access: "custom";
|
|
74
|
+
actions: readonly [ConnectActionRef<T>, ...ConnectActionRef<T>[]];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** A colour as `#RRGGBB`. */
|
|
78
|
+
export type ConnectHexColor = `#${string}`;
|
|
79
|
+
|
|
80
|
+
/** How the connect card and its sign-in email look. Omit it to keep
|
|
81
|
+
* One's own look. */
|
|
82
|
+
export interface ConnectAppBranding {
|
|
83
|
+
/** The card's side panel. */
|
|
84
|
+
panelColor: ConnectHexColor;
|
|
85
|
+
/** Authorize, Continue and the other primary buttons. */
|
|
86
|
+
buttonColor: ConnectHexColor;
|
|
87
|
+
/** Whether the sign-in email carries the app's name, logo and colours.
|
|
88
|
+
* `false` when omitted. */
|
|
89
|
+
cobrandEmail?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ConnectAppConfig {
|
|
93
|
+
/** Shown on the connect card and in the user's list of apps. */
|
|
94
|
+
name: string;
|
|
95
|
+
/** Every callback URL the app's routes run on, character for character:
|
|
96
|
+
* local first is fine, then production. */
|
|
97
|
+
redirectUris: readonly [string, ...string[]];
|
|
98
|
+
description?: string;
|
|
99
|
+
/** A public https URL to a square image. */
|
|
100
|
+
logoUrl?: string;
|
|
101
|
+
homepageUrl?: string;
|
|
102
|
+
/** One sentence on the card telling users why the app asks, at most
|
|
103
|
+
* 200 characters. */
|
|
104
|
+
reason?: string;
|
|
105
|
+
branding?: ConnectAppBranding;
|
|
106
|
+
/** The tools the app asks for, keyed by slug. Omit it to let users
|
|
107
|
+
* choose from everything they have connected. */
|
|
108
|
+
ask?: { [T in ConnectToolSlug]?: ConnectToolAsk<T> };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns `config` unchanged. It exists for the editor: the argument is
|
|
113
|
+
* checked against {@link ConnectAppConfig}, and after `one connect types`
|
|
114
|
+
* tool slugs and action names autocomplete.
|
|
115
|
+
*/
|
|
116
|
+
export function defineConnectApp<const C extends ConnectAppConfig>(
|
|
117
|
+
config: C,
|
|
118
|
+
): C {
|
|
119
|
+
return config;
|
|
120
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -83,9 +83,9 @@ export interface ConnectButtonLogo {
|
|
|
83
83
|
imageUrl: string;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.
|
|
86
|
+
/** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.20.0. */
|
|
87
87
|
export type ConnectButtonPlatformInput = ConnectButtonLogoInput;
|
|
88
|
-
/** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.
|
|
88
|
+
/** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.20.0. */
|
|
89
89
|
export type ConnectButtonPlatform = ConnectButtonLogo;
|
|
90
90
|
|
|
91
91
|
export type ConnectButtonVariant = "default" | "accent" | "block";
|
|
@@ -102,7 +102,7 @@ export interface ConnectButtonProps {
|
|
|
102
102
|
* into a "+N" chip. Decoration only; the permission set decides what
|
|
103
103
|
* One asks for. */
|
|
104
104
|
logos?: ConnectButtonLogoInput[];
|
|
105
|
-
/** @deprecated Renamed to `logos`; removed in 0.
|
|
105
|
+
/** @deprecated Renamed to `logos`; removed in 0.20.0. */
|
|
106
106
|
platforms?: ConnectButtonLogoInput[];
|
|
107
107
|
/** Whether this user has a live grant, from your server. When set, it
|
|
108
108
|
* decides the Connected state. When omitted, the button shows
|