allye-pi 2.5.1 → 2.6.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 Allye Assistant
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 CHANGED
@@ -83,10 +83,56 @@ Then add the `allye` MCP server as described in [install-pi.md](docs/install-pi.
83
83
  export ALLYE_PAT=pat_... # Allye → Settings → API
84
84
  ./install.sh list --scope team # approved skills you can see
85
85
  ./install.sh install claude team-standards # claude | codex | opencode | pi | omp
86
+ ./install.sh install claude team-standards --reinstall # replace an edited copy (kept as a backup)
86
87
  ./install.sh status # offline integrity check
87
88
  ```
88
89
 
89
- Each install downloads the skill's approved release, verifies every file and the canonical hash, opens a distribution for this machine, verifies the API-signed execution token, publishes the files into the harness's user skills directory with a provenance sidecar, and records the result on the API. Local edits and directories it did not install are never overwritten. The API decides which runtimes may receive skills and who may distribute them; the installer reports its decision rather than working around it. Use `--team <id>` (or `ALLYE_TEAM_ID`) when you belong to several teams. Requires `bash`, `curl`, `jq` and Node.js 18+.
90
+ Each install downloads the skill's approved release, verifies every file and the canonical hash, opens a distribution for this machine, verifies the API-signed execution token, publishes the files into the harness's user skills directory with a provenance sidecar, and records the result on the API. The API decides which runtimes may receive skills; the installer reports its decision rather than working around it. Requires `bash`, `curl`, `jq` and Node.js 18+. The installer speaks compatibility contract `1.1.0`.
91
+
92
+ Until the allye-api PROD release that supports contract `1.1.0`, the installer works only against HML (`ALLYE_API_URL` pointing at the HML API). Against PROD it fails with `409 RUNTIME_INCOMPATIBLE` or an execution-context error about a missing signing key, and changes nothing.
93
+
94
+ ### Who can install
95
+
96
+ Any member with view access to a skill can install it: any tenant member for an organization skill, members of the skill's team for a team skill, the author for a personal skill. Each person's installs are recorded separately. The installer needs no team for the install itself; use `--team <id>` (or `ALLYE_TEAM_ID`) only when a skill slug matches skills in several teams, so the slug can be resolved (or pass the skill id instead). If view access is lost while an install is in progress, the API answers `409`, the installer rolls the swap back and tells you to get access and rerun.
97
+
98
+ ### Where skills go
99
+
100
+ | Runtime | Skills directory |
101
+ |---|---|
102
+ | `claude` | `~/.claude/skills` |
103
+ | `codex` | `$CODEX_HOME/skills` when `CODEX_HOME` is set, otherwise `~/.codex/skills` |
104
+ | `opencode` | `${XDG_CONFIG_HOME:-~/.config}/opencode/allye-skills` |
105
+ | `pi` | `$PI_CODING_AGENT_DIR/skills` when set, otherwise `~/.pi/agent/skills` |
106
+ | `omp` | `~/.omp/agent/skills` |
107
+
108
+ `CODEX_HOME` and `PI_CODING_AGENT_DIR` must be absolute paths; only the empty string means unset, a trailing `/` is stripped (`/` itself stays `/`), and a relative value stops that runtime's install before any request (other runtimes are unaffected). An empty or relative `XDG_CONFIG_HOME` is treated as unset. OpenCode does not discover this directory by itself: the `allye-opencode` plugin adds it to `skills.paths` when it is a real directory (not a symlink) owned by you and not group- or world-writable, so restart OpenCode after the first install creates it. The installer refuses to install into an `allye-skills` directory that is a symlink or group/world-writable (fix with `chmod go-w <dir>`); it does not check ownership, so the plugin additionally ignores a directory that is not owned by you. OMP presents itself to the API as runtime `pi`; Pi and OMP skills are installed as plain directories, with no package receipt.
109
+
110
+ Changing `CODEX_HOME` or `PI_CODING_AGENT_DIR` changes the install target, and the installer does not migrate skills left in the old directory (for example `~/.codex/skills`). Those copies stay where they are; a copy found under the new directory is handled as in the next section.
111
+
112
+ ### Edited, deleted and moved copies
113
+
114
+ | State of `<skills dir>/<name>` | What the installer does |
115
+ |---|---|
116
+ | Missing (first install or deleted) | Installs it again; the API records a reinstall when it already had an install for this machine |
117
+ | Unchanged copy of the same release | Reports "already installed"; nothing is sent |
118
+ | Unchanged copy of an older release | Updates it |
119
+ | Edited by you | Refuses and changes nothing; rerun with `--reinstall` |
120
+ | Not installed by the installer, or installed from another skill | Never touched, with or without `--reinstall` |
121
+ | Contains a symlink or special file | Refuses and names the entry; with or without `--reinstall` |
122
+
123
+ `--reinstall` replaces an edited copy with the approved release and keeps your edited folder at `~/.allye/backups/<adapter-id>/<name>.allye.backup.<UTC ts>` (`.<pid>` is appended if that name exists), outside every skills directory so no harness loads it. Backup directories are created with mode 0700, and the installer refuses to keep backups in a `~/.allye`, `backups` or `backups/<id>` that is a symlink, owned by someone else or group/world-writable (it changes nothing; fix the directory and rerun). The installer prints the path; backups are never overwritten or deleted, and `./install.sh status` does not list them. If anything fails before the install is recorded, your edited folder is put back.
124
+
125
+ A copy records the machine and skills path it was installed for (as a digest; the hostname and path never leave the machine). After a hostname change, a different home or a changed `CODEX_HOME`, `PI_CODING_AGENT_DIR` or `XDG_CONFIG_HOME`, existing copies are "other-target": an unchanged one is reinstalled automatically here with no backup, while an edited one needs `--reinstall`.
126
+
127
+ If an earlier run could not finish moving your edited folder (a failed restore, a failed backup move, or a killed run), it leaves `<skills dir>/.allye-backup.<name>.<UTC ts>.<pid>`. The installer then refuses to install `<name>`, prints each such path and sends nothing. Move the folder into `~/.allye/backups/<adapter-id>/` to keep it, or delete it, then rerun.
128
+
129
+ ### Errors
130
+
131
+ - `409 DISTRIBUTION_NOT_AUTHORIZED`: you need view access to the skill (team membership or authorship); get access and rerun.
132
+ - `410 MARKETPLACE_RETIRED`: the skill is a retired, read-only marketplace skill; nothing was installed. Use an organization or team skill.
133
+ - `422 TEAM_SELECTION_REQUIRED`: the slug matches skills in several teams; rerun with `--team <team>` or use the skill id.
134
+ - `409 RUNTIME_INCOMPATIBLE`: the API has no `1.1.0` profile, which is the case on PROD before the allye-api release. Use HML or wait for that release; there is nothing to reset and no admin to ask.
135
+ - `400` mentioning a request field such as `idempotencyKey` or `target`: the API and the installer disagree on the request shape. Upgrade the installer; there is nothing to reset and no admin to ask.
90
136
 
91
137
  ## Repository layout
92
138
 
@@ -125,4 +171,4 @@ Releases are automated by semantic-release on every push to `main` (Conventional
125
171
 
126
172
  ## License
127
173
 
128
- MIT
174
+ MIT, see [LICENSE](LICENSE).
@@ -22,10 +22,18 @@ OMP has a native MCP client (no `pi-mcp-adapter`). Add the server once, at user
22
22
 
23
23
  OMP also loads servers from a project `.mcp.json`. Do not add tokens or OAuth client metadata: OMP discovers the OAuth endpoints and opens the browser on first use.
24
24
 
25
- Because OMP's MCP client is not the in-process `pi-mcp-adapter` bridge, the extension does not preload Allye context in OMP. It injects only the git remote and any unverified `.allye/project.json` claim; the bootstrap tells the agent to call `projects.project_resolve` itself. The team follows the project, so no team selection is needed.
25
+ Because OMP's MCP client is not the in-process `pi-mcp-adapter` bridge (as in OMP today), the extension does not resolve the project. It injects only the git remote and any unverified `.allye/project.json` claim; the bootstrap tells the agent to call `projects.project_resolve` itself. The team follows the project, so no team selection is needed.
26
26
 
27
27
  ## Confirm
28
28
 
29
29
  Tell the user:
30
30
 
31
31
  > Allye is installed for OMP: the `allye-pi` extension (bootstrap and Bridge) and the `allye` MCP server. Start a new OMP session and run `/bridge survey` (or another mode).
32
+
33
+ ## Organization skills
34
+
35
+ `./install.sh install omp <skill>` (see the [README](../README.md#your-organizations-skills)) installs into `~/.omp/agent/skills`. OMP presents itself to the API as runtime `pi`, and the skill is installed as a plain directory, with no package receipt.
36
+
37
+ - Edited copies: `./install.sh install omp <skill> --reinstall` replaces your edited copy and keeps it at `~/.allye/backups/omp/<skill>.allye.backup.<UTC ts>`. A copy of the same release on the same target is already installed (a no-op); older-release copies are updated, and copies recorded for another target (after a hostname or skills-path change) are reinstalled when unchanged, while an edited one needs `--reinstall`. Foreign or unmanaged folders are never touched.
38
+ - Leftovers: if the installer reports a `.allye-backup.<skill>.*` entry in the skills directory, an earlier run kept your edited folder there. Move it into `~/.allye/backups/omp/` or delete it, then rerun.
39
+ - Any member with view access to a skill can install it. If the installer is too old for the API, upgrade the installer; there is nothing to ask an admin to reset. Until the allye-api PROD release the installer works only against HML. See the [README](../README.md#your-organizations-skills).
@@ -4,7 +4,7 @@ Allye for Pi is the native package `allye-pi`. It:
4
4
 
5
5
  - exposes the Bridge skill (`/bridge <mode>`) from the package's `skills/` directory;
6
6
  - adds the Allye bootstrap (`bootstrap/allye.md`) to the system prompt;
7
- - preloads Allye context (`initialize`, `intelligence`) when an in-process MCP bridge is available, and identifies the repository's project: it resolves the git remote with `projects.project_resolve` and verifies any `.allye/project.json` claim against it, injecting "this repo = project X / app Y (team Z)", the ambiguous candidates, or the disagreement to ask the user about.
7
+ - identifies the repository's project (it loads no startup or per-prompt memory context): when an in-process MCP bridge is available it resolves the git remote with `projects.project_resolve` and verifies any `.allye/project.json` claim against it, injecting "this repo = project X / app Y (team Z)", the ambiguous candidates, or the disagreement to ask the user about.
8
8
 
9
9
  It never edits Pi's MCP configuration or reads credentials.
10
10
 
@@ -32,11 +32,11 @@ Do not pass headers, bearer tokens or OAuth client ids: Pi discovers the OAuth e
32
32
 
33
33
  On older Pi versions without `pi mcp`, install `pi-mcp-adapter` (`pi install npm:pi-mcp-adapter`), define one server named `allye` (`{"type": "http", "url": "https://mcp.allye.app/mcp"}` under `mcpServers`) in the effective source shown by `/mcp` — for example `~/.pi/agent/mcp.json` or `~/.config/mcp/mcp.json` — then run `/mcp-auth allye` and `/mcp reconnect allye`.
34
34
 
35
- The extension preloads Allye context only when `pi-mcp-adapter` exposes its in-process bridge. Otherwise it injects only the git remote and any unverified link claim, and the bootstrap tells the agent to call `projects.project_resolve` itself.
35
+ The extension resolves the repository's project only when `pi-mcp-adapter` exposes its in-process bridge. Otherwise it injects only the git remote and any unverified link claim, and the bootstrap tells the agent to call `projects.project_resolve` itself.
36
36
 
37
37
  ## Teams
38
38
 
39
- The team follows the project: the extension never blocks work on team selection. A default team is needed only to create projects; `/allye-team <name|prefix|id>` sets it through the `team` tool (`team_set_default`). Set `ALLYE_PI_NATIVE_BOOTSTRAP=0` to skip the context preload (the bootstrap text and the Bridge skill stay available), or `ALLYE_PI_MCP=0` to disable MCP calls from the extension.
39
+ The team follows the project: the extension never blocks work on team selection. A default team is needed only to create projects; `/allye-team <name|prefix|id>` sets it through the `team` tool (`team_set_default`). Set `ALLYE_PI_NATIVE_BOOTSTRAP=0` to skip the repository block (the bootstrap text and the Bridge skill stay available), or `ALLYE_PI_MCP=0` to disable MCP calls from the extension.
40
40
 
41
41
  ## Local development
42
42
 
@@ -48,3 +48,11 @@ npm run typecheck
48
48
  npm run test:pi
49
49
  pi -e ./packages/allye-pi/src/index.ts -p "Which Allye tools and Bridge modes are available?"
50
50
  ```
51
+
52
+ ## Organization skills
53
+
54
+ `./install.sh install pi <skill>` (see the [README](../README.md#your-organizations-skills)) installs into `$PI_CODING_AGENT_DIR/skills` when `PI_CODING_AGENT_DIR` is set, otherwise `~/.pi/agent/skills`. The value must be an absolute path; only the empty string means unset, a trailing `/` is stripped, and a relative value stops the Pi install before any request. Changing it changes the install target; copies left in the old directory are not migrated. The API sees runtime `pi` and the skill is installed as a plain directory, with no package receipt.
55
+
56
+ - Edited copies: `./install.sh install pi <skill> --reinstall` replaces your edited copy and keeps it at `~/.allye/backups/pi/<skill>.allye.backup.<UTC ts>`. A copy of the same release on the same target is already installed (a no-op); older-release copies are updated, and copies recorded for another target (after a hostname or skills-path change) are reinstalled when unchanged, while an edited one needs `--reinstall`. Foreign or unmanaged folders are never touched.
57
+ - Leftovers: if the installer reports a `.allye-backup.<skill>.*` entry in the skills directory, an earlier run kept your edited folder there. Move it into `~/.allye/backups/pi/` or delete it, then rerun.
58
+ - Any member with view access to a skill can install it. If the installer is too old for the API, upgrade the installer; there is nothing to ask an admin to reset. Until the allye-api PROD release the installer works only against HML. See the [README](../README.md#your-organizations-skills).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allye-pi",
3
- "version": "2.5.1",
3
+ "version": "2.6.0",
4
4
  "description": "Allye extension for Pi and OMP: the Allye bootstrap, the Bridge skill and the repository-to-project link",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -43,11 +43,12 @@
43
43
  "scripts": {
44
44
  "typecheck": "tsc --noEmit -p packages/allye-pi/tsconfig.json",
45
45
  "test:pi": "node --experimental-strip-types --test packages/allye-pi/src/index.test.ts",
46
+ "test:opencode": "node --experimental-strip-types --test packages/allye-opencode/src/skills-paths.test.ts",
46
47
  "test:installer": "bash install/test-validation.sh && bash install/test-installer.sh",
47
48
  "test:bootstrap": "bash test/test-bootstrap.sh && bash hooks/test-session-start.sh",
48
49
  "test:agents": "node test/test-agents.mjs",
49
- "test:skills": "node test/test-skills-text.mjs && node test/test-ci-proof.mjs",
50
- "test": "npm run test:bootstrap && npm run test:agents && npm run test:skills && npm run test:installer && npm run test:pi"
50
+ "test:skills": "node test/test-skills-text.mjs && node test/test-ci-proof.mjs && node test/test-ci-workflows.mjs",
51
+ "test": "npm run test:bootstrap && npm run test:agents && npm run test:skills && npm run test:installer && npm run test:pi && npm run test:opencode"
51
52
  },
52
53
  "repository": {
53
54
  "type": "git",
@@ -9,14 +9,14 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
9
  *
10
10
  * - Exposes the repository's skills/ directory (Bridge) through resources_discover.
11
11
  * - Adds the shared bootstrap (bootstrap/allye.md) to the system prompt.
12
- * - Preloads Allye context through the configured MCP bridge when one is
13
- * available, and identifies the repo's project with `projects.project_resolve`,
14
- * verifying any `.allye/project.json` claim against the git remote.
12
+ * - Identifies the repo's project with `projects.project_resolve` (through the
13
+ * configured MCP bridge when one is available), verifying any
14
+ * `.allye/project.json` claim against the git remote. It loads no startup or
15
+ * per-prompt memory context.
15
16
  * The team follows the project: nothing is gated on team selection.
16
17
  */
17
18
 
18
19
  const MCP_SERVER = "allye";
19
- const MAX_CONTEXT_CHARS = 12_000;
20
20
  const LINK_FILE = ".allye/project.json";
21
21
  const PROJECT_KEY = /^[A-Z][A-Z0-9]{1,9}$/;
22
22
  const APP_NAME = /^[A-Za-z0-9._-]{1,120}$/;
@@ -25,11 +25,6 @@ const MAX_CANDIDATES = 10;
25
25
  const MAX_REPO_CHARS = 4_000;
26
26
 
27
27
  type McpResult = { content?: Array<{ type?: string; text?: string }> };
28
- export type StartupContext = {
29
- text: string;
30
- repo: string;
31
- allyeUnavailable: boolean;
32
- };
33
28
  type ActiveMcpToolCaller = (
34
29
  serverName: string,
35
30
  toolName: string,
@@ -83,7 +78,7 @@ async function callAllye(toolName: string, args: Record<string, unknown>): Promi
83
78
  // pi-mcp-adapter may expose an in-process bridge for cooperating extensions.
84
79
  // Without it, the agent still reaches Allye through its own MCP tools.
85
80
  const bridge = (globalThis as McpBridgeRuntime).__piMcpAdapterActiveToolCaller;
86
- if (!bridge) throw new Error("no in-process MCP bridge is available to preload Allye context");
81
+ if (!bridge) throw new Error("no in-process MCP bridge is available");
87
82
  return resultText(await bridge(MCP_SERVER, toolName, args));
88
83
  }
89
84
 
@@ -300,82 +295,27 @@ async function buildRepoContext(cwd: string, online: boolean): Promise<string> {
300
295
  }
301
296
  }
302
297
 
303
- export function unavailableStartupContext(reason: string, repo = ""): StartupContext {
304
- return {
305
- text: `## Allye context not preloaded\n${reason}\nUse the Allye MCP tools directly when the task needs them: call \`initialize\` first.`,
306
- repo,
307
- allyeUnavailable: true,
308
- };
309
- }
310
-
311
- async function loadStartupContext(cwd: string): Promise<StartupContext> {
312
- if (!mcpBridgeAvailable()) {
313
- return unavailableStartupContext("No in-process MCP bridge is available in this session.", await loadRepoContext(cwd, false));
314
- }
315
- try {
316
- const initialization = await callAllye("initialize", { action: "init", include_user_docs: true });
317
- const preferences = await callAllye("intelligence", { action: "memory_preferences" });
318
- return {
319
- text: [initialization, preferences].filter(Boolean).map((section) => limitText(section, MAX_CONTEXT_CHARS)).join("\n\n"),
320
- repo: await loadRepoContext(cwd, true),
321
- allyeUnavailable: false,
322
- };
323
- } catch (error) {
324
- return unavailableStartupContext(
325
- `Allye context could not be preloaded: ${error instanceof Error ? error.message : String(error)}`,
326
- await loadRepoContext(cwd, false),
327
- );
328
- }
329
- }
330
-
331
- async function loadPromptContext(prompt: string): Promise<string> {
332
- try {
333
- return limitText(await callAllye("intelligence", {
334
- action: "memory_search",
335
- query: prompt,
336
- limit: 5,
337
- return_content: true,
338
- }), MAX_CONTEXT_CHARS);
339
- } catch {
340
- return "";
341
- }
342
- }
343
-
344
- export function buildSystemSections(bootstrap: string, startup: StartupContext, firstPrompt: boolean, nativeBootstrap = true): string[] {
298
+ export function buildSystemSections(bootstrap: string, repo: string, nativeBootstrap = true): string[] {
345
299
  const sections: string[] = [];
346
300
  if (bootstrap) sections.push(`<allye-bootstrap>\n${bootstrap.trim()}\n</allye-bootstrap>`);
347
- if (firstPrompt && nativeBootstrap && startup.text) sections.push(`<allye-startup-context>\n${startup.text}\n</allye-startup-context>`);
348
- if (nativeBootstrap && startup.repo) sections.push(`<allye-repo>\n${startup.repo}\n</allye-repo>`);
301
+ if (nativeBootstrap && repo) sections.push(`<allye-repo>\n${repo}\n</allye-repo>`);
349
302
  return sections;
350
303
  }
351
304
 
352
305
  export default function allyePiExtension(pi: ExtensionAPI): void {
353
306
  const nativeBootstrap = process.env.ALLYE_PI_NATIVE_BOOTSTRAP !== "0";
354
- let startupContext = unavailableStartupContext("Allye context has not been loaded yet.");
355
307
  let bootstrap = "";
356
- let firstPrompt = true;
308
+ let repo = "";
357
309
 
358
310
  pi.on("resources_discover", () => ({ skillPaths: [skillsPath()] }));
359
311
 
360
312
  pi.on("session_start", async (_event, ctx) => {
361
- firstPrompt = true;
362
313
  bootstrap = loadBootstrap();
363
- if (nativeBootstrap) {
364
- if (ctx.hasUI) ctx.ui.setStatus("allye", "loading Allye context…");
365
- startupContext = await loadStartupContext(ctx.cwd ?? process.cwd());
366
- if (ctx.hasUI) ctx.ui.setStatus("allye", startupContext.allyeUnavailable ? "Allye: context not preloaded" : "Allye ready");
367
- }
314
+ if (nativeBootstrap) repo = await loadRepoContext(ctx.cwd ?? process.cwd(), mcpBridgeAvailable());
368
315
  });
369
316
 
370
- pi.on("before_agent_start", async (event) => {
371
- const sections = buildSystemSections(bootstrap, startupContext, firstPrompt, nativeBootstrap);
372
- if (firstPrompt) {
373
- firstPrompt = false;
374
- if (nativeBootstrap && !startupContext.allyeUnavailable) {
375
- const relevant = await loadPromptContext(event.prompt);
376
- if (relevant) sections.push(`<allye-relevant-memory>\n${relevant}\n</allye-relevant-memory>`);
377
- }
378
- }
317
+ pi.on("before_agent_start", (event) => {
318
+ const sections = buildSystemSections(bootstrap, repo, nativeBootstrap);
379
319
  return sections.length ? { systemPrompt: `${event.systemPrompt}\n\n${sections.join("\n\n")}` } : undefined;
380
320
  });
381
321