Files
thatguygriffandClaude Opus 5 87851de921
CI / Tests (push) Successful in 36s
CI / Generated images (push) Successful in 1m1s
Support instances that serve SSH from another hostname
Discovery matched a git remote's host against the instance URL's host, so
a server answering SSH on a different name than its web UI resolved to
nothing — and said so only in a debug log.

Resolution now runs in three stages. A remote host is matched directly,
then against configured aliases, and failing both the instances are asked
for the repository: Gitea publishes its SSH hostname in a repository's
ssh_url, so the right instance identifies itself. What that turns up is
saved as a host alias, so later repositories on the same host resolve
with no lookup at all, and the mapping is visible and editable rather
than hidden. Each unknown host is probed at most once per session.

When nothing resolves the sidebar now names the unmatched host and offers
Add Host Alias, instead of showing an empty section.

Aliases can also be written by hand as "remote-host = instance URL",
accepting =, -> and =>, ignoring ports, and skipping # comments. List
preferences now merge workspace entries onto global ones rather than
letting an empty global array mask them.

Adds Tests/host-aliases.test.js covering both directions: unmatched hosts
reported and nothing persisted, a hand-written alias, detection from
ssh_url, and a later repository resolving from the stored alias without a
probe. 131 checks across three suites.

Also adds CLAUDE.md, and .gitea/workflows/ci.yml running the suites,
script syntax checks, manifest validation, and a generated-image check.
Tools/make-icons.py gains --check, which compares decompressed pixels so
a differing zlib version cannot fail it spuriously.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MQuusXgZC2dzwpJJ1qhtti
2026-08-28 20:10:05 -03:00

361 lines
21 KiB
JSON

{
"identifier": "unsupervised.giteanova",
"name": "Gitea",
"organization": "Unsupervised",
"description": "Gitea Actions, pull requests, reviews, secrets, and variables — without leaving Nova.",
"version": "1.0",
"license": "MIT",
"categories": ["sidebars", "commands", "issues"],
"repository": "https://git.unsupervised.ca/unsupervised/gitea-nova.novaextension",
"bugs": "https://git.unsupervised.ca/unsupervised/gitea-nova.novaextension/issues",
"main": "main.js",
"activationEvents": ["*"],
"entitlements": {
"requests": true,
"process": true,
"filesystem": "readwrite",
"clipboard": true
},
"commands": {
"extensions": [
{ "title": "Refresh Gitea", "command": "gitea.refresh", "shortcut": "ctrl-alt-r" },
{ "title": "Set Token…", "command": "gitea.setToken" },
{ "title": "Sign Out…", "command": "gitea.clearToken" },
{ "title": "Test Connection", "command": "gitea.testConnection" },
{ "separator": true },
{ "title": "Add Gitea Instance…", "command": "gitea.addInstance" },
{ "title": "Remove Gitea Instance…", "command": "gitea.removeInstance" },
{ "title": "Add Host Alias…", "command": "gitea.addHostAlias" },
{ "title": "Open Gitea Preferences", "command": "gitea.openPreferences" },
{ "separator": true },
{ "title": "Change Branch Filter…", "command": "gitea.switchBranchFilter" },
{ "title": "Open Latest Failed Job Logs", "command": "gitea.openLatestFailedJobLogs" },
{ "separator": true },
{ "title": "Create Pull Request…", "command": "gitea.createPullRequest" },
{ "title": "Add Secret…", "command": "gitea.createSecret" },
{ "title": "Add Variable…", "command": "gitea.createVariable" }
],
"editor": [
{
"title": "Add Gitea Review Comment…",
"command": "gitea.addReviewComment",
"when": "editorHasFocus && documentHasPath"
}
]
},
"sidebars": [
{
"id": "gitea",
"name": "Gitea",
"smallImage": "gitea-small",
"largeImage": "gitea-large",
"sections": [
{
"id": "gitea.branch",
"name": "Current Branch",
"placeholderText": "No Gitea repository detected in this workspace.",
"headerCommands": [
{ "title": "Refresh", "image": "refresh", "tooltip": "Refresh", "command": "gitea.refresh" },
{ "title": "Branch Filter", "image": "branch", "tooltip": "Change branch filter", "command": "gitea.switchBranchFilter" }
],
"contextCommands": [
{ "title": "Add Host Alias…", "command": "gitea.addHostAlias", "when": "viewItem == 'unmatchedHost'" },
{ "title": "Open in Browser", "command": "gitea.openInBrowser", "when": "viewItem == 'run' || viewItem == 'job' || viewItem == 'pr' || viewItem == 'artifact' || viewItem == 'repo' || viewItem == 'review' || viewItem == 'comment'" },
{ "title": "Copy URL", "command": "gitea.copyUrl", "when": "viewItem == 'run' || viewItem == 'job' || viewItem == 'pr' || viewItem == 'artifact' || viewItem == 'repo' || viewItem == 'review' || viewItem == 'comment'" },
{ "separator": true },
{ "title": "View Job Logs", "command": "gitea.viewJobLogs", "when": "viewItem == 'job'" },
{ "title": "Re-run Workflow Run", "command": "gitea.rerunRun", "when": "viewItem == 'run'" },
{ "title": "Re-run Failed Jobs", "command": "gitea.rerunFailedJobs", "when": "viewItem == 'run'" },
{ "title": "Re-run Job", "command": "gitea.rerunJob", "when": "viewItem == 'job'" },
{ "title": "Cancel Run", "command": "gitea.cancelRun", "when": "viewItem == 'run'" },
{ "separator": true },
{ "title": "Download Artifact", "command": "gitea.downloadArtifact", "when": "viewItem == 'artifact'" },
{ "separator": true },
{ "title": "Open Pull Request Overview", "command": "gitea.openPullRequestOverview", "when": "viewItem == 'pr'" },
{ "title": "Open Pull Request Diff", "command": "gitea.openPullRequestDiff", "when": "viewItem == 'pr'" },
{ "title": "Checkout Branch", "command": "gitea.checkoutPrBranch", "when": "viewItem == 'pr'" },
{ "title": "Approve", "command": "gitea.reviewApprove", "when": "viewItem == 'pr'" },
{ "title": "Request Changes…", "command": "gitea.reviewRequestChanges", "when": "viewItem == 'pr'" },
{ "title": "Comment…", "command": "gitea.reviewComment", "when": "viewItem == 'pr'" },
{ "title": "Merge…", "command": "gitea.mergePullRequest", "when": "viewItem == 'pr'" },
{ "title": "Close", "command": "gitea.closePullRequest", "when": "viewItem == 'pr'" },
{ "separator": true },
{ "title": "Reply…", "command": "gitea.replyToReviewComment", "when": "viewItem == 'comment'" },
{ "title": "Resolve Thread", "command": "gitea.resolveReviewThread", "when": "viewItem == 'comment'" },
{ "title": "Unresolve Thread", "command": "gitea.unresolveReviewThread", "when": "viewItem == 'comment'" }
]
},
{
"id": "gitea.workflows",
"name": "Workflows",
"placeholderText": "No workflow runs found.",
"headerCommands": [
{ "title": "Refresh", "image": "refresh", "tooltip": "Refresh workflow runs", "command": "gitea.refreshRuns" }
],
"contextCommands": [
{ "title": "Add Host Alias…", "command": "gitea.addHostAlias", "when": "viewItem == 'unmatchedHost'" },
{ "title": "Open in Browser", "command": "gitea.openInBrowser", "when": "viewItem == 'run' || viewItem == 'job' || viewItem == 'artifact' || viewItem == 'repo' || viewItem == 'workflow'" },
{ "title": "Copy URL", "command": "gitea.copyUrl", "when": "viewItem == 'run' || viewItem == 'job' || viewItem == 'artifact' || viewItem == 'repo' || viewItem == 'workflow'" },
{ "separator": true },
{ "title": "View Job Logs", "command": "gitea.viewJobLogs", "when": "viewItem == 'job'" },
{ "title": "Re-run Workflow Run", "command": "gitea.rerunRun", "when": "viewItem == 'run'" },
{ "title": "Re-run Failed Jobs", "command": "gitea.rerunFailedJobs", "when": "viewItem == 'run'" },
{ "title": "Re-run Job", "command": "gitea.rerunJob", "when": "viewItem == 'job'" },
{ "title": "Cancel Run", "command": "gitea.cancelRun", "when": "viewItem == 'run'" },
{ "separator": true },
{ "title": "Download Artifact", "command": "gitea.downloadArtifact", "when": "viewItem == 'artifact'" },
{ "title": "Open Artifact", "command": "gitea.openArtifact", "when": "viewItem == 'artifact'" },
{ "title": "Reveal Artifact in Finder", "command": "gitea.revealArtifact", "when": "viewItem == 'artifact'" },
{ "separator": true },
{ "title": "Refresh Repository", "command": "gitea.refreshRepo", "when": "viewItem == 'repo'" }
]
},
{
"id": "gitea.pullRequests",
"name": "Pull Requests",
"placeholderText": "No open pull requests.",
"headerCommands": [
{ "title": "Refresh", "image": "refresh", "tooltip": "Refresh pull requests", "command": "gitea.refreshPullRequests" },
{ "title": "Create", "image": "__builtin.add", "tooltip": "Create a pull request", "command": "gitea.createPullRequest" }
],
"contextCommands": [
{ "title": "Add Host Alias…", "command": "gitea.addHostAlias", "when": "viewItem == 'unmatchedHost'" },
{ "title": "Open Pull Request Overview", "command": "gitea.openPullRequestOverview", "when": "viewItem == 'pr'" },
{ "title": "Open Pull Request Diff", "command": "gitea.openPullRequestDiff", "when": "viewItem == 'pr'" },
{ "title": "Open Changed File", "command": "gitea.openChangedFile", "when": "viewItem == 'prFile'" },
{ "separator": true },
{ "title": "Open in Browser", "command": "gitea.openInBrowser", "when": "viewItem == 'pr' || viewItem == 'repo' || viewItem == 'review' || viewItem == 'comment' || viewItem == 'prFile'" },
{ "title": "Copy URL", "command": "gitea.copyUrl", "when": "viewItem == 'pr' || viewItem == 'repo' || viewItem == 'review' || viewItem == 'comment' || viewItem == 'prFile'" },
{ "separator": true },
{ "title": "Checkout Branch", "command": "gitea.checkoutPrBranch", "when": "viewItem == 'pr'" },
{ "title": "Approve", "command": "gitea.reviewApprove", "when": "viewItem == 'pr'" },
{ "title": "Request Changes…", "command": "gitea.reviewRequestChanges", "when": "viewItem == 'pr'" },
{ "title": "Comment…", "command": "gitea.reviewComment", "when": "viewItem == 'pr'" },
{ "separator": true },
{ "title": "Merge…", "command": "gitea.mergePullRequest", "when": "viewItem == 'pr'" },
{ "title": "Close", "command": "gitea.closePullRequest", "when": "viewItem == 'pr'" },
{ "separator": true },
{ "title": "Reply…", "command": "gitea.replyToReviewComment", "when": "viewItem == 'comment'" },
{ "title": "Resolve Thread", "command": "gitea.resolveReviewThread", "when": "viewItem == 'comment'" },
{ "title": "Unresolve Thread", "command": "gitea.unresolveReviewThread", "when": "viewItem == 'comment'" }
]
},
{
"id": "gitea.settings",
"name": "Settings",
"placeholderText": "No Gitea instances configured.",
"headerCommands": [
{ "title": "Refresh", "image": "refresh", "tooltip": "Refresh settings", "command": "gitea.refreshSettings" },
{ "title": "Add Instance", "image": "__builtin.add", "tooltip": "Add a Gitea instance", "command": "gitea.addInstance" }
],
"contextCommands": [
{ "title": "Add Host Alias…", "command": "gitea.addHostAlias", "when": "viewItem == 'unmatchedHost'" },
{ "title": "Set Token…", "command": "gitea.setToken", "when": "viewItem == 'instance'" },
{ "title": "Sign Out…", "command": "gitea.clearToken", "when": "viewItem == 'instance'" },
{ "title": "Test Connection", "command": "gitea.testConnection", "when": "viewItem == 'instance'" },
{ "title": "Remove Instance…", "command": "gitea.removeInstance", "when": "viewItem == 'instance'" },
{ "separator": true },
{ "title": "Refresh Secrets", "command": "gitea.refreshSecrets", "when": "viewItem == 'secretsFolder' || viewItem == 'secret'" },
{ "title": "Add Secret…", "command": "gitea.createSecret", "when": "viewItem == 'secretsFolder' || viewItem == 'secret'" },
{ "title": "Update Secret…", "command": "gitea.updateSecret", "when": "viewItem == 'secret'" },
{ "title": "Delete Secret…", "command": "gitea.deleteSecret", "when": "viewItem == 'secret'" },
{ "separator": true },
{ "title": "Refresh Variables", "command": "gitea.refreshVariables", "when": "viewItem == 'variablesFolder' || viewItem == 'variable'" },
{ "title": "Add Variable…", "command": "gitea.createVariable", "when": "viewItem == 'variablesFolder' || viewItem == 'variable'" },
{ "title": "Update Variable…", "command": "gitea.updateVariable", "when": "viewItem == 'variable'" },
{ "title": "Delete Variable…", "command": "gitea.deleteVariable", "when": "viewItem == 'variable'" },
{ "separator": true },
{ "title": "Open in Browser", "command": "gitea.openInBrowser", "when": "viewItem == 'instance' || viewItem == 'repo'" },
{ "title": "Copy URL", "command": "gitea.copyUrl", "when": "viewItem == 'instance' || viewItem == 'repo'" }
]
}
]
}
],
"config": [
{
"key": "gitea.section.connection",
"title": "Connection",
"type": "section",
"children": [
{
"key": "gitea.baseUrl",
"title": "Default Instance URL",
"description": "The Gitea instance used when a repository's remote host does not match any other configured instance.",
"type": "string",
"default": "https://gitea.com",
"placeholder": "https://gitea.com"
},
{
"key": "gitea.instances",
"title": "Additional Instances",
"description": "Extra Gitea instance URLs. Repositories are routed to an instance by matching their git remote host. Tokens are stored per instance in the macOS Keychain.",
"type": "stringArray"
},
{
"key": "gitea.hostAliases",
"title": "Host Aliases",
"description": "Map alternate git remote hosts onto a configured instance, one entry per line, as \"remote-host = instance URL\". Use this when your server answers SSH on a different hostname than its web UI, for example \"ssh.example.com = https://git.example.com\".",
"type": "stringArray"
},
{
"key": "gitea.addHostAlias",
"title": "Add Host Alias",
"description": "Map a git remote host onto one of the configured instances.",
"type": "command",
"command": "gitea.addHostAlias"
},
{
"key": "gitea.setToken",
"title": "Sign In",
"description": "Store a personal access token for a configured instance.",
"type": "command",
"command": "gitea.setToken"
},
{
"key": "gitea.testConnection",
"title": "Test Connection",
"description": "Verify the stored token against each configured instance.",
"type": "command",
"command": "gitea.testConnection"
}
]
},
{
"key": "gitea.section.discovery",
"title": "Repositories",
"type": "section",
"children": [
{
"key": "gitea.discovery.mode",
"title": "Discovery Mode",
"description": "How repositories are found. \"Workspace\" reads git remotes from the open workspace. \"All Accessible\" lists every repository your token can reach.",
"type": "enum",
"values": [
["workspace", "Workspace Git Remotes"],
["allAccessible", "All Accessible Repositories"]
],
"default": "workspace"
},
{
"key": "gitea.maxRunsPerRepo",
"title": "Max Runs per Repository",
"type": "number",
"default": 20,
"min": 1,
"max": 200
},
{
"key": "gitea.maxJobsPerRun",
"title": "Max Jobs per Run",
"type": "number",
"default": 50,
"min": 1,
"max": 200
}
]
},
{
"key": "gitea.section.refresh",
"title": "Refresh",
"type": "section",
"children": [
{
"key": "gitea.refresh.runningIntervalSeconds",
"title": "Active Interval (seconds)",
"description": "Polling interval while runs are queued or in progress.",
"type": "number",
"default": 15,
"min": 5,
"max": 3600
},
{
"key": "gitea.refresh.idleIntervalSeconds",
"title": "Idle Interval (seconds)",
"description": "Polling interval when nothing is running.",
"type": "number",
"default": 60,
"min": 10,
"max": 3600
},
{
"key": "gitea.notifications.failedRuns",
"title": "Notify on Failed Runs",
"description": "Post a notification when a workflow run finishes with a failure.",
"type": "boolean",
"default": true
}
]
},
{
"key": "gitea.section.review",
"title": "Reviews & Logs",
"type": "section",
"children": [
{
"key": "gitea.reviewComments.enabled",
"title": "Show Review Comments Inline",
"description": "Surface pull request review comments for the current branch as issues in the editor gutter and the Issues sidebar.",
"type": "boolean",
"default": true
},
{
"key": "gitea.jobLogs.saveToRepo",
"title": "Save Job Logs to Repository",
"description": "Write job logs to .tmp/gitea-logs/ inside the repository so they open as saved files. When off, logs open in an untitled tab.",
"type": "boolean",
"default": true
},
{
"key": "gitea.artifacts.downloadPath",
"title": "Artifact Download Path",
"description": "Where downloaded artifacts are written. Relative paths resolve against the repository root.",
"type": "string",
"default": ".tmp/gitea-artifacts/"
}
]
},
{
"key": "gitea.logging.debug",
"title": "Enable Debug Logging",
"description": "Write verbose diagnostics to the extension console.",
"type": "boolean",
"default": false
}
],
"configWorkspace": [
{
"key": "gitea.baseUrl",
"title": "Instance URL",
"description": "Override the default Gitea instance for this workspace.",
"type": "string",
"placeholder": "Inherit from global preferences"
},
{
"key": "gitea.branchFilter",
"title": "Branch Filter",
"description": "Which branch the Current Branch section tracks.",
"type": "enum",
"values": [
["current", "Checked-out Branch"],
["all", "All Branches"]
],
"default": "current"
},
{
"key": "gitea.branchFilter.custom",
"title": "Custom Branch",
"description": "When set, the Current Branch section tracks this branch instead of the checked-out one.",
"type": "string",
"placeholder": "e.g. main"
}
]
}