attio 0.0.1-experimental.20251104.4 → 0.0.1-experimental.20251105.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.
Files changed (62) hide show
  1. package/README.md +6 -0
  2. package/lib/api/api.js +183 -0
  3. package/lib/api/fetcher.js +69 -0
  4. package/lib/api/schemas.js +89 -0
  5. package/lib/attio-logo.js +24 -0
  6. package/lib/attio.js +22 -39637
  7. package/lib/auth/auth.js +174 -0
  8. package/lib/auth/keychain.js +108 -0
  9. package/lib/build/client/generate-client-entry.js +1 -1
  10. package/lib/build/server/generate-server-entry.js +1 -1
  11. package/lib/build/workflow-block-modules.js +1 -0
  12. package/lib/build.js +1 -1
  13. package/lib/commands/build.js +2 -1
  14. package/lib/commands/dev/boot.js +2 -1
  15. package/lib/commands/dev/graphql-code-gen.js +1 -1
  16. package/lib/commands/dev/graphql-server.js +6 -5
  17. package/lib/commands/dev/onboarding.js +2 -1
  18. package/lib/commands/dev/upload.js +2 -1
  19. package/lib/commands/dev.js +4 -5
  20. package/lib/commands/init/create-project.js +36 -0
  21. package/lib/commands/init.js +41 -9
  22. package/lib/commands/login.js +2 -1
  23. package/lib/commands/logout.js +2 -1
  24. package/lib/commands/logs.js +3 -1
  25. package/lib/commands/version/create.js +4 -1
  26. package/lib/commands/version/list.js +2 -1
  27. package/lib/commands/whoami.js +3 -1
  28. package/lib/constants/settings-files.js +2 -0
  29. package/lib/env.js +5 -0
  30. package/lib/errors.js +1 -0
  31. package/lib/print-errors.js +177 -0
  32. package/lib/sdk-version.js +1 -0
  33. package/lib/spinners/determine-workspace.spinner.js +2 -1
  34. package/lib/spinners/get-app-info.spinner.js +2 -1
  35. package/lib/spinners/get-versions.spinner.js +2 -1
  36. package/lib/template/README.md +21 -0
  37. package/lib/template/biome.jsonc +59 -0
  38. package/lib/template/eslint.attio.config.js +41 -0
  39. package/lib/template/graphql.config.json +9 -0
  40. package/lib/template/package.json +35 -0
  41. package/lib/template/src/app.settings.ts +8 -0
  42. package/lib/template/src/app.ts +10 -0
  43. package/lib/template/src/assets/icon.png +0 -0
  44. package/lib/template/src/get-stoic-quote.server.ts +14 -0
  45. package/lib/template/src/hello-world-action.tsx +18 -0
  46. package/lib/template/src/hello-world-dialog.tsx +27 -0
  47. package/lib/template/src/stoic-quote.tsx +21 -0
  48. package/lib/template/tsconfig.json +42 -0
  49. package/lib/util/assert-app-settings.js +1 -1
  50. package/lib/util/can-write.js +11 -0
  51. package/lib/util/copy-with-replace.js +56 -0
  52. package/lib/util/create-directory.js +27 -0
  53. package/lib/util/exit-with-missing-app-settings.js +1 -1
  54. package/lib/util/exit-with-missing-entry-point.js +1 -1
  55. package/lib/util/find-available-port.js +37 -0
  56. package/lib/util/generate-settings-files.js +1 -1
  57. package/lib/util/hard-exit.js +6 -0
  58. package/lib/util/print-logo.js +5 -0
  59. package/lib/util/realtime.js +1 -1
  60. package/lib/util/spinner.js +61 -0
  61. package/lib/util/text-gradient.js +28 -0
  62. package/package.json +1 -1
package/README.md CHANGED
@@ -1,3 +1,9 @@
1
1
  # Attio App SDK CLI
2
2
 
3
3
  ## ⚠️ This package is experimental and not yet ready for use. ⚠️
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx attio
9
+ ```
package/lib/api/api.js ADDED
@@ -0,0 +1,183 @@
1
+ import { complete, errored, isErrored } from "@attio/fetchable-npm";
2
+ import { APP } from "../env.js";
3
+ import { SDK_VERSION } from "../sdk-version.js";
4
+ import { Fetcher } from "./fetcher.js";
5
+ import { ablyAuthResponseSchema, appInfoSchema, completeBundleUploadSchema, createDevVersionSchema, createVersionSchema, installationSchema, listDevWorkspacesResponseSchema, startUploadSchema, TEST_APP_INFO, TEST_WORKSPACES, tokenResponseSchema, versionsSchema, whoamiSchema, } from "./schemas.js";
6
+ class ApiImpl {
7
+ _fetcher;
8
+ constructor() {
9
+ this._fetcher = new Fetcher();
10
+ }
11
+ async whoami() {
12
+ const result = await this._fetcher.get({
13
+ path: `${APP}/api/auth/whoami`,
14
+ schema: whoamiSchema,
15
+ });
16
+ if (isErrored(result)) {
17
+ return errored({ code: "WHOAMI_ERROR", error: result.error });
18
+ }
19
+ return complete(result.value.user);
20
+ }
21
+ async fetchWorkspaces() {
22
+ if (process.env.NODE_ENV === "test") {
23
+ return complete(TEST_WORKSPACES);
24
+ }
25
+ const result = await this._fetcher.get({
26
+ path: "/dev-workspaces",
27
+ schema: listDevWorkspacesResponseSchema,
28
+ });
29
+ if (isErrored(result)) {
30
+ return errored({ code: "FETCH_WORKSPACES_ERROR", error: result.error });
31
+ }
32
+ return complete(result.value.workspaces);
33
+ }
34
+ async createVersion({ appId, major, cliVersion, }) {
35
+ const result = await this._fetcher.post({
36
+ path: `/apps/${appId}/prod-versions`,
37
+ body: {
38
+ major,
39
+ cli_version: cliVersion,
40
+ sdk_version: SDK_VERSION,
41
+ },
42
+ schema: createVersionSchema,
43
+ });
44
+ if (isErrored(result)) {
45
+ return errored({ code: "CREATE_VERSION_ERROR", error: result.error });
46
+ }
47
+ return result;
48
+ }
49
+ async fetchAppInfo(appSlug) {
50
+ if (process.env.NODE_ENV === "test") {
51
+ return complete(TEST_APP_INFO.app);
52
+ }
53
+ const result = await this._fetcher.get({
54
+ path: `/apps/by-slug/${appSlug}`,
55
+ schema: appInfoSchema,
56
+ });
57
+ if (isErrored(result)) {
58
+ return errored({ code: "FETCH_APP_INFO_ERROR", error: result.error });
59
+ }
60
+ return complete(result.value.app);
61
+ }
62
+ async completeBundleUpload({ appId, devVersionId, bundleId, }) {
63
+ const result = await this._fetcher.post({
64
+ path: `/apps/${appId}/dev-versions/${devVersionId}/bundles/${bundleId}/complete`,
65
+ body: {},
66
+ schema: completeBundleUploadSchema,
67
+ });
68
+ if (isErrored(result)) {
69
+ return errored({ code: "COMPLETE_BUNDLE_UPLOAD_ERROR", error: result.error });
70
+ }
71
+ return complete(undefined);
72
+ }
73
+ async completeProdBundleUpload({ appId, bundleId, major, minor, }) {
74
+ const result = await this._fetcher.post({
75
+ path: `/apps/${appId}/prod-versions/${major}/${minor}/bundles/${bundleId}/complete`,
76
+ body: {},
77
+ schema: completeBundleUploadSchema,
78
+ });
79
+ if (isErrored(result)) {
80
+ return errored({ code: "COMPLETE_PROD_BUNDLE_UPLOAD_ERROR", error: result.error });
81
+ }
82
+ return complete(undefined);
83
+ }
84
+ async startUpload({ appId, devVersionId, }) {
85
+ const result = await this._fetcher.post({
86
+ path: `/apps/${appId}/dev-versions/${devVersionId}/bundles`,
87
+ body: {
88
+ sdk_version: SDK_VERSION,
89
+ },
90
+ schema: startUploadSchema,
91
+ });
92
+ if (isErrored(result)) {
93
+ return errored({ code: "START_UPLOAD_ERROR", error: result.error });
94
+ }
95
+ return result;
96
+ }
97
+ async createDevVersion({ appId, cliVersion, targetWorkspaceId, environmentVariables, }) {
98
+ const result = await this._fetcher.post({
99
+ path: `/apps/${appId}/dev-versions`,
100
+ body: {
101
+ major: 1,
102
+ target_workspace_id: targetWorkspaceId,
103
+ environment_variables: environmentVariables,
104
+ cli_version: cliVersion,
105
+ },
106
+ schema: createDevVersionSchema,
107
+ });
108
+ if (isErrored(result)) {
109
+ return errored({ code: "CREATE_DEV_VERSION_ERROR", error: result.error });
110
+ }
111
+ return result;
112
+ }
113
+ async fetchInstallation({ appId, workspaceId, }) {
114
+ const result = await this._fetcher.get({
115
+ path: `/apps/${appId}/workspace/${workspaceId}/dev-installation`,
116
+ schema: installationSchema,
117
+ });
118
+ if (isErrored(result)) {
119
+ if (result.error.code === "HTTP_ERROR" && result.error.status === 404) {
120
+ return complete(null);
121
+ }
122
+ return errored({ code: "FETCH_INSTALLATION_ERROR", error: result.error });
123
+ }
124
+ return result;
125
+ }
126
+ async fetchVersions(appId) {
127
+ const result = await this._fetcher.get({
128
+ path: `/apps/${appId}/prod-versions`,
129
+ schema: versionsSchema,
130
+ });
131
+ if (isErrored(result)) {
132
+ return errored({ code: "FETCH_VERSIONS_ERROR", error: result.error });
133
+ }
134
+ return complete(result.value.app_prod_versions);
135
+ }
136
+ async exchangeToken({ receivedCode, verifierString, redirectUri, clientId, }) {
137
+ const params = new URLSearchParams();
138
+ params.append("grant_type", "authorization_code");
139
+ params.append("code", receivedCode);
140
+ params.append("client_id", clientId);
141
+ params.append("redirect_uri", redirectUri);
142
+ params.append("code_verifier", verifierString);
143
+ const result = await this._fetcher.post({
144
+ path: `${APP}/oidc/token`,
145
+ body: params,
146
+ schema: tokenResponseSchema,
147
+ authenticated: "Not Authenticated",
148
+ });
149
+ if (isErrored(result)) {
150
+ return errored({ code: "GET_TOKEN_ERROR", error: result.error });
151
+ }
152
+ return result;
153
+ }
154
+ async refreshToken({ refreshToken, clientId, }) {
155
+ const params = new URLSearchParams();
156
+ params.append("grant_type", "refresh_token");
157
+ params.append("refresh_token", refreshToken);
158
+ params.append("client_id", clientId);
159
+ const result = await this._fetcher.post({
160
+ path: `${APP}/oidc/token`,
161
+ body: params,
162
+ schema: tokenResponseSchema,
163
+ });
164
+ if (isErrored(result)) {
165
+ return errored({ code: "REFRESH_TOKEN_ERROR", error: result.error });
166
+ }
167
+ return result;
168
+ }
169
+ async authenticateAbly(tokenParams) {
170
+ const result = await this._fetcher.post({
171
+ path: "/integrations/ably/auth",
172
+ body: {
173
+ token_params: tokenParams,
174
+ },
175
+ schema: ablyAuthResponseSchema,
176
+ });
177
+ if (isErrored(result)) {
178
+ return errored({ code: "ABLY_AUTHENTICATION_ERROR", error: result.error });
179
+ }
180
+ return result;
181
+ }
182
+ }
183
+ export const api = new ApiImpl();
@@ -0,0 +1,69 @@
1
+ import { complete, errored, isErrored } from "@attio/fetchable-npm";
2
+ import { authenticator } from "../auth/auth.js";
3
+ import { API } from "../env.js";
4
+ export class Fetcher {
5
+ async _fetch({ path, fetchOptions, schema, authenticated, }) {
6
+ const tokenResult = authenticated === "Authenticated" ? await authenticator.ensureAuthed() : complete(null);
7
+ if (isErrored(tokenResult)) {
8
+ return errored({
9
+ code: "UNAUTHORIZED",
10
+ error: tokenResult.error,
11
+ });
12
+ }
13
+ const token = tokenResult.value;
14
+ try {
15
+ const response = await fetch(path.startsWith("https") ? path : `${API}${path}`, {
16
+ ...fetchOptions,
17
+ headers: {
18
+ "x-attio-platform": "developer-cli",
19
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
20
+ "Content-Type": "application/json",
21
+ ...fetchOptions.headers,
22
+ },
23
+ });
24
+ if (!response.ok) {
25
+ const errorText = await response.text();
26
+ return errored({
27
+ code: "HTTP_ERROR",
28
+ status: response.status,
29
+ message: errorText,
30
+ });
31
+ }
32
+ const data = await response.json();
33
+ const result = schema.safeParse(data);
34
+ if (!result.success) {
35
+ return errored({
36
+ code: "INVALID_RESPONSE",
37
+ message: result.error.message,
38
+ });
39
+ }
40
+ return complete(result.data);
41
+ }
42
+ catch (error) {
43
+ return errored({
44
+ code: "NETWORK_ERROR",
45
+ message: error instanceof Error ? error.message : String(error),
46
+ });
47
+ }
48
+ }
49
+ async get({ path, schema, headers = {}, authenticated = "Authenticated", }) {
50
+ return this._fetch({ path, fetchOptions: { method: "GET", headers }, schema, authenticated });
51
+ }
52
+ async post({ path, body, schema, headers = {}, authenticated = "Authenticated", }) {
53
+ return this._fetch({
54
+ path,
55
+ fetchOptions: {
56
+ method: "POST",
57
+ body: body instanceof URLSearchParams ? body.toString() : JSON.stringify(body),
58
+ headers: {
59
+ "Content-Type": body instanceof URLSearchParams
60
+ ? "application/x-www-form-urlencoded"
61
+ : "application/json",
62
+ ...headers,
63
+ },
64
+ },
65
+ schema,
66
+ authenticated,
67
+ });
68
+ }
69
+ }
@@ -0,0 +1,89 @@
1
+ import { z } from "zod";
2
+ export const whoamiSchema = z.object({
3
+ user: z.object({
4
+ id: z.string(),
5
+ email_address: z.string(),
6
+ name: z.object({
7
+ full: z.string(),
8
+ }),
9
+ }),
10
+ });
11
+ export const workspaceResponseSchema = z.object({
12
+ workspace_id: z.string().uuid(),
13
+ slug: z.string(),
14
+ name: z.string(),
15
+ logo_url: z.string().nullable(),
16
+ });
17
+ export const TEST_WORKSPACES = [
18
+ workspaceResponseSchema.parse({
19
+ workspace_id: "a85e3bcf-a9a2-4df9-9e92-e708bf98d238",
20
+ slug: "test-slug",
21
+ name: "Test Workspace",
22
+ logo_url: null,
23
+ }),
24
+ ];
25
+ export const listDevWorkspacesResponseSchema = z.object({
26
+ workspaces: z.array(workspaceResponseSchema),
27
+ });
28
+ export const createVersionSchema = z.object({
29
+ app_id: z.string().uuid(),
30
+ major: z.number(),
31
+ minor: z.number(),
32
+ app_prod_version_bundle_id: z.string(),
33
+ client_bundle_upload_url: z.string(),
34
+ server_bundle_upload_url: z.string(),
35
+ });
36
+ export const appInfoSchema = z.object({
37
+ app: z.object({
38
+ app_id: z.string().uuid(),
39
+ title: z.string(),
40
+ }),
41
+ });
42
+ export const TEST_APP_INFO = appInfoSchema.parse({
43
+ app: {
44
+ app_id: "0cbafb09-2ef7-473c-a048-08a5d190a395",
45
+ title: "Test App",
46
+ },
47
+ });
48
+ export const completeBundleUploadSchema = z.object({
49
+ success: z.literal(true),
50
+ });
51
+ export const startUploadSchema = z.object({
52
+ app_dev_version_id: z.string().uuid(),
53
+ app_dev_version_bundle_id: z.string().uuid(),
54
+ client_bundle_upload_url: z.string().url(),
55
+ server_bundle_upload_url: z.string().url(),
56
+ });
57
+ export const createDevVersionSchema = z.object({
58
+ app_id: z.string().uuid(),
59
+ app_dev_version_id: z.string(),
60
+ });
61
+ export const installationSchema = z.object({
62
+ app_id: z.string().uuid(),
63
+ workspace_id: z.string(),
64
+ });
65
+ export const versionSchema = z.object({
66
+ major: z.number(),
67
+ minor: z.number(),
68
+ created_at: z.string(),
69
+ released_at: z.string().nullable().optional(),
70
+ is_published: z.boolean(),
71
+ num_installations: z.number(),
72
+ publication_status: z.enum(["private", "in-review", "published", "rejected"]),
73
+ });
74
+ export const versionsSchema = z.object({
75
+ app_prod_versions: z.array(versionSchema),
76
+ });
77
+ export const tokenResponseSchema = z.object({
78
+ access_token: z.string(),
79
+ token_type: z.literal("Bearer"),
80
+ refresh_token: z.string(),
81
+ expires_in: z.number(),
82
+ });
83
+ export const ablyAuthResponseSchema = z.object({
84
+ capability: z.string(),
85
+ clientId: z.string().optional(),
86
+ expires: z.number(),
87
+ issued: z.number(),
88
+ token: z.string(),
89
+ });
@@ -0,0 +1,24 @@
1
+ export const attioLogo = `
2
+ ▟██▛╲
3
+ ▟██▛ ╲
4
+ ▟██▛ ╱
5
+ ▟██▛ ╱
6
+ ▟██▛ ╱ ▟█▛╲
7
+ 🬇██▛ ╱ ▟█▛ ╲
8
+ ▜█▙ ╱ 🬇██▙ ╱
9
+ ▜█▙╱ ▜█▙╱
10
+ `;
11
+ export const attioName = `
12
+
13
+
14
+ ▟ ▟ ██
15
+ ██ ██
16
+ ▟███▙▟ ████████████ ██ ▟███▙
17
+ ▟█ ██ ██ ██ ██ █ █
18
+ ██ ██ ██ ██ ██ █ █
19
+ ▜████▛█ ▜███ ▜███ ██ ▜███▛
20
+ `;
21
+ export const attioLogoAndName = `${attioLogo
22
+ .split("\n")
23
+ .map((line, i) => i < attioLogo.split("\n").length - 1 ? `${line} ${attioName.split("\n")[i] || ""}` : line)
24
+ .join("\n")}`;