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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +229 -0
  3. package/bin/omakit +3 -0
  4. package/package.json +40 -0
  5. package/skills/omarchy-plugin-submit/SKILL.md +87 -0
  6. package/skills/omarchy-plugin-validation-watch/SKILL.md +62 -0
  7. package/tests/parity/corpus.mjs +62 -0
  8. package/tests/parity/run.mjs +194 -0
  9. package/tools/marketplace/README.md +64 -0
  10. package/tools/marketplace/agent-control.mjs +72 -0
  11. package/tools/marketplace/banner.mjs +307 -0
  12. package/tools/marketplace/cli.mjs +283 -0
  13. package/tools/marketplace/completion.mjs +263 -0
  14. package/tools/marketplace/doctor.mjs +156 -0
  15. package/tools/marketplace/effect.mjs +115 -0
  16. package/tools/marketplace/form.mjs +209 -0
  17. package/tools/marketplace/github.mjs +206 -0
  18. package/tools/marketplace/issue.mjs +80 -0
  19. package/tools/marketplace/local-transport.mjs +162 -0
  20. package/tools/marketplace/parity-output.mjs +26 -0
  21. package/tools/marketplace/paths.mjs +10 -0
  22. package/tools/marketplace/pin.mjs +203 -0
  23. package/tools/marketplace/plugin.mjs +64 -0
  24. package/tools/marketplace/preflight.mjs +164 -0
  25. package/tools/marketplace/progress.mjs +96 -0
  26. package/tools/marketplace/registry.mjs +216 -0
  27. package/tools/marketplace/report.mjs +192 -0
  28. package/tools/marketplace/run-baseline.mjs +72 -0
  29. package/tools/marketplace/setup.mjs +136 -0
  30. package/tools/marketplace/style.mjs +443 -0
  31. package/tools/marketplace/submit.mjs +341 -0
  32. package/tools/marketplace/tree.mjs +50 -0
  33. package/tools/marketplace/upgrade.mjs +179 -0
  34. package/tools/marketplace/usage.mjs +191 -0
  35. package/tools/marketplace/verify.mjs +70 -0
  36. package/tools/marketplace/watch.mjs +262 -0
  37. package/tools/marketplace/yaml.mjs +164 -0
  38. 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
+ }