@grosspoetrysystems/oompf 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gross Poetry Systems
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # oompf
2
+
3
+ `oompf` publishes, inspects, searches, and installs OMP (Oh My Pi) agent
4
+ profiles through the OOMPF index at [oompf.run](https://oompf.run).
5
+
6
+ A local profile becomes a public GitHub Gist, its metadata is registered in the
7
+ index, and anyone can reinstall it as a native OMP profile:
8
+
9
+ ```text
10
+ oompf publish <profile> → public Gist → indexed → https://oompf.run/p/<id>
11
+ oompf add <url> → installs a native OMP profile
12
+ omp --profile <name> → run it
13
+ ```
14
+
15
+ The canonical profile artifact always stays at its public Gist; OOMPF persists
16
+ and serves **metadata only**.
17
+
18
+ ## Requirements
19
+
20
+ - **Node >= 22 or Bun.** The CLI uses `Promise.withResolvers`, which requires
21
+ Node 22+ (or any Bun release).
22
+ - **GitHub CLI (`gh`), authenticated** — required for `publish`, which creates a
23
+ public Gist with your `gh` account. Verify with `gh auth status`; run
24
+ `gh auth login` first if needed.
25
+ - **OMP CLI (`omp`) on `PATH`** — required for `publish` (resolves the local
26
+ profile's config path) and `add` (resolves the install directory). OOMPF asks
27
+ OMP where a profile lives rather than hardcoding a home path. `omp` is itself
28
+ a Bun program.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ bunx @grosspoetrysystems/oompf@latest --help
34
+ ```
35
+
36
+ Or keep it on `PATH`:
37
+
38
+ ```bash
39
+ npm install -g @grosspoetrysystems/oompf
40
+ ```
41
+
42
+ The package is scoped; the binary is plain `oompf`. Examples below use the
43
+ binary name.
44
+
45
+ Working in a clone of the repository? Run it from source with
46
+ `bun apps/cli/src/index.ts <command>`.
47
+
48
+ ## Commands
49
+
50
+ Every command prints human-readable output by default and structured JSON with
51
+ `--json`. On failure, every command exits non-zero and prints a stable error
52
+ envelope: a machine-readable `code`, a human `message`, and optional
53
+ value-free `details`.
54
+
55
+ All commands target `https://oompf.run` by default. Set `OOMPF_BASE_URL` to point
56
+ elsewhere, for example at a local instance:
57
+
58
+ ```bash
59
+ OOMPF_BASE_URL=http://localhost:4321 oompf search anthropic
60
+ ```
61
+
62
+ ### `oompf publish [profile]`
63
+
64
+ Resolve a local OMP profile (pass the name, or omit it when exactly one profile
65
+ is unambiguous), validate and secret-scan its `config.yml`, create a public
66
+ one-file Gist, and register its metadata with the index.
67
+
68
+ ```bash
69
+ oompf publish work
70
+ ```
71
+
72
+ Output: the `githubUrl`, the canonical `oompfUrl` (`https://oompf.run/p/<id>`),
73
+ a copyable `addCommand`, the artifact `hash`, the structural verdict, and any
74
+ warnings.
75
+
76
+ Failure modes:
77
+
78
+ - `no_profile` — no OMP profiles were found; pass an explicit name.
79
+ - `ambiguous_profile` — more than one profile exists; pass the one to publish.
80
+ - `missing_config` — the profile has no `config.yml` / `config.yaml`.
81
+ - `invalid_artifact` — the profile failed structural validation.
82
+ - `blocking_secrets` — high-confidence secrets were detected in the config;
83
+ nothing is published. Remove them and retry.
84
+ - GitHub authentication or Gist-creation failure (e.g. `gh` not installed or
85
+ not authenticated), or registration failure against the index
86
+ (`network_error`, or the server's own code such as `validation_failed`).
87
+
88
+ Nothing is registered if local validation fails.
89
+
90
+ ### `oompf add <ref> [--name <name>]`
91
+
92
+ Install a shared profile as a native OMP profile. The reference may be an OOMPF
93
+ URL or id, a public Gist URL, or a bare Gist id.
94
+
95
+ ```bash
96
+ oompf add https://oompf.run/p/prof_1b7c9e0a4d2f3a5b6c8d9e0f1a2b3c4d
97
+ ```
98
+
99
+ The canonical YAML is fetched and re-validated before anything is written. The
100
+ local name defaults to `<owner>-<profile>` and overrides with `--name`. The
101
+ install directory is resolved by asking OMP itself. An existing config is
102
+ refused outright — there is no overwrite and no `--force`.
103
+
104
+ Output: the installed `name`, the `path` written, the artifact `hash`, the
105
+ `command` to run it, and any warnings.
106
+
107
+ Failure modes:
108
+
109
+ - `invalid_artifact` — the fetched artifact failed structural validation.
110
+ - `invalid_name` — the derived (or `--name`) profile name violates OMP's naming
111
+ rules; pass an explicit valid `--name`.
112
+ - `target_exists` — a profile with that name already has a config; OOMPF refuses
113
+ to overwrite it.
114
+ - `network_error` — the index or Gist source could not be reached.
115
+ - The Gist is missing or private, or the reference is not a supported Gist/URL.
116
+
117
+ ### `oompf inspect <ref>`
118
+
119
+ Show a shared profile's metadata without installing it.
120
+
121
+ ```bash
122
+ oompf inspect https://oompf.run/p/prof_1b7c9e0a4d2f3a5b6c8d9e0f1a2b3c4d
123
+ ```
124
+
125
+ An OOMPF reference is answered from the index; a Gist reference is fetched and
126
+ validated live. Output is metadata only — models, providers, aliases, the
127
+ structural verdict, provenance, and the install command. The canonical artifact
128
+ content is never emitted.
129
+
130
+ Failure modes: `not_found` for an unknown indexed id; `network_error` when the
131
+ index is unreachable; Gist fetch errors when the source is missing, private, or
132
+ unsupported.
133
+
134
+ ### `oompf search [query]`
135
+
136
+ Free-text search over the index. Omitting the query lists all indexed profiles.
137
+
138
+ ```bash
139
+ oompf search anthropic
140
+ ```
141
+
142
+ Output: the normalized `query`, the result `count`, and one compact metadata-only
143
+ record per match (id, name, owner, models, providers, revision, structural
144
+ verdict, source, and canonical URL).
145
+
146
+ Failure mode: `network_error` when the index is unreachable.
147
+
148
+ ## JSON output and errors
149
+
150
+ - `--json` on any command returns the same data the human output renders,
151
+ machine-readable.
152
+ - Failures use a stable envelope with a machine-readable `code` — see the codes
153
+ listed per command above. Network failures map to `network_error`; unexpected
154
+ errors fall back to the generic code `error`. Server errors surface the
155
+ server's own code (for example `not_found`, `validation_failed`,
156
+ `internal_error`).
157
+
158
+ ## Safety
159
+
160
+ - **Secrets are scanned before publish.** High-confidence findings block the
161
+ publish with `blocking_secrets`; nothing leaves your machine.
162
+ - **Installs never overwrite.** An existing target raises `target_exists`; there
163
+ is no `--force`.
164
+ - **Metadata only.** OOMPF indexes metadata and validation results; the canonical
165
+ artifact stays at its public Gist.
166
+
167
+ ## Links
168
+
169
+ - Documentation: [https://oompf.run/docs/](https://oompf.run/docs/)
170
+ - Repository: [grosspoetrysystems/oompf](https://github.com/grosspoetrysystems/oompf)
171
+ - Issues: [https://github.com/grosspoetrysystems/oompf/issues](https://github.com/grosspoetrysystems/oompf/issues)
@@ -0,0 +1,179 @@
1
+ import { Cli } from "incur";
2
+ //#region ../../packages/core/dist/omp-profile.d.ts
3
+ /**
4
+ * Portable OMP profile discovery and path resolution.
5
+ *
6
+ * OOMPF never hardcodes `~/.omp`. Instead it treats the OMP CLI as the single
7
+ * source of truth for where a profile's agent directory lives, invoking
8
+ * `omp --profile <name> config path` (and the profile-less `omp config path`)
9
+ * with the current environment propagated. This honours `PI_CONFIG_DIR`,
10
+ * `PI_CODING_AGENT_DIR`, XDG base directories, and OMP's own profile
11
+ * resolution without OOMPF having to re-implement any of it.
12
+ *
13
+ * OMP is always invoked as an argv array (never a shell string), so profile
14
+ * names — even hostile ones — are passed as opaque arguments with no shell
15
+ * interpolation.
16
+ */
17
+ /** A profile found on disk under OMP's resolved profiles directory. */
18
+ interface DiscoveredProfile {
19
+ /** Absolute path to the profile's agent directory. */
20
+ readonly agentDir: string;
21
+ /** Absolute path to `config.yml`/`config.yaml`, or `null` if neither exists. */
22
+ readonly configPath: string | null;
23
+ /** The profile name (directory basename), already OMP-valid. */
24
+ readonly name: string;
25
+ }
26
+ /** The fully resolved configuration for a single existing profile. */
27
+ interface ResolvedProfileConfig {
28
+ /** Absolute path to the profile's agent directory (verified to exist). */
29
+ readonly agentDir: string;
30
+ /** Absolute path to the loaded config file, or `null` when none is present. */
31
+ readonly configPath: string | null;
32
+ /** Parsed config document (mapping root), or `null` when no config exists. */
33
+ readonly document: Record<string, unknown> | null;
34
+ /** The profile name that was resolved. */
35
+ readonly profile: string;
36
+ }
37
+ /** Options accepted by every resolver; `ompCommand` overrides the binary. */
38
+ interface OmpProfileOptions {
39
+ /** Executable used to invoke OMP. Defaults to `"omp"` on `PATH`. */
40
+ readonly ompCommand?: string;
41
+ }
42
+ /**
43
+ * Resolve the agent directory OMP would use to install a profile named `name`.
44
+ *
45
+ * Validates the name first, then asks OMP for the path. Succeeds for a profile
46
+ * that does not yet exist — the returned directory is an install target, so it
47
+ * is not required to be present on disk.
48
+ */
49
+ declare function resolveInstallTarget(name: string, options?: OmpProfileOptions): Promise<string>;
50
+ /**
51
+ * Resolve the full configuration for an existing profile.
52
+ *
53
+ * Validates the name, resolves the agent directory via OMP (which MUST exist),
54
+ * then loads `config.yml`/`config.yaml` if present. Rejects when the profile's
55
+ * agent directory is missing.
56
+ */
57
+ declare function resolveProfileConfig(profile: string, options?: OmpProfileOptions): Promise<ResolvedProfileConfig>;
58
+ /**
59
+ * Discover every profile present under OMP's resolved profiles directory.
60
+ *
61
+ * The profiles root is derived from OMP's own default agent path (`omp config
62
+ * path`), so it follows whatever `PI_CONFIG_DIR`/XDG/etc. resolution OMP
63
+ * applies. Directory entries whose names are not OMP-valid, and profiles whose
64
+ * agent directory is absent, are skipped. Results are sorted by name.
65
+ */
66
+ declare function discoverProfiles(options?: OmpProfileOptions): Promise<DiscoveredProfile[]>;
67
+ //#endregion
68
+ //#region ../../packages/github/dist/gh.d.ts
69
+ /**
70
+ * CLI-side `gh` (GitHub CLI) integration for OOMPF.
71
+ *
72
+ * This module shells out to the `gh` binary to read the authenticated identity
73
+ * and to publish public Gists. It MUST only ever be imported by CLI-side code:
74
+ * it spawns child processes and therefore cannot run inside a Cloudflare
75
+ * Worker. Every invocation passes an explicit argument array to the runner —
76
+ * there is no shell string and no interpolation, so profile names,
77
+ * descriptions, and file contents can never be reinterpreted as shell syntax.
78
+ *
79
+ * All process spawning is funnelled through an injectable {@link CommandRunner}
80
+ * seam so tests can exercise identity resolution and Gist publishing
81
+ * hermetically, without a real `gh` binary and without creating real Gists.
82
+ */
83
+ /** Outcome of running a single command. */
84
+ interface CommandResult {
85
+ /** Process exit code; `null` when the process was terminated by a signal. */
86
+ readonly exitCode: number | null;
87
+ readonly stderr: string;
88
+ readonly stdout: string;
89
+ }
90
+ /** A single command invocation: an executable plus a literal argument array. */
91
+ interface CommandInput {
92
+ /** Arguments passed verbatim; never concatenated into a shell command. */
93
+ readonly args: readonly string[];
94
+ /** Executable to run (e.g. `"gh"`). Never a shell string. */
95
+ readonly command: string;
96
+ /** Optional UTF-8 text piped to the process stdin. */
97
+ readonly stdin?: string;
98
+ }
99
+ /**
100
+ * Injectable command seam. Implementations MUST NOT invoke a shell; they run
101
+ * `input.command` with `input.args` as discrete argv entries.
102
+ *
103
+ * A missing executable MUST reject (e.g. an `ENOENT` error) rather than
104
+ * resolve with a non-zero exit code, so callers can distinguish "not
105
+ * installed" from "ran and failed".
106
+ */
107
+ type CommandRunner = (input: CommandInput) => Promise<CommandResult>;
108
+ //#endregion
109
+ //#region ../../packages/github/dist/gists.d.ts
110
+ /** Minimal structural view of a `fetch` response used by this module. */
111
+ interface GistFetchResponse {
112
+ readonly ok: boolean;
113
+ readonly status: number;
114
+ text(): Promise<string>;
115
+ }
116
+ /** Injectable `fetch` seam so tests never hit the network. */
117
+ type GistFetch = (url: string, init?: {
118
+ readonly headers?: Record<string, string>;
119
+ }) => Promise<GistFetchResponse>;
120
+ //#endregion
121
+ //#region src/deps.d.ts
122
+ /** Minimal structural view of an HTTP response the API client consumes. */
123
+ interface HttpResponse {
124
+ readonly ok: boolean;
125
+ readonly status: number;
126
+ text(): Promise<string>;
127
+ }
128
+ /** Injectable HTTP seam for the OOMPF web API (register/search/metadata). */
129
+ type HttpFetch = (url: string, init?: {
130
+ readonly method?: string;
131
+ readonly headers?: Record<string, string>;
132
+ readonly body?: string;
133
+ }) => Promise<HttpResponse>;
134
+ /** Injectable filesystem seam for reading and atomically installing configs. */
135
+ interface FsSeam {
136
+ /** True when a path exists (file or directory). */
137
+ exists(path: string): Promise<boolean>;
138
+ /** Recursively create a directory with the given octal permission mode. */
139
+ mkdir(path: string, mode: number): Promise<void>;
140
+ /** Read a UTF-8 text file. */
141
+ readFile(path: string): Promise<string>;
142
+ /** Write a UTF-8 text file with the given octal permission mode. */
143
+ writeFile(path: string, data: string, mode: number): Promise<void>;
144
+ }
145
+ /** Every seam a command may need, each optional and defaulted at the boundary. */
146
+ interface CliDeps {
147
+ /** Profile discovery seam (publish default-profile derivation). */
148
+ readonly discoverProfiles?: typeof discoverProfiles;
149
+ /** Filesystem seam. */
150
+ readonly fs?: FsSeam;
151
+ /** `gh` executable name; defaults to `"gh"`. */
152
+ readonly ghCommand?: string;
153
+ /** Gist raw-fetch seam; defaults to the global `fetch`. */
154
+ readonly gistFetch?: GistFetch;
155
+ /** OOMPF web API HTTP seam; defaults to the global `fetch`. */
156
+ readonly httpFetch?: HttpFetch;
157
+ /** `omp` executable name; defaults to `"omp"`. */
158
+ readonly ompCommand?: string;
159
+ /** Install-target resolver seam (add). */
160
+ readonly resolveInstallTarget?: typeof resolveInstallTarget;
161
+ /** Existing-profile resolver seam (publish). */
162
+ readonly resolveProfileConfig?: typeof resolveProfileConfig;
163
+ /** `gh` command runner seam (auth check + Gist creation). */
164
+ readonly runner?: CommandRunner;
165
+ }
166
+ //#endregion
167
+ //#region src/index.d.ts
168
+ /**
169
+ * OOMPF CLI version, surfaced by `oompf --version`.
170
+ *
171
+ * Must match `package.json`; `index.test.ts` asserts it, because a published
172
+ * binary reporting the wrong version makes every bug report ambiguous.
173
+ */
174
+ declare const CLI_VERSION = "0.1.0";
175
+ /** Build the OOMPF CLI with the given (optional) injectable seams. */
176
+ declare function createCli(deps?: CliDeps): Cli.Cli<{}, undefined, undefined, undefined>;
177
+ //#endregion
178
+ export { CLI_VERSION, createCli };
179
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../packages/core/dist/omp-profile.d.ts","../../../packages/github/dist/gh.d.ts","../../../packages/github/dist/gists.d.ts","../src/deps.ts","../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;;UAeiB;;WAEJ;;WAEA;;WAEA;;;UAGI;;WAEJ;;WAEA;;WAEA,UAAU;;WAEV;;;UAGI;;WAEJ;;;;;;;;;iBASW,qBAAqB,cAAc,UAAU,oBAAoB;;;;;;;;iBAQjE,qBAAqB,iBAAiB,UAAU,oBAAoB,QAAQ;;;;;;;;;iBAS5E,iBAAiB,UAAU,oBAAoB,QAAQ;;;;;;;;;;;;;;;;;;UChD9D;;WAEJ;WACA;WACA;;;UAGI;;WAEJ;;WAEA;;WAEA;;;;;;;;;;KAUD,iBAAiB,OAAO,iBAAiB,QAAQ;;;;UCG5C;WACJ;WACA;EACT,QAAQ;;;KAGA,aAAa,aAAa;WACzB,UAAU;MACjB,QAAQ;;;;UC3BG;WACN;WACA;EACT,QAAQ;;;KAIE,aACV,aACA;WACW;WACA,UAAU;WACV;MAER,QAAQ;;UAGI;;EAEf,OAAO,eAAe;;EAEtB,MAAM,cAAc,eAAe;;EAEnC,SAAS,eAAe;;EAExB,UAAU,cAAc,cAAc,eAAe;;;UAoCtC;;WAEN,0BAA0B;;WAE1B,KAAK;;WAEL;;WAEA,YAAY;;WAEZ,YAAY;;WAEZ;;WAEA,8BAA8B;;WAE9B,8BAA8B;;WAE9B,SAAS;;;;;;;;;;cC5EP;;iBAGG,UAAU,OAAM,UAAY,IAAA"}