attio 0.0.1-experimental.20250409 → 0.0.1-experimental.20250425

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/lib/api/api.js CHANGED
@@ -2,7 +2,7 @@ import { Fetcher } from "./fetcher.js";
2
2
  import { APP } from "../env.js";
3
3
  import { isErrored, complete, errored } from "@attio/fetchable";
4
4
  import { whoamiSchema, listDevWorkspacesResponseSchema, createVersionSchema, appInfoSchema, completeBundleUploadSchema, startUploadSchema, createDevVersionSchema, installationSchema, versionsSchema, TEST_WORKSPACES, TEST_APP_INFO, tokenResponseSchema, } from "./schemas.js";
5
- class CliApi {
5
+ class ApiImpl {
6
6
  _fetcher;
7
7
  constructor() {
8
8
  this._fetcher = new Fetcher();
@@ -163,4 +163,4 @@ class CliApi {
163
163
  return result;
164
164
  }
165
165
  }
166
- export const API = new CliApi();
166
+ export const api = new ApiImpl();
@@ -1,9 +1,9 @@
1
1
  import { complete, errored, isErrored } from "@attio/fetchable";
2
2
  import { API } from "../env.js";
3
- import { Authenticator } from "../auth/auth.js";
3
+ import { authenticator } from "../auth/auth.js";
4
4
  export class Fetcher {
5
5
  async _fetch({ path, fetchOptions, schema, authenticated, }) {
6
- const tokenResult = authenticated === "Authenticated" ? await Authenticator.ensureAuthed() : complete(null);
6
+ const tokenResult = authenticated === "Authenticated" ? await authenticator.ensureAuthed() : complete(null);
7
7
  if (isErrored(tokenResult)) {
8
8
  return errored({
9
9
  code: "UNAUTHORIZED",
package/lib/auth/auth.js CHANGED
@@ -4,20 +4,20 @@ import { randomBytes, createHash } from "crypto";
4
4
  import open from "open";
5
5
  import { APP } from "../env.js";
6
6
  import { findAvailablePort } from "../util/find-available-port.js";
7
- import { Keychain } from "./keychain.js";
8
- import { API } from "../api/api.js";
7
+ import { keychain } from "./keychain.js";
8
+ import { api } from "../api/api.js";
9
9
  import { isErrored, complete, errored } from "@attio/fetchable";
10
10
  import { printFetcherError, printKeychainError } from "../print-errors.js";
11
11
  class AuthenticatorImpl {
12
12
  clientId = "f881c6f1-82d7-48a5-a581-649596167845";
13
13
  refreshTimeout = null;
14
14
  async ensureAuthed() {
15
- const existingTokenResult = await Keychain.load();
15
+ const existingTokenResult = await keychain.load();
16
16
  if (isErrored(existingTokenResult)) {
17
17
  return this.promptToAuthenticate();
18
18
  }
19
19
  const existingToken = existingTokenResult.value;
20
- if (existingToken === null || existingToken.expires_in < Date.now()) {
20
+ if (existingToken === null || existingToken.expires_at < Date.now()) {
21
21
  return this.promptToAuthenticate();
22
22
  }
23
23
  this.scheduleRefresh(existingToken);
@@ -31,12 +31,12 @@ class AuthenticatorImpl {
31
31
  return this.authenticate();
32
32
  }
33
33
  async authenticate() {
34
- const existingTokenResult = await Keychain.load();
34
+ const existingTokenResult = await keychain.load();
35
35
  if (isErrored(existingTokenResult)) {
36
36
  return existingTokenResult;
37
37
  }
38
38
  const existingToken = existingTokenResult.value;
39
- if (existingToken !== null && existingToken.expires_in > Date.now()) {
39
+ if (existingToken !== null && existingToken.expires_at > Date.now()) {
40
40
  return complete(existingToken.access_token);
41
41
  }
42
42
  const verifier = randomBytes(32);
@@ -64,7 +64,7 @@ class AuthenticatorImpl {
64
64
  if (!receivedCode) {
65
65
  resolveAsyncResult(errored({ code: "NO_AUTHORIZATION_CODE" }));
66
66
  }
67
- const tokenResult = await API.exchangeToken({
67
+ const tokenResult = await api.exchangeToken({
68
68
  receivedCode,
69
69
  verifierString,
70
70
  redirectUri,
@@ -106,9 +106,9 @@ class AuthenticatorImpl {
106
106
  access_token: token.access_token,
107
107
  refresh_token: token.refresh_token,
108
108
  token_type: token.token_type,
109
- expires_in: token.expires_in,
109
+ expires_at: Date.now() + token.expires_in * 1000,
110
110
  };
111
- const saveResult = await Keychain.save(keychainToken);
111
+ const saveResult = await keychain.save(keychainToken);
112
112
  if (isErrored(saveResult)) {
113
113
  return saveResult;
114
114
  }
@@ -123,10 +123,10 @@ class AuthenticatorImpl {
123
123
  if (this.refreshTimeout !== null) {
124
124
  clearTimeout(this.refreshTimeout);
125
125
  }
126
- this.refreshTimeout = setTimeout(async () => await this.refreshToken(token), Math.max(0, token.expires_in - Date.now() - 5_000));
126
+ this.refreshTimeout = setTimeout(async () => await this.refreshToken(token), Math.max(0, token.expires_at - Date.now() - 5_000));
127
127
  }
128
128
  async refreshToken(token) {
129
- const refreshTokenResult = await API.refreshToken({
129
+ const refreshTokenResult = await api.refreshToken({
130
130
  refreshToken: token.refresh_token,
131
131
  clientId: this.clientId,
132
132
  });
@@ -139,14 +139,21 @@ class AuthenticatorImpl {
139
139
  access_token: refreshedToken.access_token,
140
140
  refresh_token: refreshedToken.refresh_token,
141
141
  token_type: refreshedToken.token_type,
142
- expires_in: refreshedToken.expires_in,
142
+ expires_at: Date.now() + refreshedToken.expires_in * 1000,
143
143
  };
144
- const saveResult = await Keychain.save(keychainToken);
144
+ const saveResult = await keychain.save(keychainToken);
145
145
  if (isErrored(saveResult)) {
146
146
  printKeychainError(saveResult.error);
147
147
  return;
148
148
  }
149
149
  this.scheduleRefresh(keychainToken);
150
150
  }
151
+ async logout() {
152
+ if (this.refreshTimeout !== null) {
153
+ clearTimeout(this.refreshTimeout);
154
+ this.refreshTimeout = null;
155
+ }
156
+ await keychain.delete();
157
+ }
151
158
  }
152
- export const Authenticator = new AuthenticatorImpl();
159
+ export const authenticator = new AuthenticatorImpl();
@@ -1,10 +1,10 @@
1
- import { complete, errored } from "@attio/fetchable";
1
+ import { complete, errored, isErrored } from "@attio/fetchable";
2
2
  import { z } from "zod";
3
3
  const authTokenSchema = z.object({
4
4
  access_token: z.string(),
5
5
  refresh_token: z.string(),
6
6
  token_type: z.literal("Bearer"),
7
- expires_in: z.number(),
7
+ expires_at: z.number(),
8
8
  });
9
9
  class KeytarKeychain {
10
10
  SERVICE_NAME = "attio-cli";
@@ -43,17 +43,28 @@ class KeytarKeychain {
43
43
  jsonToken = JSON.parse(unparsedToken);
44
44
  }
45
45
  catch (error) {
46
- return errored({
47
- code: "LOAD_KEYCHAIN_ERROR",
48
- error,
49
- });
46
+ const deleteResult = await this.delete();
47
+ if (isErrored(deleteResult)) {
48
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error: deleteResult.error });
49
+ }
50
+ console.debug("Wiped keychain entry due to invalid JSON");
51
+ return complete(null);
50
52
  }
51
53
  const parsedToken = authTokenSchema.safeParse(jsonToken);
52
54
  if (!parsedToken.success) {
53
- return errored({
54
- code: "LOAD_KEYCHAIN_ERROR",
55
- error: parsedToken.error,
56
- });
55
+ const deleteResult = await this.delete();
56
+ if (isErrored(deleteResult)) {
57
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error: deleteResult.error });
58
+ }
59
+ console.debug("Wiped keychain entry due to schema mismatch");
60
+ return complete(null);
61
+ }
62
+ if (parsedToken.data.expires_at < Date.now() + 60_000 * 5) {
63
+ const deleteResult = await this.delete();
64
+ if (isErrored(deleteResult)) {
65
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error: deleteResult.error });
66
+ }
67
+ return complete(null);
57
68
  }
58
69
  return complete(parsedToken.data);
59
70
  }
@@ -76,7 +87,7 @@ class TestKeychain {
76
87
  access_token: "TEST",
77
88
  refresh_token: "TEST",
78
89
  token_type: "Bearer",
79
- expires_in: Number.MAX_SAFE_INTEGER,
90
+ expires_at: Number.MAX_SAFE_INTEGER,
80
91
  };
81
92
  async save(_token) {
82
93
  return complete(undefined);
@@ -88,4 +99,4 @@ class TestKeychain {
88
99
  return complete(undefined);
89
100
  }
90
101
  }
91
- export const Keychain = process.env.NODE_ENV === "test" ? new TestKeychain() : new KeytarKeychain();
102
+ export const keychain = process.env.NODE_ENV === "test" ? new TestKeychain() : new KeytarKeychain();
@@ -5,9 +5,15 @@ import { spinnerify } from "../util/spinner.js";
5
5
  import { isErrored } from "@attio/fetchable";
6
6
  import { printJsError, printTsError } from "../util/typescript.js";
7
7
  import { printBuildContextError } from "./dev/prepare-build-contexts.js";
8
+ import { graphqlCodeGen } from "./dev/graphql-code-gen.js";
9
+ import { hardExit } from "../util/hard-exit.js";
8
10
  export const build = new Command("build")
9
11
  .description("Build your Attio extension locally")
10
12
  .action(async () => {
13
+ const generateGraphqlOperationsResult = await spinnerify("Generating GraphQL types...", "GraphQL types generated successfully", graphqlCodeGen);
14
+ if (isErrored(generateGraphqlOperationsResult)) {
15
+ hardExit(generateGraphqlOperationsResult.error.error.toString());
16
+ }
11
17
  const tsResult = await spinnerify("Validating TypeScript...", "TypeScript validation passed", validateTypeScript);
12
18
  if (isErrored(tsResult)) {
13
19
  switch (tsResult.error.code) {
@@ -3,7 +3,7 @@ import { printFetcherError, printCliVersionError, printPackageJsonError, printDe
3
3
  import { getAppSlugFromPackageJson } from "../../spinners/get-app-slug-from-package-json.js";
4
4
  import { determineWorkspace } from "../../spinners/determine-workspace.spinner.js";
5
5
  import { loadAttioCliVersion } from "../../util/load-attio-cli-version.js";
6
- import { API } from "../../api/api.js";
6
+ import { api } from "../../api/api.js";
7
7
  import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
8
8
  import { loadEnv } from "../../util/load-env.js";
9
9
  export async function boot({ workspaceSlug }) {
@@ -32,7 +32,7 @@ export async function boot({ workspaceSlug }) {
32
32
  process.exit(1);
33
33
  }
34
34
  const cliVersion = cliVersionResult.value;
35
- const devVersionResult = await API.createDevVersion({
35
+ const devVersionResult = await api.createDevVersion({
36
36
  appId: appInfo.app_id,
37
37
  cliVersion,
38
38
  targetWorkspaceId: workspace.workspace_id,
@@ -1,39 +1,65 @@
1
1
  import chokidar from "chokidar";
2
2
  import { readFileSync } from "fs";
3
3
  import { parse } from "graphql";
4
+ import { complete, errored, isErrored } from "@attio/fetchable";
4
5
  import { findNodeModulesPath } from "../../util/find-node-modules-path.js";
5
6
  import { generateOperations } from "../../graphql/generate-operations.js";
6
7
  import { GraphQLError } from "../../graphql/graphql-error.js";
7
8
  import { hardExit } from "../../util/hard-exit.js";
8
- export function graphqlCodeGen(onSuccess) {
9
+ export async function graphqlCodeGen() {
10
+ try {
11
+ const schemaPath = await findNodeModulesPath(["schema.graphql"]);
12
+ if (!schemaPath) {
13
+ hardExit("No schema.graphql found in node_modules");
14
+ }
15
+ const schema = parse(readFileSync(schemaPath, "utf8"));
16
+ await generateOperations(".", schema);
17
+ return complete(true);
18
+ }
19
+ catch (error) {
20
+ if (error instanceof GraphQLError) {
21
+ return errored({ code: "GRAPHQL_ERROR", error: error });
22
+ }
23
+ else {
24
+ return errored({
25
+ code: "GRAPHQL_CODEGEN_ERROR",
26
+ error: error instanceof Error ? error.message : "GraphQL code generation failed",
27
+ });
28
+ }
29
+ }
30
+ }
31
+ export function watchGraphqlCodegen(onSuccess, onError) {
9
32
  const watcher = chokidar.watch(["**/*.graphql", "**/*.gql"], {
10
33
  ignored: ["**/node_modules/**", "**/dist/**"],
11
34
  cwd: ".",
12
35
  });
13
- async function handleSchemaAndGenerate() {
14
- try {
15
- const schemaPath = await findNodeModulesPath(["schema.graphql"]);
16
- if (!schemaPath) {
17
- hardExit("No schema.graphql found in node_modules");
18
- }
19
- const schema = parse(readFileSync(schemaPath, "utf8"));
20
- await generateOperations(".", schema);
21
- onSuccess?.();
22
- }
23
- catch (error) {
24
- if (error instanceof GraphQLError) {
25
- process.stderr.write(error.toString());
36
+ watcher.on("ready", async () => {
37
+ const result = await graphqlCodeGen();
38
+ if (isErrored(result)) {
39
+ if (result.error.code === "GRAPHQL_ERROR") {
40
+ onError?.(result.error.error.toString());
26
41
  }
27
42
  else {
28
- hardExit(error instanceof Error ? error.message : "GraphQL code generation failed");
43
+ hardExit(result.error.error.toString());
29
44
  }
30
45
  }
31
- }
32
- watcher.on("ready", () => {
33
- handleSchemaAndGenerate();
34
- watcher.on("all", (event, path) => {
46
+ else {
47
+ onSuccess?.();
48
+ }
49
+ watcher.on("all", async (event, path) => {
35
50
  if (event === "add" || event === "change" || event === "unlink") {
36
- handleSchemaAndGenerate();
51
+ const result = await graphqlCodeGen();
52
+ if (isErrored(result)) {
53
+ if (result.error.code === "GRAPHQL_ERROR") {
54
+ onError?.(result.error.error.toString());
55
+ }
56
+ else {
57
+ hardExit(result.error.error.toString());
58
+ }
59
+ }
60
+ else {
61
+ onSuccess?.();
62
+ }
37
63
  }
38
64
  });
39
65
  });
@@ -1,7 +1,7 @@
1
1
  import { listenForKey } from "../../util/listen-for-key.js";
2
2
  import open from "open";
3
3
  import { APP } from "../../env.js";
4
- import { API } from "../../api/api.js";
4
+ import { api } from "../../api/api.js";
5
5
  import { isErrored } from "@attio/fetchable";
6
6
  function prompt() {
7
7
  process.stdout.write(`🚨 IMPORTANT: You will need to install your app in your workspace. Press "i" to open the app settings page, and then click "Install".\n\n`);
@@ -11,7 +11,7 @@ export function onboarding({ appId, appSlug, workspace, }) {
11
11
  open(`${APP}/${workspace.slug}/settings/apps/${appSlug}`);
12
12
  });
13
13
  const poll = async () => {
14
- const installationResult = await API.fetchInstallation({
14
+ const installationResult = await api.fetchInstallation({
15
15
  appId,
16
16
  workspaceId: workspace.workspace_id,
17
17
  });
@@ -23,7 +23,7 @@ export function onboarding({ appId, appSlug, workspace, }) {
23
23
  prompt();
24
24
  while (!installation) {
25
25
  await new Promise((resolve) => setTimeout(resolve, 60_000));
26
- const installationResult = await API.fetchInstallation({
26
+ const installationResult = await api.fetchInstallation({
27
27
  appId,
28
28
  workspaceId: workspace.workspace_id,
29
29
  });
@@ -1,11 +1,11 @@
1
1
  import notifier from "node-notifier";
2
2
  import { spinnerify } from "../../util/spinner.js";
3
3
  import { uploadBundle } from "../../util/upload-bundle.js";
4
- import { API } from "../../api/api.js";
4
+ import { api } from "../../api/api.js";
5
5
  import { isErrored, complete, combineAsync } from "@attio/fetchable";
6
6
  export async function upload({ contents, devVersionId, appId, }) {
7
7
  return await spinnerify("Uploading...", () => `Upload complete at ${new Date().toLocaleTimeString()}`, async () => {
8
- const startUploadResult = await API.startUpload({
8
+ const startUploadResult = await api.startUpload({
9
9
  appId,
10
10
  devVersionId,
11
11
  });
@@ -21,7 +21,7 @@ export async function upload({ contents, devVersionId, appId, }) {
21
21
  if (isErrored(uploadResults)) {
22
22
  return uploadResults;
23
23
  }
24
- const completeBundleUploadResult = await API.completeBundleUpload({
24
+ const completeBundleUploadResult = await api.completeBundleUpload({
25
25
  appId,
26
26
  devVersionId,
27
27
  bundleId,
@@ -3,7 +3,7 @@ import { z } from "zod";
3
3
  import chalk from "chalk";
4
4
  import { printMessage } from "../util/print-message.js";
5
5
  import { validateTypeScript } from "./dev/validate-typescript.js";
6
- import { graphqlCodeGen } from "./dev/graphql-code-gen.js";
6
+ import { watchGraphqlCodegen } from "./dev/graphql-code-gen.js";
7
7
  import { bundleJavaScript } from "./dev/bundle-javascript.js";
8
8
  import { boot } from "./dev/boot.js";
9
9
  import { onboarding } from "./dev/onboarding.js";
@@ -76,7 +76,17 @@ export const dev = new Command("dev")
76
76
  notifyTsErrors(errors);
77
77
  });
78
78
  cleanupFunctions.push(cleanupTs);
79
- const cleanupGraphqlCodeGen = graphqlCodeGen(triggerTs);
79
+ let hasGraphqlCodeGenError = false;
80
+ const cleanupGraphqlCodeGen = watchGraphqlCodegen(() => {
81
+ if (hasGraphqlCodeGenError) {
82
+ process.stdout.write(`${chalk.green("✓")} GraphQL errors fixed\n`);
83
+ hasGraphqlCodeGenError = false;
84
+ }
85
+ triggerTs();
86
+ }, (error) => {
87
+ hasGraphqlCodeGenError = true;
88
+ process.stderr.write(error);
89
+ });
80
90
  cleanupFunctions.push(cleanupGraphqlCodeGen);
81
91
  let haveJsErrors = false;
82
92
  const cleanupJs = bundleJavaScript(async (contents) => {
@@ -1,11 +1,11 @@
1
1
  import { Command } from "commander";
2
- import { Authenticator } from "../auth/auth.js";
2
+ import { authenticator } from "../auth/auth.js";
3
3
  import { isErrored } from "@attio/fetchable";
4
4
  import { printAuthenticationError } from "../print-errors.js";
5
5
  export const login = new Command("login")
6
6
  .description("Authenticate with Attio")
7
7
  .action(async () => {
8
- const result = await Authenticator.authenticate();
8
+ const result = await authenticator.authenticate();
9
9
  if (isErrored(result)) {
10
10
  printAuthenticationError(result.error);
11
11
  process.exit(1);
@@ -1,9 +1,9 @@
1
1
  import { Command } from "commander";
2
- import { Keychain } from "../auth/keychain.js";
2
+ import { keychain } from "../auth/keychain.js";
3
3
  import { printKeychainError } from "../print-errors.js";
4
4
  import { isErrored } from "@attio/fetchable";
5
5
  export const logout = new Command("logout").description("Log out from Attio").action(async () => {
6
- const result = await Keychain.delete();
6
+ const result = await keychain.delete();
7
7
  if (isErrored(result)) {
8
8
  printKeychainError(result.error);
9
9
  process.exit(1);
@@ -10,7 +10,7 @@ import { combineAsync, isErrored } from "@attio/fetchable";
10
10
  import { getAppSlugFromPackageJson } from "../../spinners/get-app-slug-from-package-json.js";
11
11
  import { bundleJavaScript } from "./create/bundle-javascript.js";
12
12
  import { printBuildContextError } from "../dev/prepare-build-contexts.js";
13
- import { API } from "../../api/api.js";
13
+ import { api } from "../../api/api.js";
14
14
  import { printJsError } from "../../util/typescript.js";
15
15
  export const versionCreate = new Command("create")
16
16
  .description("Create a new unpublished version of your Attio app")
@@ -59,7 +59,7 @@ export const versionCreate = new Command("create")
59
59
  process.exit(1);
60
60
  }
61
61
  const cliVersion = cliVersionResult.value;
62
- const versionResult = await API.createVersion({
62
+ const versionResult = await api.createVersion({
63
63
  appId: appInfo.app_id,
64
64
  major: versions.length === 0
65
65
  ? 1
@@ -86,7 +86,7 @@ export const versionCreate = new Command("create")
86
86
  process.exit(1);
87
87
  }
88
88
  const version = versionResult.value;
89
- const signingResult = await spinnerify("Signing bundles...", "Bundles signed", async () => await API.completeProdBundleUpload({
89
+ const signingResult = await spinnerify("Signing bundles...", "Bundles signed", async () => await api.completeProdBundleUpload({
90
90
  appId: appInfo.app_id,
91
91
  major: version.major,
92
92
  minor: version.minor,
@@ -1,18 +1,18 @@
1
1
  import { Command } from "commander";
2
- import { Keychain } from "../auth/keychain.js";
3
- import { API } from "../api/api.js";
2
+ import { keychain } from "../auth/keychain.js";
3
+ import { api } from "../api/api.js";
4
4
  import { isErrored } from "@attio/fetchable";
5
5
  import { printFetcherError } from "../print-errors.js";
6
6
  export const whoami = new Command()
7
7
  .name("whoami")
8
8
  .description("Identify the current user")
9
9
  .action(async () => {
10
- const tokenResult = await Keychain.load();
10
+ const tokenResult = await keychain.load();
11
11
  if (isErrored(tokenResult) || tokenResult.value === null) {
12
12
  process.stdout.write("🔒 Not logged in.\n");
13
13
  process.exit(0);
14
14
  }
15
- const result = await API.whoami();
15
+ const result = await api.whoami();
16
16
  if (isErrored(result)) {
17
17
  printFetcherError("Error fetching user", result.error);
18
18
  process.exit(1);
@@ -1,9 +1,9 @@
1
1
  import { select } from "@inquirer/prompts";
2
2
  import { spinnerify } from "../util/spinner.js";
3
- import { API } from "../api/api.js";
3
+ import { api } from "../api/api.js";
4
4
  import { isErrored, complete, errored } from "@attio/fetchable";
5
5
  export async function determineWorkspace(workspaceSlug) {
6
- const workspacesResult = await spinnerify("Loading workspaces...", "Workspaces loaded", async () => await API.fetchWorkspaces());
6
+ const workspacesResult = await spinnerify("Loading workspaces...", "Workspaces loaded", async () => await api.fetchWorkspaces());
7
7
  if (isErrored(workspacesResult)) {
8
8
  return workspacesResult;
9
9
  }
@@ -1,7 +1,7 @@
1
- import { API } from "../api/api.js";
1
+ import { api } from "../api/api.js";
2
2
  import { spinnerify } from "../util/spinner.js";
3
3
  export async function getAppInfo(appSlug) {
4
4
  return await spinnerify("Loading app information...", (app) => `App found: ${app.title}`, async () => {
5
- return await API.fetchAppInfo(appSlug);
5
+ return await api.fetchAppInfo(appSlug);
6
6
  });
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import { spinnerify } from "../util/spinner.js";
2
- import { API } from "../api/api.js";
2
+ import { api } from "../api/api.js";
3
3
  export async function getVersions(appInfo) {
4
- return await spinnerify("Loading versions...", "Versions loaded", async () => await API.fetchVersions(appInfo.app_id));
4
+ return await spinnerify("Loading versions...", "Versions loaded", async () => await api.fetchVersions(appInfo.app_id));
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attio",
3
- "version": "0.0.1-experimental.20250409",
3
+ "version": "0.0.1-experimental.20250425",
4
4
  "bin": "lib/attio.js",
5
5
  "type": "module",
6
6
  "files": [