likho-cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/likho.js +27 -2
  2. package/package.json +1 -1
package/bin/likho.js CHANGED
@@ -57,6 +57,22 @@ async function answer(question) {
57
57
  const say = (line = "") => console.log(line);
58
58
  const mcpUrl = (opts, org) => `${opts.server}/mcp${org ? `?org=${encodeURIComponent(org)}` : ""}`;
59
59
 
60
+ /**
61
+ * The host a "likho" entry really points at, when that isn't this Likho
62
+ * ("" when it is). A shared or downloaded project can carry a settings file
63
+ * whose "likho" leads to someone else's server; it's never trusted silently.
64
+ */
65
+ function foreignHost(url, opts) {
66
+ try {
67
+ const u = new URL(url);
68
+ const ours = new URL(opts.server);
69
+ if (u.host === ours.host && u.protocol === ours.protocol && u.pathname.replace(/\/+$/, "") === "/mcp") return "";
70
+ return u.protocol === "https:" ? u.host : `${u.protocol}//${u.host} (not https)`;
71
+ } catch {
72
+ return String(url).slice(0, 60) || "an unreadable address";
73
+ }
74
+ }
75
+
60
76
  /** The organization in a Likho address written by this tool ("" if none). */
61
77
  function orgOfUrl(url) {
62
78
  try {
@@ -120,9 +136,17 @@ async function connect(opts) {
120
136
  // what works. In a project, the all-projects setting is shown too: the
121
137
  // project one overrides it in this folder.
122
138
  const existing = new Map(usable.filter((t) => !t.manual).map((t) => [t.id, t.current(ctx, scope)]).filter(([, url]) => url));
139
+ // Entries named "likho" that point elsewhere are replaced, not kept.
140
+ const foreign = new Map([...existing].map(([id, url]) => [id, foreignHost(url, opts)]).filter(([, host]) => host));
141
+ for (const id of foreign.keys()) existing.delete(id);
142
+ if (foreign.size) {
143
+ const lines = usable.filter((t) => foreign.has(t.id)).map((t) => `${t.name}'s "likho" entry points to ${foreign.get(t.id)}, not Likho. It will be replaced.`);
144
+ ui.log.warn(lines.join("\n"));
145
+ }
123
146
  const inherited = (t) => (scope === "project" && t.global && !t.manual ? t.current(ctx, "global") : null);
124
147
  const hintFor = (t) => {
125
148
  if (t.manual) return "you add it in its settings";
149
+ if (foreign.has(t.id)) return `⚠ points to ${foreign.get(t.id)}, not Likho: will be replaced`;
126
150
  if (existing.has(t.id)) return `already connected → ${orgOfUrl(existing.get(t.id)) || "Likho"}`;
127
151
  const all = inherited(t);
128
152
  return all ? `connected for all projects → ${orgOfUrl(all) || "Likho"}; this would override it here` : undefined;
@@ -250,8 +274,9 @@ function status(opts) {
250
274
  for (const t of selectTools(opts, ctx)) {
251
275
  if (t.manual) continue;
252
276
  const rows = [];
253
- if (t.global) rows.push(`all projects: ${t.current(ctx, "global") || "not connected"}`);
254
- if (t.project && inProject) rows.push(`this project: ${t.current(ctx, "project") || "not connected"}`);
277
+ const show = (url) => (!url ? "not connected" : foreignHost(url, opts) ? `⚠ ${url} (not Likho: run npx likho-cli to replace it)` : url);
278
+ if (t.global) rows.push(`all projects: ${show(t.current(ctx, "global"))}`);
279
+ if (t.project && inProject) rows.push(`this project: ${show(t.current(ctx, "project"))}`);
255
280
  say(` ${t.name.padEnd(14)} ${t.detect() ? "" : "(not installed) "}${rows.join(" · ")}`);
256
281
  }
257
282
  say();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "likho-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Connect every AI tool on your computer (Claude Code, Codex, Cursor, VS Code, Gemini CLI, opencode, Windsurf) to Likho in one command.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://likho.me",