@sangyaahub/pi-marg 1.0.0 → 1.0.1
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/.omp-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +8 -0
- package/MODEL-SETUP.md +8 -6
- package/PUBLISHING.md +1 -1
- package/README.md +25 -6
- package/adapters/omp/extensions/model-router.ts +169 -6
- package/bin/pi-marg.js +76 -0
- package/core/model-routing.ts +37 -15
- package/package.json +5 -1
- package/scripts/install.sh +1 -1
- package/scripts/validate.sh +4 -1
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "PiMarg — a governed development path for Pi Agent and Oh My Pi.",
|
|
10
|
-
"version": "1.0.
|
|
10
|
+
"version": "1.0.1"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
14
14
|
"name": "pi-marg",
|
|
15
15
|
"description": "Intent-led development workflow with live A/B/C/D routing, capability detection, verification, and approval gates.",
|
|
16
16
|
"source": "./",
|
|
17
|
-
"version": "1.0.
|
|
17
|
+
"version": "1.0.1",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "Sangyaa",
|
|
20
20
|
"email": "contact@sangyaa.co"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.1 — 2026-09-10
|
|
4
|
+
|
|
5
|
+
- Added the global `pi-marg start` and `pi-marg "<work prompt>"` OMP launch commands.
|
|
6
|
+
- Canonicalized the npm executable mapping so npm installs the global `pi-marg` command.
|
|
7
|
+
- Added native interactive OMP intake for work boundary, work type, workflow skill, and required A/B/C/D model choices.
|
|
8
|
+
- Changed model pickers to show every authenticated and enabled runtime model while ranking stage-appropriate choices first.
|
|
9
|
+
- Preserved independent-review enforcement: A cannot equal C, and B cannot equal D.
|
|
10
|
+
|
|
3
11
|
## 1.0.0 — 2026-09-08
|
|
4
12
|
|
|
5
13
|
- First PiMarg release for Pi Agent and Oh My Pi.
|
package/MODEL-SETUP.md
CHANGED
|
@@ -39,20 +39,22 @@ The Pi adapter reads `ctx.scopedModels` when the session has a model allowlist;
|
|
|
39
39
|
Use OMP `/login`, then inspect `/model` or:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
omp models
|
|
42
|
+
omp models
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
The OMP adapter reads `ctx.models.list()
|
|
45
|
+
The OMP adapter reads `ctx.models.list()`, which supplies authenticated and enabled models, and activates an exact catalog entry with `pi.setModel()`.
|
|
46
46
|
|
|
47
47
|
## Selection workflow
|
|
48
48
|
|
|
49
|
-
1. Run `/auto-models choose
|
|
50
|
-
2.
|
|
51
|
-
3. Select A and C
|
|
52
|
-
4. Select B and D when implementation is required.
|
|
49
|
+
1. Run `/pi-marg` for the full native intake, or `/auto-models choose` to change saved models.
|
|
50
|
+
2. Confirm the work type so PiMarg knows which stages are required.
|
|
51
|
+
3. Select A and C when discovery/review is required; recommended frontier choices appear first.
|
|
52
|
+
4. Select B and D when implementation is required; recommended execution choices appear first.
|
|
53
53
|
5. Resolve any A=C or B=D identity conflict before continuing.
|
|
54
54
|
6. Activate the saved selector immediately before its stage.
|
|
55
55
|
|
|
56
|
+
Every authenticated and enabled runtime model remains selectable at every required stage. The preferred classes above control ranking, not visibility.
|
|
57
|
+
|
|
56
58
|
The normalized identity check treats the same underlying model reached through different providers as the same candidate when their identifiers normalize equally.
|
|
57
59
|
|
|
58
60
|
## Devin
|
package/PUBLISHING.md
CHANGED
|
@@ -5,7 +5,7 @@ This checklist is for maintainers. Public mutations remain subject to PiMarg's o
|
|
|
5
5
|
## Before a release
|
|
6
6
|
|
|
7
7
|
1. Update `package.json`, `.omp-plugin/marketplace.json`, and `CHANGELOG.md` to the same version.
|
|
8
|
-
2. Run `./scripts/validate.sh`, `bun test tests`, and `npm pack --dry-run`.
|
|
8
|
+
2. Run `./scripts/validate.sh`, `bun test tests`, `node bin/pi-marg.js --version`, and `npm pack --dry-run`.
|
|
9
9
|
3. Smoke-test the package root with the supported Pi and OMP versions.
|
|
10
10
|
4. Inspect the packed file list for secrets, generated debris, and missing runtime files.
|
|
11
11
|
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/sangyaahub/pi-marg/releases"><img alt="Version 1.0.
|
|
6
|
+
<a href="https://github.com/sangyaahub/pi-marg/releases"><img alt="Version 1.0.1" src="https://img.shields.io/badge/version-1.0.1-0451F7"></a>
|
|
7
7
|
<a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-C27B1A"></a>
|
|
8
8
|
<a href="https://pi.dev/packages?name=pi-marg"><img alt="Pi package" src="https://img.shields.io/badge/Pi-package-0338A8"></a>
|
|
9
9
|
<a href="https://omp.sh"><img alt="Oh My Pi plugin" src="https://img.shields.io/badge/OMP-plugin-021E5C"></a>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
# PiMarg
|
|
13
13
|
|
|
14
|
-
**Version 1.0.
|
|
14
|
+
**Version 1.0.1 · MIT licensed · Pi Agent + Oh My Pi · by [Sangyaa](https://sangyaa.co)**
|
|
15
15
|
|
|
16
16
|
PiMarg turns an ordinary prompt into a governed development path. It identifies the work boundary, selects one engineering method, discovers the models and tools the active runtime really provides, separates thinking from execution, records evidence, and stops before protected actions.
|
|
17
17
|
|
|
@@ -42,7 +42,7 @@ The same model-selection and approval logic lives in `core/`. Runtime-specific c
|
|
|
42
42
|
- Confirms job/client versus personal work; job/client mode suppresses Sangyaa branding by default.
|
|
43
43
|
- Routes seven work types: idea, new feature, improvement, proactive bug fix, security, repository analysis, and business/sales analysis.
|
|
44
44
|
- Uses one primary Compound Engineering, Superpowers, GSD Core, or automatically selected workflow spine.
|
|
45
|
-
-
|
|
45
|
+
- Shows every current authenticated and enabled model, ranking the best fit for each stage first instead of pinning dated model names.
|
|
46
46
|
- Enforces that A and C use different underlying models, and B and D use different underlying models.
|
|
47
47
|
- Uses CodeGraph for repository architecture and impact when available, with LSP/AST or repository-native tools for exact code questions.
|
|
48
48
|
- Adds conditional testing, debugger, browser/desktop, security, Advisor, task, and memory routes only when detected.
|
|
@@ -62,7 +62,7 @@ The same model-selection and approval logic lives in `core/`. Runtime-specific c
|
|
|
62
62
|
| Advisor, checkpoint, security scan, Hindsight | Extension/fallback when detected | Native capability when enabled |
|
|
63
63
|
| Parallel agents | Only when a compatible task extension exists | Native `task`/workflow routes when available |
|
|
64
64
|
|
|
65
|
-
Tested locally against Pi Agent `0.85.1` and OMP `18.1.
|
|
65
|
+
Tested locally against Pi Agent `0.85.1` and OMP `18.1.15`. Optional features are always capability-detected, so their absence produces a recorded fallback rather than a false success claim.
|
|
66
66
|
|
|
67
67
|
## Try the demo
|
|
68
68
|
|
|
@@ -98,6 +98,12 @@ Install PiMarg from the Pi package catalog/npm:
|
|
|
98
98
|
pi install npm:@sangyaahub/pi-marg
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
Install the global OMP launcher:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm install --global @sangyaahub/pi-marg
|
|
105
|
+
```
|
|
106
|
+
|
|
101
107
|
Or install Pi and OMP directly from this GitHub repository:
|
|
102
108
|
|
|
103
109
|
```bash
|
|
@@ -106,6 +112,8 @@ omp plugin marketplace add sangyaahub/pi-marg
|
|
|
106
112
|
omp plugin install --scope user pi-marg@pi-marg-marketplace
|
|
107
113
|
```
|
|
108
114
|
|
|
115
|
+
The npm command launches OMP; the OMP plugin installation supplies PiMarg's extensions, skills, commands, and rules. Install both when you want to start the complete workflow with `pi-marg` from any repository.
|
|
116
|
+
|
|
109
117
|
## Configure models
|
|
110
118
|
|
|
111
119
|
Authenticate providers using the active runtime's normal login flow, then run:
|
|
@@ -114,16 +122,27 @@ Authenticate providers using the active runtime's normal login flow, then run:
|
|
|
114
122
|
/auto-models choose
|
|
115
123
|
```
|
|
116
124
|
|
|
117
|
-
The router
|
|
125
|
+
The router shows every model in the live authenticated and enabled registry. Stage-appropriate choices appear first and all other enabled choices remain selectable. It does not promise that a subscription route is free or unlimited. See [MODEL-SETUP.md](MODEL-SETUP.md).
|
|
118
126
|
|
|
119
127
|
## Start a workflow
|
|
120
128
|
|
|
121
|
-
|
|
129
|
+
From a terminal in the repository you want to work on, run either:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pi-marg start
|
|
133
|
+
pi-marg "add CSV export to this repository"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`pi-marg start` opens OMP and asks for the work prompt. Supplying a prompt starts the same guided flow immediately. Inside an existing OMP session, invoke:
|
|
122
137
|
|
|
123
138
|
```text
|
|
139
|
+
/pi-marg
|
|
140
|
+
/pi-marg add CSV export to this repository
|
|
124
141
|
/auto add CSV export to this repository
|
|
125
142
|
```
|
|
126
143
|
|
|
144
|
+
The native picker asks for work boundary, work type, workflow skill, and every model required by that work type. It shows only models reported as authenticated and enabled by OMP, with recommended stage fits first.
|
|
145
|
+
|
|
127
146
|
Useful entry points:
|
|
128
147
|
|
|
129
148
|
- `/auto-models choose` — refresh A/B/C/D choices.
|
|
@@ -1,21 +1,130 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MODEL_STATE_TYPE,
|
|
3
3
|
STAGES,
|
|
4
|
+
buildModelCatalog,
|
|
4
5
|
emptyModelRouteState,
|
|
5
6
|
executeModelRoute,
|
|
7
|
+
requiredStagesForWorkType,
|
|
6
8
|
restoreModelRouteState,
|
|
9
|
+
type AutoStage,
|
|
10
|
+
type ModelCandidate,
|
|
7
11
|
type ModelLike,
|
|
8
12
|
type ModelRouteParams,
|
|
13
|
+
type ModelRouteState,
|
|
9
14
|
} from "../../../core/model-routing";
|
|
15
|
+
import { normalizeCapabilityNames } from "../../../core/runtime-capabilities";
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
const WORK_BOUNDARIES = ["Job/client", "Personal"] as const;
|
|
18
|
+
|
|
19
|
+
const WORK_TYPES = [
|
|
20
|
+
"1. New idea or development from scratch",
|
|
21
|
+
"2. New feature for an existing repository",
|
|
22
|
+
"3. Improve an existing feature",
|
|
23
|
+
"4. Pro-active bug fix",
|
|
24
|
+
"5. Security review or fixes",
|
|
25
|
+
"6. Thoughts about an existing repository or features",
|
|
26
|
+
"7. Business, sales, or other non-technical analysis",
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
const SKILL_MODES = [
|
|
30
|
+
"1. Compound Engineering",
|
|
31
|
+
"2. Superpowers",
|
|
32
|
+
"3. GSD Core",
|
|
33
|
+
"4. PiMarg chooses the best available skill",
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
const STAGE_PURPOSE: Record<AutoStage, string> = {
|
|
37
|
+
A: "thinking/discovery",
|
|
38
|
+
B: "implementation/execution",
|
|
39
|
+
C: "plan second-eye review",
|
|
40
|
+
D: "implementation second-eye review",
|
|
41
|
+
};
|
|
16
42
|
|
|
17
43
|
function hasExternalDevin(pi: any): boolean {
|
|
18
|
-
return
|
|
44
|
+
return normalizeCapabilityNames(pi.getAllTools?.() ?? [])
|
|
45
|
+
.some((name) => /(?:^|[_-])devin(?:[_-]|$)/i.test(name));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function commandPrompt(args: string): string {
|
|
49
|
+
const trimmed = args.trim();
|
|
50
|
+
if (trimmed.toLowerCase() === "start") return "";
|
|
51
|
+
return trimmed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function optionLabel(candidate: ModelCandidate): string {
|
|
55
|
+
return `${candidate.label} — ${candidate.selector}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function optionDescription(stage: AutoStage, candidate: ModelCandidate): string {
|
|
59
|
+
const preferredTier = stage === "A" || stage === "C" ? "frontier" : "execution";
|
|
60
|
+
const fit = candidate.tier === preferredTier ? "Recommended fit" : "Available";
|
|
61
|
+
return `${fit} for ${STAGE_PURPOSE[stage]}${candidate.billingNote ? ` · ${candidate.billingNote}` : ""}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function chooseModel(
|
|
65
|
+
ctx: any,
|
|
66
|
+
stage: AutoStage,
|
|
67
|
+
candidates: ModelCandidate[],
|
|
68
|
+
selected: Partial<Record<AutoStage, ModelCandidate>>,
|
|
69
|
+
): Promise<ModelCandidate | undefined> {
|
|
70
|
+
const opposite: Record<AutoStage, AutoStage> = { A: "C", C: "A", B: "D", D: "B" };
|
|
71
|
+
const other = selected[opposite[stage]];
|
|
72
|
+
const eligible = candidates.filter((candidate) => !other || candidate.identity !== other.identity);
|
|
73
|
+
if (eligible.length === 0) {
|
|
74
|
+
ctx.ui.notify(
|
|
75
|
+
"Paired stages require two distinct authenticated model identities. Configure another model, then run /pi-marg again.",
|
|
76
|
+
"error",
|
|
77
|
+
);
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const options = eligible.map((candidate) => ({
|
|
81
|
+
label: optionLabel(candidate),
|
|
82
|
+
description: optionDescription(stage, candidate),
|
|
83
|
+
}));
|
|
84
|
+
const answer = await ctx.ui.select(`Stage ${stage} model`, options, {
|
|
85
|
+
selectionMarker: "radio",
|
|
86
|
+
helpText: `${eligible.length} authenticated and enabled choices · ${STAGE_PURPOSE[stage]}`,
|
|
87
|
+
});
|
|
88
|
+
return eligible.find((candidate) => optionLabel(candidate) === answer);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function selectedState(
|
|
92
|
+
workType: number,
|
|
93
|
+
selected: Partial<Record<AutoStage, ModelCandidate>>,
|
|
94
|
+
): ModelRouteState {
|
|
95
|
+
const selectedAt = new Date().toISOString();
|
|
96
|
+
return {
|
|
97
|
+
version: 1,
|
|
98
|
+
workType,
|
|
99
|
+
selections: Object.fromEntries(
|
|
100
|
+
Object.entries(selected).map(([stage, candidate]) => [stage, { ...candidate, selectedAt }]),
|
|
101
|
+
) as ModelRouteState["selections"],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function intakeMessage(
|
|
106
|
+
request: string,
|
|
107
|
+
boundary: string,
|
|
108
|
+
workType: string,
|
|
109
|
+
skillMode: string,
|
|
110
|
+
selected: Partial<Record<AutoStage, ModelCandidate>>,
|
|
111
|
+
): string {
|
|
112
|
+
const models = STAGES
|
|
113
|
+
.filter((stage) => selected[stage])
|
|
114
|
+
.map((stage) => `- Stage ${stage} (${STAGE_PURPOSE[stage]}): ${selected[stage]!.selector}`)
|
|
115
|
+
.join("\n");
|
|
116
|
+
return [
|
|
117
|
+
"Start PiMarg for the following request:",
|
|
118
|
+
request,
|
|
119
|
+
"",
|
|
120
|
+
"Interactive intake is already confirmed; do not ask these questions again:",
|
|
121
|
+
`- Boundary: ${boundary}`,
|
|
122
|
+
`- Work type: ${workType}`,
|
|
123
|
+
`- Workflow skill: ${skillMode}`,
|
|
124
|
+
models,
|
|
125
|
+
"",
|
|
126
|
+
"Load auto-mode-router, record these choices in the session ledger, activate each saved model before its stage, and continue the workflow. Ask interactively only for information that is still missing.",
|
|
127
|
+
].join("\n");
|
|
19
128
|
}
|
|
20
129
|
|
|
21
130
|
export default function modelRouter(pi: any) {
|
|
@@ -30,6 +139,60 @@ export default function modelRouter(pi: any) {
|
|
|
30
139
|
pi.on("session_branch", restore);
|
|
31
140
|
pi.on("session_tree", restore);
|
|
32
141
|
|
|
142
|
+
pi.registerCommand("pi-marg", {
|
|
143
|
+
description: "Start PiMarg with interactive intake, or pass a work prompt.",
|
|
144
|
+
async handler(args: string, ctx: any) {
|
|
145
|
+
if (typeof ctx.isIdle === "function" && ctx.isIdle() === false) {
|
|
146
|
+
ctx.ui.notify("PiMarg intake can start after the current turn finishes.", "warning");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let request = commandPrompt(args);
|
|
151
|
+
if (!ctx.hasUI) {
|
|
152
|
+
if (!request) {
|
|
153
|
+
ctx.ui.notify("PiMarg interactive intake requires an OMP TUI session.", "error");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
pi.sendUserMessage(`Start PiMarg for this request:\n${request}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!request) {
|
|
160
|
+
request = (await ctx.ui.input("PiMarg", "What would you like to work on?"))?.trim() ?? "";
|
|
161
|
+
}
|
|
162
|
+
if (!request) return;
|
|
163
|
+
|
|
164
|
+
const boundaryLabel = await ctx.ui.select("Work boundary", [...WORK_BOUNDARIES], {
|
|
165
|
+
selectionMarker: "radio",
|
|
166
|
+
});
|
|
167
|
+
if (!WORK_BOUNDARIES.includes(boundaryLabel)) return;
|
|
168
|
+
|
|
169
|
+
const workTypeLabel = await ctx.ui.select("Work type", [...WORK_TYPES], { selectionMarker: "radio" });
|
|
170
|
+
const workType = Number.parseInt(workTypeLabel?.match(/^([1-7])\./)?.[1] ?? "", 10);
|
|
171
|
+
if (!Number.isInteger(workType)) return;
|
|
172
|
+
|
|
173
|
+
const skillMode = await ctx.ui.select("Workflow skill", [...SKILL_MODES], { selectionMarker: "radio" });
|
|
174
|
+
if (!skillMode) return;
|
|
175
|
+
|
|
176
|
+
const liveModels = ctx.models.list() as ModelLike[];
|
|
177
|
+
if (liveModels.length === 0) {
|
|
178
|
+
ctx.ui.notify("No authenticated and enabled OMP models were found. Configure a provider, then run /pi-marg again.", "error");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const catalog = buildModelCatalog(liveModels, hasExternalDevin(pi));
|
|
182
|
+
const selected: Partial<Record<AutoStage, ModelCandidate>> = {};
|
|
183
|
+
for (const stage of requiredStagesForWorkType(workType)) {
|
|
184
|
+
const choice = await chooseModel(ctx, stage, catalog[stage], selected);
|
|
185
|
+
if (!choice) return;
|
|
186
|
+
selected[stage] = choice;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const nextState = selectedState(workType, selected);
|
|
190
|
+
state = nextState;
|
|
191
|
+
pi.appendEntry(MODEL_STATE_TYPE, nextState);
|
|
192
|
+
pi.sendUserMessage(intakeMessage(request, boundaryLabel, workTypeLabel!, skillMode, selected));
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
33
196
|
pi.registerTool({
|
|
34
197
|
name: "auto_model_route",
|
|
35
198
|
label: "PiMarg Model Router",
|
package/bin/pi-marg.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
export const VERSION = JSON.parse(
|
|
8
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
9
|
+
).version;
|
|
10
|
+
|
|
11
|
+
const HELP = `PiMarg ${VERSION}
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
pi-marg start
|
|
15
|
+
pi-marg "<work prompt>"
|
|
16
|
+
pi-marg start "<work prompt>"
|
|
17
|
+
|
|
18
|
+
Starts Oh My Pi and invokes PiMarg's interactive workflow.`;
|
|
19
|
+
|
|
20
|
+
export function parsePiMargArgs(args) {
|
|
21
|
+
if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
|
|
22
|
+
return { action: "help", prompt: "" };
|
|
23
|
+
}
|
|
24
|
+
if (args.length === 1 && (args[0] === "--version" || args[0] === "-v")) {
|
|
25
|
+
return { action: "version", prompt: "" };
|
|
26
|
+
}
|
|
27
|
+
const promptParts = args[0]?.toLowerCase() === "start" ? args.slice(1) : args;
|
|
28
|
+
return { action: "launch", prompt: promptParts.join(" ").trim() };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildOmpInvocation(parsed) {
|
|
32
|
+
if (parsed.action !== "launch") return [];
|
|
33
|
+
return [parsed.prompt ? `/pi-marg ${parsed.prompt}` : "/pi-marg"];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function runPiMarg(args = process.argv.slice(2)) {
|
|
37
|
+
const parsed = parsePiMargArgs(args);
|
|
38
|
+
if (parsed.action === "help") {
|
|
39
|
+
console.log(HELP);
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
if (parsed.action === "version") {
|
|
43
|
+
console.log(VERSION);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const executable = process.env.PIMARG_OMP_BIN || "omp";
|
|
48
|
+
const result = spawnSync(executable, buildOmpInvocation(parsed), {
|
|
49
|
+
cwd: process.cwd(),
|
|
50
|
+
env: process.env,
|
|
51
|
+
stdio: "inherit",
|
|
52
|
+
shell: false,
|
|
53
|
+
});
|
|
54
|
+
if (result.error) {
|
|
55
|
+
if (result.error.code === "ENOENT") {
|
|
56
|
+
console.error("PiMarg could not find the `omp` command. Install Oh My Pi and make sure `omp` is on PATH.");
|
|
57
|
+
return 127;
|
|
58
|
+
}
|
|
59
|
+
console.error(`PiMarg could not start OMP: ${result.error.message}`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
return result.status ?? (result.signal ? 1 : 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isMainModule() {
|
|
66
|
+
if (!process.argv[1]) return false;
|
|
67
|
+
try {
|
|
68
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isMainModule()) {
|
|
75
|
+
process.exitCode = runPiMarg();
|
|
76
|
+
}
|
package/core/model-routing.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type AutoStage = "A" | "B" | "C" | "D";
|
|
2
|
-
export type CandidateTier = "frontier" | "execution";
|
|
2
|
+
export type CandidateTier = "frontier" | "execution" | "general";
|
|
3
3
|
export type RuntimeName = "OMP" | "Pi";
|
|
4
4
|
|
|
5
5
|
export interface ModelLike {
|
|
@@ -25,6 +25,7 @@ export interface ModelCandidate {
|
|
|
25
25
|
| "included-cursor"
|
|
26
26
|
| "devin-swe"
|
|
27
27
|
| "included-devin"
|
|
28
|
+
| "runtime-available"
|
|
28
29
|
| "devin-external";
|
|
29
30
|
access: "runtime-model" | "external-agent";
|
|
30
31
|
billingNote?: string;
|
|
@@ -130,7 +131,7 @@ export function normalizeModelIdentity(value: string): string {
|
|
|
130
131
|
return normalized;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
export function classifyModel(model: ModelLike): ModelCandidate
|
|
134
|
+
export function classifyModel(model: ModelLike): ModelCandidate {
|
|
134
135
|
const selector = `${model.provider}/${model.id}`;
|
|
135
136
|
const haystack = `${model.provider} ${model.id} ${model.name ?? ""}`;
|
|
136
137
|
|
|
@@ -156,17 +157,20 @@ export function classifyModel(model: ModelLike): ModelCandidate | undefined {
|
|
|
156
157
|
billingNote: "The runtime reports zero metered token cost; Devin plan quotas or pool limits may still apply.",
|
|
157
158
|
};
|
|
158
159
|
}
|
|
159
|
-
return
|
|
160
|
+
return candidate(model, selector, "runtime-available", "general");
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
export function buildModelCatalog(models: ModelLike[], hasExternalDevin: boolean) {
|
|
163
|
-
const candidates = models
|
|
164
|
-
|
|
165
|
-
.
|
|
166
|
-
|
|
164
|
+
const candidates = [...new Map(models.map((model) => {
|
|
165
|
+
const item = classifyModel(model);
|
|
166
|
+
return [item.selector, item] as const;
|
|
167
|
+
})).values()];
|
|
168
|
+
|
|
169
|
+
const frontier = [...candidates].sort((left, right) => compareForStage(left, right, "frontier"));
|
|
170
|
+
const execution = [...candidates].sort((left, right) => compareForStage(left, right, "execution"));
|
|
167
171
|
|
|
168
172
|
if (hasExternalDevin) {
|
|
169
|
-
|
|
173
|
+
execution.push({
|
|
170
174
|
selector: "devin/external-session",
|
|
171
175
|
identity: "devin-external-session",
|
|
172
176
|
label: "Devin external session",
|
|
@@ -176,11 +180,15 @@ export function buildModelCatalog(models: ModelLike[], hasExternalDevin: boolean
|
|
|
176
180
|
access: "external-agent",
|
|
177
181
|
billingNote: "Uses Devin quota rather than a runtime model slot; session creation requires post-web approval.",
|
|
178
182
|
});
|
|
183
|
+
execution.sort((left, right) => compareForStage(left, right, "execution"));
|
|
179
184
|
}
|
|
180
185
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
return {
|
|
187
|
+
A: frontier,
|
|
188
|
+
B: execution,
|
|
189
|
+
C: frontier,
|
|
190
|
+
D: execution,
|
|
191
|
+
} satisfies Record<AutoStage, ModelCandidate[]>;
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
export function validateDistinctSelection(
|
|
@@ -328,6 +336,16 @@ function compareCandidates(left: ModelCandidate, right: ModelCandidate): number
|
|
|
328
336
|
return left.selector.localeCompare(right.selector);
|
|
329
337
|
}
|
|
330
338
|
|
|
339
|
+
function compareForStage(
|
|
340
|
+
left: ModelCandidate,
|
|
341
|
+
right: ModelCandidate,
|
|
342
|
+
preferredTier: Exclude<CandidateTier, "general">,
|
|
343
|
+
): number {
|
|
344
|
+
const leftRank = left.tier === preferredTier ? 0 : left.tier === "general" ? 1 : 2;
|
|
345
|
+
const rightRank = right.tier === preferredTier ? 0 : right.tier === "general" ? 1 : 2;
|
|
346
|
+
return leftRank - rightRank || compareCandidates(left, right);
|
|
347
|
+
}
|
|
348
|
+
|
|
331
349
|
function toolResult(text: string, details: unknown, isError = false) {
|
|
332
350
|
return {
|
|
333
351
|
content: [{ type: "text" as const, text }],
|
|
@@ -338,11 +356,15 @@ function toolResult(text: string, details: unknown, isError = false) {
|
|
|
338
356
|
|
|
339
357
|
function catalogNotes(runtimeName: RuntimeName, catalog: Record<AutoStage, ModelCandidate[]>): string[] {
|
|
340
358
|
const notes = [
|
|
341
|
-
`The catalog
|
|
342
|
-
"A and C
|
|
359
|
+
`The catalog shows every model in ${runtimeName}'s authenticated, enabled registry at call time; selectors are never guessed or pinned.`,
|
|
360
|
+
"A and C rank frontier fits first. B and D rank execution fits first. Every live model remains selectable for every stage.",
|
|
343
361
|
"Zero-metered describes the runtime's reported token price; it does not promise unlimited subscription usage.",
|
|
344
362
|
];
|
|
345
|
-
if (catalog.A.
|
|
346
|
-
|
|
363
|
+
if (!catalog.A.some((candidate) => candidate.tier === "frontier")) {
|
|
364
|
+
notes.push("No preferred frontier matches are authenticated; choose another available model or connect a provider.");
|
|
365
|
+
}
|
|
366
|
+
if (!catalog.B.some((candidate) => candidate.tier === "execution")) {
|
|
367
|
+
notes.push("No preferred execution matches are authenticated and no external Devin tool was detected; choose another available model.");
|
|
368
|
+
}
|
|
347
369
|
return notes;
|
|
348
370
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sangyaahub/pi-marg",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A governed, model-routed development path for Pi Agent and Oh My Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
"multi-agent",
|
|
25
25
|
"sangyaa"
|
|
26
26
|
],
|
|
27
|
+
"bin": {
|
|
28
|
+
"pi-marg": "bin/pi-marg.js"
|
|
29
|
+
},
|
|
27
30
|
"files": [
|
|
28
31
|
".mcp.json",
|
|
29
32
|
".omp-plugin",
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
"adapters",
|
|
41
44
|
"agents",
|
|
42
45
|
"assets",
|
|
46
|
+
"bin",
|
|
43
47
|
"commands",
|
|
44
48
|
"core",
|
|
45
49
|
"rules",
|
package/scripts/install.sh
CHANGED
|
@@ -71,4 +71,4 @@ if [[ "${runtime}" == "omp" || "${runtime}" == "both" ]]; then
|
|
|
71
71
|
omp plugin install --scope user pi-marg@pi-marg-marketplace
|
|
72
72
|
fi
|
|
73
73
|
|
|
74
|
-
echo "PiMarg v1.0.
|
|
74
|
+
echo "PiMarg v1.0.1 installed for ${runtime}. Start a fresh runtime session, then run /pi-marg."
|
package/scripts/validate.sh
CHANGED
|
@@ -17,10 +17,13 @@ bun -e '
|
|
|
17
17
|
const packageJson = await Bun.file("package.json").json();
|
|
18
18
|
const marketplace = await Bun.file(".omp-plugin/marketplace.json").json();
|
|
19
19
|
if (packageJson.name !== "@sangyaahub/pi-marg") throw new Error("Unexpected package name");
|
|
20
|
-
if (packageJson.version !== "1.0.
|
|
20
|
+
if (packageJson.version !== "1.0.1") throw new Error("package.json must be v1.0.1");
|
|
21
21
|
if (packageJson.license !== "MIT") throw new Error("package.json must declare MIT");
|
|
22
22
|
if (packageJson.author !== "Sangyaa") throw new Error("Unexpected package author");
|
|
23
23
|
if (!packageJson.keywords?.includes("pi-package")) throw new Error("Pi catalog keyword is missing");
|
|
24
|
+
if (packageJson.bin?.["pi-marg"] !== "bin/pi-marg.js") throw new Error("PiMarg CLI bin mapping is missing");
|
|
25
|
+
if (!packageJson.files?.includes("bin")) throw new Error("PiMarg CLI is excluded from npm files");
|
|
26
|
+
if (!existsSync("bin/pi-marg.js")) throw new Error("PiMarg CLI entry point is missing");
|
|
24
27
|
if (marketplace.name !== "pi-marg-marketplace") throw new Error("Unexpected marketplace name");
|
|
25
28
|
if (marketplace.metadata?.version !== packageJson.version) throw new Error("Marketplace version mismatch");
|
|
26
29
|
if (marketplace.plugins?.[0]?.version !== packageJson.version) throw new Error("Plugin version mismatch");
|