@supalytics/cli 0.3.2 → 0.3.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supalytics/cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "CLI for Supalytics web analytics",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.ts CHANGED
@@ -61,11 +61,11 @@ export async function query(
61
61
  const sites = await getSites();
62
62
  if (sites.length === 0) {
63
63
  throw new Error(
64
- "No sites configured. Run `supalytics login <api-key>` to add a site."
64
+ "No sites configured. Run `supalytics login` to authenticate."
65
65
  );
66
66
  }
67
67
  throw new Error(
68
- `No API key found for '${site}'. Available sites: ${sites.join(", ")}`
68
+ `Not authenticated or site '${site}' not found. Available sites: ${sites.join(", ")}`
69
69
  );
70
70
  }
71
71
 
@@ -75,7 +75,7 @@ export async function query(
75
75
  Authorization: `Bearer ${apiKey}`,
76
76
  "Content-Type": "application/json",
77
77
  },
78
- body: JSON.stringify(request),
78
+ body: JSON.stringify({ ...request, domain: site }),
79
79
  });
80
80
 
81
81
  if (!response.ok) {
@@ -169,15 +169,15 @@ export async function listEvents(
169
169
  const sites = await getSites();
170
170
  if (sites.length === 0) {
171
171
  throw new Error(
172
- "No sites configured. Run `supalytics login <api-key>` to add a site."
172
+ "No sites configured. Run `supalytics login` to authenticate."
173
173
  );
174
174
  }
175
175
  throw new Error(
176
- `No API key found for '${site}'. Available sites: ${sites.join(", ")}`
176
+ `Not authenticated or site '${site}' not found. Available sites: ${sites.join(", ")}`
177
177
  );
178
178
  }
179
179
 
180
- const params = new URLSearchParams({ period, limit: String(limit) });
180
+ const params = new URLSearchParams({ period, limit: String(limit), domain: site });
181
181
  if (isDev) {
182
182
  params.set("is_dev", "true");
183
183
  }
@@ -205,10 +205,10 @@ export async function getEventProperties(
205
205
  const apiKey = await getApiKeyForSite(site);
206
206
 
207
207
  if (!apiKey) {
208
- throw new Error(`No API key found for '${site}'.`);
208
+ throw new Error(`Not authenticated. Run \`supalytics login\` first.`);
209
209
  }
210
210
 
211
- const params = new URLSearchParams({ period });
211
+ const params = new URLSearchParams({ period, domain: site });
212
212
  if (isDev) {
213
213
  params.set("is_dev", "true");
214
214
  }
@@ -240,13 +240,14 @@ export async function getPropertyBreakdown(
240
240
  const apiKey = await getApiKeyForSite(site);
241
241
 
242
242
  if (!apiKey) {
243
- throw new Error(`No API key found for '${site}'.`);
243
+ throw new Error(`Not authenticated. Run \`supalytics login\` first.`);
244
244
  }
245
245
 
246
246
  const params = new URLSearchParams({
247
247
  period,
248
248
  limit: String(limit),
249
249
  include_revenue: String(includeRevenue),
250
+ domain: site,
250
251
  });
251
252
  if (isDev) {
252
253
  params.set("is_dev", "true");
@@ -303,20 +304,19 @@ export async function getRealtime(site: string, isDev: boolean = false): Promise
303
304
  const sites = await getSites();
304
305
  if (sites.length === 0) {
305
306
  throw new Error(
306
- "No sites configured. Run `supalytics login <api-key>` to add a site."
307
+ "No sites configured. Run `supalytics login` to authenticate."
307
308
  );
308
309
  }
309
310
  throw new Error(
310
- `No API key found for '${site}'. Available sites: ${sites.join(", ")}`
311
+ `Not authenticated or site '${site}' not found. Available sites: ${sites.join(", ")}`
311
312
  );
312
313
  }
313
314
 
314
- const params = new URLSearchParams();
315
+ const params = new URLSearchParams({ domain: site });
315
316
  if (isDev) {
316
317
  params.set("is_dev", "true");
317
318
  }
318
- const url = params.toString() ? `${API_BASE}/v1/realtime?${params}` : `${API_BASE}/v1/realtime`;
319
- const response = await fetch(url, {
319
+ const response = await fetch(`${API_BASE}/v1/realtime?${params}`, {
320
320
  headers: { Authorization: `Bearer ${apiKey}` },
321
321
  });
322
322
 
@@ -64,7 +64,7 @@ export const initCommand = new Command("init")
64
64
  console.log(chalk.dim("Creating site..."));
65
65
 
66
66
  const result = await createSiteViaApi(auth.accessToken, identifier);
67
- await addSiteWithId(result.site.domain, result.apiKey.key, result.site.site_id, result.site.id);
67
+ await addSiteWithId(result.site.domain, result.site.site_id, result.site.id);
68
68
 
69
69
  console.log(chalk.green(`✓ Created ${result.site.domain}`));
70
70
 
@@ -19,7 +19,6 @@ interface DeviceCodeResponse {
19
19
  interface TokenResponse {
20
20
  access_token: string;
21
21
  token_type: string;
22
- expires_in: number;
23
22
  user: {
24
23
  id: string;
25
24
  email: string;
@@ -100,10 +99,6 @@ interface SyncSite {
100
99
  domain: string;
101
100
  name: string | null;
102
101
  };
103
- apiKey: {
104
- key: string;
105
- prefix: string;
106
- };
107
102
  }
108
103
 
109
104
  async function syncSites(accessToken: string): Promise<SyncSite[]> {
@@ -133,7 +128,7 @@ export async function loginWithDeviceFlow(forceResync = false): Promise<void> {
133
128
  const existingAuth = await getAuth();
134
129
  if (existingAuth && !forceResync) {
135
130
  console.log(chalk.dim(`Already logged in as ${existingAuth.email}`));
136
- console.log(chalk.dim(`Run 'supalytics login --resync' to refresh API keys, or 'supalytics logout' to log out.`));
131
+ console.log(chalk.dim(`Run 'supalytics login --resync' to refresh sites, or 'supalytics logout' to log out.`));
137
132
  return;
138
133
  }
139
134
 
@@ -143,15 +138,15 @@ export async function loginWithDeviceFlow(forceResync = false): Promise<void> {
143
138
  try {
144
139
  const sites = await syncSites(existingAuth.accessToken);
145
140
  if (sites.length > 0) {
146
- for (const { site, apiKey } of sites) {
147
- await addSiteWithId(site.domain, apiKey.key, site.site_id, site.id);
141
+ for (const { site } of sites) {
142
+ await addSiteWithId(site.domain, site.site_id, site.id);
148
143
  }
149
144
  console.log(chalk.green(`✓ Synced ${sites.length} site${sites.length > 1 ? "s" : ""}`));
150
145
  } else {
151
146
  console.log(chalk.dim("No sites found."));
152
147
  }
153
148
  } catch {
154
- console.log(chalk.yellow("Could not sync. Your session may have expired. Run 'supalytics logout' then 'supalytics login'."));
149
+ console.log(chalk.yellow("Could not sync. Your token may have been revoked. Run 'supalytics logout' then 'supalytics login'."));
155
150
  }
156
151
  return;
157
152
  }
@@ -187,12 +182,11 @@ export async function loginWithDeviceFlow(forceResync = false): Promise<void> {
187
182
  // 5. Ensure config directory exists
188
183
  await mkdir(dirname(CONFIG_FILE), { recursive: true });
189
184
 
190
- // 6. Save auth
185
+ // 6. Save auth (PAT doesn't expire by default)
191
186
  await saveAuth({
192
187
  accessToken: tokenResponse.access_token,
193
188
  email: tokenResponse.user.email,
194
189
  name: tokenResponse.user.name,
195
- expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000).toISOString(),
196
190
  });
197
191
 
198
192
  console.log(chalk.green(`✓ Logged in as ${tokenResponse.user.email}`));
@@ -202,8 +196,8 @@ export async function loginWithDeviceFlow(forceResync = false): Promise<void> {
202
196
  try {
203
197
  const sites = await syncSites(tokenResponse.access_token);
204
198
  if (sites.length > 0) {
205
- for (const { site, apiKey } of sites) {
206
- await addSiteWithId(site.domain, apiKey.key, site.site_id, site.id);
199
+ for (const { site } of sites) {
200
+ await addSiteWithId(site.domain, site.site_id, site.id);
207
201
  }
208
202
  console.log(chalk.green(`✓ Synced ${sites.length} site${sites.length > 1 ? "s" : ""}`));
209
203
  } else {
@@ -216,7 +210,7 @@ export async function loginWithDeviceFlow(forceResync = false): Promise<void> {
216
210
 
217
211
  export const loginCommand = new Command("login")
218
212
  .description("Authenticate with Supalytics")
219
- .option("--resync", "Refresh API keys for all sites")
213
+ .option("--resync", "Refresh the list of sites")
220
214
  .action(async (options: { resync?: boolean }) => {
221
215
  try {
222
216
  await loginWithDeviceFlow(options.resync || false);
@@ -17,10 +17,6 @@ interface CreateSiteResponse {
17
17
  site_id: string;
18
18
  domain: string;
19
19
  };
20
- apiKey: {
21
- key: string;
22
- prefix: string;
23
- };
24
20
  }
25
21
 
26
22
  interface UpdateSiteResponse {
@@ -81,13 +77,12 @@ export const sitesCommand = new Command("sites")
81
77
 
82
78
  for (const site of sites) {
83
79
  const isDefault = config.defaultSite === site;
84
- const apiKey = config.sites[site].apiKey;
85
- const maskedKey = apiKey.slice(0, 8) + "..." + apiKey.slice(-4);
80
+ const siteId = config.sites[site].siteId;
86
81
 
87
82
  if (isDefault) {
88
- console.log(` ${chalk.green("●")} ${site} ${chalk.dim("(default)")} ${chalk.dim(maskedKey)}`);
83
+ console.log(` ${chalk.green("●")} ${site} ${chalk.dim("(default)")}${siteId ? ` ${chalk.dim(siteId)}` : ""}`);
89
84
  } else {
90
- console.log(` ${chalk.dim("○")} ${site} ${chalk.dim(maskedKey)}`);
85
+ console.log(` ${chalk.dim("○")} ${site}${siteId ? ` ${chalk.dim(siteId)}` : ""}`);
91
86
  }
92
87
  }
93
88
  console.log();
@@ -110,11 +105,10 @@ const addCommand = new Command("add")
110
105
  const result = await createSiteViaApi(auth.accessToken, identifier);
111
106
 
112
107
  // Store locally (including website UUID for updates)
113
- await addSiteWithId(result.site.domain, result.apiKey.key, result.site.site_id, result.site.id);
108
+ await addSiteWithId(result.site.domain, result.site.site_id, result.site.id);
114
109
 
115
110
  console.log(chalk.green(`✓ Created ${result.site.domain}`));
116
111
  console.log(chalk.dim(` Site ID: ${result.site.site_id}`));
117
- console.log(chalk.dim(` API key stored locally`));
118
112
  console.log();
119
113
  console.log(chalk.dim("Add this to your HTML <head>:"));
120
114
  console.log();
@@ -203,11 +197,11 @@ const updateCommand = new Command("update")
203
197
  const result: UpdateSiteResponse = await updateResponse.json();
204
198
 
205
199
  // Update local config (preserve all fields)
206
- const { apiKey, siteId: existingSiteId, id: existingId } = siteConfig;
200
+ const { siteId: existingSiteId, id: existingId } = siteConfig;
207
201
  const siteId = existingSiteId || result.site.site_id;
208
202
  const id = existingId || websiteId;
209
203
  delete config.sites[identifier];
210
- config.sites[options.domain] = { apiKey, siteId, id };
204
+ config.sites[options.domain] = { siteId, id };
211
205
  if (config.defaultSite === identifier) {
212
206
  config.defaultSite = options.domain;
213
207
  }
package/src/config.ts CHANGED
@@ -5,22 +5,24 @@ const CONFIG_DIR = join(homedir(), ".supalytics");
5
5
  const CONFIG_FILE = join(CONFIG_DIR, "config.json");
6
6
 
7
7
  interface SiteConfig {
8
- apiKey: string;
9
8
  siteId?: string; // The site_id for tracking snippet
10
9
  id?: string; // The website UUID for API operations
10
+ // Legacy: per-site API key (deprecated, kept for backward compatibility)
11
+ apiKey?: string;
11
12
  }
12
13
 
13
14
  interface AuthConfig {
14
- accessToken: string;
15
+ accessToken: string; // PAT token (sly_pat_xxx)
15
16
  email: string;
16
17
  name: string;
18
+ // Legacy: expiresAt (PATs don't expire by default)
17
19
  expiresAt?: string;
18
20
  }
19
21
 
20
22
  interface Config {
21
- // User authentication
23
+ // User authentication (PAT-based)
22
24
  auth?: AuthConfig;
23
- // Map of domain -> API key
25
+ // Map of domain -> site config
24
26
  sites: Record<string, SiteConfig>;
25
27
  // Default site domain
26
28
  defaultSite?: string;
@@ -50,9 +52,9 @@ export async function saveConfig(config: Config): Promise<void> {
50
52
  await Bun.write(CONFIG_FILE, JSON.stringify(config, null, 2));
51
53
  }
52
54
 
53
- export async function addSite(domain: string, apiKey: string): Promise<void> {
55
+ export async function addSite(domain: string, siteId?: string, id?: string): Promise<void> {
54
56
  const config = await getConfig();
55
- config.sites[domain] = { apiKey };
57
+ config.sites[domain] = { siteId, id };
56
58
  // Set as default if it's the first site
57
59
  if (!config.defaultSite || Object.keys(config.sites).length === 1) {
58
60
  config.defaultSite = domain;
@@ -85,13 +87,14 @@ export async function setDefaultSite(domain: string): Promise<boolean> {
85
87
  return true;
86
88
  }
87
89
 
88
- export async function getApiKeyForSite(domain: string): Promise<string | undefined> {
90
+ export async function getApiKeyForSite(_domain: string): Promise<string | undefined> {
89
91
  // Check env var first
90
92
  if (process.env.SUPALYTICS_API_KEY) {
91
93
  return process.env.SUPALYTICS_API_KEY;
92
94
  }
95
+ // Use the PAT from auth - it works for all sites
93
96
  const config = await getConfig();
94
- return config.sites[domain]?.apiKey;
97
+ return config.auth?.accessToken;
95
98
  }
96
99
 
97
100
  export async function getDefaultSite(): Promise<string | undefined> {
@@ -135,12 +138,11 @@ export async function isAuthenticated(): Promise<boolean> {
135
138
  // Add site with siteId and optional website id
136
139
  export async function addSiteWithId(
137
140
  domain: string,
138
- apiKey: string,
139
141
  siteId: string,
140
142
  id?: string
141
143
  ): Promise<void> {
142
144
  const config = await getConfig();
143
- config.sites[domain] = { apiKey, siteId, id };
145
+ config.sites[domain] = { siteId, id };
144
146
  // Set as default if it's the first site
145
147
  if (!config.defaultSite || Object.keys(config.sites).length === 1) {
146
148
  config.defaultSite = domain;