lopebase 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,12 +30,14 @@ In the folder of any project you run:
30
30
  npx lopebase
31
31
  ```
32
32
 
33
- 1. **Scan.** It reads your dependency names, config file names, and the
34
- variable *names* in example env files, then shows your stack, the services
35
- you already use, and what is missing (for example analytics or error
36
- tracking). You see each thing it reads as it reads it.
37
- 2. **Connect this computer.** It opens your LopeBase workspace and shows a
38
- short code. Check it matches and press Connect.
33
+ 1. **Scan.** After you say yes, it reads your dependency names, config file
34
+ names, and the variable *names* in example env files, then shows your
35
+ stack, the services you already use, and what it did not find (for example
36
+ analytics or error logging). You see each thing it reads as it reads it.
37
+ 2. **Connect this computer.** It opens LopeBase in your browser and shows a
38
+ short code. Sign in, or create a free account if you are new, check the code
39
+ matches, and press Connect. No address to type: it finds your workspace.
40
+ For a custom domain or your own server, add `--url https://your-address`.
39
41
  3. **Pick what to connect.** Services already in your code are ticked; the
40
42
  ones you do not use yet are offered, never forced.
41
43
  4. **Hand over to your agent.** It finds the coding agents you have installed
@@ -76,7 +78,7 @@ Made for web products, and fine with anything else:
76
78
  | Command | What it does |
77
79
  | --- | --- |
78
80
  | `npx lopebase` | Scan, connect, choose, and hand over to your agent. |
79
- | `npx lopebase login` | Connect this computer to your workspace with a short code. `--paste` to paste an agent token instead. |
81
+ | `npx lopebase login` | Connect this computer to your workspace with a short code, creating an account if you need one. `--paste` to paste an agent token instead. |
80
82
  | `npx lopebase scan` | Show what the scan found. `--json` for scripts. |
81
83
  | `npx lopebase prompt` | Print the setup prompt instead of starting an agent. |
82
84
  | `npx lopebase mcp` | Run the LopeBase tools as a local MCP server (stdio) for any agent. |
package/dist/lopebase.js CHANGED
@@ -832,6 +832,24 @@ class x {
832
832
  }
833
833
  }
834
834
  }
835
+
836
+ class dD extends x {
837
+ get cursor() {
838
+ return this.value ? 0 : 1;
839
+ }
840
+ get _value() {
841
+ return this.cursor === 0;
842
+ }
843
+ constructor(u) {
844
+ super(u, !1), this.value = !!u.initialValue, this.on("value", () => {
845
+ this.value = this._value;
846
+ }), this.on("confirm", (t) => {
847
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = t, this.state = "submit", this.close();
848
+ }), this.on("cursor", () => {
849
+ this.value = !this.value;
850
+ });
851
+ }
852
+ }
835
853
  var A;
836
854
  A = /* @__PURE__ */ new WeakMap;
837
855
  var kD = Object.defineProperty, $D = (e, u, t) => (u in e) ? kD(e, u, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[u] = t, H = (e, u, t) => ($D(e, typeof u != "symbol" ? u + "" : u, t), t), SD = class extends x {
@@ -997,8 +1015,25 @@ ${import_picocolors2.default.gray(o)}` : ""}`;
997
1015
  ${import_picocolors2.default.cyan(d2)}
998
1016
  `;
999
1017
  }
1000
- } }).prompt();
1001
- var ve = (t) => {
1018
+ } }).prompt(), ye = (t) => {
1019
+ let n = t.active ?? "Yes", r2 = t.inactive ?? "No";
1020
+ return new dD({ active: n, inactive: r2, initialValue: t.initialValue ?? !0, render() {
1021
+ let i = `${import_picocolors2.default.gray(o)}
1022
+ ${b2(this.state)} ${t.message}
1023
+ `, s = this.value ? n : r2;
1024
+ switch (this.state) {
1025
+ case "submit":
1026
+ return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(s)}`;
1027
+ case "cancel":
1028
+ return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}
1029
+ ${import_picocolors2.default.gray(o)}`;
1030
+ default:
1031
+ return `${i}${import_picocolors2.default.cyan(o)} ${this.value ? `${import_picocolors2.default.green(k2)} ${n}` : `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(n)}`} ${import_picocolors2.default.dim("/")} ${this.value ? `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(r2)}` : `${import_picocolors2.default.green(k2)} ${r2}`}
1032
+ ${import_picocolors2.default.cyan(d2)}
1033
+ `;
1034
+ }
1035
+ } }).prompt();
1036
+ }, ve = (t) => {
1002
1037
  let n = (r2, i) => {
1003
1038
  let s = r2.label ?? String(r2.value);
1004
1039
  switch (i) {
@@ -1544,6 +1579,18 @@ function workspaceUrl(input) {
1544
1579
  return null;
1545
1580
  }
1546
1581
  }
1582
+ function signInUrl(input) {
1583
+ for (let [raw, from] of [
1584
+ [input.flag, "--url"],
1585
+ [input.env, "LOPEBASE_URL"]
1586
+ ]) {
1587
+ if (raw === void 0 || raw.trim() === "")
1588
+ continue;
1589
+ let url = workspaceUrl(raw);
1590
+ return url ? { url } : { error: `${from} is not an address: ${raw}` };
1591
+ }
1592
+ return { url: input.saved && workspaceUrl(input.saved) || DEFAULT_URL };
1593
+ }
1547
1594
  function configDir() {
1548
1595
  let base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
1549
1596
  return path.join(base, "lopebase");
@@ -2085,7 +2132,7 @@ function detectStack(facts) {
2085
2132
  why: "Know the moment the site goes down. Nothing to install."
2086
2133
  });
2087
2134
  if (isWebsite && !has("posthog") && !has("ga4")) {
2088
- if (gaps.push("No product analytics found. Add PostHog (free tier, one snippet) so LopeBase can show signups and active users."), !listed("posthog"))
2135
+ if (gaps.push("No product analytics found."), !listed("posthog"))
2089
2136
  sources.push({
2090
2137
  source: "posthog",
2091
2138
  label: "PostHog",
@@ -2095,7 +2142,7 @@ function detectStack(facts) {
2095
2142
  });
2096
2143
  }
2097
2144
  if (!has("sentry")) {
2098
- if (gaps.push("No error tracking found. Sentry lets LopeBase flag a spike in errors after a deploy."), !listed("sentry"))
2145
+ if (gaps.push("No error logging found."), !listed("sentry"))
2099
2146
  sources.push({
2100
2147
  source: "sentry",
2101
2148
  label: "Sentry",
@@ -2635,24 +2682,22 @@ function scanProject(root, onProgress) {
2635
2682
  // src/ui.ts
2636
2683
  var import_picocolors3 = __toESM(require_picocolors(), 1), MARK = [
2637
2684
  " ▗▖ ▗▖ ",
2638
- " ▐▌ ▐▌ ",
2639
2685
  " ▐█▖▗█▌ ",
2640
2686
  " ▜██▛ ",
2641
2687
  " ▟██▙ ",
2642
2688
  " ▟████▙ ",
2643
2689
  " ▟██████▙ ",
2644
- " ",
2645
- " ▀▀▀▀▀▀▀▀▀▀ ",
2646
- " ▀▀▀▀▀▀ "
2647
- ], HORIZON = /* @__PURE__ */ new Set([8, 9]);
2690
+ " ▄▄▄▄▄▄▄▄▄▄ ",
2691
+ " ▄▄▄▄▄▄ "
2692
+ ], HORIZON = /* @__PURE__ */ new Set([6, 7]);
2648
2693
  function motionAllowed(argv = process.argv, env = process.env, isTTY = Boolean(process.stdout.isTTY)) {
2649
2694
  return isTTY && !env.CI && !env.LOPEBASE_NO_MOTION && env.TERM !== "dumb" && !argv.includes("--no-motion");
2650
2695
  }
2651
2696
  var sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
2652
2697
  function logoLines(opts) {
2653
2698
  let right = {
2654
- 4: `${import_picocolors3.default.bold("Lope")}${import_picocolors3.default.dim("Base")}${opts.version ? import_picocolors3.default.dim(` v${opts.version}`) : ""}`,
2655
- 5: import_picocolors3.default.dim(opts.tagline)
2699
+ 3: `${import_picocolors3.default.bold("Lope")}${import_picocolors3.default.dim("Base")}${opts.version ? import_picocolors3.default.dim(` v${opts.version}`) : ""}`,
2700
+ 4: import_picocolors3.default.dim(opts.tagline)
2656
2701
  };
2657
2702
  return MARK.map((row, i) => {
2658
2703
  let mark = HORIZON.has(i) ? import_picocolors3.default.yellow(import_picocolors3.default.dim(row)) : import_picocolors3.default.yellow(row);
@@ -2683,7 +2728,7 @@ ${lines.join(`
2683
2728
  for (let i = MARK.length - 1;i >= 0; i--)
2684
2729
  shown.add(i), write(`${up}\r${frame(shown).join(`
2685
2730
  `)}
2686
- `), await sleep(i >= 8 ? 70 : 35);
2731
+ `), await sleep(HORIZON.has(i) ? 70 : 35);
2687
2732
  write(`${up}\r${lines.join(`
2688
2733
  `)}
2689
2734
 
@@ -2767,6 +2812,7 @@ ${import_picocolors4.default.bold("Usage")}
2767
2812
 
2768
2813
  ${import_picocolors4.default.bold("Options")}
2769
2814
  --print print the agent prompt instead of starting an agent
2815
+ --url <address> sign in to this workspace or server (also LOPEBASE_URL)
2770
2816
  --no-motion no animation (also LOPEBASE_NO_MOTION=1)
2771
2817
 
2772
2818
  ${import_picocolors4.default.bold("Learn more")}
@@ -2825,20 +2871,23 @@ async function checkToken(auth) {
2825
2871
  }
2826
2872
  }
2827
2873
  async function login() {
2828
- let known = option("url") ?? loadAuth()?.url, url = workspaceUrl(guard(await he({
2829
- message: "Your LopeBase workspace (name or address)",
2830
- placeholder: "yourteam or https://yourteam.lopebase.com",
2831
- initialValue: known ?? "",
2832
- validate: (v2) => workspaceUrl(v2 ?? "") ? void 0 : "Type your workspace name, like yourteam, or its full address"
2833
- })));
2874
+ let start = signInUrl({
2875
+ flag: option("url"),
2876
+ env: process.env.LOPEBASE_URL,
2877
+ saved: loadAuth()?.url
2878
+ });
2879
+ if ("error" in start)
2880
+ bail(start.error);
2881
+ let url = start.url;
2834
2882
  if (!flag("paste")) {
2835
2883
  let device = await startDeviceLogin(url);
2836
2884
  if (device) {
2837
2885
  Me([
2838
2886
  `Your code: ${import_picocolors4.default.bold(import_picocolors4.default.yellow(device.userCode))}`,
2839
2887
  "",
2840
- "We opened your workspace in the browser. Sign in if asked,",
2841
- "check the code matches, and press Connect."
2888
+ "We opened LopeBase in your browser. Sign in, or create a free",
2889
+ "account if you are new. Then check the code matches and press",
2890
+ "Connect."
2842
2891
  ].join(`
2843
2892
  `), "Connect this computer"), openBrowser(device.verifyUrl), M2.info(import_picocolors4.default.dim(device.verifyUrl));
2844
2893
  let s = Y2();
@@ -2846,14 +2895,21 @@ async function login() {
2846
2895
  let res = await pollDeviceLogin(url, device);
2847
2896
  if (res.ok) {
2848
2897
  let file2 = saveAuth({ url: res.url, token: res.token });
2849
- return s.stop(`Connected (${res.scope}). Saved to ${import_picocolors4.default.dim(file2)}`), { url: res.url, token: res.token };
2898
+ return s.stop(`Connected to ${import_picocolors4.default.bold(res.url)}. Saved to ${import_picocolors4.default.dim(file2)}`), { url: res.url, token: res.token };
2850
2899
  }
2851
2900
  if (s.stop(import_picocolors4.default.red(res.message)), res.reason === "denied")
2852
2901
  bail("Nothing was connected.");
2853
2902
  M2.warn("Falling back to pasting a token.");
2854
2903
  }
2855
2904
  }
2856
- return pasteLogin(url);
2905
+ return pasteLogin(url === DEFAULT_URL ? await askWorkspace() : url);
2906
+ }
2907
+ async function askWorkspace() {
2908
+ return workspaceUrl(guard(await he({
2909
+ message: "Your LopeBase workspace (name or address)",
2910
+ placeholder: "yourteam or https://yourteam.lopebase.com",
2911
+ validate: (v2) => workspaceUrl(v2 ?? "") ? void 0 : "Type your workspace name, like yourteam, or its full address"
2912
+ })));
2857
2913
  }
2858
2914
  async function pasteLogin(url) {
2859
2915
  let settings = `${url}/settings#agent-access`;
@@ -2889,7 +2945,7 @@ async function printDetection(d3, facts) {
2889
2945
  Me(possible.map((s) => `${import_picocolors4.default.yellow("?")} ${import_picocolors4.default.bold(s.label)} ${import_picocolors4.default.dim(`only ${s.evidence.join(", ")}`)}`).join(`
2890
2946
  `), "Maybe in use, your agent will check"), await pace(motion, 150);
2891
2947
  for (let gap of d3.gaps)
2892
- M2.warn(gap);
2948
+ M2.info(import_picocolors4.default.dim(gap));
2893
2949
  }
2894
2950
  async function scanWithProgress() {
2895
2951
  let s = Y2();
@@ -2919,8 +2975,13 @@ function registerMcp(agent) {
2919
2975
  return res.status === 0 || /already exists/i.test(`${res.stderr}${res.stdout}`);
2920
2976
  }
2921
2977
  async function init() {
2922
- console.clear(), await showLogo({ tagline: TAGLINE, version: version(), motion }), Ie(`${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} set up this project`), Me(planLines().join(`
2923
- `), "Here is what happens"), await pace(motion, 300), M2.step(stepTitle(1));
2978
+ if (console.clear(), await showLogo({ tagline: TAGLINE, version: version(), motion }), Ie(`${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} set up this project`), Me(planLines().join(`
2979
+ `), "Here is what happens"), !guard(await ye({
2980
+ message: `Scan ${import_picocolors4.default.bold(cwd)}?`,
2981
+ initialValue: !0
2982
+ })))
2983
+ xe("Nothing was read. Run lopebase again any time."), process.exit(0);
2984
+ M2.step(stepTitle(1));
2924
2985
  let { facts, detection } = await scanWithProgress();
2925
2986
  await printDetection(detection, facts), M2.step(stepTitle(2));
2926
2987
  let auth = loadAuth();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lopebase",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Set up LopeBase from your project's folder: scan your stack, connect your workspace, and let your coding agent do the rest.",
5
5
  "type": "module",
6
6
  "bin": {