Compare commits

...

3 Commits

Author SHA1 Message Date
nntrivi2001 fc7b58f15b chore(release): bump version to 1.5.10 2026-09-14 13:10:21 +07:00
nntrivi2001 c4025cd2f7 style(lanes): render the create-readme option as a selectable card
Matches LaneStripCard's selected/unselected treatment and moves the hint
inside the card. Drops the "codex has no slash commands" clause from the
hint — the option is already hidden for a codex lane.
2026-09-14 13:10:16 +07:00
nntrivi2001 ee1e07bb5e fix(update-check): resolve the release URL for a relative plugin source
This project's marketplace entry is `"source": "./"` — the plugin IS the
marketplace repo — but pluginMarketplaceUrl only accepted a URL string or
`{url}`, so it returned null and every plugin-cache install answered
`no_remote`: no install of this plugin could ever see a published release.

A relative source now falls back to the manifest's own `repository`. An
`owner/repo` shorthand still returns null — it names a different repo, so
borrowing the marketplace's URL would be a confidently wrong answer.
2026-09-14 13:10:16 +07:00
9 changed files with 81 additions and 28 deletions
+29 -12
View File
@@ -408,18 +408,35 @@ export function AddLaneModal({
</div> </div>
{tool === "claude" && ( {tool === "claude" && (
<div> <label
<label className="flex items-center gap-2 text-xs text-fg-secondary"> // Card that lights up when ticked, matching LaneStripCard's
<input // selected/unselected treatment.
type="checkbox" className={`flex cursor-pointer items-start gap-2.5 rounded-lg border p-3 shadow-sm transition-colors ${
checked={writeDocs} writeDocs
onChange={(e) => setWriteDocs(e.target.checked)} ? "border-accent bg-accent/10"
className="h-3.5 w-3.5 accent-accent" : "border-border bg-surface-2 hover:border-border-light hover:bg-surface-3"
/> }`}
{t("addLaneWriteDocsLabel")} >
</label> <input
<p className="mt-1 text-[10px] text-fg-muted">{t("addLaneWriteDocsHint")}</p> type="checkbox"
</div> // The wrapping label also holds the hint, so name the input
// itself — otherwise its accessible name is label + hint.
aria-label={t("addLaneWriteDocsLabel")}
checked={writeDocs}
onChange={(e) => setWriteDocs(e.target.checked)}
className="mt-0.5 h-3.5 w-3.5 shrink-0 accent-accent"
/>
<span>
<span
className={`block text-xs font-medium ${writeDocs ? "text-accent" : "text-fg-secondary"}`}
>
{t("addLaneWriteDocsLabel")}
</span>
<span className="mt-1 block text-[10px] text-fg-muted">
{t("addLaneWriteDocsHint")}
</span>
</span>
</label>
)} )}
{mode === "worktree" && branches && ( {mode === "worktree" && branches && (
+1 -1
View File
@@ -40,7 +40,7 @@
"addLaneToolClaude": "Claude Code", "addLaneToolClaude": "Claude Code",
"addLaneToolCodex": "Codex", "addLaneToolCodex": "Codex",
"addLaneToolHint": "Which CLI drives this lane. Cannot be changed later.", "addLaneToolHint": "Which CLI drives this lane. Cannot be changed later.",
"addLaneWriteDocsHint": "Opens the lane's terminal on creation and runs the /sg-all:sg-md-writing skill there. Claude lanes only — codex has no slash commands.", "addLaneWriteDocsHint": "Opens the lane's terminal on creation and runs the /sg-all:sg-md-writing skill there.",
"addLaneWriteDocsLabel": "Create/update readme file", "addLaneWriteDocsLabel": "Create/update readme file",
"mode.repo": "Repo", "mode.repo": "Repo",
"mode.repoHint": "Adopt this directory as a lane, as-is — no worktree, no new branch.", "mode.repoHint": "Adopt this directory as a lane, as-is — no worktree, no new branch.",
+1 -1
View File
@@ -40,7 +40,7 @@
"addLaneToolClaude": "Claude Code", "addLaneToolClaude": "Claude Code",
"addLaneToolCodex": "Codex", "addLaneToolCodex": "Codex",
"addLaneToolHint": "CLI nào điều khiển lane này. Không thể thay đổi sau này.", "addLaneToolHint": "CLI nào điều khiển lane này. Không thể thay đổi sau này.",
"addLaneWriteDocsHint": "Khi tạo lane sẽ mở terminal của lane và chạy skill /sg-all:sg-md-writing tại đó. Chỉ áp dụng cho lane Claude — codex không có slash command.", "addLaneWriteDocsHint": "Khi tạo lane sẽ mở terminal của lane và chạy skill /sg-all:sg-md-writing tại đó.",
"addLaneWriteDocsLabel": "Tạo/cập nhật file readme", "addLaneWriteDocsLabel": "Tạo/cập nhật file readme",
"mode.repo": "Repo", "mode.repo": "Repo",
"mode.repoHint": "Gắn thẳng thư mục này làm lane, nguyên trạng — không worktree, không nhánh mới.", "mode.repoHint": "Gắn thẳng thư mục này làm lane, nguyên trạng — không worktree, không nhánh mới.",
@@ -9537,7 +9537,7 @@ exports[`screen snapshots > Settings 1`] = `
<p <p
class="text-[10px] text-status-warning/90 mt-1" class="text-[10px] text-status-warning/90 mt-1"
> >
UI build v1.5.9 (rebuild client to match) UI build v1.5.10 (rebuild client to match)
</p> </p>
</div> </div>
<div <div
+1 -1
View File
@@ -3,7 +3,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: Agent Dashboard for Claude Code API title: Agent Dashboard for Claude Code API
version: 1.5.9 version: 1.5.10
description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence. description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.
contact: contact:
name: Nguyễn Ngọc Trí Vĩ name: Nguyễn Ngọc Trí Vĩ
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "agenthub", "name": "agenthub",
"version": "1.5.9", "version": "1.5.10",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "agenthub", "name": "agenthub",
"version": "1.5.9", "version": "1.5.10",
"hasInstallScript": true, "hasInstallScript": true,
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "agenthub", "name": "agenthub",
"version": "1.5.9", "version": "1.5.10",
"bin": { "bin": {
"agenthub": "bin/agenthub.js" "agenthub": "bin/agenthub.js"
}, },
@@ -64,6 +64,7 @@ function makePluginInstall(name, opts = {}) {
source = { source: "url", url: "https://forge.example.com/Owner/Repo.git", ref: "main" }, source = { source: "url", url: "https://forge.example.com/Owner/Repo.git", ref: "main" },
version = "1.5.1", version = "1.5.1",
knownMarketplaces = true, knownMarketplaces = true,
repository,
} = opts; } = opts;
const home = path.join(tmpDir, name, ".claude"); const home = path.join(tmpDir, name, ".claude");
@@ -74,7 +75,7 @@ function makePluginInstall(name, opts = {}) {
fs.writeFileSync( fs.writeFileSync(
path.join(mktDir, ".claude-plugin", "marketplace.json"), path.join(mktDir, ".claude-plugin", "marketplace.json"),
JSON.stringify({ name: "sg", plugins: [{ name: "agenthub", source }] }) JSON.stringify({ name: "sg", repository, plugins: [{ name: "agenthub", source }] })
); );
if (knownMarketplaces) { if (knownMarketplaces) {
fs.writeFileSync( fs.writeFileSync(
@@ -128,10 +129,31 @@ describe("pluginMarketplaceUrl", () => {
); );
}); });
it("falls back to the marketplace's repository for a relative source", () => {
// This project's own entry is `"source": "./"` — the plugin IS the
// marketplace repo. Without this the dashboard answered `no_remote` and
// could never see a release on any plugin-cache install.
makePluginInstall("relsrc", {
source: "./",
repository: "https://forge.example.com/Owner/Repo",
});
assert.equal(pluginMarketplaceUrl("sg", "agenthub"), "https://forge.example.com/Owner/Repo");
});
it("returns null for a relative source when the manifest declares no repository", () => {
makePluginInstall("relnorepo", { source: "./plugins/agenthub" });
assert.equal(pluginMarketplaceUrl("sg", "agenthub"), null);
});
it("returns null for a github owner/repo shorthand rather than guessing a host", () => { it("returns null for a github owner/repo shorthand rather than guessing a host", () => {
// Inventing github.com here would produce an API URL that 404s and a // Inventing github.com here would produce an API URL that 404s and a
// confidently wrong "up to date". // confidently wrong "up to date".
makePluginInstall("ghshort", { source: { source: "github", repo: "Owner/Repo" } }); // Not even when the marketplace declares a repository: a shorthand source
// names a DIFFERENT repo, so borrowing the marketplace's is a wrong answer.
makePluginInstall("ghshort", {
source: { source: "github", repo: "Owner/Repo" },
repository: "https://forge.example.com/Owner/Marketplace",
});
assert.equal(pluginMarketplaceUrl("sg", "agenthub"), null); assert.equal(pluginMarketplaceUrl("sg", "agenthub"), null);
}); });
+21 -7
View File
@@ -11,11 +11,12 @@
* rather than reporting a false "up to date". * rather than reporting a false "up to date".
* *
* **Two independent paths, chosen by install kind.** A checkout derives the API * **Two independent paths, chosen by install kind.** A checkout derives the API
* URL from its `origin` remote. A plugin-cache install does NOT: when a * URL from its `origin` remote. A plugin-cache install does NOT: Claude Code
* marketplace entry declares `"source": "url"` (this project's own does) Claude * unpacks an archive, so the cache directory has no `.git` and there is no
* Code unpacks an archive, so the cache directory has no `.git` and there is no
* remote to read. That path is answered entirely without git — the repository * remote to read. That path is answered entirely without git — the repository
* URL comes from the marketplace manifest on disk (`pluginMarketplaceUrl`) and * URL comes from the marketplace manifest on disk (`pluginMarketplaceUrl`,
* which for this project's own relative `"source": "./"` entry resolves to the
* manifest's top-level `repository`) and
* the package.json version is the only comparison signal. Getting this wrong is * the package.json version is the only comparison signal. Getting this wrong is
* what shipped in 1.5.1: the `.git` probe ran first, returned "not a git clone", * what shipped in 1.5.1: the `.git` probe ran first, returned "not a git clone",
* and skipped the release layer, so `update_available` stayed false however many * and skipped the release layer, so `update_available` stayed false however many
@@ -346,11 +347,24 @@ function pluginMarketplaceUrl(marketplace, plugin) {
const entry = (manifest?.plugins || []).find((p) => p && p.name === plugin); const entry = (manifest?.plugins || []).find((p) => p && p.name === plugin);
if (!entry) return null; if (!entry) return null;
// `source` is either a string (a bare URL or owner/repo) or an object with // `source` is either a string (a bare URL, a relative path, or owner/repo)
// its own `source` discriminator plus `url`/`repo`. // or an object with its own `source` discriminator plus `url`/`repo`.
const src = entry.source; const src = entry.source;
if (typeof src === "string") return src.includes("://") ? src : null; if (typeof src === "string" && src.includes("://")) return src;
if (src && typeof src.url === "string" && src.url) return src.url; if (src && typeof src.url === "string" && src.url) return src.url;
// A RELATIVE source ("./", "./plugins/x") means the plugin ships inside the
// marketplace repository itself — this project's own entry is exactly that
// ("source": "./") — so the manifest's declared `repository` is where its
// releases are published. Without this fallback every plugin-cache install
// of this very plugin answered `no_remote` and could never see a release.
const relative = typeof src === "string" && /^\.{0,2}\//.test(src);
if (relative) {
const repo = manifest?.repository;
const url = typeof repo === "string" ? repo : repo?.url;
if (typeof url === "string" && url.includes("://")) return url;
}
// A `github`-style `repo: "owner/repo"` shorthand carries no host; this // A `github`-style `repo: "owner/repo"` shorthand carries no host; this
// build is distributed from its own forge, so rather than guessing // build is distributed from its own forge, so rather than guessing
// github.com the caller reports "remote_not_http" and prints the manual // github.com the caller reports "remote_not_http" and prints the manual