@glassanalytics/cli 0.1.1 → 0.1.2
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/dist/cli.js +194 -57
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { parseArgs } from "util";
|
|
|
6
6
|
// src/commands.ts
|
|
7
7
|
import { spawnSync } from "child_process";
|
|
8
8
|
import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
9
|
-
import { dirname as dirname2, relative } from "path";
|
|
9
|
+
import { dirname as dirname2, join as join2, relative } from "path";
|
|
10
10
|
|
|
11
11
|
// src/output.ts
|
|
12
12
|
var ExitCode = {
|
|
@@ -165,7 +165,14 @@ var Api = class {
|
|
|
165
165
|
method,
|
|
166
166
|
headers,
|
|
167
167
|
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
168
|
-
}).catch(
|
|
168
|
+
}).catch(
|
|
169
|
+
(e) => fail(
|
|
170
|
+
ExitCode.ERROR,
|
|
171
|
+
`couldn't reach the Glass control plane at ${this.apiHost} (${String(
|
|
172
|
+
e?.message ?? e
|
|
173
|
+
)}) \u2014 check the host is correct/reachable, or pass --api-host <url> (or set GLASS_API_HOST)`
|
|
174
|
+
)
|
|
175
|
+
);
|
|
169
176
|
const text = await res.text();
|
|
170
177
|
const json = text ? JSON.parse(text) : {};
|
|
171
178
|
if (res.status === 401 || res.status === 403) fail(ExitCode.AUTH, "unauthorized", json);
|
|
@@ -180,6 +187,8 @@ var Api = class {
|
|
|
180
187
|
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
181
188
|
import { homedir } from "os";
|
|
182
189
|
import { dirname, join } from "path";
|
|
190
|
+
var DEFAULT_API_HOST = "https://glass-control-plane.isnit.workers.dev";
|
|
191
|
+
var DEFAULT_INGEST_HOST = "https://glass-ingest.isnit.workers.dev";
|
|
183
192
|
var PROJECT_FILE = join(process.cwd(), "glass.json");
|
|
184
193
|
var CRED_FILE = join(homedir(), ".glass", "credentials.json");
|
|
185
194
|
function readProject() {
|
|
@@ -195,8 +204,8 @@ function readProject() {
|
|
|
195
204
|
return {
|
|
196
205
|
project_id: envProject ?? file?.project_id ?? "",
|
|
197
206
|
ingest_key: envIngest ?? file?.ingest_key ?? "",
|
|
198
|
-
ingest_host: process.env.GLASS_INGEST_HOST ?? file?.ingest_host ??
|
|
199
|
-
api_host: process.env.GLASS_API_HOST ?? file?.api_host ??
|
|
207
|
+
ingest_host: process.env.GLASS_INGEST_HOST ?? file?.ingest_host ?? DEFAULT_INGEST_HOST,
|
|
208
|
+
api_host: process.env.GLASS_API_HOST ?? file?.api_host ?? DEFAULT_API_HOST,
|
|
200
209
|
...file?.claim_url ? { claim_url: file.claim_url } : {}
|
|
201
210
|
};
|
|
202
211
|
}
|
|
@@ -251,13 +260,13 @@ async function init(flags) {
|
|
|
251
260
|
process.stdout.write("Use `glass init --force` to provision a new project, or `glass install` to wire the SDK.\n");
|
|
252
261
|
});
|
|
253
262
|
}
|
|
254
|
-
const apiHost = flags["api-host"] ??
|
|
263
|
+
const apiHost = flags["api-host"] ?? DEFAULT_API_HOST;
|
|
255
264
|
const api = new Api(apiHost);
|
|
256
265
|
const res = await api.request("POST", "/v1/projects/provision", { name: flags.name ?? void 0 });
|
|
257
266
|
writeProject({
|
|
258
267
|
project_id: res.project_id,
|
|
259
268
|
ingest_key: res.ingest_key,
|
|
260
|
-
ingest_host: flags["ingest-host"] ??
|
|
269
|
+
ingest_host: flags["ingest-host"] ?? DEFAULT_INGEST_HOST,
|
|
261
270
|
api_host: apiHost,
|
|
262
271
|
claim_url: res.claim_url
|
|
263
272
|
});
|
|
@@ -293,32 +302,16 @@ function detectPackageManager() {
|
|
|
293
302
|
if (existsSync("bun.lockb")) return { cmd: "bun", args: ["add"] };
|
|
294
303
|
return { cmd: "npm", args: ["install"] };
|
|
295
304
|
}
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
return
|
|
299
|
-
pkg: "@glassanalytics/node",
|
|
300
|
-
file: "glass.ts",
|
|
301
|
-
code: `import { GlassNode } from '@glassanalytics/node';
|
|
302
|
-
|
|
303
|
-
export const glass = new GlassNode({ projectKey: '${project.ingest_key}', ingestHost: '${project.ingest_host}' });
|
|
304
|
-
`
|
|
305
|
-
};
|
|
305
|
+
function findNextAppLayout() {
|
|
306
|
+
for (const p of ["app/layout.tsx", "app/layout.jsx", "src/app/layout.tsx", "src/app/layout.jsx"]) {
|
|
307
|
+
if (existsSync(p)) return p;
|
|
306
308
|
}
|
|
307
|
-
return
|
|
308
|
-
pkg: "@glassanalytics/browser",
|
|
309
|
-
file: "glass.ts",
|
|
310
|
-
code: `import glass from '@glassanalytics/browser';
|
|
311
|
-
|
|
312
|
-
glass.init({ projectKey: '${project.ingest_key}', ingestHost: '${project.ingest_host}' });
|
|
313
|
-
|
|
314
|
-
export default glass;
|
|
315
|
-
`
|
|
316
|
-
};
|
|
309
|
+
return null;
|
|
317
310
|
}
|
|
318
311
|
function entryCandidates(framework) {
|
|
319
312
|
switch (framework) {
|
|
320
313
|
case "next":
|
|
321
|
-
return ["
|
|
314
|
+
return ["pages/_app.tsx", "pages/_app.jsx", "src/pages/_app.tsx", "src/pages/_app.jsx"];
|
|
322
315
|
case "react":
|
|
323
316
|
case "vite":
|
|
324
317
|
return ["src/main.tsx", "src/main.jsx", "src/index.tsx", "src/index.jsx", "src/main.ts", "src/index.ts"];
|
|
@@ -328,57 +321,200 @@ function entryCandidates(framework) {
|
|
|
328
321
|
return [];
|
|
329
322
|
}
|
|
330
323
|
}
|
|
331
|
-
function
|
|
324
|
+
function alreadyWired() {
|
|
325
|
+
const files = [
|
|
326
|
+
"glass.ts",
|
|
327
|
+
findNextAppLayout(),
|
|
328
|
+
...entryCandidates("next"),
|
|
329
|
+
...entryCandidates("react")
|
|
330
|
+
].filter((f) => !!f && existsSync(f));
|
|
331
|
+
return files.some((f) => {
|
|
332
|
+
try {
|
|
333
|
+
return /@glassanalytics\/(browser|node)|glass\.init\(|GlassProvider/.test(readFileSync2(f, "utf8"));
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
var NEXT_PROVIDER_SRC = `'use client';
|
|
340
|
+
|
|
341
|
+
import { useEffect, useRef } from 'react';
|
|
342
|
+
import glass from '@glassanalytics/browser';
|
|
343
|
+
|
|
344
|
+
const KEY = process.env.NEXT_PUBLIC_GLASS_PROJECT_KEY;
|
|
345
|
+
const INGEST_HOST = process.env.NEXT_PUBLIC_GLASS_INGEST_HOST;
|
|
346
|
+
const API_HOST = process.env.NEXT_PUBLIC_GLASS_API_HOST;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Initialises Glass on the client. The SDK auto-captures pageviews (incl. App
|
|
350
|
+
* Router navigations), autocapture, errors and session replay after init().
|
|
351
|
+
*/
|
|
352
|
+
export function GlassProvider() {
|
|
353
|
+
const started = useRef(false);
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
if (started.current || !KEY || !INGEST_HOST) return;
|
|
356
|
+
started.current = true;
|
|
357
|
+
glass.init({ projectKey: KEY, ingestHost: INGEST_HOST, apiHost: API_HOST });
|
|
358
|
+
}, []);
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export default GlassProvider;
|
|
363
|
+
`;
|
|
364
|
+
function upsertEnv(file, vars) {
|
|
365
|
+
let src = "";
|
|
366
|
+
try {
|
|
367
|
+
src = readFileSync2(file, "utf8");
|
|
368
|
+
} catch {
|
|
369
|
+
src = "";
|
|
370
|
+
}
|
|
371
|
+
let out = src;
|
|
372
|
+
let changed = false;
|
|
373
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
374
|
+
if (new RegExp(`^${k}=`, "m").test(out)) continue;
|
|
375
|
+
if (out.length && !out.endsWith("\n")) out += "\n";
|
|
376
|
+
out += `${k}=${v}
|
|
377
|
+
`;
|
|
378
|
+
changed = true;
|
|
379
|
+
}
|
|
380
|
+
if (changed) writeFileSync2(file, out);
|
|
381
|
+
return { file, wrote: changed };
|
|
382
|
+
}
|
|
383
|
+
function wireNextAppRouter(layout) {
|
|
384
|
+
const providerFile = join2(dirname2(layout), "glass-provider.tsx");
|
|
385
|
+
if (!existsSync(providerFile)) writeFileSync2(providerFile, NEXT_PROVIDER_SRC);
|
|
386
|
+
let src = readFileSync2(layout, "utf8");
|
|
387
|
+
const importLine = "import { GlassProvider } from './glass-provider';\n";
|
|
388
|
+
let injectedImport = false;
|
|
389
|
+
if (!src.includes("./glass-provider")) {
|
|
390
|
+
const directive = /^\s*(['"])use (client|server)\1;?\s*\n/.exec(src);
|
|
391
|
+
src = directive ? src.slice(0, directive[0].length) + importLine + src.slice(directive[0].length) : importLine + src;
|
|
392
|
+
injectedImport = true;
|
|
393
|
+
}
|
|
394
|
+
let injectedRender = false;
|
|
395
|
+
if (!src.includes("<GlassProvider")) {
|
|
396
|
+
const bodyOpen = /(<body[^>]*>)/;
|
|
397
|
+
if (bodyOpen.test(src)) {
|
|
398
|
+
src = src.replace(bodyOpen, "$1\n <GlassProvider />");
|
|
399
|
+
injectedRender = true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
writeFileSync2(layout, src);
|
|
403
|
+
return { provider: providerFile, injectedImport, injectedRender };
|
|
404
|
+
}
|
|
405
|
+
function wireBareImport(framework, project) {
|
|
406
|
+
const file = "glass.ts";
|
|
407
|
+
const code = framework === "node" ? `import { GlassNode } from '@glassanalytics/node';
|
|
408
|
+
|
|
409
|
+
export const glass = new GlassNode({
|
|
410
|
+
projectKey: process.env.GLASS_INGEST_KEY ?? '${project.ingest_key}',
|
|
411
|
+
ingestHost: process.env.GLASS_INGEST_HOST ?? '${project.ingest_host}',
|
|
412
|
+
});
|
|
413
|
+
` : `import glass from '@glassanalytics/browser';
|
|
414
|
+
|
|
415
|
+
glass.init({ projectKey: '${project.ingest_key}', ingestHost: '${project.ingest_host}', apiHost: '${project.api_host}' });
|
|
416
|
+
|
|
417
|
+
export default glass;
|
|
418
|
+
`;
|
|
419
|
+
let wroteFile = false;
|
|
420
|
+
if (!existsSync(file)) {
|
|
421
|
+
writeFileSync2(file, code);
|
|
422
|
+
wroteFile = true;
|
|
423
|
+
}
|
|
332
424
|
for (const entry of entryCandidates(framework)) {
|
|
333
425
|
if (!existsSync(entry)) continue;
|
|
334
426
|
const src = readFileSync2(entry, "utf8");
|
|
335
|
-
let spec = relative(dirname2(entry),
|
|
427
|
+
let spec = relative(dirname2(entry), file).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
336
428
|
if (!spec.startsWith(".")) spec = `./${spec}`;
|
|
337
|
-
if (src.includes(`'${spec}'`) || src.includes(`"${spec}"`)) return { entry, injected: false };
|
|
429
|
+
if (src.includes(`'${spec}'`) || src.includes(`"${spec}"`)) return { file, wroteFile, entry, injected: false };
|
|
338
430
|
const importLine = `import '${spec}';
|
|
339
431
|
`;
|
|
340
432
|
const directive = /^\s*(['"])use (client|server)\1;?\s*\n/.exec(src);
|
|
341
433
|
const next = directive ? src.slice(0, directive[0].length) + importLine + src.slice(directive[0].length) : importLine + src;
|
|
342
434
|
writeFileSync2(entry, next);
|
|
343
|
-
return { entry, injected: true };
|
|
435
|
+
return { file, wroteFile, entry, injected: true };
|
|
344
436
|
}
|
|
345
|
-
return { entry: null, injected: false };
|
|
437
|
+
return { file, wroteFile, entry: null, injected: false };
|
|
346
438
|
}
|
|
347
439
|
function install(flags) {
|
|
348
440
|
const project = requireProject();
|
|
349
441
|
const framework = flags.framework ?? detectFramework();
|
|
350
|
-
const
|
|
442
|
+
const pkg = framework === "node" ? "@glassanalytics/node" : "@glassanalytics/browser";
|
|
443
|
+
if (alreadyWired() && flags.force !== true) {
|
|
444
|
+
return ok({ framework, package: pkg, already_wired: true }, () => {
|
|
445
|
+
process.stdout.write("Glass already appears to be wired in this project \u2014 nothing to do.\n");
|
|
446
|
+
process.stdout.write("Re-run with `--force` to wire it again anyway.\n");
|
|
447
|
+
});
|
|
448
|
+
}
|
|
351
449
|
let installed = false;
|
|
352
450
|
if (flags["no-install"] !== true) {
|
|
353
451
|
const pm = detectPackageManager();
|
|
354
|
-
const
|
|
452
|
+
const args = [...pm.args, pkg];
|
|
453
|
+
if (typeof flags.registry === "string" && flags.registry) args.push(`--registry=${flags.registry}`);
|
|
454
|
+
const r = spawnSync(pm.cmd, args, { stdio: "inherit" });
|
|
355
455
|
installed = r.status === 0;
|
|
356
|
-
if (!installed
|
|
357
|
-
|
|
456
|
+
if (!installed) {
|
|
457
|
+
const detail = r.error ? String(r.error.message) : `exit ${r.status}`;
|
|
458
|
+
return fail(
|
|
459
|
+
ExitCode.ERROR,
|
|
460
|
+
`failed to install ${pkg} via ${pm.cmd} (${detail}) \u2014 install it manually, or retry with \`--registry https://registry.npmjs.org/\` if you're behind a private registry`
|
|
461
|
+
);
|
|
358
462
|
}
|
|
359
463
|
}
|
|
360
|
-
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
|
|
464
|
+
const appLayout = framework === "next" ? findNextAppLayout() : null;
|
|
465
|
+
if (appLayout) {
|
|
466
|
+
const env = upsertEnv(".env.local", {
|
|
467
|
+
NEXT_PUBLIC_GLASS_PROJECT_KEY: project.ingest_key,
|
|
468
|
+
NEXT_PUBLIC_GLASS_INGEST_HOST: project.ingest_host,
|
|
469
|
+
NEXT_PUBLIC_GLASS_API_HOST: project.api_host
|
|
470
|
+
});
|
|
471
|
+
const w2 = wireNextAppRouter(appLayout);
|
|
472
|
+
return ok(
|
|
473
|
+
{
|
|
474
|
+
framework: "next-app",
|
|
475
|
+
package: pkg,
|
|
476
|
+
installed,
|
|
477
|
+
provider: w2.provider,
|
|
478
|
+
env_file: env.file,
|
|
479
|
+
wrote_env: env.wrote,
|
|
480
|
+
layout: appLayout,
|
|
481
|
+
injected: w2.injectedImport,
|
|
482
|
+
rendered: w2.injectedRender
|
|
483
|
+
},
|
|
484
|
+
() => {
|
|
485
|
+
process.stdout.write("Detected: Next.js (App Router)\n");
|
|
486
|
+
process.stdout.write(installed ? `Installed ${pkg}.
|
|
487
|
+
` : `Skipped install \u2014 add ${pkg} yourself.
|
|
488
|
+
`);
|
|
489
|
+
process.stdout.write(`Wrote ${w2.provider}${env.wrote ? ` and Glass keys to ${env.file}` : ""}.
|
|
490
|
+
`);
|
|
491
|
+
if (w2.injectedRender) process.stdout.write(`Rendered <GlassProvider /> in ${appLayout}.
|
|
492
|
+
`);
|
|
493
|
+
else process.stdout.write(`Couldn't auto-insert <GlassProvider /> \u2014 add it inside <body> in ${appLayout}.
|
|
494
|
+
`);
|
|
495
|
+
}
|
|
496
|
+
);
|
|
364
497
|
}
|
|
365
|
-
const
|
|
366
|
-
return ok(
|
|
367
|
-
|
|
498
|
+
const w = wireBareImport(framework, project);
|
|
499
|
+
return ok(
|
|
500
|
+
{ framework, package: pkg, installed, file: w.file, wrote_file: w.wroteFile, entry: w.entry, injected: w.injected },
|
|
501
|
+
() => {
|
|
502
|
+
process.stdout.write(`Detected: ${framework}${framework === "next" ? " (Pages Router)" : ""}
|
|
368
503
|
`);
|
|
369
|
-
|
|
504
|
+
process.stdout.write(installed ? `Installed ${pkg}.
|
|
370
505
|
` : `Skipped install \u2014 add ${pkg} yourself.
|
|
371
506
|
`);
|
|
372
|
-
|
|
373
|
-
` : `${file} already exists; left untouched.
|
|
507
|
+
process.stdout.write(w.wroteFile ? `Wrote ${w.file}.
|
|
508
|
+
` : `${w.file} already exists; left untouched.
|
|
374
509
|
`);
|
|
375
|
-
|
|
510
|
+
if (w.injected && w.entry) process.stdout.write(`Wired ${w.file} into ${w.entry}.
|
|
376
511
|
`);
|
|
377
|
-
|
|
512
|
+
else if (w.entry) process.stdout.write(`${w.entry} already imports ${w.file}.
|
|
378
513
|
`);
|
|
379
|
-
|
|
514
|
+
else process.stdout.write(`No entry point found \u2014 import "./${w.file.replace(/\.(ts|tsx)$/, "")}" in your app entry yourself.
|
|
380
515
|
`);
|
|
381
|
-
|
|
516
|
+
}
|
|
517
|
+
);
|
|
382
518
|
}
|
|
383
519
|
async function status(flags) {
|
|
384
520
|
const { api } = authedApi(flags);
|
|
@@ -426,7 +562,7 @@ async function claim(flags) {
|
|
|
426
562
|
}
|
|
427
563
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
428
564
|
async function login(flags) {
|
|
429
|
-
const apiHost = flags["api-host"] ?? readProject()?.api_host ??
|
|
565
|
+
const apiHost = flags["api-host"] ?? readProject()?.api_host ?? DEFAULT_API_HOST;
|
|
430
566
|
const provided = flags.token;
|
|
431
567
|
if (provided) {
|
|
432
568
|
const creds = readCredentials();
|
|
@@ -486,14 +622,14 @@ async function whoami(flags) {
|
|
|
486
622
|
const project = readProject();
|
|
487
623
|
const token = resolveToken(flags, project?.project_id);
|
|
488
624
|
if (!token) fail(ExitCode.AUTH, "not logged in \u2014 run `glass login` or set GLASS_TOKEN");
|
|
489
|
-
const api = new Api(project?.api_host ?? flags["api-host"] ??
|
|
625
|
+
const api = new Api(project?.api_host ?? flags["api-host"] ?? DEFAULT_API_HOST, token, project?.project_id);
|
|
490
626
|
return ok(await api.request("GET", "/v1/me"));
|
|
491
627
|
}
|
|
492
628
|
async function link(flags, positionals = []) {
|
|
493
629
|
const projectId = flags.project ?? positionals[0];
|
|
494
630
|
if (!projectId) fail(ExitCode.USAGE, "link needs a project id: `glass link <id>`");
|
|
495
631
|
const existing = readProject();
|
|
496
|
-
const apiHost = flags["api-host"] ?? existing?.api_host ??
|
|
632
|
+
const apiHost = flags["api-host"] ?? existing?.api_host ?? DEFAULT_API_HOST;
|
|
497
633
|
const token = resolveToken(flags, projectId);
|
|
498
634
|
if (!token) fail(ExitCode.AUTH, "no credentials \u2014 run `glass login` or set GLASS_TOKEN");
|
|
499
635
|
const api = new Api(apiHost, token, projectId);
|
|
@@ -501,7 +637,7 @@ async function link(flags, positionals = []) {
|
|
|
501
637
|
writeProject({
|
|
502
638
|
project_id: projectId,
|
|
503
639
|
ingest_key: proj.publishableKey ?? existing?.ingest_key ?? "",
|
|
504
|
-
ingest_host: proj.ingestHost ?? existing?.ingest_host ??
|
|
640
|
+
ingest_host: proj.ingestHost ?? existing?.ingest_host ?? DEFAULT_INGEST_HOST,
|
|
505
641
|
api_host: apiHost
|
|
506
642
|
});
|
|
507
643
|
return ok({ linked: projectId }, () => process.stdout.write(`Linked glass.json to ${projectId}.
|
|
@@ -643,7 +779,7 @@ var HELP = `glass <command> [options]
|
|
|
643
779
|
|
|
644
780
|
Setup
|
|
645
781
|
init [--force] Provision a project with no signup (writes glass.json)
|
|
646
|
-
install [--framework F] [--no-install] Install + wire the SDK
|
|
782
|
+
install [--framework F] [--no-install] [--registry URL] [--force] Install + wire the SDK
|
|
647
783
|
status Show whether events are being received
|
|
648
784
|
doctor Diagnose setup problems
|
|
649
785
|
claim Get the claim URL to keep your data
|
|
@@ -668,7 +804,7 @@ Manage
|
|
|
668
804
|
|
|
669
805
|
Global: --json --csv --ndjson --api-host --ingest-host
|
|
670
806
|
--max-scan <500MB|1GB> --yes/--no-input (auth: --token | GLASS_TOKEN)
|
|
671
|
-
Env: GLASS_TOKEN GLASS_PROJECT GLASS_INGEST_KEY GLASS_API_HOST`;
|
|
807
|
+
Env: GLASS_TOKEN GLASS_PROJECT GLASS_INGEST_KEY GLASS_API_HOST GLASS_INGEST_HOST`;
|
|
672
808
|
async function main() {
|
|
673
809
|
const argv = process.argv.slice(2);
|
|
674
810
|
const command = argv[0];
|
|
@@ -714,7 +850,8 @@ async function main() {
|
|
|
714
850
|
"budget-r2sql": { type: "string" },
|
|
715
851
|
token: { type: "string" },
|
|
716
852
|
"api-host": { type: "string" },
|
|
717
|
-
"ingest-host": { type: "string" }
|
|
853
|
+
"ingest-host": { type: "string" },
|
|
854
|
+
registry: { type: "string" }
|
|
718
855
|
}
|
|
719
856
|
});
|
|
720
857
|
const flags = values;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands.ts","../src/output.ts","../src/api.ts","../src/config.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport * as cmd from './commands.js';\nimport { ExitCode, fail, setFormat, setJsonMode } from './output.js';\n\n/**\n * `glass` — the agent-friendly terminal surface (`CLI.md`). Setup in one command\n * (`glass init`, no signup), then the same typed query primitives the dashboard\n * uses. `--json` + stable exit codes make it scriptable by humans and agents.\n */\n\nconst HELP = `glass <command> [options]\n\nSetup\n init [--force] Provision a project with no signup (writes glass.json)\n install [--framework F] [--no-install] Install + wire the SDK\n status Show whether events are being received\n doctor Diagnose setup problems\n claim Get the claim URL to keep your data\n login [--token T] Store a Clerk session for human-scoped access\n logout Clear the stored session\n whoami Show the current principal (GET /v1/me)\n link --project ID Point glass.json at another project you can access\n\nQuery (all accept --last 7d, --json, --confirm, --max-scan <MB>)\n trend --event E [--bucket day] [--breakdown B] [--aggregate count|unique_users|sum]\n funnel --steps a,b,c | --id <def> [--window 1d]\n retention --id <def> --return E [--periods 8]\n segment --group-by a,b [--event E]\n errors\n replays [--limit 50]\n query --query '<json>' | -f <file> | (piped stdin)\n\nManage\n define --file spec.json | --kind funnel --steps a,b | --list\n tokens list | create [--budget-r2sql 1GB/day] | revoke <id>\n link <project-id>\n\nGlobal: --json --csv --ndjson --api-host --ingest-host\n --max-scan <500MB|1GB> --yes/--no-input (auth: --token | GLASS_TOKEN)\nEnv: GLASS_TOKEN GLASS_PROJECT GLASS_INGEST_KEY GLASS_API_HOST`;\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const command = argv[0];\n const sub = argv[1] && !argv[1].startsWith('-') ? argv[1] : undefined;\n\n const { values, positionals } = parseArgs({\n args: argv.slice(1),\n allowPositionals: true,\n options: {\n json: { type: 'boolean' },\n csv: { type: 'boolean' },\n ndjson: { type: 'boolean' },\n confirm: { type: 'boolean' },\n reissue: { type: 'boolean' },\n force: { type: 'boolean' },\n 'no-install': { type: 'boolean' },\n yes: { type: 'boolean' },\n 'no-input': { type: 'boolean' },\n list: { type: 'boolean' },\n 'max-scan': { type: 'string' },\n last: { type: 'string' },\n event: { type: 'string' },\n bucket: { type: 'string' },\n breakdown: { type: 'string' },\n aggregate: { type: 'string' },\n steps: { type: 'string' },\n window: { type: 'string' },\n scope: { type: 'string' },\n kind: { type: 'string' },\n id: { type: 'string' },\n return: { type: 'string' },\n periods: { type: 'string' },\n 'group-by': { type: 'string' },\n group: { type: 'string' },\n status: { type: 'string' },\n limit: { type: 'string' },\n user: { type: 'string' },\n query: { type: 'string' },\n file: { type: 'string', short: 'f' },\n name: { type: 'string' },\n role: { type: 'string' },\n framework: { type: 'string' },\n project: { type: 'string' },\n 'budget-r2sql': { type: 'string' },\n token: { type: 'string' },\n 'api-host': { type: 'string' },\n 'ingest-host': { type: 'string' },\n },\n });\n\n const flags = values as Record<string, string | boolean>;\n if (flags.json) setJsonMode(true);\n else if (flags.ndjson) setFormat('ndjson');\n else if (flags.csv) setFormat('csv');\n\n switch (command) {\n case 'init':\n return void (await cmd.init(flags));\n case 'install':\n return void cmd.install(flags);\n case 'status':\n return void (await cmd.status(flags));\n case 'doctor':\n return void (await cmd.doctor(flags));\n case 'claim':\n return void (await cmd.claim(flags));\n case 'login':\n return void (await cmd.login(flags));\n case 'logout':\n return void cmd.logout(flags);\n case 'whoami':\n return void (await cmd.whoami(flags));\n case 'link':\n return void (await cmd.link(flags, positionals));\n case 'trend':\n return void (await cmd.trend(flags));\n case 'funnel':\n return void (await cmd.funnel(flags));\n case 'retention':\n return void (await cmd.retention(flags));\n case 'segment':\n return void (await cmd.segment(flags));\n case 'errors':\n return void (await cmd.errors(flags));\n case 'replays':\n return void (await cmd.replays(flags));\n case 'query':\n return void (await cmd.query(flags));\n case 'define':\n return void (await cmd.define(flags));\n case 'tokens':\n // positionals[0] is the sub-action; pass the remainder (e.g. token id).\n return void (await cmd.tokens(sub ?? 'list', flags, positionals.slice(1)));\n case 'help':\n case undefined:\n case '--help':\n case '-h':\n process.stdout.write(`${HELP}\\n`);\n process.exit(ExitCode.OK);\n break;\n default:\n fail(ExitCode.USAGE, `unknown command: ${command}\\n\\n${HELP}`);\n }\n}\n\nmain().catch((e) => fail(ExitCode.ERROR, String(e?.message ?? e)));\n","import { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, relative } from 'node:path';\nimport type { GlassQuery } from '@glassanalytics/core';\nimport { Api } from './api.js';\nimport { readCredentials, readProject, tokenFor, writeCredentials, writeProject } from './config.js';\nimport { ExitCode, fail, ok, parseBytes } from './output.js';\n\ntype Flags = Record<string, string | boolean>;\n\nfunction requireProject() {\n const project = readProject();\n if (!project) fail(ExitCode.USAGE, 'no glass.json found — run `glass init` first');\n return project;\n}\n\n/**\n * Token precedence (`CLI.md` §2): explicit --token > GLASS_TOKEN env > stored\n * per-project token > stored Clerk session. Lets CI inject a token without\n * touching the filesystem.\n */\nfunction resolveToken(flags: Flags, projectId?: string): string | undefined {\n if (typeof flags.token === 'string' && flags.token) return flags.token;\n if (process.env.GLASS_TOKEN) return process.env.GLASS_TOKEN;\n if (projectId) {\n const t = tokenFor(projectId);\n if (t) return t;\n }\n return readCredentials().session;\n}\n\nfunction authedApi(flags: Flags = {}) {\n const project = requireProject();\n const token = resolveToken(flags, project.project_id);\n if (!token) fail(ExitCode.AUTH, 'no credentials — run `glass init`, `glass login`, or set GLASS_TOKEN');\n return { api: new Api(project.api_host, token, project.project_id), project };\n}\n\n// --- glass init (zero-auth provisioning) -------------------------------------\n\nexport async function init(flags: Flags): Promise<never> {\n // Idempotent: don't re-provision if a project is already linked here.\n const existing = readProject();\n if (existing && flags.force !== true) {\n return ok({ project_id: existing.project_id, linked: true }, () => {\n process.stdout.write(`Already linked to ${existing.project_id} (glass.json present).\\n`);\n process.stdout.write('Use `glass init --force` to provision a new project, or `glass install` to wire the SDK.\\n');\n });\n }\n\n const apiHost = (flags['api-host'] as string) ?? 'https://api.glass.dev';\n const api = new Api(apiHost);\n const res = await api.request<{\n project_id: string;\n ingest_key: string;\n provisioning_token: string;\n claim_url: string;\n }>('POST', '/v1/projects/provision', { name: (flags.name as string) ?? undefined });\n\n writeProject({\n project_id: res.project_id,\n ingest_key: res.ingest_key,\n ingest_host: (flags['ingest-host'] as string) ?? 'https://in.glass.dev',\n api_host: apiHost,\n claim_url: res.claim_url,\n });\n const creds = readCredentials();\n creds.tokens[res.project_id] = res.provisioning_token;\n writeCredentials(creds);\n\n return ok(res, () => {\n process.stdout.write(`Created project ${res.project_id}\\n`);\n process.stdout.write('Wrote glass.json and ~/.glass/credentials.json (0600)\\n');\n process.stdout.write(`\\nClaim it to keep your data:\\n ${res.claim_url}\\n`);\n process.stdout.write('\\nNext: `glass install` to wire up the SDK.\\n');\n });\n}\n\n// --- glass install (framework detection) -------------------------------------\n\ntype Framework = 'next' | 'react' | 'vite' | 'node' | 'browser';\n\nfunction detectFramework(): Framework {\n try {\n const pkg = JSON.parse(readFileSync('package.json', 'utf8')) as { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps.next) return 'next';\n if (deps.express || deps.fastify || deps.hono) return 'node';\n if (deps.react) return 'react';\n if (deps.vite) return 'vite';\n } catch {\n /* no package.json — assume browser */\n }\n return 'browser';\n}\n\n/** Pick the package manager from the lockfile present in cwd. */\nfunction detectPackageManager(): { cmd: string; args: string[] } {\n if (existsSync('pnpm-lock.yaml')) return { cmd: 'pnpm', args: ['add'] };\n if (existsSync('yarn.lock')) return { cmd: 'yarn', args: ['add'] };\n if (existsSync('bun.lockb')) return { cmd: 'bun', args: ['add'] };\n return { cmd: 'npm', args: ['install'] };\n}\n\nfunction snippetFor(framework: Framework, project: { ingest_key: string; ingest_host: string }): { file: string; code: string; pkg: string } {\n if (framework === 'node') {\n return {\n pkg: '@glassanalytics/node',\n file: 'glass.ts',\n code: `import { GlassNode } from '@glassanalytics/node';\\n\\nexport const glass = new GlassNode({ projectKey: '${project.ingest_key}', ingestHost: '${project.ingest_host}' });\\n`,\n };\n }\n return {\n pkg: '@glassanalytics/browser',\n file: 'glass.ts',\n code: `import glass from '@glassanalytics/browser';\\n\\nglass.init({ projectKey: '${project.ingest_key}', ingestHost: '${project.ingest_host}' });\\n\\nexport default glass;\\n`,\n };\n}\n\n/** Candidate entry-point files per framework, in preference order. */\nfunction entryCandidates(framework: Framework): string[] {\n switch (framework) {\n case 'next':\n return ['app/layout.tsx', 'app/layout.jsx', 'src/app/layout.tsx', 'src/app/layout.jsx'];\n case 'react':\n case 'vite':\n return ['src/main.tsx', 'src/main.jsx', 'src/index.tsx', 'src/index.jsx', 'src/main.ts', 'src/index.ts'];\n case 'node':\n return ['src/index.ts', 'src/server.ts', 'index.ts', 'server.ts', 'src/main.ts'];\n default:\n return [];\n }\n}\n\n/**\n * Inject `import '<glass>'` into the app's entry point so the SDK actually\n * initialises (`CLI.md` §3.1) — writing `glass.ts` alone wires nothing. We only\n * touch a file that exists and doesn't already import it, and we never reorder\n * existing code (the import is prepended, after a leading \"use client\" if present).\n */\nfunction injectEntryPoint(framework: Framework, glassFile: string): { entry: string | null; injected: boolean } {\n for (const entry of entryCandidates(framework)) {\n if (!existsSync(entry)) continue;\n const src = readFileSync(entry, 'utf8');\n let spec = relative(dirname(entry), glassFile).replace(/\\.(ts|tsx|js|jsx)$/, '');\n if (!spec.startsWith('.')) spec = `./${spec}`;\n if (src.includes(`'${spec}'`) || src.includes(`\"${spec}\"`)) return { entry, injected: false };\n const importLine = `import '${spec}';\\n`;\n // Preserve a leading directive (\"use client\"/\"use server\") as the first line.\n const directive = /^\\s*(['\"])use (client|server)\\1;?\\s*\\n/.exec(src);\n const next = directive\n ? src.slice(0, directive[0].length) + importLine + src.slice(directive[0].length)\n : importLine + src;\n writeFileSync(entry, next);\n return { entry, injected: true };\n }\n return { entry: null, injected: false };\n}\n\nexport function install(flags: Flags): never {\n const project = requireProject();\n const framework = ((flags.framework as Framework) ?? detectFramework()) as Framework;\n const { pkg, file, code } = snippetFor(framework, project);\n\n // 1. Install the SDK dependency (fixed argv — never a shell string).\n let installed = false;\n if (flags['no-install'] !== true) {\n const pm = detectPackageManager();\n const r = spawnSync(pm.cmd, [...pm.args, pkg], { stdio: 'inherit' });\n installed = r.status === 0;\n if (!installed && r.error) {\n return fail(ExitCode.ERROR, `failed to run ${pm.cmd}: ${String(r.error.message)} — install ${pkg} manually`);\n }\n }\n\n // 2. Write the wiring file if absent (never clobber existing app code).\n let wrote = false;\n if (!existsSync(file)) {\n writeFileSync(file, code);\n wrote = true;\n }\n\n // 3. Inject the import into the framework entry point so init actually runs.\n const { entry, injected } = injectEntryPoint(framework, file);\n\n return ok({ framework, package: pkg, installed, file, wrote_file: wrote, entry, injected, snippet: code }, () => {\n process.stdout.write(`Detected: ${framework}\\n`);\n process.stdout.write(installed ? `Installed ${pkg}.\\n` : `Skipped install — add ${pkg} yourself.\\n`);\n process.stdout.write(wrote ? `Wrote ${file}.\\n` : `${file} already exists; left untouched.\\n`);\n if (injected && entry) process.stdout.write(`Wired ${file} into ${entry}.\\n`);\n else if (entry) process.stdout.write(`${entry} already imports ${file}.\\n`);\n else process.stdout.write(`No entry point found — import \"./${file.replace(/\\.(ts|tsx)$/, '')}\" yourself.\\n`);\n });\n}\n\n// --- status / doctor ----------------------------------------------------------\n\nexport async function status(flags: Flags): Promise<never> {\n const { api } = authedApi(flags);\n const res = await api.request('GET', '/v1/ingest/status');\n return ok(res);\n}\n\nexport async function doctor(flags: Flags): Promise<never> {\n const project = readProject();\n const checks: { name: string; ok: boolean; detail?: string }[] = [];\n checks.push({ name: 'glass.json present', ok: !!project });\n if (project) {\n const tok = resolveToken(flags, project.project_id);\n checks.push({ name: 'credentials present', ok: !!tok });\n try {\n const api = new Api(project.api_host, tok, project.project_id);\n const s = await api.request<{ receiving: boolean }>('GET', '/v1/ingest/status');\n checks.push({ name: 'events received', ok: s.receiving, detail: s.receiving ? undefined : 'no events in last 24h' });\n } catch {\n checks.push({ name: 'control plane reachable', ok: false });\n }\n }\n const allOk = checks.every((c) => c.ok);\n return allOk ? ok({ checks }) : fail(ExitCode.ERROR, 'doctor found problems', { checks });\n}\n\n// --- claim / login ------------------------------------------------------------\n\nexport async function claim(flags: Flags): Promise<never> {\n const project = requireProject();\n\n // Default: show the claim URL stored at `glass init` WITHOUT contacting the\n // control plane (so we never invalidate a link the user may already be using).\n if (flags.reissue !== true && project.claim_url) {\n return ok({ claim_url: project.claim_url, reissued: false }, () =>\n process.stdout.write(`Claim URL:\\n ${project.claim_url}\\n\\n(Use \\`glass claim --reissue\\` to mint a fresh single-use link.)\\n`),\n );\n }\n\n // `--reissue` (or no stored URL): mint a fresh single-use claim link.\n const token = tokenFor(project.project_id);\n if (!token) fail(ExitCode.AUTH, 'no provisioning token for this project');\n const api = new Api(project.api_host, token);\n const res = await api.request<{ claim_url: string }>('POST', `/v1/projects/${project.project_id}/claim-url`);\n // Persist the new link so subsequent `glass claim` shows it without reissuing.\n writeProject({ ...project, claim_url: res.claim_url });\n return ok({ ...res, reissued: true }, () => process.stdout.write(`Claim URL (reissued):\\n ${res.claim_url}\\n`));\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/**\n * `glass login` (`CLI.md` §2). `--token <session>` stores a session directly\n * (CI/non-interactive); otherwise runs the OAuth-style device flow: start a\n * grant, print the verification URL + short user-code, then poll until a\n * signed-in human approves it in the dashboard.\n */\nexport async function login(flags: Flags): Promise<never> {\n const apiHost = (flags['api-host'] as string) ?? readProject()?.api_host ?? 'https://api.glass.dev';\n\n // Non-interactive path: store an explicitly-provided session.\n const provided = flags.token as string | undefined;\n if (provided) {\n const creds = readCredentials();\n creds.session = provided;\n writeCredentials(creds);\n return ok({ message: 'Logged in. Session stored in ~/.glass/credentials.json' }, () =>\n process.stdout.write('Logged in. Session stored in ~/.glass/credentials.json\\n'),\n );\n }\n\n // The device flow needs a human to approve in a browser; in non-interactive\n // mode (`--no-input`/`--yes`, or CI) fail fast and ask for a token instead.\n if (flags['no-input'] === true || flags.yes === true || (!process.stdin.isTTY && !process.stdout.isTTY)) {\n return fail(ExitCode.AUTH, 'login is interactive — pass `--token <session>` or set GLASS_TOKEN for non-interactive use');\n }\n\n // Device flow.\n const api = new Api(apiHost);\n const grant = await api.request<{\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete?: string;\n interval: number;\n expires_in: number;\n }>('POST', '/v1/cli/device/start');\n\n if (!flags.json) {\n process.stdout.write('\\nTo sign in, open:\\n');\n process.stdout.write(` ${grant.verification_uri_complete ?? grant.verification_uri}\\n\\n`);\n process.stdout.write(`and enter the code: ${grant.user_code}\\n\\n`);\n process.stdout.write('Waiting for approval…\\n');\n }\n\n const deadline = Date.now() + grant.expires_in * 1000;\n const intervalMs = Math.max(1, grant.interval) * 1000;\n while (Date.now() < deadline) {\n await sleep(intervalMs);\n let resp: { session?: string; status?: string } | null = null;\n try {\n resp = await api.request<{ session?: string; status?: string }>('POST', '/v1/cli/device/token', {\n device_code: grant.device_code,\n });\n } catch {\n // 202 (pending) / 410 (expired) surface as request errors depending on the\n // client; treat transient failures as \"keep polling\" until the deadline.\n continue;\n }\n if (resp?.session) {\n const creds = readCredentials();\n creds.session = resp.session;\n writeCredentials(creds);\n return ok({ message: 'Logged in. Session stored in ~/.glass/credentials.json' }, () =>\n process.stdout.write('\\nLogged in. Session stored in ~/.glass/credentials.json\\n'),\n );\n }\n }\n return fail(ExitCode.AUTH, 'device authorization timed out — run `glass login` again');\n}\n\nexport function logout(_flags: Flags): never {\n const creds = readCredentials();\n creds.session = undefined;\n writeCredentials(creds);\n return ok({ message: 'Logged out. Cleared stored session.' }, () => process.stdout.write('Logged out.\\n'));\n}\n\nexport async function whoami(flags: Flags): Promise<never> {\n const project = readProject();\n const token = resolveToken(flags, project?.project_id);\n if (!token) fail(ExitCode.AUTH, 'not logged in — run `glass login` or set GLASS_TOKEN');\n const api = new Api(project?.api_host ?? (flags['api-host'] as string) ?? 'https://api.glass.dev', token, project?.project_id);\n return ok(await api.request('GET', '/v1/me'));\n}\n\n/** Switch the active project in glass.json after verifying access. */\nexport async function link(flags: Flags, positionals: string[] = []): Promise<never> {\n // Accept both `glass link <id>` and `glass link --project <id>`.\n const projectId = (flags.project as string | undefined) ?? positionals[0];\n if (!projectId) fail(ExitCode.USAGE, 'link needs a project id: `glass link <id>`');\n const existing = readProject();\n const apiHost = (flags['api-host'] as string) ?? existing?.api_host ?? 'https://api.glass.dev';\n const token = resolveToken(flags, projectId);\n if (!token) fail(ExitCode.AUTH, 'no credentials — run `glass login` or set GLASS_TOKEN');\n const api = new Api(apiHost, token, projectId);\n const proj = await api.request<{ id: string; publishableKey?: string; ingestHost?: string }>('GET', '/v1/project');\n writeProject({\n project_id: projectId,\n ingest_key: proj.publishableKey ?? existing?.ingest_key ?? '',\n ingest_host: proj.ingestHost ?? existing?.ingest_host ?? 'https://in.glass.dev',\n api_host: apiHost,\n });\n return ok({ linked: projectId }, () => process.stdout.write(`Linked glass.json to ${projectId}.\\n`));\n}\n\n// --- query + sugar ------------------------------------------------------------\n\n/**\n * `glass query` source resolution (`CLI.md` §4): a GlassQuery can come from\n * `--query '<json>'`, `--file/-f <path>`, or stdin (so it composes in a pipe:\n * `cat q.json | glass query --json`). Exactly one source is used, in that order.\n */\nexport async function query(flags: Flags): Promise<never> {\n let raw: string | undefined;\n if (typeof flags.query === 'string' && flags.query) raw = flags.query;\n else if (typeof flags.file === 'string' && flags.file) raw = readFileSync(flags.file, 'utf8');\n else if (!process.stdin.isTTY) raw = readFileSync(0, 'utf8').trim() || undefined;\n\n if (!raw) fail(ExitCode.USAGE, \"query needs --query '<json>', --file <path>, or piped stdin\");\n let parsed: GlassQuery;\n try {\n parsed = JSON.parse(raw) as GlassQuery;\n } catch (e) {\n return fail(ExitCode.USAGE, `query is not valid JSON: ${String((e as Error).message)}`);\n }\n return runQuery(parsed, flags);\n}\n\nexport async function runQuery(query: GlassQuery, flags: Flags): Promise<never> {\n const { api } = authedApi(flags);\n const body: Record<string, unknown> = { query, confirm: flags.confirm === true };\n if (flags['max-scan']) {\n // Accept a bare number (legacy: MB) or a unit string (`500MB`, `1GB`).\n const raw = String(flags['max-scan']);\n const bytes = /^[0-9]*\\.?[0-9]+$/.test(raw.trim())\n ? Number(raw) * 1024 * 1024\n : parseBytes(raw);\n if (bytes == null) fail(ExitCode.USAGE, `invalid --max-scan: ${raw} (try 500MB or 1GB)`);\n body.max_scan_bytes = bytes;\n }\n const res = await api.request('POST', '/v1/query', body);\n return ok(res);\n}\n\nfunction parseTime(flags: Flags) {\n return { last: (flags.last as string) ?? '7d' };\n}\n\nexport async function trend(flags: Flags): Promise<never> {\n return runQuery(\n {\n v: 1,\n kind: 'trend',\n time: parseTime(flags),\n measure: { aggregate: (flags.aggregate as 'count' | 'unique_users' | 'sum') ?? 'count', ...(flags.event ? { event: flags.event as string } : {}) },\n bucket: (flags.bucket as 'hour' | 'day' | 'week' | 'month') ?? 'day',\n ...(flags.breakdown ? { breakdown: flags.breakdown as string } : {}),\n },\n flags,\n );\n}\n\nexport async function funnel(flags: Flags): Promise<never> {\n const steps = String(flags.steps ?? '').split(',').map((e) => ({ event: e.trim() })).filter((s) => s.event);\n if (flags.id) return runQuery({ v: 1, kind: 'funnel', time: parseTime(flags), definition_id: flags.id as string }, flags);\n if (steps.length < 2) fail(ExitCode.USAGE, 'funnel needs --steps a,b,c (>=2) or --id <definition>');\n return runQuery({ v: 1, kind: 'funnel', time: parseTime(flags), steps, ...(flags.window ? { window: flags.window as string } : {}) }, flags);\n}\n\nexport async function retention(flags: Flags): Promise<never> {\n if (!flags.id) fail(ExitCode.USAGE, 'retention needs --id <definition>');\n return runQuery({ v: 1, kind: 'retention', time: parseTime(flags), definition_id: flags.id as string, return_event: (flags.return as string) ?? '$pageview', periods: Number(flags.periods ?? 8) }, flags);\n}\n\nexport async function segment(flags: Flags): Promise<never> {\n const groupBy = String(flags['group-by'] ?? '').split(',').map((s) => s.trim()).filter(Boolean);\n if (groupBy.length === 0) fail(ExitCode.USAGE, 'segment needs --group-by a,b');\n return runQuery({ v: 1, kind: 'segmentation', time: parseTime(flags), group_by: groupBy, ...(flags.event ? { event: flags.event as string } : {}) }, flags);\n}\n\nexport async function errors(flags: Flags): Promise<never> {\n return runQuery(\n {\n v: 1,\n kind: 'errors',\n time: parseTime(flags),\n limit: Number(flags.limit ?? 50),\n ...(flags.group ? { group: flags.group as string } : {}),\n ...(flags.status ? { status: flags.status as 'unresolved' | 'resolved' | 'ignored' } : {}),\n },\n flags,\n );\n}\n\nexport async function replays(flags: Flags): Promise<never> {\n return runQuery(\n {\n v: 1,\n kind: 'replays',\n time: parseTime(flags),\n limit: Number(flags.limit ?? 50),\n ...(flags.user ? { session_filters: [{ property: 'user_id', op: 'eq' as const, value: flags.user as string }] } : {}),\n },\n flags,\n );\n}\n\n// --- define / tokens ----------------------------------------------------------\n\nexport async function define(flags: Flags): Promise<never> {\n const { api } = authedApi(flags);\n\n // `define --list` — show existing definitions.\n if (flags.list) return ok(await api.request('GET', '/v1/definitions'));\n\n // Flag-based sugar: build a spec from flags without writing JSON.\n let spec: unknown;\n if (flags.kind === 'funnel') {\n const steps = String(flags.steps ?? '').split(',').map((e) => ({ event: e.trim() })).filter((s) => s.event);\n if (steps.length < 2) fail(ExitCode.USAGE, 'define --kind funnel needs --steps a,b,c (>=2)');\n spec = { v: 1, kind: 'funnel', name: (flags.name as string) ?? 'funnel', steps, scope: (flags.scope as string) ?? 'within-session', ...(flags.window ? { window: flags.window as string } : {}) };\n } else if (flags.kind === 'metric') {\n spec = { v: 1, kind: 'metric', name: (flags.name as string) ?? 'metric', aggregate: (flags.aggregate as string) ?? 'count', bucket: (flags.bucket as string) ?? 'day', ...(flags.event ? { event: flags.event as string } : {}) };\n } else {\n const raw = flags.file ? readFileSync(flags.file as string, 'utf8') : readFileSync(0, 'utf8');\n spec = JSON.parse(raw);\n }\n const res = await api.request('POST', '/v1/definitions', { spec });\n return ok(res);\n}\n\nexport async function tokens(action: string, flags: Flags, positionals: string[] = []): Promise<never> {\n const { api } = authedApi(flags);\n if (action === 'list') return ok(await api.request('GET', '/v1/tokens'));\n if (action === 'create') {\n let budget: number | undefined;\n if (flags['budget-r2sql']) {\n // Accept human units + an optional `/day` rate suffix (`1GB/day`).\n const parsed = parseBytes(String(flags['budget-r2sql']));\n if (parsed == null) fail(ExitCode.USAGE, `invalid --budget-r2sql: ${flags['budget-r2sql']} (try 1GB/day)`);\n budget = parsed;\n }\n const res = await api.request<{ id: string; token: string; prefix: string }>('POST', '/v1/tokens', {\n name: (flags.name as string) ?? 'agent',\n role: (flags.role as string) ?? 'read',\n ...(budget != null ? { budget_r2sql_bytes_per_day: budget } : {}),\n });\n // The secret is shown ONCE — print it verbatim (not through the redactor).\n return ok(res, () => {\n process.stdout.write(`Created token ${res.id}\\n\\n ${res.token}\\n\\nThis is shown once. Store it now (e.g. as GLASS_TOKEN).\\n`);\n });\n }\n if (action === 'revoke') {\n // Accept both `glass tokens revoke <id>` and `--id <id>`.\n const id = (flags.id as string | undefined) ?? positionals[0];\n if (!id) fail(ExitCode.USAGE, 'tokens revoke needs a token id: `glass tokens revoke <id>`');\n return ok(await api.request('DELETE', `/v1/tokens/${id}`));\n }\n return fail(ExitCode.USAGE, 'tokens <list|create|revoke>');\n}\n","/**\n * Stable, scriptable output (`CLI.md` §5). The CLI is built for agents as much\n * as humans: `--json` emits a uniform envelope and EXIT CODES are stable so a\n * caller can branch on them without parsing prose.\n */\n\nexport const ExitCode = {\n OK: 0,\n ERROR: 1,\n USAGE: 2,\n AUTH: 3,\n COST_GATE: 4,\n BUDGET: 5,\n} as const;\nexport type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];\n\ntype OutputFormat = 'human' | 'json' | 'csv' | 'ndjson';\nlet format: OutputFormat = 'human';\nexport function setFormat(f: OutputFormat): void {\n format = f;\n}\n/** Back-compat shim for `--json`. */\nexport function setJsonMode(on: boolean): void {\n if (on) format = 'json';\n}\n\n/** Mask token-like secrets in human output so they're never accidentally logged. */\nconst SECRET_RE =\n /\\b(glass_(?:sk|pt)_[A-Za-z0-9]+)\\b|\\b(eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,})\\b/g;\nexport function redact(text: string): string {\n return text.replace(SECRET_RE, (m) => `${m.slice(0, 12)}…<redacted>`);\n}\n\n/** Flatten an array of flat records to CSV; falls back to JSON for non-tabular data. */\nexport function toCsv(data: unknown): string | null {\n const rows = Array.isArray(data) ? data : extractRows(data);\n if (!rows || rows.length === 0) return null;\n const cols = [...new Set(rows.flatMap((r) => Object.keys(r)))];\n const esc = (v: unknown) => {\n const s = v == null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v);\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n return [\n cols.join(','),\n ...rows.map((r) => cols.map((c) => esc((r as Record<string, unknown>)[c])).join(',')),\n ].join('\\n');\n}\n\n/**\n * Parse a human byte budget into bytes (`CLI.md` §4): accepts plain numbers and\n * unit suffixes (`500MB`, `1GB`, `2 TiB`), with an optional `/day` rate suffix\n * that we strip (the API stores per-day budgets). Returns null on garbage.\n */\nconst BYTE_UNITS: Record<string, number> = {\n b: 1,\n kb: 1e3,\n mb: 1e6,\n gb: 1e9,\n tb: 1e12,\n kib: 1024,\n mib: 1024 ** 2,\n gib: 1024 ** 3,\n tib: 1024 ** 4,\n};\nexport function parseBytes(input: string): number | null {\n const s = input.trim().toLowerCase().replace(/\\/(day|d|hr|hour|h)$/, '');\n const m = /^([0-9]*\\.?[0-9]+)\\s*([a-z]+)?$/.exec(s);\n if (!m) return null;\n const value = Number(m[1]);\n if (!Number.isFinite(value)) return null;\n if (!m[2]) return value; // bare number = bytes\n const unit = BYTE_UNITS[m[2]];\n if (!unit) return null;\n return Math.round(value * unit);\n}\n\n/** Find the first array-of-objects field (e.g. {rows:[...]}, {issues:[...]}). */\nexport function extractRows(data: unknown): Record<string, unknown>[] | null {\n if (Array.isArray(data)) return data as Record<string, unknown>[];\n if (data && typeof data === 'object') {\n for (const v of Object.values(data)) {\n if (Array.isArray(v) && v.every((x) => x && typeof x === 'object'))\n return v as Record<string, unknown>[];\n }\n }\n return null;\n}\n\n/**\n * Render an array of flat records as an aligned ASCII table for human output.\n * Returns null when the data isn't tabular so callers can fall back to JSON.\n */\nexport function humanTable(data: unknown): string | null {\n const rows = extractRows(data);\n if (!rows || rows.length === 0) return null;\n const cols = [...new Set(rows.flatMap((r) => Object.keys(r)))];\n if (cols.length === 0) return null;\n const cell = (v: unknown) => {\n const s = v == null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v);\n return redact(s).replace(/\\n/g, ' ');\n };\n const widths = cols.map((c) =>\n Math.max(c.length, ...rows.map((r) => cell((r as Record<string, unknown>)[c]).length)),\n );\n const line = (cells: string[]) => cells.map((s, i) => s.padEnd(widths[i] ?? 0)).join(' ').trimEnd();\n const header = line(cols);\n const sep = widths.map((w) => '-'.repeat(w)).join(' ');\n const body = rows.map((r) => line(cols.map((c) => cell((r as Record<string, unknown>)[c]))));\n return [header, sep, ...body].join('\\n');\n}\n\n/**\n * One-line provenance badge for a query result (`UI.md` ResultBadge parity):\n * surfaces whether a number is exact or sampled, and its source, so an agent\n * reading stdout knows how much to trust it. Returns null when not applicable.\n */\nexport function resultBadge(data: unknown): string | null {\n if (!data || typeof data !== 'object') return null;\n const d = data as Record<string, unknown>;\n if (typeof d.exact !== 'boolean' && !d.source && !d.sampled) return null;\n const parts: string[] = [];\n parts.push(d.exact === false ? 'sampled' : 'exact');\n if (d.source) parts.push(String(d.source));\n const sampled = d.sampled as { interval?: number } | undefined;\n if (sampled?.interval && sampled.interval > 1) parts.push(`1/${sampled.interval}`);\n return `[${parts.join(' · ')}]`;\n}\n\nexport function ok(data: unknown, human?: () => void): never {\n if (format === 'json') {\n process.stdout.write(`${JSON.stringify({ ok: true, data }, null, 2)}\\n`);\n } else if (format === 'ndjson') {\n const rows = extractRows(data) ?? [data];\n for (const r of rows) process.stdout.write(`${JSON.stringify(r)}\\n`);\n } else if (format === 'csv') {\n const csv = toCsv(data);\n process.stdout.write(csv ? `${csv}\\n` : `${JSON.stringify(data, null, 2)}\\n`);\n } else if (human) {\n human();\n } else {\n const table = humanTable(data);\n if (table) {\n const badge = resultBadge(data);\n process.stdout.write(`${table}\\n`);\n if (badge) process.stdout.write(`${badge}\\n`);\n } else {\n process.stdout.write(`${redact(JSON.stringify(data, null, 2))}\\n`);\n }\n }\n process.exit(ExitCode.OK);\n}\n\nexport function fail(code: ExitCodeValue, message: string, extra?: unknown): never {\n if (format === 'json' || format === 'ndjson') {\n process.stdout.write(\n `${JSON.stringify({ ok: false, error: { code, message, ...(extra ? { extra } : {}) } }, null, 2)}\\n`,\n );\n } else {\n process.stderr.write(`glass: ${message}\\n`);\n if (extra) process.stderr.write(`${redact(JSON.stringify(extra, null, 2))}\\n`);\n }\n process.exit(code);\n}\n","import { ExitCode, fail } from './output.js';\n\n/**\n * Thin control-plane client. Maps HTTP status to the CLI's stable exit codes so\n * `--json` callers (agents) can branch deterministically (`CLI.md` §5):\n * 401/403 → AUTH(3), 402 → COST_GATE(4), 429 → BUDGET(5).\n */\nexport class Api {\n constructor(\n private apiHost: string,\n private token?: string,\n private projectId?: string,\n ) {}\n\n async request<T = unknown>(method: string, path: string, body?: unknown): Promise<T> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.token) headers.Authorization = `Bearer ${this.token}`;\n if (this.projectId) headers['X-Glass-Project'] = this.projectId;\n\n const res = await fetch(`${this.apiHost}${path}`, {\n method,\n headers,\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n }).catch((e) => fail(ExitCode.ERROR, `network error: ${String(e)}`));\n\n const text = await res.text();\n const json = text ? (JSON.parse(text) as T) : ({} as T);\n\n if (res.status === 401 || res.status === 403) fail(ExitCode.AUTH, 'unauthorized', json);\n if (res.status === 402) fail(ExitCode.COST_GATE, 'cost gate: re-run with --confirm', json);\n if (res.status === 429) fail(ExitCode.BUDGET, 'budget or rate limit exceeded', json);\n if (!res.ok) fail(ExitCode.ERROR, `request failed (${res.status})`, json);\n return json;\n }\n}\n","import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\n/**\n * Local config (`CLI.md` §2/§3). Project pointer in `glass.json` (committable,\n * contains only the write-only publishable key); SECRETS in\n * `~/.glass/credentials.json` with `0600` perms, never in the repo.\n */\n\nexport interface ProjectFile {\n project_id: string;\n ingest_key: string;\n ingest_host: string;\n api_host: string;\n /** The claim URL minted at provisioning (single-use); shown by `glass claim`. */\n claim_url?: string;\n}\n\nexport interface Credentials {\n /** provisioning/agent tokens keyed by project_id. */\n tokens: Record<string, string>;\n /** Clerk session from `glass login` (device flow). */\n session?: string;\n}\n\nconst PROJECT_FILE = join(process.cwd(), 'glass.json');\nconst CRED_FILE = join(homedir(), '.glass', 'credentials.json');\n\n/**\n * Read the project pointer, with environment overrides layered on top\n * (`CLI.md` §2): `GLASS_PROJECT` selects/overrides the project id and\n * `GLASS_INGEST_KEY` the publishable key — so CI can run without a committed\n * `glass.json`. If neither the file nor `GLASS_PROJECT` is present, returns null.\n */\nexport function readProject(): ProjectFile | null {\n let file: ProjectFile | null = null;\n try {\n file = JSON.parse(readFileSync(PROJECT_FILE, 'utf8')) as ProjectFile;\n } catch {\n file = null;\n }\n\n const envProject = process.env.GLASS_PROJECT;\n const envIngest = process.env.GLASS_INGEST_KEY;\n if (!file && !envProject) return null;\n\n return {\n project_id: envProject ?? file?.project_id ?? '',\n ingest_key: envIngest ?? file?.ingest_key ?? '',\n ingest_host: process.env.GLASS_INGEST_HOST ?? file?.ingest_host ?? 'https://in.glass.dev',\n api_host: process.env.GLASS_API_HOST ?? file?.api_host ?? 'https://api.glass.dev',\n ...(file?.claim_url ? { claim_url: file.claim_url } : {}),\n };\n}\n\nexport function writeProject(p: ProjectFile): void {\n writeFileSync(PROJECT_FILE, `${JSON.stringify(p, null, 2)}\\n`);\n}\n\nexport function readCredentials(): Credentials {\n try {\n return JSON.parse(readFileSync(CRED_FILE, 'utf8')) as Credentials;\n } catch {\n return { tokens: {} };\n }\n}\n\nexport function writeCredentials(c: Credentials): void {\n mkdirSync(dirname(CRED_FILE), { recursive: true, mode: 0o700 });\n writeFileSync(CRED_FILE, `${JSON.stringify(c, null, 2)}\\n`, { mode: 0o600 });\n chmodSync(CRED_FILE, 0o600); // enforce even if the file pre-existed\n}\n\nexport function tokenFor(projectId: string): string | undefined {\n return readCredentials().tokens[projectId];\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","dirname","readFileSync","dirname","writeFileSync","query"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands.ts","../src/output.ts","../src/api.ts","../src/config.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport * as cmd from './commands.js';\nimport { ExitCode, fail, setFormat, setJsonMode } from './output.js';\n\n/**\n * `glass` — the agent-friendly terminal surface (`CLI.md`). Setup in one command\n * (`glass init`, no signup), then the same typed query primitives the dashboard\n * uses. `--json` + stable exit codes make it scriptable by humans and agents.\n */\n\nconst HELP = `glass <command> [options]\n\nSetup\n init [--force] Provision a project with no signup (writes glass.json)\n install [--framework F] [--no-install] [--registry URL] [--force] Install + wire the SDK\n status Show whether events are being received\n doctor Diagnose setup problems\n claim Get the claim URL to keep your data\n login [--token T] Store a Clerk session for human-scoped access\n logout Clear the stored session\n whoami Show the current principal (GET /v1/me)\n link --project ID Point glass.json at another project you can access\n\nQuery (all accept --last 7d, --json, --confirm, --max-scan <MB>)\n trend --event E [--bucket day] [--breakdown B] [--aggregate count|unique_users|sum]\n funnel --steps a,b,c | --id <def> [--window 1d]\n retention --id <def> --return E [--periods 8]\n segment --group-by a,b [--event E]\n errors\n replays [--limit 50]\n query --query '<json>' | -f <file> | (piped stdin)\n\nManage\n define --file spec.json | --kind funnel --steps a,b | --list\n tokens list | create [--budget-r2sql 1GB/day] | revoke <id>\n link <project-id>\n\nGlobal: --json --csv --ndjson --api-host --ingest-host\n --max-scan <500MB|1GB> --yes/--no-input (auth: --token | GLASS_TOKEN)\nEnv: GLASS_TOKEN GLASS_PROJECT GLASS_INGEST_KEY GLASS_API_HOST GLASS_INGEST_HOST`;\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const command = argv[0];\n const sub = argv[1] && !argv[1].startsWith('-') ? argv[1] : undefined;\n\n const { values, positionals } = parseArgs({\n args: argv.slice(1),\n allowPositionals: true,\n options: {\n json: { type: 'boolean' },\n csv: { type: 'boolean' },\n ndjson: { type: 'boolean' },\n confirm: { type: 'boolean' },\n reissue: { type: 'boolean' },\n force: { type: 'boolean' },\n 'no-install': { type: 'boolean' },\n yes: { type: 'boolean' },\n 'no-input': { type: 'boolean' },\n list: { type: 'boolean' },\n 'max-scan': { type: 'string' },\n last: { type: 'string' },\n event: { type: 'string' },\n bucket: { type: 'string' },\n breakdown: { type: 'string' },\n aggregate: { type: 'string' },\n steps: { type: 'string' },\n window: { type: 'string' },\n scope: { type: 'string' },\n kind: { type: 'string' },\n id: { type: 'string' },\n return: { type: 'string' },\n periods: { type: 'string' },\n 'group-by': { type: 'string' },\n group: { type: 'string' },\n status: { type: 'string' },\n limit: { type: 'string' },\n user: { type: 'string' },\n query: { type: 'string' },\n file: { type: 'string', short: 'f' },\n name: { type: 'string' },\n role: { type: 'string' },\n framework: { type: 'string' },\n project: { type: 'string' },\n 'budget-r2sql': { type: 'string' },\n token: { type: 'string' },\n 'api-host': { type: 'string' },\n 'ingest-host': { type: 'string' },\n registry: { type: 'string' },\n },\n });\n\n const flags = values as Record<string, string | boolean>;\n if (flags.json) setJsonMode(true);\n else if (flags.ndjson) setFormat('ndjson');\n else if (flags.csv) setFormat('csv');\n\n switch (command) {\n case 'init':\n return void (await cmd.init(flags));\n case 'install':\n return void cmd.install(flags);\n case 'status':\n return void (await cmd.status(flags));\n case 'doctor':\n return void (await cmd.doctor(flags));\n case 'claim':\n return void (await cmd.claim(flags));\n case 'login':\n return void (await cmd.login(flags));\n case 'logout':\n return void cmd.logout(flags);\n case 'whoami':\n return void (await cmd.whoami(flags));\n case 'link':\n return void (await cmd.link(flags, positionals));\n case 'trend':\n return void (await cmd.trend(flags));\n case 'funnel':\n return void (await cmd.funnel(flags));\n case 'retention':\n return void (await cmd.retention(flags));\n case 'segment':\n return void (await cmd.segment(flags));\n case 'errors':\n return void (await cmd.errors(flags));\n case 'replays':\n return void (await cmd.replays(flags));\n case 'query':\n return void (await cmd.query(flags));\n case 'define':\n return void (await cmd.define(flags));\n case 'tokens':\n // positionals[0] is the sub-action; pass the remainder (e.g. token id).\n return void (await cmd.tokens(sub ?? 'list', flags, positionals.slice(1)));\n case 'help':\n case undefined:\n case '--help':\n case '-h':\n process.stdout.write(`${HELP}\\n`);\n process.exit(ExitCode.OK);\n break;\n default:\n fail(ExitCode.USAGE, `unknown command: ${command}\\n\\n${HELP}`);\n }\n}\n\nmain().catch((e) => fail(ExitCode.ERROR, String(e?.message ?? e)));\n","import { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, relative } from 'node:path';\nimport type { GlassQuery } from '@glassanalytics/core';\nimport { Api } from './api.js';\nimport {\n DEFAULT_API_HOST,\n DEFAULT_INGEST_HOST,\n readCredentials,\n readProject,\n tokenFor,\n writeCredentials,\n writeProject,\n} from './config.js';\nimport { ExitCode, fail, ok, parseBytes } from './output.js';\n\ntype Flags = Record<string, string | boolean>;\n\nfunction requireProject() {\n const project = readProject();\n if (!project) fail(ExitCode.USAGE, 'no glass.json found — run `glass init` first');\n return project;\n}\n\n/**\n * Token precedence (`CLI.md` §2): explicit --token > GLASS_TOKEN env > stored\n * per-project token > stored Clerk session. Lets CI inject a token without\n * touching the filesystem.\n */\nfunction resolveToken(flags: Flags, projectId?: string): string | undefined {\n if (typeof flags.token === 'string' && flags.token) return flags.token;\n if (process.env.GLASS_TOKEN) return process.env.GLASS_TOKEN;\n if (projectId) {\n const t = tokenFor(projectId);\n if (t) return t;\n }\n return readCredentials().session;\n}\n\nfunction authedApi(flags: Flags = {}) {\n const project = requireProject();\n const token = resolveToken(flags, project.project_id);\n if (!token) fail(ExitCode.AUTH, 'no credentials — run `glass init`, `glass login`, or set GLASS_TOKEN');\n return { api: new Api(project.api_host, token, project.project_id), project };\n}\n\n// --- glass init (zero-auth provisioning) -------------------------------------\n\nexport async function init(flags: Flags): Promise<never> {\n // Idempotent: don't re-provision if a project is already linked here.\n const existing = readProject();\n if (existing && flags.force !== true) {\n return ok({ project_id: existing.project_id, linked: true }, () => {\n process.stdout.write(`Already linked to ${existing.project_id} (glass.json present).\\n`);\n process.stdout.write('Use `glass init --force` to provision a new project, or `glass install` to wire the SDK.\\n');\n });\n }\n\n const apiHost = (flags['api-host'] as string) ?? DEFAULT_API_HOST;\n const api = new Api(apiHost);\n const res = await api.request<{\n project_id: string;\n ingest_key: string;\n provisioning_token: string;\n claim_url: string;\n }>('POST', '/v1/projects/provision', { name: (flags.name as string) ?? undefined });\n\n writeProject({\n project_id: res.project_id,\n ingest_key: res.ingest_key,\n ingest_host: (flags['ingest-host'] as string) ?? DEFAULT_INGEST_HOST,\n api_host: apiHost,\n claim_url: res.claim_url,\n });\n const creds = readCredentials();\n creds.tokens[res.project_id] = res.provisioning_token;\n writeCredentials(creds);\n\n return ok(res, () => {\n process.stdout.write(`Created project ${res.project_id}\\n`);\n process.stdout.write('Wrote glass.json and ~/.glass/credentials.json (0600)\\n');\n process.stdout.write(`\\nClaim it to keep your data:\\n ${res.claim_url}\\n`);\n process.stdout.write('\\nNext: `glass install` to wire up the SDK.\\n');\n });\n}\n\n// --- glass install (framework detection) -------------------------------------\n\ntype Framework = 'next' | 'react' | 'vite' | 'node' | 'browser';\n\nfunction detectFramework(): Framework {\n try {\n const pkg = JSON.parse(readFileSync('package.json', 'utf8')) as { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps.next) return 'next';\n if (deps.express || deps.fastify || deps.hono) return 'node';\n if (deps.react) return 'react';\n if (deps.vite) return 'vite';\n } catch {\n /* no package.json — assume browser */\n }\n return 'browser';\n}\n\n/** Pick the package manager from the lockfile present in cwd. */\nfunction detectPackageManager(): { cmd: string; args: string[] } {\n if (existsSync('pnpm-lock.yaml')) return { cmd: 'pnpm', args: ['add'] };\n if (existsSync('yarn.lock')) return { cmd: 'yarn', args: ['add'] };\n if (existsSync('bun.lockb')) return { cmd: 'bun', args: ['add'] };\n return { cmd: 'npm', args: ['install'] };\n}\n\n/** Next App Router root layout, if this is an App Router project. */\nfunction findNextAppLayout(): string | null {\n for (const p of ['app/layout.tsx', 'app/layout.jsx', 'src/app/layout.tsx', 'src/app/layout.jsx']) {\n if (existsSync(p)) return p;\n }\n return null;\n}\n\n/** Bare-import entry points for non-App-Router client apps + Node servers. */\nfunction entryCandidates(framework: Framework): string[] {\n switch (framework) {\n case 'next': // Pages Router (App Router is handled separately).\n return ['pages/_app.tsx', 'pages/_app.jsx', 'src/pages/_app.tsx', 'src/pages/_app.jsx'];\n case 'react':\n case 'vite':\n return ['src/main.tsx', 'src/main.jsx', 'src/index.tsx', 'src/index.jsx', 'src/main.ts', 'src/index.ts'];\n case 'node':\n return ['src/index.ts', 'src/server.ts', 'index.ts', 'server.ts', 'src/main.ts'];\n default:\n return [];\n }\n}\n\n/** True if any candidate entry already wires Glass (idempotency guard). */\nfunction alreadyWired(): boolean {\n const files = [\n 'glass.ts',\n findNextAppLayout(),\n ...entryCandidates('next'),\n ...entryCandidates('react'),\n ].filter((f): f is string => !!f && existsSync(f));\n return files.some((f) => {\n try {\n return /@glassanalytics\\/(browser|node)|glass\\.init\\(|GlassProvider/.test(readFileSync(f, 'utf8'));\n } catch {\n return false;\n }\n });\n}\n\n/** The client provider component that actually initialises Glass in the browser. */\nconst NEXT_PROVIDER_SRC = `'use client';\n\nimport { useEffect, useRef } from 'react';\nimport glass from '@glassanalytics/browser';\n\nconst KEY = process.env.NEXT_PUBLIC_GLASS_PROJECT_KEY;\nconst INGEST_HOST = process.env.NEXT_PUBLIC_GLASS_INGEST_HOST;\nconst API_HOST = process.env.NEXT_PUBLIC_GLASS_API_HOST;\n\n/**\n * Initialises Glass on the client. The SDK auto-captures pageviews (incl. App\n * Router navigations), autocapture, errors and session replay after init().\n */\nexport function GlassProvider() {\n const started = useRef(false);\n useEffect(() => {\n if (started.current || !KEY || !INGEST_HOST) return;\n started.current = true;\n glass.init({ projectKey: KEY, ingestHost: INGEST_HOST, apiHost: API_HOST });\n }, []);\n return null;\n}\n\nexport default GlassProvider;\n`;\n\n/** Append missing KEY=VALUE lines to an env file; never clobbers existing values. */\nfunction upsertEnv(file: string, vars: Record<string, string>): { file: string; wrote: boolean } {\n let src = '';\n try {\n src = readFileSync(file, 'utf8');\n } catch {\n src = '';\n }\n let out = src;\n let changed = false;\n for (const [k, v] of Object.entries(vars)) {\n if (new RegExp(`^${k}=`, 'm').test(out)) continue; // respect an existing value\n if (out.length && !out.endsWith('\\n')) out += '\\n';\n out += `${k}=${v}\\n`;\n changed = true;\n }\n if (changed) writeFileSync(file, out);\n return { file, wrote: changed };\n}\n\n/**\n * Wire Glass into a Next App Router layout the ONLY way that runs client-side:\n * a `\"use client\"` provider component rendered inside <body>. A bare\n * side-effect import in a Server Component would never execute in the browser.\n */\nfunction wireNextAppRouter(layout: string): {\n provider: string;\n injectedImport: boolean;\n injectedRender: boolean;\n} {\n const providerFile = join(dirname(layout), 'glass-provider.tsx');\n if (!existsSync(providerFile)) writeFileSync(providerFile, NEXT_PROVIDER_SRC);\n\n let src = readFileSync(layout, 'utf8');\n const importLine = \"import { GlassProvider } from './glass-provider';\\n\";\n let injectedImport = false;\n if (!src.includes('./glass-provider')) {\n const directive = /^\\s*(['\"])use (client|server)\\1;?\\s*\\n/.exec(src);\n src = directive\n ? src.slice(0, directive[0].length) + importLine + src.slice(directive[0].length)\n : importLine + src;\n injectedImport = true;\n }\n\n let injectedRender = false;\n if (!src.includes('<GlassProvider')) {\n const bodyOpen = /(<body[^>]*>)/;\n if (bodyOpen.test(src)) {\n src = src.replace(bodyOpen, '$1\\n <GlassProvider />');\n injectedRender = true;\n }\n }\n\n writeFileSync(layout, src);\n return { provider: providerFile, injectedImport, injectedRender };\n}\n\n/**\n * Bare side-effect wiring for non-App-Router client apps (Pages Router, Vite,\n * CRA) and Node servers — a top-level `import './glass'` in the client/server\n * entry is correct there. Writes glass.ts (env-driven for the browser) if absent.\n */\nfunction wireBareImport(\n framework: Framework,\n project: { ingest_key: string; ingest_host: string; api_host: string },\n): { file: string; wroteFile: boolean; entry: string | null; injected: boolean } {\n const file = 'glass.ts';\n const code =\n framework === 'node'\n ? `import { GlassNode } from '@glassanalytics/node';\\n\\nexport const glass = new GlassNode({\\n projectKey: process.env.GLASS_INGEST_KEY ?? '${project.ingest_key}',\\n ingestHost: process.env.GLASS_INGEST_HOST ?? '${project.ingest_host}',\\n});\\n`\n : `import glass from '@glassanalytics/browser';\\n\\nglass.init({ projectKey: '${project.ingest_key}', ingestHost: '${project.ingest_host}', apiHost: '${project.api_host}' });\\n\\nexport default glass;\\n`;\n\n let wroteFile = false;\n if (!existsSync(file)) {\n writeFileSync(file, code);\n wroteFile = true;\n }\n\n for (const entry of entryCandidates(framework)) {\n if (!existsSync(entry)) continue;\n const src = readFileSync(entry, 'utf8');\n let spec = relative(dirname(entry), file).replace(/\\.(ts|tsx|js|jsx)$/, '');\n if (!spec.startsWith('.')) spec = `./${spec}`;\n if (src.includes(`'${spec}'`) || src.includes(`\"${spec}\"`)) return { file, wroteFile, entry, injected: false };\n const importLine = `import '${spec}';\\n`;\n const directive = /^\\s*(['\"])use (client|server)\\1;?\\s*\\n/.exec(src);\n const next = directive\n ? src.slice(0, directive[0].length) + importLine + src.slice(directive[0].length)\n : importLine + src;\n writeFileSync(entry, next);\n return { file, wroteFile, entry, injected: true };\n }\n return { file, wroteFile, entry: null, injected: false };\n}\n\nexport function install(flags: Flags): never {\n const project = requireProject();\n const framework = ((flags.framework as Framework) ?? detectFramework()) as Framework;\n const pkg = framework === 'node' ? '@glassanalytics/node' : '@glassanalytics/browser';\n\n // Idempotency: don't add a second, conflicting integration.\n if (alreadyWired() && flags.force !== true) {\n return ok({ framework, package: pkg, already_wired: true }, () => {\n process.stdout.write('Glass already appears to be wired in this project — nothing to do.\\n');\n process.stdout.write('Re-run with `--force` to wire it again anyway.\\n');\n });\n }\n\n // 1. Install the SDK dependency (fixed argv — never a shell string). A\n // `--registry` passthrough helps past enterprise/mirror registries.\n let installed = false;\n if (flags['no-install'] !== true) {\n const pm = detectPackageManager();\n const args = [...pm.args, pkg];\n if (typeof flags.registry === 'string' && flags.registry) args.push(`--registry=${flags.registry}`);\n const r = spawnSync(pm.cmd, args, { stdio: 'inherit' });\n installed = r.status === 0;\n if (!installed) {\n const detail = r.error ? String(r.error.message) : `exit ${r.status}`;\n return fail(\n ExitCode.ERROR,\n `failed to install ${pkg} via ${pm.cmd} (${detail}) — install it manually, or retry with \\`--registry https://registry.npmjs.org/\\` if you're behind a private registry`,\n );\n }\n }\n\n // 2. Wire it up the correct way for the detected framework.\n const appLayout = framework === 'next' ? findNextAppLayout() : null;\n\n if (appLayout) {\n const env = upsertEnv('.env.local', {\n NEXT_PUBLIC_GLASS_PROJECT_KEY: project.ingest_key,\n NEXT_PUBLIC_GLASS_INGEST_HOST: project.ingest_host,\n NEXT_PUBLIC_GLASS_API_HOST: project.api_host,\n });\n const w = wireNextAppRouter(appLayout);\n return ok(\n {\n framework: 'next-app',\n package: pkg,\n installed,\n provider: w.provider,\n env_file: env.file,\n wrote_env: env.wrote,\n layout: appLayout,\n injected: w.injectedImport,\n rendered: w.injectedRender,\n },\n () => {\n process.stdout.write('Detected: Next.js (App Router)\\n');\n process.stdout.write(installed ? `Installed ${pkg}.\\n` : `Skipped install — add ${pkg} yourself.\\n`);\n process.stdout.write(`Wrote ${w.provider}${env.wrote ? ` and Glass keys to ${env.file}` : ''}.\\n`);\n if (w.injectedRender) process.stdout.write(`Rendered <GlassProvider /> in ${appLayout}.\\n`);\n else process.stdout.write(`Couldn't auto-insert <GlassProvider /> — add it inside <body> in ${appLayout}.\\n`);\n },\n );\n }\n\n const w = wireBareImport(framework, project);\n return ok(\n { framework, package: pkg, installed, file: w.file, wrote_file: w.wroteFile, entry: w.entry, injected: w.injected },\n () => {\n process.stdout.write(`Detected: ${framework}${framework === 'next' ? ' (Pages Router)' : ''}\\n`);\n process.stdout.write(installed ? `Installed ${pkg}.\\n` : `Skipped install — add ${pkg} yourself.\\n`);\n process.stdout.write(w.wroteFile ? `Wrote ${w.file}.\\n` : `${w.file} already exists; left untouched.\\n`);\n if (w.injected && w.entry) process.stdout.write(`Wired ${w.file} into ${w.entry}.\\n`);\n else if (w.entry) process.stdout.write(`${w.entry} already imports ${w.file}.\\n`);\n else process.stdout.write(`No entry point found — import \"./${w.file.replace(/\\.(ts|tsx)$/, '')}\" in your app entry yourself.\\n`);\n },\n );\n}\n\n// --- status / doctor ----------------------------------------------------------\n\nexport async function status(flags: Flags): Promise<never> {\n const { api } = authedApi(flags);\n const res = await api.request('GET', '/v1/ingest/status');\n return ok(res);\n}\n\nexport async function doctor(flags: Flags): Promise<never> {\n const project = readProject();\n const checks: { name: string; ok: boolean; detail?: string }[] = [];\n checks.push({ name: 'glass.json present', ok: !!project });\n if (project) {\n const tok = resolveToken(flags, project.project_id);\n checks.push({ name: 'credentials present', ok: !!tok });\n try {\n const api = new Api(project.api_host, tok, project.project_id);\n const s = await api.request<{ receiving: boolean }>('GET', '/v1/ingest/status');\n checks.push({ name: 'events received', ok: s.receiving, detail: s.receiving ? undefined : 'no events in last 24h' });\n } catch {\n checks.push({ name: 'control plane reachable', ok: false });\n }\n }\n const allOk = checks.every((c) => c.ok);\n return allOk ? ok({ checks }) : fail(ExitCode.ERROR, 'doctor found problems', { checks });\n}\n\n// --- claim / login ------------------------------------------------------------\n\nexport async function claim(flags: Flags): Promise<never> {\n const project = requireProject();\n\n // Default: show the claim URL stored at `glass init` WITHOUT contacting the\n // control plane (so we never invalidate a link the user may already be using).\n if (flags.reissue !== true && project.claim_url) {\n return ok({ claim_url: project.claim_url, reissued: false }, () =>\n process.stdout.write(`Claim URL:\\n ${project.claim_url}\\n\\n(Use \\`glass claim --reissue\\` to mint a fresh single-use link.)\\n`),\n );\n }\n\n // `--reissue` (or no stored URL): mint a fresh single-use claim link.\n const token = tokenFor(project.project_id);\n if (!token) fail(ExitCode.AUTH, 'no provisioning token for this project');\n const api = new Api(project.api_host, token);\n const res = await api.request<{ claim_url: string }>('POST', `/v1/projects/${project.project_id}/claim-url`);\n // Persist the new link so subsequent `glass claim` shows it without reissuing.\n writeProject({ ...project, claim_url: res.claim_url });\n return ok({ ...res, reissued: true }, () => process.stdout.write(`Claim URL (reissued):\\n ${res.claim_url}\\n`));\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/**\n * `glass login` (`CLI.md` §2). `--token <session>` stores a session directly\n * (CI/non-interactive); otherwise runs the OAuth-style device flow: start a\n * grant, print the verification URL + short user-code, then poll until a\n * signed-in human approves it in the dashboard.\n */\nexport async function login(flags: Flags): Promise<never> {\n const apiHost = (flags['api-host'] as string) ?? readProject()?.api_host ?? DEFAULT_API_HOST;\n\n // Non-interactive path: store an explicitly-provided session.\n const provided = flags.token as string | undefined;\n if (provided) {\n const creds = readCredentials();\n creds.session = provided;\n writeCredentials(creds);\n return ok({ message: 'Logged in. Session stored in ~/.glass/credentials.json' }, () =>\n process.stdout.write('Logged in. Session stored in ~/.glass/credentials.json\\n'),\n );\n }\n\n // The device flow needs a human to approve in a browser; in non-interactive\n // mode (`--no-input`/`--yes`, or CI) fail fast and ask for a token instead.\n if (flags['no-input'] === true || flags.yes === true || (!process.stdin.isTTY && !process.stdout.isTTY)) {\n return fail(ExitCode.AUTH, 'login is interactive — pass `--token <session>` or set GLASS_TOKEN for non-interactive use');\n }\n\n // Device flow.\n const api = new Api(apiHost);\n const grant = await api.request<{\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete?: string;\n interval: number;\n expires_in: number;\n }>('POST', '/v1/cli/device/start');\n\n if (!flags.json) {\n process.stdout.write('\\nTo sign in, open:\\n');\n process.stdout.write(` ${grant.verification_uri_complete ?? grant.verification_uri}\\n\\n`);\n process.stdout.write(`and enter the code: ${grant.user_code}\\n\\n`);\n process.stdout.write('Waiting for approval…\\n');\n }\n\n const deadline = Date.now() + grant.expires_in * 1000;\n const intervalMs = Math.max(1, grant.interval) * 1000;\n while (Date.now() < deadline) {\n await sleep(intervalMs);\n let resp: { session?: string; status?: string } | null = null;\n try {\n resp = await api.request<{ session?: string; status?: string }>('POST', '/v1/cli/device/token', {\n device_code: grant.device_code,\n });\n } catch {\n // 202 (pending) / 410 (expired) surface as request errors depending on the\n // client; treat transient failures as \"keep polling\" until the deadline.\n continue;\n }\n if (resp?.session) {\n const creds = readCredentials();\n creds.session = resp.session;\n writeCredentials(creds);\n return ok({ message: 'Logged in. Session stored in ~/.glass/credentials.json' }, () =>\n process.stdout.write('\\nLogged in. Session stored in ~/.glass/credentials.json\\n'),\n );\n }\n }\n return fail(ExitCode.AUTH, 'device authorization timed out — run `glass login` again');\n}\n\nexport function logout(_flags: Flags): never {\n const creds = readCredentials();\n creds.session = undefined;\n writeCredentials(creds);\n return ok({ message: 'Logged out. Cleared stored session.' }, () => process.stdout.write('Logged out.\\n'));\n}\n\nexport async function whoami(flags: Flags): Promise<never> {\n const project = readProject();\n const token = resolveToken(flags, project?.project_id);\n if (!token) fail(ExitCode.AUTH, 'not logged in — run `glass login` or set GLASS_TOKEN');\n const api = new Api(project?.api_host ?? (flags['api-host'] as string) ?? DEFAULT_API_HOST, token, project?.project_id);\n return ok(await api.request('GET', '/v1/me'));\n}\n\n/** Switch the active project in glass.json after verifying access. */\nexport async function link(flags: Flags, positionals: string[] = []): Promise<never> {\n // Accept both `glass link <id>` and `glass link --project <id>`.\n const projectId = (flags.project as string | undefined) ?? positionals[0];\n if (!projectId) fail(ExitCode.USAGE, 'link needs a project id: `glass link <id>`');\n const existing = readProject();\n const apiHost = (flags['api-host'] as string) ?? existing?.api_host ?? DEFAULT_API_HOST;\n const token = resolveToken(flags, projectId);\n if (!token) fail(ExitCode.AUTH, 'no credentials — run `glass login` or set GLASS_TOKEN');\n const api = new Api(apiHost, token, projectId);\n const proj = await api.request<{ id: string; publishableKey?: string; ingestHost?: string }>('GET', '/v1/project');\n writeProject({\n project_id: projectId,\n ingest_key: proj.publishableKey ?? existing?.ingest_key ?? '',\n ingest_host: proj.ingestHost ?? existing?.ingest_host ?? DEFAULT_INGEST_HOST,\n api_host: apiHost,\n });\n return ok({ linked: projectId }, () => process.stdout.write(`Linked glass.json to ${projectId}.\\n`));\n}\n\n// --- query + sugar ------------------------------------------------------------\n\n/**\n * `glass query` source resolution (`CLI.md` §4): a GlassQuery can come from\n * `--query '<json>'`, `--file/-f <path>`, or stdin (so it composes in a pipe:\n * `cat q.json | glass query --json`). Exactly one source is used, in that order.\n */\nexport async function query(flags: Flags): Promise<never> {\n let raw: string | undefined;\n if (typeof flags.query === 'string' && flags.query) raw = flags.query;\n else if (typeof flags.file === 'string' && flags.file) raw = readFileSync(flags.file, 'utf8');\n else if (!process.stdin.isTTY) raw = readFileSync(0, 'utf8').trim() || undefined;\n\n if (!raw) fail(ExitCode.USAGE, \"query needs --query '<json>', --file <path>, or piped stdin\");\n let parsed: GlassQuery;\n try {\n parsed = JSON.parse(raw) as GlassQuery;\n } catch (e) {\n return fail(ExitCode.USAGE, `query is not valid JSON: ${String((e as Error).message)}`);\n }\n return runQuery(parsed, flags);\n}\n\nexport async function runQuery(query: GlassQuery, flags: Flags): Promise<never> {\n const { api } = authedApi(flags);\n const body: Record<string, unknown> = { query, confirm: flags.confirm === true };\n if (flags['max-scan']) {\n // Accept a bare number (legacy: MB) or a unit string (`500MB`, `1GB`).\n const raw = String(flags['max-scan']);\n const bytes = /^[0-9]*\\.?[0-9]+$/.test(raw.trim())\n ? Number(raw) * 1024 * 1024\n : parseBytes(raw);\n if (bytes == null) fail(ExitCode.USAGE, `invalid --max-scan: ${raw} (try 500MB or 1GB)`);\n body.max_scan_bytes = bytes;\n }\n const res = await api.request('POST', '/v1/query', body);\n return ok(res);\n}\n\nfunction parseTime(flags: Flags) {\n return { last: (flags.last as string) ?? '7d' };\n}\n\nexport async function trend(flags: Flags): Promise<never> {\n return runQuery(\n {\n v: 1,\n kind: 'trend',\n time: parseTime(flags),\n measure: { aggregate: (flags.aggregate as 'count' | 'unique_users' | 'sum') ?? 'count', ...(flags.event ? { event: flags.event as string } : {}) },\n bucket: (flags.bucket as 'hour' | 'day' | 'week' | 'month') ?? 'day',\n ...(flags.breakdown ? { breakdown: flags.breakdown as string } : {}),\n },\n flags,\n );\n}\n\nexport async function funnel(flags: Flags): Promise<never> {\n const steps = String(flags.steps ?? '').split(',').map((e) => ({ event: e.trim() })).filter((s) => s.event);\n if (flags.id) return runQuery({ v: 1, kind: 'funnel', time: parseTime(flags), definition_id: flags.id as string }, flags);\n if (steps.length < 2) fail(ExitCode.USAGE, 'funnel needs --steps a,b,c (>=2) or --id <definition>');\n return runQuery({ v: 1, kind: 'funnel', time: parseTime(flags), steps, ...(flags.window ? { window: flags.window as string } : {}) }, flags);\n}\n\nexport async function retention(flags: Flags): Promise<never> {\n if (!flags.id) fail(ExitCode.USAGE, 'retention needs --id <definition>');\n return runQuery({ v: 1, kind: 'retention', time: parseTime(flags), definition_id: flags.id as string, return_event: (flags.return as string) ?? '$pageview', periods: Number(flags.periods ?? 8) }, flags);\n}\n\nexport async function segment(flags: Flags): Promise<never> {\n const groupBy = String(flags['group-by'] ?? '').split(',').map((s) => s.trim()).filter(Boolean);\n if (groupBy.length === 0) fail(ExitCode.USAGE, 'segment needs --group-by a,b');\n return runQuery({ v: 1, kind: 'segmentation', time: parseTime(flags), group_by: groupBy, ...(flags.event ? { event: flags.event as string } : {}) }, flags);\n}\n\nexport async function errors(flags: Flags): Promise<never> {\n return runQuery(\n {\n v: 1,\n kind: 'errors',\n time: parseTime(flags),\n limit: Number(flags.limit ?? 50),\n ...(flags.group ? { group: flags.group as string } : {}),\n ...(flags.status ? { status: flags.status as 'unresolved' | 'resolved' | 'ignored' } : {}),\n },\n flags,\n );\n}\n\nexport async function replays(flags: Flags): Promise<never> {\n return runQuery(\n {\n v: 1,\n kind: 'replays',\n time: parseTime(flags),\n limit: Number(flags.limit ?? 50),\n ...(flags.user ? { session_filters: [{ property: 'user_id', op: 'eq' as const, value: flags.user as string }] } : {}),\n },\n flags,\n );\n}\n\n// --- define / tokens ----------------------------------------------------------\n\nexport async function define(flags: Flags): Promise<never> {\n const { api } = authedApi(flags);\n\n // `define --list` — show existing definitions.\n if (flags.list) return ok(await api.request('GET', '/v1/definitions'));\n\n // Flag-based sugar: build a spec from flags without writing JSON.\n let spec: unknown;\n if (flags.kind === 'funnel') {\n const steps = String(flags.steps ?? '').split(',').map((e) => ({ event: e.trim() })).filter((s) => s.event);\n if (steps.length < 2) fail(ExitCode.USAGE, 'define --kind funnel needs --steps a,b,c (>=2)');\n spec = { v: 1, kind: 'funnel', name: (flags.name as string) ?? 'funnel', steps, scope: (flags.scope as string) ?? 'within-session', ...(flags.window ? { window: flags.window as string } : {}) };\n } else if (flags.kind === 'metric') {\n spec = { v: 1, kind: 'metric', name: (flags.name as string) ?? 'metric', aggregate: (flags.aggregate as string) ?? 'count', bucket: (flags.bucket as string) ?? 'day', ...(flags.event ? { event: flags.event as string } : {}) };\n } else {\n const raw = flags.file ? readFileSync(flags.file as string, 'utf8') : readFileSync(0, 'utf8');\n spec = JSON.parse(raw);\n }\n const res = await api.request('POST', '/v1/definitions', { spec });\n return ok(res);\n}\n\nexport async function tokens(action: string, flags: Flags, positionals: string[] = []): Promise<never> {\n const { api } = authedApi(flags);\n if (action === 'list') return ok(await api.request('GET', '/v1/tokens'));\n if (action === 'create') {\n let budget: number | undefined;\n if (flags['budget-r2sql']) {\n // Accept human units + an optional `/day` rate suffix (`1GB/day`).\n const parsed = parseBytes(String(flags['budget-r2sql']));\n if (parsed == null) fail(ExitCode.USAGE, `invalid --budget-r2sql: ${flags['budget-r2sql']} (try 1GB/day)`);\n budget = parsed;\n }\n const res = await api.request<{ id: string; token: string; prefix: string }>('POST', '/v1/tokens', {\n name: (flags.name as string) ?? 'agent',\n role: (flags.role as string) ?? 'read',\n ...(budget != null ? { budget_r2sql_bytes_per_day: budget } : {}),\n });\n // The secret is shown ONCE — print it verbatim (not through the redactor).\n return ok(res, () => {\n process.stdout.write(`Created token ${res.id}\\n\\n ${res.token}\\n\\nThis is shown once. Store it now (e.g. as GLASS_TOKEN).\\n`);\n });\n }\n if (action === 'revoke') {\n // Accept both `glass tokens revoke <id>` and `--id <id>`.\n const id = (flags.id as string | undefined) ?? positionals[0];\n if (!id) fail(ExitCode.USAGE, 'tokens revoke needs a token id: `glass tokens revoke <id>`');\n return ok(await api.request('DELETE', `/v1/tokens/${id}`));\n }\n return fail(ExitCode.USAGE, 'tokens <list|create|revoke>');\n}\n","/**\n * Stable, scriptable output (`CLI.md` §5). The CLI is built for agents as much\n * as humans: `--json` emits a uniform envelope and EXIT CODES are stable so a\n * caller can branch on them without parsing prose.\n */\n\nexport const ExitCode = {\n OK: 0,\n ERROR: 1,\n USAGE: 2,\n AUTH: 3,\n COST_GATE: 4,\n BUDGET: 5,\n} as const;\nexport type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];\n\ntype OutputFormat = 'human' | 'json' | 'csv' | 'ndjson';\nlet format: OutputFormat = 'human';\nexport function setFormat(f: OutputFormat): void {\n format = f;\n}\n/** Back-compat shim for `--json`. */\nexport function setJsonMode(on: boolean): void {\n if (on) format = 'json';\n}\n\n/** Mask token-like secrets in human output so they're never accidentally logged. */\nconst SECRET_RE =\n /\\b(glass_(?:sk|pt)_[A-Za-z0-9]+)\\b|\\b(eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,})\\b/g;\nexport function redact(text: string): string {\n return text.replace(SECRET_RE, (m) => `${m.slice(0, 12)}…<redacted>`);\n}\n\n/** Flatten an array of flat records to CSV; falls back to JSON for non-tabular data. */\nexport function toCsv(data: unknown): string | null {\n const rows = Array.isArray(data) ? data : extractRows(data);\n if (!rows || rows.length === 0) return null;\n const cols = [...new Set(rows.flatMap((r) => Object.keys(r)))];\n const esc = (v: unknown) => {\n const s = v == null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v);\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n return [\n cols.join(','),\n ...rows.map((r) => cols.map((c) => esc((r as Record<string, unknown>)[c])).join(',')),\n ].join('\\n');\n}\n\n/**\n * Parse a human byte budget into bytes (`CLI.md` §4): accepts plain numbers and\n * unit suffixes (`500MB`, `1GB`, `2 TiB`), with an optional `/day` rate suffix\n * that we strip (the API stores per-day budgets). Returns null on garbage.\n */\nconst BYTE_UNITS: Record<string, number> = {\n b: 1,\n kb: 1e3,\n mb: 1e6,\n gb: 1e9,\n tb: 1e12,\n kib: 1024,\n mib: 1024 ** 2,\n gib: 1024 ** 3,\n tib: 1024 ** 4,\n};\nexport function parseBytes(input: string): number | null {\n const s = input.trim().toLowerCase().replace(/\\/(day|d|hr|hour|h)$/, '');\n const m = /^([0-9]*\\.?[0-9]+)\\s*([a-z]+)?$/.exec(s);\n if (!m) return null;\n const value = Number(m[1]);\n if (!Number.isFinite(value)) return null;\n if (!m[2]) return value; // bare number = bytes\n const unit = BYTE_UNITS[m[2]];\n if (!unit) return null;\n return Math.round(value * unit);\n}\n\n/** Find the first array-of-objects field (e.g. {rows:[...]}, {issues:[...]}). */\nexport function extractRows(data: unknown): Record<string, unknown>[] | null {\n if (Array.isArray(data)) return data as Record<string, unknown>[];\n if (data && typeof data === 'object') {\n for (const v of Object.values(data)) {\n if (Array.isArray(v) && v.every((x) => x && typeof x === 'object'))\n return v as Record<string, unknown>[];\n }\n }\n return null;\n}\n\n/**\n * Render an array of flat records as an aligned ASCII table for human output.\n * Returns null when the data isn't tabular so callers can fall back to JSON.\n */\nexport function humanTable(data: unknown): string | null {\n const rows = extractRows(data);\n if (!rows || rows.length === 0) return null;\n const cols = [...new Set(rows.flatMap((r) => Object.keys(r)))];\n if (cols.length === 0) return null;\n const cell = (v: unknown) => {\n const s = v == null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v);\n return redact(s).replace(/\\n/g, ' ');\n };\n const widths = cols.map((c) =>\n Math.max(c.length, ...rows.map((r) => cell((r as Record<string, unknown>)[c]).length)),\n );\n const line = (cells: string[]) => cells.map((s, i) => s.padEnd(widths[i] ?? 0)).join(' ').trimEnd();\n const header = line(cols);\n const sep = widths.map((w) => '-'.repeat(w)).join(' ');\n const body = rows.map((r) => line(cols.map((c) => cell((r as Record<string, unknown>)[c]))));\n return [header, sep, ...body].join('\\n');\n}\n\n/**\n * One-line provenance badge for a query result (`UI.md` ResultBadge parity):\n * surfaces whether a number is exact or sampled, and its source, so an agent\n * reading stdout knows how much to trust it. Returns null when not applicable.\n */\nexport function resultBadge(data: unknown): string | null {\n if (!data || typeof data !== 'object') return null;\n const d = data as Record<string, unknown>;\n if (typeof d.exact !== 'boolean' && !d.source && !d.sampled) return null;\n const parts: string[] = [];\n parts.push(d.exact === false ? 'sampled' : 'exact');\n if (d.source) parts.push(String(d.source));\n const sampled = d.sampled as { interval?: number } | undefined;\n if (sampled?.interval && sampled.interval > 1) parts.push(`1/${sampled.interval}`);\n return `[${parts.join(' · ')}]`;\n}\n\nexport function ok(data: unknown, human?: () => void): never {\n if (format === 'json') {\n process.stdout.write(`${JSON.stringify({ ok: true, data }, null, 2)}\\n`);\n } else if (format === 'ndjson') {\n const rows = extractRows(data) ?? [data];\n for (const r of rows) process.stdout.write(`${JSON.stringify(r)}\\n`);\n } else if (format === 'csv') {\n const csv = toCsv(data);\n process.stdout.write(csv ? `${csv}\\n` : `${JSON.stringify(data, null, 2)}\\n`);\n } else if (human) {\n human();\n } else {\n const table = humanTable(data);\n if (table) {\n const badge = resultBadge(data);\n process.stdout.write(`${table}\\n`);\n if (badge) process.stdout.write(`${badge}\\n`);\n } else {\n process.stdout.write(`${redact(JSON.stringify(data, null, 2))}\\n`);\n }\n }\n process.exit(ExitCode.OK);\n}\n\nexport function fail(code: ExitCodeValue, message: string, extra?: unknown): never {\n if (format === 'json' || format === 'ndjson') {\n process.stdout.write(\n `${JSON.stringify({ ok: false, error: { code, message, ...(extra ? { extra } : {}) } }, null, 2)}\\n`,\n );\n } else {\n process.stderr.write(`glass: ${message}\\n`);\n if (extra) process.stderr.write(`${redact(JSON.stringify(extra, null, 2))}\\n`);\n }\n process.exit(code);\n}\n","import { ExitCode, fail } from './output.js';\n\n/**\n * Thin control-plane client. Maps HTTP status to the CLI's stable exit codes so\n * `--json` callers (agents) can branch deterministically (`CLI.md` §5):\n * 401/403 → AUTH(3), 402 → COST_GATE(4), 429 → BUDGET(5).\n */\nexport class Api {\n constructor(\n private apiHost: string,\n private token?: string,\n private projectId?: string,\n ) {}\n\n async request<T = unknown>(method: string, path: string, body?: unknown): Promise<T> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.token) headers.Authorization = `Bearer ${this.token}`;\n if (this.projectId) headers['X-Glass-Project'] = this.projectId;\n\n const res = await fetch(`${this.apiHost}${path}`, {\n method,\n headers,\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n }).catch((e) =>\n fail(\n ExitCode.ERROR,\n `couldn't reach the Glass control plane at ${this.apiHost} (${String(\n (e as Error)?.message ?? e,\n )}) — check the host is correct/reachable, or pass --api-host <url> (or set GLASS_API_HOST)`,\n ),\n );\n\n const text = await res.text();\n const json = text ? (JSON.parse(text) as T) : ({} as T);\n\n if (res.status === 401 || res.status === 403) fail(ExitCode.AUTH, 'unauthorized', json);\n if (res.status === 402) fail(ExitCode.COST_GATE, 'cost gate: re-run with --confirm', json);\n if (res.status === 429) fail(ExitCode.BUDGET, 'budget or rate limit exceeded', json);\n if (!res.ok) fail(ExitCode.ERROR, `request failed (${res.status})`, json);\n return json;\n }\n}\n","import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\n/**\n * Local config (`CLI.md` §2/§3). Project pointer in `glass.json` (committable,\n * contains only the write-only publishable key); SECRETS in\n * `~/.glass/credentials.json` with `0600` perms, never in the repo.\n */\n\nexport interface ProjectFile {\n project_id: string;\n ingest_key: string;\n ingest_host: string;\n api_host: string;\n /** The claim URL minted at provisioning (single-use); shown by `glass claim`. */\n claim_url?: string;\n}\n\nexport interface Credentials {\n /** provisioning/agent tokens keyed by project_id. */\n tokens: Record<string, string>;\n /** Clerk session from `glass login` (device flow). */\n session?: string;\n}\n\n/**\n * Default Glass hosts. These are the live production Workers; a bare\n * `glass init` / `install` works with no flags. Override per-invocation with\n * `--api-host` / `--ingest-host` or the `GLASS_API_HOST` / `GLASS_INGEST_HOST`\n * env vars (self-hosters point these at their own deployment).\n */\nexport const DEFAULT_API_HOST = 'https://glass-control-plane.isnit.workers.dev';\nexport const DEFAULT_INGEST_HOST = 'https://glass-ingest.isnit.workers.dev';\n\nconst PROJECT_FILE = join(process.cwd(), 'glass.json');\nconst CRED_FILE = join(homedir(), '.glass', 'credentials.json');\n\n/**\n * Read the project pointer, with environment overrides layered on top\n * (`CLI.md` §2): `GLASS_PROJECT` selects/overrides the project id and\n * `GLASS_INGEST_KEY` the publishable key — so CI can run without a committed\n * `glass.json`. If neither the file nor `GLASS_PROJECT` is present, returns null.\n */\nexport function readProject(): ProjectFile | null {\n let file: ProjectFile | null = null;\n try {\n file = JSON.parse(readFileSync(PROJECT_FILE, 'utf8')) as ProjectFile;\n } catch {\n file = null;\n }\n\n const envProject = process.env.GLASS_PROJECT;\n const envIngest = process.env.GLASS_INGEST_KEY;\n if (!file && !envProject) return null;\n\n return {\n project_id: envProject ?? file?.project_id ?? '',\n ingest_key: envIngest ?? file?.ingest_key ?? '',\n ingest_host: process.env.GLASS_INGEST_HOST ?? file?.ingest_host ?? DEFAULT_INGEST_HOST,\n api_host: process.env.GLASS_API_HOST ?? file?.api_host ?? DEFAULT_API_HOST,\n ...(file?.claim_url ? { claim_url: file.claim_url } : {}),\n };\n}\n\nexport function writeProject(p: ProjectFile): void {\n writeFileSync(PROJECT_FILE, `${JSON.stringify(p, null, 2)}\\n`);\n}\n\nexport function readCredentials(): Credentials {\n try {\n return JSON.parse(readFileSync(CRED_FILE, 'utf8')) as Credentials;\n } catch {\n return { tokens: {} };\n }\n}\n\nexport function writeCredentials(c: Credentials): void {\n mkdirSync(dirname(CRED_FILE), { recursive: true, mode: 0o700 });\n writeFileSync(CRED_FILE, `${JSON.stringify(c, null, 2)}\\n`, { mode: 0o600 });\n chmodSync(CRED_FILE, 0o600); // enforce even if the file pre-existed\n}\n\nexport function tokenFor(projectId: string): string | undefined {\n return readCredentials().tokens[projectId];\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","dirname","join","readFileSync","writeFileSync","join","dirname","w","query"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassanalytics/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Glass CLI — zero-auth provisioning, SDK install, and typed querying from the terminal. Agent-friendly: --json + stable exit codes.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|