omakit 0.1.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/LICENSE +21 -0
- package/README.md +229 -0
- package/bin/omakit +3 -0
- package/package.json +40 -0
- package/skills/omarchy-plugin-submit/SKILL.md +87 -0
- package/skills/omarchy-plugin-validation-watch/SKILL.md +62 -0
- package/tests/parity/corpus.mjs +62 -0
- package/tests/parity/run.mjs +194 -0
- package/tools/marketplace/README.md +64 -0
- package/tools/marketplace/agent-control.mjs +72 -0
- package/tools/marketplace/banner.mjs +307 -0
- package/tools/marketplace/cli.mjs +283 -0
- package/tools/marketplace/completion.mjs +263 -0
- package/tools/marketplace/doctor.mjs +156 -0
- package/tools/marketplace/effect.mjs +115 -0
- package/tools/marketplace/form.mjs +209 -0
- package/tools/marketplace/github.mjs +206 -0
- package/tools/marketplace/issue.mjs +80 -0
- package/tools/marketplace/local-transport.mjs +162 -0
- package/tools/marketplace/parity-output.mjs +26 -0
- package/tools/marketplace/paths.mjs +10 -0
- package/tools/marketplace/pin.mjs +203 -0
- package/tools/marketplace/plugin.mjs +64 -0
- package/tools/marketplace/preflight.mjs +164 -0
- package/tools/marketplace/progress.mjs +96 -0
- package/tools/marketplace/registry.mjs +216 -0
- package/tools/marketplace/report.mjs +192 -0
- package/tools/marketplace/run-baseline.mjs +72 -0
- package/tools/marketplace/setup.mjs +136 -0
- package/tools/marketplace/style.mjs +443 -0
- package/tools/marketplace/submit.mjs +341 -0
- package/tools/marketplace/tree.mjs +50 -0
- package/tools/marketplace/upgrade.mjs +179 -0
- package/tools/marketplace/usage.mjs +191 -0
- package/tools/marketplace/verify.mjs +70 -0
- package/tools/marketplace/watch.mjs +262 -0
- package/tools/marketplace/yaml.mjs +164 -0
- package/tools/subject/resolve.mjs +124 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Subject resolution. A subject is a plugin repository at an exact commit:
|
|
2
|
+
// author mode - a local Git repository path (HEAD, clean unless allowed)
|
|
3
|
+
// reviewer mode - <https url>@<40-char sha>, fetched read-only into
|
|
4
|
+
// .cache/subjects/<owner>__<repo>/ and never executed.
|
|
5
|
+
import { execFileSync } from "node:child_process"
|
|
6
|
+
import { existsSync, mkdirSync } from "node:fs"
|
|
7
|
+
import { homedir } from "node:os"
|
|
8
|
+
import { join, resolve } from "node:path"
|
|
9
|
+
|
|
10
|
+
export class SubjectError extends Error {
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(message)
|
|
13
|
+
this.code = code
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function git(dir, args) {
|
|
18
|
+
return execFileSync("git", ["-C", dir, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** "https://github.com/owner/repo(.git)" -> { owner, repository, url } or null. */
|
|
22
|
+
export function parseGitHubUrl(value) {
|
|
23
|
+
const text = String(value || "").trim()
|
|
24
|
+
const m = text.match(/^(?:https:\/\/github\.com\/|git@github\.com:)([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?\/?$/)
|
|
25
|
+
if (!m) return null
|
|
26
|
+
return { owner: m[1], repository: m[2], url: `https://github.com/${m[1]}/${m[2]}` }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** "https://github.com/owner/repo" -> "owner__repo", the cache directory name. */
|
|
30
|
+
export function subjectSlug(repoUrl) {
|
|
31
|
+
return String(repoUrl).replace(/^https:\/\/github\.com\//, "").replace(/\//g, "__")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseTarget(target) {
|
|
35
|
+
const text = String(target || "")
|
|
36
|
+
const m = text.match(/^(https:\/\/[^@\s]+)@([0-9a-fA-F]{40})$/)
|
|
37
|
+
if (m) return { mode: "reviewer", url: m[1], commit: m[2].toLowerCase() }
|
|
38
|
+
if (/^https?:\/\//.test(text)) throw new SubjectError("usage", "reviewer targets are <https url>@<40-char sha>")
|
|
39
|
+
return { mode: "author", path: resolve(expandHome(text)) }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A leading `~` or `~/` is the home directory. The shell does this for an
|
|
44
|
+
* unquoted argument; a quoted one, or one an agent assembled, arrives as the
|
|
45
|
+
* literal character, and `resolve()` then glued it onto the working directory
|
|
46
|
+
* and refused a path that does not exist. `~user` needs a password database
|
|
47
|
+
* and is left alone; a `~` anywhere later in the path is not special.
|
|
48
|
+
*/
|
|
49
|
+
function expandHome(text) {
|
|
50
|
+
if (text === "~") return homedir()
|
|
51
|
+
if (text.startsWith("~/")) return join(homedir(), text.slice(2))
|
|
52
|
+
return text
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} target
|
|
57
|
+
* @param {{ cacheRoot: string, allowDirty?: boolean }} options
|
|
58
|
+
* @returns {{ mode, dir, commit, clean, repository: { kind: "git", url: string|null, declared: boolean } }}
|
|
59
|
+
*/
|
|
60
|
+
export function resolveSubject(target, options) {
|
|
61
|
+
const parsed = parseTarget(target)
|
|
62
|
+
if (parsed.mode === "author") {
|
|
63
|
+
if (!existsSync(parsed.path)) throw new SubjectError("subject-not-found", `no such directory: ${parsed.path}`)
|
|
64
|
+
let top
|
|
65
|
+
try {
|
|
66
|
+
top = git(parsed.path, ["rev-parse", "--show-toplevel"]).trim()
|
|
67
|
+
} catch {
|
|
68
|
+
throw new SubjectError("not-a-git-repository", `${parsed.path} is not inside a Git repository`)
|
|
69
|
+
}
|
|
70
|
+
let commit
|
|
71
|
+
try {
|
|
72
|
+
commit = git(top, ["rev-parse", "HEAD"]).trim()
|
|
73
|
+
} catch {
|
|
74
|
+
throw new SubjectError("commit-not-found", `${top} has no commit yet`)
|
|
75
|
+
}
|
|
76
|
+
const clean = git(top, ["status", "--porcelain"]).trim().length === 0
|
|
77
|
+
if (!clean && !options.allowDirty) throw new SubjectError("dirty-worktree", `${top} has uncommitted changes; commit them or pass --allow-dirty`)
|
|
78
|
+
let originUrl = null
|
|
79
|
+
try {
|
|
80
|
+
originUrl = git(top, ["remote", "get-url", "origin"]).trim()
|
|
81
|
+
} catch {
|
|
82
|
+
originUrl = null
|
|
83
|
+
}
|
|
84
|
+
const gh = parseGitHubUrl(originUrl)
|
|
85
|
+
return {
|
|
86
|
+
mode: "author",
|
|
87
|
+
dir: top,
|
|
88
|
+
subdir: resolve(parsed.path) === top ? "" : resolve(parsed.path).slice(top.length + 1),
|
|
89
|
+
commit,
|
|
90
|
+
clean,
|
|
91
|
+
repository: { kind: "git", url: gh ? gh.url : null, declared: Boolean(gh) },
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const gh = parseGitHubUrl(parsed.url)
|
|
95
|
+
if (!gh) throw new SubjectError("usage", `reviewer mode needs a github.com repository URL, got ${parsed.url}`)
|
|
96
|
+
const dir = join(resolve(options.cacheRoot), "subjects", `${gh.owner}__${gh.repository}`)
|
|
97
|
+
if (!existsSync(join(dir, ".git"))) {
|
|
98
|
+
mkdirSync(dir, { recursive: true })
|
|
99
|
+
execFileSync("git", ["init", "-q", dir], { encoding: "utf8" })
|
|
100
|
+
git(dir, ["remote", "add", "origin", gh.url])
|
|
101
|
+
}
|
|
102
|
+
let present = false
|
|
103
|
+
try {
|
|
104
|
+
git(dir, ["cat-file", "-e", `${parsed.commit}^{commit}`])
|
|
105
|
+
present = true
|
|
106
|
+
} catch {
|
|
107
|
+
present = false
|
|
108
|
+
}
|
|
109
|
+
if (!present) {
|
|
110
|
+
try {
|
|
111
|
+
git(dir, ["fetch", "-q", "--depth", "1", "origin", parsed.commit])
|
|
112
|
+
} catch (e) {
|
|
113
|
+
throw new SubjectError("commit-not-found", `${gh.url} does not offer commit ${parsed.commit}: ${String(e.stderr || e.message).trim().split("\n").pop()}`)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
mode: "reviewer",
|
|
118
|
+
dir,
|
|
119
|
+
subdir: "",
|
|
120
|
+
commit: parsed.commit,
|
|
121
|
+
clean: true,
|
|
122
|
+
repository: { kind: "git", url: gh.url, declared: true },
|
|
123
|
+
}
|
|
124
|
+
}
|