hlassiege/panopliepublic Fork
⑂ b0cc64e
Commits
⬇ Clone ▾
git clone https://git.rickub.com/hlassiege/panoplie.git
git clone ssh://git@rickub.com/hlassiege/panoplie.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

feat: rename the project from skilly to panoplie

Existing installs keep working: ~/.skilly, skilly.json, skilly.lock, the catalog .skilly/index.json
and the skilly AGENTS.md markers are read and migrated to the new names (see packages/core/src/legacy.ts).
hlassiege committed 2026-11-07T18:53:14+01:00 Browse files
b0cc64e parent: 6fcf0c1
modified .gitignore +3 -3
@@ -11,6 +11,6 @@ dist/
1111 docs/
1212 design/
1313
14-# Local skilly config
15-skilly.json
16-skilly.lock
14+# Local panoplie config
15+panoplie.json
16+panoplie.lock
@@ -11,6 +11,6 @@ dist/
11 docs/11 docs/
12 design/12 design/
13 13
14-# Local skilly config14+# Local panoplie config
15-skilly.json15+panoplie.json
16-skilly.lock16+panoplie.lock
modified .rickub/workflows/deploy-site.yml +1 -1
@@ -1,4 +1,4 @@
1-# Builds the showcase site (site/) and uploads it to Bunny CDN, served as https://skilly.eventuallycoding.com.
1+# Builds the showcase site (site/) and uploads it to Bunny CDN, served as https://panoplie.eventuallycoding.com.
22 # Only runs when the site, the screenshots or the favicon it uses, or this file change.
33 #
44 # The build has no dependencies (plain node), so there is no install step and ubuntu-latest is enough.
@@ -1,4 +1,4 @@
1-# Builds the showcase site (site/) and uploads it to Bunny CDN, served as https://skilly.eventuallycoding.com.1+# Builds the showcase site (site/) and uploads it to Bunny CDN, served as https://panoplie.eventuallycoding.com.
2 # Only runs when the site, the screenshots or the favicon it uses, or this file change.2 # Only runs when the site, the screenshots or the favicon it uses, or this file change.
3 #3 #
4 # The build has no dependencies (plain node), so there is no install step and ubuntu-latest is enough.4 # The build has no dependencies (plain node), so there is no install step and ubuntu-latest is enough.
modified README.md +35 -35
@@ -1,8 +1,8 @@
11 <p align="center">
2- <img src="packages/ui/public/favicon.svg" alt="skilly" width="96" height="96">
2+ <img src="packages/ui/public/favicon.svg" alt="panoplie" width="96" height="96">
33 </p>
44
5-<h1 align="center">skilly</h1>
5+<h1 align="center">panoplie</h1>
66
77 <p align="center">
88 One place to manage the skills, rules, MCP servers, hooks and subagents of your coding agents,<br>
@@ -10,12 +10,12 @@
1010 </p>
1111
1212 <p align="center">
13- <img src="media/screenshots/project.png" alt="A project in skilly: presets, items and their sync state" width="900">
13+ <img src="media/screenshots/project.png" alt="A project in panoplie: presets, items and their sync state" width="900">
1414 </p>
1515
1616 ---
1717
18-## Why skilly
18+## Why panoplie
1919
2020 Coding agents are configured through a growing pile of files: `SKILL.md` folders, rules, `.mcp.json`, `settings.json`, `AGENTS.md`, `config.toml`… Once you use them on more than a couple of projects, three problems show up:
2121
@@ -23,7 +23,7 @@ Coding agents are configured through a growing pile of files: `SKILL.md` folders
2323 - **Global pollution.** Putting everything in `~/.claude` or `~/.codex` makes every item load in every session of every project. It costs context and triggers skills where they make no sense.
2424 - **No overview.** Nothing tells you what exists, where it is used, and which copy is the right one.
2525
26-skilly fixes this with a **catalog** (a git repository holding your items) and a local UI that **writes the right files into each project**, for each agent, and keeps them in sync.
26+panoplie fixes this with a **catalog** (a git repository holding your items) and a local UI that **writes the right files into each project**, for each agent, and keeps them in sync.
2727
2828 ## How it works
2929
@@ -38,12 +38,12 @@ skilly fixes this with a **catalog** (a git repository holding your items) and a
3838 acme-shop/ billing-api/ blog/
3939 .claude/skills/… .agents/skills/… .claude/skills/…
4040 .agents/skills/… AGENTS.md (block) .claude/rules/…
41- .mcp.json .codex/config.toml skilly.json
42- skilly.json skilly.json
41+ .mcp.json .codex/config.toml panoplie.json
42+ panoplie.json panoplie.json
4343 ```
4444
4545 - **The catalog is the source of truth.** It is a plain git repository, hosted wherever you like (or nowhere). Sync it across your machines or share it with your team.
46-- **Enabling an item writes real files into the project**, where each agent expects them. The project then works without skilly, for you and for anyone who clones it. You commit those files with the project.
46+- **Enabling an item writes real files into the project**, where each agent expects them. The project then works without panoplie, for you and for anyone who clones it. You commit those files with the project.
4747 - **Nothing is written until you apply.** Every change goes through a review, and a file edited by hand is never overwritten without asking.
4848 - **Global stays minimal.** Keep in `~` only what you really use everywhere, and move the rest to the projects that need it.
4949
@@ -54,7 +54,7 @@ skilly fixes this with a **catalog** (a git repository holding your items) and a
5454 | **Item** | A skill, rule, MCP server, hook or subagent. |
5555 | **Catalog** | Your library of items and presets, stored in a git repository. |
5656 | **Preset** | A named bundle of items, for example *nuxt-saas* = Nuxt and Tailwind skills, TypeScript rules and the Nuxt docs MCP server. A preset can include other presets. |
57-| **Project** | A code folder you added to skilly. |
57+| **Project** | A code folder you added to panoplie. |
5858 | **Global** | Your user-level agent configuration (`~/.claude`, `~/.agents`, `~/.codex`). It loads in every project. |
5959 | **Automatic / on demand** | Skill mode. *Automatic*: the agent sees the skill and uses it when it thinks it is relevant. *On demand*: the skill is installed but hidden from the agent until you call it yourself (`/name` in Claude Code, `$name` in Codex). Ideal for large families of niche skills you want at hand without paying for them in context. |
6060
@@ -66,7 +66,7 @@ Pick presets, add or exclude single items, switch skills between automatic and o
6666
6767 ### Review before anything is written
6868
69-skilly computes a plan: files to add, update or remove, for each agent. You can undo any line before applying. When a file already exists or was edited by hand, you choose: **keep the project version**, **take the catalog version**, or **push the change back to the catalog**. You can compare both versions first.
69+panoplie computes a plan: files to add, update or remove, for each agent. You can undo any line before applying. When a file already exists or was edited by hand, you choose: **keep the project version**, **take the catalog version**, or **push the change back to the catalog**. You can compare both versions first.
7070
7171 <p align="center">
7272 <img src="media/screenshots/review.png" alt="Review changes before applying them, with conflict resolution" width="900">
@@ -74,7 +74,7 @@ skilly computes a plan: files to add, update or remove, for each agent. You can
7474
7575 ### Clean up Global
7676
77-skilly lists everything loaded from your home folder and spots the items that are only used in some projects. **Move to projects** copies them to the catalog, enables them where they are needed, then removes them from Global. Nothing is removed from your home folder without an explicit action.
77+panoplie lists everything loaded from your home folder and spots the items that are only used in some projects. **Move to projects** copies them to the catalog, enables them where they are needed, then removes them from Global. Nothing is removed from your home folder without an explicit action.
7878
7979 <p align="center">
8080 <img src="media/screenshots/global.png" alt="Global items loaded in every project, with tidy-up suggestions" width="900">
@@ -118,74 +118,74 @@ Commit, pull and push from the UI, against any remote: GitHub, GitLab, Codeberg,
118118 | Subagents | `.claude/agents/<name>.md` | `.codex/agents/<name>.toml` |
119119 | Hide a Global skill or MCP server | Yes | Not supported by Codex yet |
120120
121-skilly also writes two files at the root of each project, meant to be committed:
121+panoplie also writes two files at the root of each project, meant to be committed:
122122
123-- **`skilly.json`**: what the project uses (target agents, presets, added and excluded items, on-demand skills, hidden Global items).
124-- **`skilly.lock`**: what skilly installed, with content hashes, so it knows which files it owns and can detect manual edits.
123+- **`panoplie.json`**: what the project uses (target agents, presets, added and excluded items, on-demand skills, hidden Global items).
124+- **`panoplie.lock`**: what panoplie installed, with content hashes, so it knows which files it owns and can detect manual edits.
125125
126-Shared files (`.mcp.json`, `.claude/settings.json`, `.codex/config.toml`, `AGENTS.md`) are edited surgically: skilly only touches the keys or the block it owns and leaves the rest as is.
126+Shared files (`.mcp.json`, `.claude/settings.json`, `.codex/config.toml`, `AGENTS.md`) are edited surgically: panoplie only touches the keys or the block it owns and leaves the rest as is.
127127
128-skilly also warns you about agent-specific pitfalls: a Codex project that is not trusted (Codex then ignores its `.codex/` folder), an `AGENTS.md` close to the 32 KiB Codex limit, or rules that Claude Code would load twice.
128+panoplie also warns you about agent-specific pitfalls: a Codex project that is not trusted (Codex then ignores its `.codex/` folder), an `AGENTS.md` close to the 32 KiB Codex limit, or rules that Claude Code would load twice.
129129
130130 `CLAUDE.md` and `AGENTS.md` are shown read-only in each project, with their size. They are specific to each project and never go into the catalog.
131131
132132 ## Install and run
133133
134-> skilly is in early development and not published on npm yet. Until the first release, run it from source (see below).
134+> panoplie is in early development and not published on npm yet. Until the first release, run it from source (see below).
135135
136136 Requirements: **Node.js 22+**, **git**, and preferably **Microsoft Edge or Google Chrome** for the app window.
137137
138138 ```sh
139-npx @skilly/cli
139+npx @panoplie/cli
140140 ```
141141
142-skilly starts a local server and opens the UI in an app window (Edge or Chrome in app mode, your default browser otherwise). Closing the window stops skilly. Running the command again while skilly is open brings the existing window back.
142+panoplie starts a local server and opens the UI in an app window (Edge or Chrome in app mode, your default browser otherwise). Closing the window stops panoplie. Running the command again while panoplie is open brings the existing window back.
143143
144144 | Option | Effect |
145145 |---|---|
146146 | `--no-open` | Do not open a window, only print the URL. |
147147 | `--port <n>` | Use this port instead of the default one (47821, or a free port if taken). |
148148
149-The first launch walks you through the setup: create a new catalog or clone an existing one from git, check the detected agents, then add your projects (skilly can scan your usual code folders for you).
149+The first launch walks you through the setup: create a new catalog or clone an existing one from git, check the detected agents, then add your projects (panoplie can scan your usual code folders for you).
150150
151151 ### From source
152152
153153 ```sh
154-git clone <repository-url> skilly
155-cd skilly
154+git clone <repository-url> panoplie
155+cd panoplie
156156 pnpm install
157157 pnpm build # builds the UI
158-pnpm start # starts skilly and opens the window
158+pnpm start # starts panoplie and opens the window
159159 ```
160160
161161 ## Where your data lives
162162
163163 | What | Where |
164164 |---|---|
165-| Catalog | `~/.skilly/catalog` by default (a git repository, the location can be changed in Settings) |
166-| Machine settings (projects list, preferences, Global configuration) | `~/.skilly/` |
167-| Project configuration | `skilly.json` and `skilly.lock` in each project |
165+| Catalog | `~/.panoplie/catalog` by default (a git repository, the location can be changed in Settings) |
166+| Machine settings (projects list, preferences, Global configuration) | `~/.panoplie/` |
167+| Project configuration | `panoplie.json` and `panoplie.lock` in each project |
168168
169-Set `SKILLY_CONFIG_DIR` to use another folder than `~/.skilly`, and `CODEX_HOME` if your Codex home is not `~/.codex`.
169+Set `PANOPLIE_CONFIG_DIR` to use another folder than `~/.panoplie`, and `CODEX_HOME` if your Codex home is not `~/.codex`.
170170
171171 ## Security and privacy
172172
173173 - **Local only.** The server listens on `127.0.0.1`, rejects other host names, and requires a random token generated at each launch, so other websites open in your browser cannot call it.
174174 - **No secrets in the catalog.** MCP configurations store the *name* of environment variables, never their values. Credentials found in MCP arguments, URLs or environment when importing are redacted.
175-- **Limited writes.** skilly writes only in the projects you added, in the catalog, and in your home folder when you explicitly apply a change to Global.
176-- **No telemetry.** skilly only goes online to fetch the marketplace sources you browse and to sync your catalog with the git remote you configure.
177-- skilly never executes the scripts contained in skills.
175+- **Limited writes.** panoplie writes only in the projects you added, in the catalog, and in your home folder when you explicitly apply a change to Global.
176+- **No telemetry.** panoplie only goes online to fetch the marketplace sources you browse and to sync your catalog with the git remote you configure.
177+- panoplie never executes the scripts contained in skills.
178178
179179 ## FAQ
180180
181-**Do my teammates need skilly?**
182-No. The generated files are regular agent files committed in the project. skilly only makes them easier to manage, and it makes catalog sharing easier for those who use it.
181+**Do my teammates need panoplie?**
182+No. The generated files are regular agent files committed in the project. panoplie only makes them easier to manage, and it makes catalog sharing easier for those who use it.
183183
184184 **What if I edit a generated file by hand?**
185-skilly detects it (thanks to `skilly.lock`) and asks what to do on the next apply: keep your version, take the catalog version, or push your change back to the catalog.
185+panoplie detects it (thanks to `panoplie.lock`) and asks what to do on the next apply: keep your version, take the catalog version, or push your change back to the catalog.
186186
187187 **Why are skills copied twice instead of symlinked?**
188-Claude Code reads `.claude/skills` and Codex reads `.agents/skills`. Symlinks are fragile on Windows and do not survive a clone, so skilly writes plain copies and keeps them in sync.
188+Claude Code reads `.claude/skills` and Codex reads `.agents/skills`. Symlinks are fragile on Windows and do not survive a clone, so panoplie writes plain copies and keeps them in sync.
189189
190190 **Does it work with other agents (Cursor, Copilot, Gemini CLI…)?**
191191 Not yet. Claude Code and Codex are supported today; more agents are planned.
@@ -199,7 +199,7 @@ The repository is a TypeScript monorepo managed with pnpm:
199199 | `packages/core` | Model, inventory scanners, catalog, resolution, rendering per agent, sync plan and apply. No UI dependency. |
200200 | `packages/server` | Local HTTP API over `core`. |
201201 | `packages/ui` | Nuxt single-page app served by the server. |
202-| `packages/cli` | The `skilly` command: starts the server and opens the window. |
202+| `packages/cli` | The `panoplie` command: starts the server and opens the window. |
203203
204204 ```sh
205205 pnpm dev # API on :4300 and the UI with hot reload on :3000
@@ -1,8 +1,8 @@
1 <p align="center">1 <p align="center">
2- <img src="packages/ui/public/favicon.svg" alt="skilly" width="96" height="96">2+ <img src="packages/ui/public/favicon.svg" alt="panoplie" width="96" height="96">
3 </p>3 </p>
4 4
5-<h1 align="center">skilly</h1>5+<h1 align="center">panoplie</h1>
6 6
7 <p align="center">7 <p align="center">
8 One place to manage the skills, rules, MCP servers, hooks and subagents of your coding agents,<br>8 One place to manage the skills, rules, MCP servers, hooks and subagents of your coding agents,<br>
@@ -10,12 +10,12 @@
10 </p>10 </p>
11 11
12 <p align="center">12 <p align="center">
13- <img src="media/screenshots/project.png" alt="A project in skilly: presets, items and their sync state" width="900">13+ <img src="media/screenshots/project.png" alt="A project in panoplie: presets, items and their sync state" width="900">
14 </p>14 </p>
15 15
16 ---16 ---
17 17
18-## Why skilly18+## Why panoplie
19 19
20 Coding agents are configured through a growing pile of files: `SKILL.md` folders, rules, `.mcp.json`, `settings.json`, `AGENTS.md`, `config.toml`… Once you use them on more than a couple of projects, three problems show up:20 Coding agents are configured through a growing pile of files: `SKILL.md` folders, rules, `.mcp.json`, `settings.json`, `AGENTS.md`, `config.toml`… Once you use them on more than a couple of projects, three problems show up:
21 21
@@ -23,7 +23,7 @@ Coding agents are configured through a growing pile of files: `SKILL.md` folders
23 - **Global pollution.** Putting everything in `~/.claude` or `~/.codex` makes every item load in every session of every project. It costs context and triggers skills where they make no sense.23 - **Global pollution.** Putting everything in `~/.claude` or `~/.codex` makes every item load in every session of every project. It costs context and triggers skills where they make no sense.
24 - **No overview.** Nothing tells you what exists, where it is used, and which copy is the right one.24 - **No overview.** Nothing tells you what exists, where it is used, and which copy is the right one.
25 25
26-skilly fixes this with a **catalog** (a git repository holding your items) and a local UI that **writes the right files into each project**, for each agent, and keeps them in sync.26+panoplie fixes this with a **catalog** (a git repository holding your items) and a local UI that **writes the right files into each project**, for each agent, and keeps them in sync.
27 27
28 ## How it works28 ## How it works
29 29
@@ -38,12 +38,12 @@ skilly fixes this with a **catalog** (a git repository holding your items) and a
38 acme-shop/ billing-api/ blog/38 acme-shop/ billing-api/ blog/
39 .claude/skills/… .agents/skills/… .claude/skills/…39 .claude/skills/… .agents/skills/… .claude/skills/…
40 .agents/skills/… AGENTS.md (block) .claude/rules/…40 .agents/skills/… AGENTS.md (block) .claude/rules/…
41- .mcp.json .codex/config.toml skilly.json41+ .mcp.json .codex/config.toml panoplie.json
42- skilly.json skilly.json42+ panoplie.json panoplie.json
43 ```43 ```
44 44
45 - **The catalog is the source of truth.** It is a plain git repository, hosted wherever you like (or nowhere). Sync it across your machines or share it with your team.45 - **The catalog is the source of truth.** It is a plain git repository, hosted wherever you like (or nowhere). Sync it across your machines or share it with your team.
46-- **Enabling an item writes real files into the project**, where each agent expects them. The project then works without skilly, for you and for anyone who clones it. You commit those files with the project.46+- **Enabling an item writes real files into the project**, where each agent expects them. The project then works without panoplie, for you and for anyone who clones it. You commit those files with the project.
47 - **Nothing is written until you apply.** Every change goes through a review, and a file edited by hand is never overwritten without asking.47 - **Nothing is written until you apply.** Every change goes through a review, and a file edited by hand is never overwritten without asking.
48 - **Global stays minimal.** Keep in `~` only what you really use everywhere, and move the rest to the projects that need it.48 - **Global stays minimal.** Keep in `~` only what you really use everywhere, and move the rest to the projects that need it.
49 49
@@ -54,7 +54,7 @@ skilly fixes this with a **catalog** (a git repository holding your items) and a
54 | **Item** | A skill, rule, MCP server, hook or subagent. |54 | **Item** | A skill, rule, MCP server, hook or subagent. |
55 | **Catalog** | Your library of items and presets, stored in a git repository. |55 | **Catalog** | Your library of items and presets, stored in a git repository. |
56 | **Preset** | A named bundle of items, for example *nuxt-saas* = Nuxt and Tailwind skills, TypeScript rules and the Nuxt docs MCP server. A preset can include other presets. |56 | **Preset** | A named bundle of items, for example *nuxt-saas* = Nuxt and Tailwind skills, TypeScript rules and the Nuxt docs MCP server. A preset can include other presets. |
57-| **Project** | A code folder you added to skilly. |57+| **Project** | A code folder you added to panoplie. |
58 | **Global** | Your user-level agent configuration (`~/.claude`, `~/.agents`, `~/.codex`). It loads in every project. |58 | **Global** | Your user-level agent configuration (`~/.claude`, `~/.agents`, `~/.codex`). It loads in every project. |
59 | **Automatic / on demand** | Skill mode. *Automatic*: the agent sees the skill and uses it when it thinks it is relevant. *On demand*: the skill is installed but hidden from the agent until you call it yourself (`/name` in Claude Code, `$name` in Codex). Ideal for large families of niche skills you want at hand without paying for them in context. |59 | **Automatic / on demand** | Skill mode. *Automatic*: the agent sees the skill and uses it when it thinks it is relevant. *On demand*: the skill is installed but hidden from the agent until you call it yourself (`/name` in Claude Code, `$name` in Codex). Ideal for large families of niche skills you want at hand without paying for them in context. |
60 60
@@ -66,7 +66,7 @@ Pick presets, add or exclude single items, switch skills between automatic and o
66 66
67 ### Review before anything is written67 ### Review before anything is written
68 68
69-skilly computes a plan: files to add, update or remove, for each agent. You can undo any line before applying. When a file already exists or was edited by hand, you choose: **keep the project version**, **take the catalog version**, or **push the change back to the catalog**. You can compare both versions first.69+panoplie computes a plan: files to add, update or remove, for each agent. You can undo any line before applying. When a file already exists or was edited by hand, you choose: **keep the project version**, **take the catalog version**, or **push the change back to the catalog**. You can compare both versions first.
70 70
71 <p align="center">71 <p align="center">
72 <img src="media/screenshots/review.png" alt="Review changes before applying them, with conflict resolution" width="900">72 <img src="media/screenshots/review.png" alt="Review changes before applying them, with conflict resolution" width="900">
@@ -74,7 +74,7 @@ skilly computes a plan: files to add, update or remove, for each agent. You can
74 74
75 ### Clean up Global75 ### Clean up Global
76 76
77-skilly lists everything loaded from your home folder and spots the items that are only used in some projects. **Move to projects** copies them to the catalog, enables them where they are needed, then removes them from Global. Nothing is removed from your home folder without an explicit action.77+panoplie lists everything loaded from your home folder and spots the items that are only used in some projects. **Move to projects** copies them to the catalog, enables them where they are needed, then removes them from Global. Nothing is removed from your home folder without an explicit action.
78 78
79 <p align="center">79 <p align="center">
80 <img src="media/screenshots/global.png" alt="Global items loaded in every project, with tidy-up suggestions" width="900">80 <img src="media/screenshots/global.png" alt="Global items loaded in every project, with tidy-up suggestions" width="900">
@@ -118,74 +118,74 @@ Commit, pull and push from the UI, against any remote: GitHub, GitLab, Codeberg,
118 | Subagents | `.claude/agents/<name>.md` | `.codex/agents/<name>.toml` |118 | Subagents | `.claude/agents/<name>.md` | `.codex/agents/<name>.toml` |
119 | Hide a Global skill or MCP server | Yes | Not supported by Codex yet |119 | Hide a Global skill or MCP server | Yes | Not supported by Codex yet |
120 120
121-skilly also writes two files at the root of each project, meant to be committed:121+panoplie also writes two files at the root of each project, meant to be committed:
122 122
123-- **`skilly.json`**: what the project uses (target agents, presets, added and excluded items, on-demand skills, hidden Global items).123+- **`panoplie.json`**: what the project uses (target agents, presets, added and excluded items, on-demand skills, hidden Global items).
124-- **`skilly.lock`**: what skilly installed, with content hashes, so it knows which files it owns and can detect manual edits.124+- **`panoplie.lock`**: what panoplie installed, with content hashes, so it knows which files it owns and can detect manual edits.
125 125
126-Shared files (`.mcp.json`, `.claude/settings.json`, `.codex/config.toml`, `AGENTS.md`) are edited surgically: skilly only touches the keys or the block it owns and leaves the rest as is.126+Shared files (`.mcp.json`, `.claude/settings.json`, `.codex/config.toml`, `AGENTS.md`) are edited surgically: panoplie only touches the keys or the block it owns and leaves the rest as is.
127 127
128-skilly also warns you about agent-specific pitfalls: a Codex project that is not trusted (Codex then ignores its `.codex/` folder), an `AGENTS.md` close to the 32 KiB Codex limit, or rules that Claude Code would load twice.128+panoplie also warns you about agent-specific pitfalls: a Codex project that is not trusted (Codex then ignores its `.codex/` folder), an `AGENTS.md` close to the 32 KiB Codex limit, or rules that Claude Code would load twice.
129 129
130 `CLAUDE.md` and `AGENTS.md` are shown read-only in each project, with their size. They are specific to each project and never go into the catalog.130 `CLAUDE.md` and `AGENTS.md` are shown read-only in each project, with their size. They are specific to each project and never go into the catalog.
131 131
132 ## Install and run132 ## Install and run
133 133
134-> skilly is in early development and not published on npm yet. Until the first release, run it from source (see below).134+> panoplie is in early development and not published on npm yet. Until the first release, run it from source (see below).
135 135
136 Requirements: **Node.js 22+**, **git**, and preferably **Microsoft Edge or Google Chrome** for the app window.136 Requirements: **Node.js 22+**, **git**, and preferably **Microsoft Edge or Google Chrome** for the app window.
137 137
138 ```sh138 ```sh
139-npx @skilly/cli139+npx @panoplie/cli
140 ```140 ```
141 141
142-skilly starts a local server and opens the UI in an app window (Edge or Chrome in app mode, your default browser otherwise). Closing the window stops skilly. Running the command again while skilly is open brings the existing window back.142+panoplie starts a local server and opens the UI in an app window (Edge or Chrome in app mode, your default browser otherwise). Closing the window stops panoplie. Running the command again while panoplie is open brings the existing window back.
143 143
144 | Option | Effect |144 | Option | Effect |
145 |---|---|145 |---|---|
146 | `--no-open` | Do not open a window, only print the URL. |146 | `--no-open` | Do not open a window, only print the URL. |
147 | `--port <n>` | Use this port instead of the default one (47821, or a free port if taken). |147 | `--port <n>` | Use this port instead of the default one (47821, or a free port if taken). |
148 148
149-The first launch walks you through the setup: create a new catalog or clone an existing one from git, check the detected agents, then add your projects (skilly can scan your usual code folders for you).149+The first launch walks you through the setup: create a new catalog or clone an existing one from git, check the detected agents, then add your projects (panoplie can scan your usual code folders for you).
150 150
151 ### From source151 ### From source
152 152
153 ```sh153 ```sh
154-git clone <repository-url> skilly154+git clone <repository-url> panoplie
155-cd skilly155+cd panoplie
156 pnpm install156 pnpm install
157 pnpm build # builds the UI157 pnpm build # builds the UI
158-pnpm start # starts skilly and opens the window158+pnpm start # starts panoplie and opens the window
159 ```159 ```
160 160
161 ## Where your data lives161 ## Where your data lives
162 162
163 | What | Where |163 | What | Where |
164 |---|---|164 |---|---|
165-| Catalog | `~/.skilly/catalog` by default (a git repository, the location can be changed in Settings) |165+| Catalog | `~/.panoplie/catalog` by default (a git repository, the location can be changed in Settings) |
166-| Machine settings (projects list, preferences, Global configuration) | `~/.skilly/` |166+| Machine settings (projects list, preferences, Global configuration) | `~/.panoplie/` |
167-| Project configuration | `skilly.json` and `skilly.lock` in each project |167+| Project configuration | `panoplie.json` and `panoplie.lock` in each project |
168 168
169-Set `SKILLY_CONFIG_DIR` to use another folder than `~/.skilly`, and `CODEX_HOME` if your Codex home is not `~/.codex`.169+Set `PANOPLIE_CONFIG_DIR` to use another folder than `~/.panoplie`, and `CODEX_HOME` if your Codex home is not `~/.codex`.
170 170
171 ## Security and privacy171 ## Security and privacy
172 172
173 - **Local only.** The server listens on `127.0.0.1`, rejects other host names, and requires a random token generated at each launch, so other websites open in your browser cannot call it.173 - **Local only.** The server listens on `127.0.0.1`, rejects other host names, and requires a random token generated at each launch, so other websites open in your browser cannot call it.
174 - **No secrets in the catalog.** MCP configurations store the *name* of environment variables, never their values. Credentials found in MCP arguments, URLs or environment when importing are redacted.174 - **No secrets in the catalog.** MCP configurations store the *name* of environment variables, never their values. Credentials found in MCP arguments, URLs or environment when importing are redacted.
175-- **Limited writes.** skilly writes only in the projects you added, in the catalog, and in your home folder when you explicitly apply a change to Global.175+- **Limited writes.** panoplie writes only in the projects you added, in the catalog, and in your home folder when you explicitly apply a change to Global.
176-- **No telemetry.** skilly only goes online to fetch the marketplace sources you browse and to sync your catalog with the git remote you configure.176+- **No telemetry.** panoplie only goes online to fetch the marketplace sources you browse and to sync your catalog with the git remote you configure.
177-- skilly never executes the scripts contained in skills.177+- panoplie never executes the scripts contained in skills.
178 178
179 ## FAQ179 ## FAQ
180 180
181-**Do my teammates need skilly?**181+**Do my teammates need panoplie?**
182-No. The generated files are regular agent files committed in the project. skilly only makes them easier to manage, and it makes catalog sharing easier for those who use it.182+No. The generated files are regular agent files committed in the project. panoplie only makes them easier to manage, and it makes catalog sharing easier for those who use it.
183 183
184 **What if I edit a generated file by hand?**184 **What if I edit a generated file by hand?**
185-skilly detects it (thanks to `skilly.lock`) and asks what to do on the next apply: keep your version, take the catalog version, or push your change back to the catalog.185+panoplie detects it (thanks to `panoplie.lock`) and asks what to do on the next apply: keep your version, take the catalog version, or push your change back to the catalog.
186 186
187 **Why are skills copied twice instead of symlinked?**187 **Why are skills copied twice instead of symlinked?**
188-Claude Code reads `.claude/skills` and Codex reads `.agents/skills`. Symlinks are fragile on Windows and do not survive a clone, so skilly writes plain copies and keeps them in sync.188+Claude Code reads `.claude/skills` and Codex reads `.agents/skills`. Symlinks are fragile on Windows and do not survive a clone, so panoplie writes plain copies and keeps them in sync.
189 189
190 **Does it work with other agents (Cursor, Copilot, Gemini CLI…)?**190 **Does it work with other agents (Cursor, Copilot, Gemini CLI…)?**
191 Not yet. Claude Code and Codex are supported today; more agents are planned.191 Not yet. Claude Code and Codex are supported today; more agents are planned.
@@ -199,7 +199,7 @@ The repository is a TypeScript monorepo managed with pnpm:
199 | `packages/core` | Model, inventory scanners, catalog, resolution, rendering per agent, sync plan and apply. No UI dependency. |199 | `packages/core` | Model, inventory scanners, catalog, resolution, rendering per agent, sync plan and apply. No UI dependency. |
200 | `packages/server` | Local HTTP API over `core`. |200 | `packages/server` | Local HTTP API over `core`. |
201 | `packages/ui` | Nuxt single-page app served by the server. |201 | `packages/ui` | Nuxt single-page app served by the server. |
202-| `packages/cli` | The `skilly` command: starts the server and opens the window. |202+| `packages/cli` | The `panoplie` command: starts the server and opens the window. |
203 203
204 ```sh204 ```sh
205 pnpm dev # API on :4300 and the UI with hot reload on :3000205 pnpm dev # API on :4300 and the UI with hot reload on :3000
modified package.json +4 -4
@@ -1,13 +1,13 @@
11 {
2- "name": "skilly-monorepo",
2+ "name": "panoplie-monorepo",
33 "private": true,
44 "license": "MIT",
55 "type": "module",
66 "packageManager": "pnpm@10.23.0",
77 "scripts": {
8- "dev": "pnpm --parallel --filter @skilly/server --filter @skilly/ui dev",
9- "build": "pnpm --filter @skilly/ui build",
10- "start": "pnpm --filter skilly start",
8+ "dev": "pnpm --parallel --filter @panoplie/server --filter @panoplie/ui dev",
9+ "build": "pnpm --filter @panoplie/ui build",
10+ "start": "pnpm --filter panoplie start",
1111 "test": "vitest run",
1212 "typecheck": "tsc -p packages/core && tsc -p packages/server && tsc -p packages/cli",
1313 "site:build": "node site/build.mjs"
@@ -1,13 +1,13 @@
1 {1 {
2- "name": "skilly-monorepo",2+ "name": "panoplie-monorepo",
3 "private": true,3 "private": true,
4 "license": "MIT",4 "license": "MIT",
5 "type": "module",5 "type": "module",
6 "packageManager": "pnpm@10.23.0",6 "packageManager": "pnpm@10.23.0",
7 "scripts": {7 "scripts": {
8- "dev": "pnpm --parallel --filter @skilly/server --filter @skilly/ui dev",8+ "dev": "pnpm --parallel --filter @panoplie/server --filter @panoplie/ui dev",
9- "build": "pnpm --filter @skilly/ui build",9+ "build": "pnpm --filter @panoplie/ui build",
10- "start": "pnpm --filter skilly start",10+ "start": "pnpm --filter panoplie start",
11 "test": "vitest run",11 "test": "vitest run",
12 "typecheck": "tsc -p packages/core && tsc -p packages/server && tsc -p packages/cli",12 "typecheck": "tsc -p packages/core && tsc -p packages/server && tsc -p packages/cli",
13 "site:build": "node site/build.mjs"13 "site:build": "node site/build.mjs"
renamed packages/cli/bin/panoplie.js +0 -0
similarity index 100%
rename from packages/cli/bin/skilly.js
rename to packages/cli/bin/panoplie.js
similarity index 100%
rename from packages/cli/bin/skilly.js
rename to packages/cli/bin/panoplie.js
modified packages/cli/package.json +4 -4
@@ -1,17 +1,17 @@
11 {
2- "name": "skilly",
2+ "name": "panoplie",
33 "version": "0.0.0",
44 "private": true,
55 "license": "MIT",
66 "type": "module",
77 "bin": {
8- "skilly": "./bin/skilly.js"
8+ "panoplie": "./bin/panoplie.js"
99 },
1010 "scripts": {
1111 "start": "tsx src/main.ts"
1212 },
1313 "dependencies": {
14- "@skilly/core": "workspace:*",
15- "@skilly/server": "workspace:*"
14+ "@panoplie/core": "workspace:*",
15+ "@panoplie/server": "workspace:*"
1616 }
1717 }
@@ -1,17 +1,17 @@
1 {1 {
2- "name": "skilly",2+ "name": "panoplie",
3 "version": "0.0.0",3 "version": "0.0.0",
4 "private": true,4 "private": true,
5 "license": "MIT",5 "license": "MIT",
6 "type": "module",6 "type": "module",
7 "bin": {7 "bin": {
8- "skilly": "./bin/skilly.js"8+ "panoplie": "./bin/panoplie.js"
9 },9 },
10 "scripts": {10 "scripts": {
11 "start": "tsx src/main.ts"11 "start": "tsx src/main.ts"
12 },12 },
13 "dependencies": {13 "dependencies": {
14- "@skilly/core": "workspace:*",14+ "@panoplie/core": "workspace:*",
15- "@skilly/server": "workspace:*"15+ "@panoplie/server": "workspace:*"
16 }16 }
17 }17 }
modified packages/cli/src/main.ts +12 -7
@@ -4,7 +4,8 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
44 import os from 'node:os'
55 import path from 'node:path'
66 import { fileURLToPath } from 'node:url'
7-import { startServer } from '@skilly/server'
7+import { migrateLegacyConfigDir } from '@panoplie/core'
8+import { startServer } from '@panoplie/server'
89 import { openWindow } from './window.ts'
910
1011 const IDLE_EXIT_MS = 20_000
@@ -54,7 +55,7 @@ async function readInstance(file: string): Promise<InstanceInfo | undefined> {
5455 async function isAlive(info: InstanceInfo): Promise<boolean> {
5556 try {
5657 const res = await fetch(`http://127.0.0.1:${info.port}/api/health`, {
57- headers: { 'x-skilly-token': info.token },
58+ headers: { 'x-panoplie-token': info.token },
5859 signal: AbortSignal.timeout(1500),
5960 })
6061 return res.ok
@@ -90,11 +91,15 @@ async function startOnPreferredPort(options: Omit<ServerOptions, 'port'>, explic
9091 async function main(): Promise<void> {
9192 const args = parseArgs(process.argv.slice(2))
9293 if (args.help) {
93- console.log('Usage: skilly [--no-open] [--port N]')
94+ console.log('Usage: panoplie [--no-open] [--port N]')
9495 return
9596 }
9697
97- const configDir = process.env.SKILLY_CONFIG_DIR || path.join(os.homedir(), '.skilly')
98+ const configDir = process.env.PANOPLIE_CONFIG_DIR || path.join(os.homedir(), '.panoplie')
99+ // Installs made under the previous name (skilly) keep their catalog, projects and settings.
100+ if (!process.env.PANOPLIE_CONFIG_DIR && (await migrateLegacyConfigDir(configDir))) {
101+ console.log(`panoplie: moved your settings from ~/.skilly to ${configDir}`)
102+ }
98103 const instanceFile = path.join(configDir, 'instance.json')
99104 const profileDir = path.join(configDir, 'browser-profile')
100105 await mkdir(configDir, { recursive: true })
@@ -104,11 +109,11 @@ async function main(): Promise<void> {
104109 if (existing && (await isAlive(existing))) {
105110 const url = windowUrl(existing.port, existing.token)
106111 if (args.open) await launch(url, profileDir)
107- console.log(args.open ? `skilly is already running: ${url}` : url)
112+ console.log(args.open ? `panoplie is already running: ${url}` : url)
108113 return
109114 }
110115
111- const staticDir = process.env.SKILLY_STATIC_DIR || fileURLToPath(new URL('../../ui/.output/public', import.meta.url))
116+ const staticDir = process.env.PANOPLIE_STATIC_DIR || fileURLToPath(new URL('../../ui/.output/public', import.meta.url))
112117 if (!existsSync(path.join(staticDir, 'index.html')) && !existsSync(path.join(staticDir, '200.html'))) {
113118 console.error(`The UI is not built (missing ${staticDir}).\nRun "pnpm build" from the repository root first.`)
114119 process.exitCode = 1
@@ -134,7 +139,7 @@ async function main(): Promise<void> {
134139 process.on('SIGTERM', () => void cleanup())
135140
136141 const url = windowUrl(server.port, token)
137- console.log(`skilly: ${url} (close the window to stop)`)
142+ console.log(`panoplie: ${url} (close the window to stop)`)
138143 if (args.open) await launch(url, profileDir)
139144 }
140145
@@ -4,7 +4,8 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
4 import os from 'node:os'4 import os from 'node:os'
5 import path from 'node:path'5 import path from 'node:path'
6 import { fileURLToPath } from 'node:url'6 import { fileURLToPath } from 'node:url'
7-import { startServer } from '@skilly/server'7+import { migrateLegacyConfigDir } from '@panoplie/core'
8+import { startServer } from '@panoplie/server'
8 import { openWindow } from './window.ts'9 import { openWindow } from './window.ts'
9 10
10 const IDLE_EXIT_MS = 20_00011 const IDLE_EXIT_MS = 20_000
@@ -54,7 +55,7 @@ async function readInstance(file: string): Promise<InstanceInfo | undefined> {
54 async function isAlive(info: InstanceInfo): Promise<boolean> {55 async function isAlive(info: InstanceInfo): Promise<boolean> {
55 try {56 try {
56 const res = await fetch(`http://127.0.0.1:${info.port}/api/health`, {57 const res = await fetch(`http://127.0.0.1:${info.port}/api/health`, {
57- headers: { 'x-skilly-token': info.token },58+ headers: { 'x-panoplie-token': info.token },
58 signal: AbortSignal.timeout(1500),59 signal: AbortSignal.timeout(1500),
59 })60 })
60 return res.ok61 return res.ok
@@ -90,11 +91,15 @@ async function startOnPreferredPort(options: Omit<ServerOptions, 'port'>, explic
90 async function main(): Promise<void> {91 async function main(): Promise<void> {
91 const args = parseArgs(process.argv.slice(2))92 const args = parseArgs(process.argv.slice(2))
92 if (args.help) {93 if (args.help) {
93- console.log('Usage: skilly [--no-open] [--port N]')94+ console.log('Usage: panoplie [--no-open] [--port N]')
94 return95 return
95 }96 }
96 97
97- const configDir = process.env.SKILLY_CONFIG_DIR || path.join(os.homedir(), '.skilly')98+ const configDir = process.env.PANOPLIE_CONFIG_DIR || path.join(os.homedir(), '.panoplie')
99+ // Installs made under the previous name (skilly) keep their catalog, projects and settings.
100+ if (!process.env.PANOPLIE_CONFIG_DIR && (await migrateLegacyConfigDir(configDir))) {
101+ console.log(`panoplie: moved your settings from ~/.skilly to ${configDir}`)
102+ }
98 const instanceFile = path.join(configDir, 'instance.json')103 const instanceFile = path.join(configDir, 'instance.json')
99 const profileDir = path.join(configDir, 'browser-profile')104 const profileDir = path.join(configDir, 'browser-profile')
100 await mkdir(configDir, { recursive: true })105 await mkdir(configDir, { recursive: true })
@@ -104,11 +109,11 @@ async function main(): Promise<void> {
104 if (existing && (await isAlive(existing))) {109 if (existing && (await isAlive(existing))) {
105 const url = windowUrl(existing.port, existing.token)110 const url = windowUrl(existing.port, existing.token)
106 if (args.open) await launch(url, profileDir)111 if (args.open) await launch(url, profileDir)
107- console.log(args.open ? `skilly is already running: ${url}` : url)112+ console.log(args.open ? `panoplie is already running: ${url}` : url)
108 return113 return
109 }114 }
110 115
111- const staticDir = process.env.SKILLY_STATIC_DIR || fileURLToPath(new URL('../../ui/.output/public', import.meta.url))116+ const staticDir = process.env.PANOPLIE_STATIC_DIR || fileURLToPath(new URL('../../ui/.output/public', import.meta.url))
112 if (!existsSync(path.join(staticDir, 'index.html')) && !existsSync(path.join(staticDir, '200.html'))) {117 if (!existsSync(path.join(staticDir, 'index.html')) && !existsSync(path.join(staticDir, '200.html'))) {
113 console.error(`The UI is not built (missing ${staticDir}).\nRun "pnpm build" from the repository root first.`)118 console.error(`The UI is not built (missing ${staticDir}).\nRun "pnpm build" from the repository root first.`)
114 process.exitCode = 1119 process.exitCode = 1
@@ -134,7 +139,7 @@ async function main(): Promise<void> {
134 process.on('SIGTERM', () => void cleanup())139 process.on('SIGTERM', () => void cleanup())
135 140
136 const url = windowUrl(server.port, token)141 const url = windowUrl(server.port, token)
137- console.log(`skilly: ${url} (close the window to stop)`)142+ console.log(`panoplie: ${url} (close the window to stop)`)
138 if (args.open) await launch(url, profileDir)143 if (args.open) await launch(url, profileDir)
139 }144 }
140 145
modified packages/core/package.json +1 -1
@@ -1,5 +1,5 @@
11 {
2- "name": "@skilly/core",
2+ "name": "@panoplie/core",
33 "version": "0.0.0",
44 "private": true,
55 "license": "MIT",
@@ -1,5 +1,5 @@
1 {1 {
2- "name": "@skilly/core",2+ "name": "@panoplie/core",
3 "version": "0.0.0",3 "version": "0.0.0",
4 "private": true,4 "private": true,
5 "license": "MIT",5 "license": "MIT",
modified packages/core/src/api.ts +6 -6
@@ -1,8 +1,8 @@
11 /**
2- * HTTP API contract between @skilly/server and @skilly/ui.
2+ * HTTP API contract between @panoplie/server and @panoplie/ui.
33 *
44 * All routes live under `/api` and require the session token, sent as the
5- * `x-skilly-token` header (the UI reads it once from the `?t=` query of the
5+ * `x-panoplie-token` header (the UI reads it once from the `?t=` query of the
66 * launch URL and keeps it in sessionStorage).
77 *
88 * GET /api/health -> { ok: true, version }
@@ -112,7 +112,7 @@ export interface FileContent {
112112 *
113113 * Scope configuration and sync (projects and Home)
114114 * GET /api/scopes/:scope/state -> ScopeState
115- * PUT /api/scopes/:scope/config ScopeConfig -> ScopeState (writes skilly.json / home.json; does not apply)
115+ * PUT /api/scopes/:scope/config ScopeConfig -> ScopeState (writes panoplie.json / home.json; does not apply)
116116 * POST /api/scopes/:scope/apply ApplyBody -> ApplyResponse (re-plans server side, applies, returns fresh state)
117117 * POST /api/home/remove-from-global RemoveFromGlobalBody -> RemoveFromGlobalResult
118118 *
@@ -142,7 +142,7 @@ import type {
142142 import type { ScopeInventory } from './model.ts'
143143
144144 export interface Settings {
145- /** Absolute path of the catalog folder. Default: `~/.skilly/catalog`. */
145+ /** Absolute path of the catalog folder. Default: `~/.panoplie/catalog`. */
146146 catalogPath: string
147147 /** Commit, pull and push the catalog automatically after changes (debounced). */
148148 autoSync: boolean
@@ -179,7 +179,7 @@ export interface ImportResult {
179179
180180 export interface ScopeState {
181181 scope: string
182- /** Null when the scope has no skilly.json yet. */
182+ /** Null when the scope has no panoplie.json yet. */
183183 config: ScopeConfig | null
184184 inventory: ScopeInventory
185185 /** Inventory item id -> status. */
@@ -202,7 +202,7 @@ export interface ApplyResponse {
202202 export interface RemoveFromGlobalBody {
203203 /** Home inventory item ids (skill, rule or agent). */
204204 itemIds: string[]
205- /** Project ids in which to enable the items (added to their skilly.json `add`, then applied). */
205+ /** Project ids in which to enable the items (added to their panoplie.json `add`, then applied). */
206206 enableIn: string[]
207207 }
208208
@@ -1,8 +1,8 @@
1 /**1 /**
2- * HTTP API contract between @skilly/server and @skilly/ui.2+ * HTTP API contract between @panoplie/server and @panoplie/ui.
3 *3 *
4 * All routes live under `/api` and require the session token, sent as the4 * All routes live under `/api` and require the session token, sent as the
5- * `x-skilly-token` header (the UI reads it once from the `?t=` query of the5+ * `x-panoplie-token` header (the UI reads it once from the `?t=` query of the
6 * launch URL and keeps it in sessionStorage).6 * launch URL and keeps it in sessionStorage).
7 *7 *
8 * GET /api/health -> { ok: true, version }8 * GET /api/health -> { ok: true, version }
@@ -112,7 +112,7 @@ export interface FileContent {
112 *112 *
113 * Scope configuration and sync (projects and Home)113 * Scope configuration and sync (projects and Home)
114 * GET /api/scopes/:scope/state -> ScopeState114 * GET /api/scopes/:scope/state -> ScopeState
115- * PUT /api/scopes/:scope/config ScopeConfig -> ScopeState (writes skilly.json / home.json; does not apply)115+ * PUT /api/scopes/:scope/config ScopeConfig -> ScopeState (writes panoplie.json / home.json; does not apply)
116 * POST /api/scopes/:scope/apply ApplyBody -> ApplyResponse (re-plans server side, applies, returns fresh state)116 * POST /api/scopes/:scope/apply ApplyBody -> ApplyResponse (re-plans server side, applies, returns fresh state)
117 * POST /api/home/remove-from-global RemoveFromGlobalBody -> RemoveFromGlobalResult117 * POST /api/home/remove-from-global RemoveFromGlobalBody -> RemoveFromGlobalResult
118 *118 *
@@ -142,7 +142,7 @@ import type {
142 import type { ScopeInventory } from './model.ts'142 import type { ScopeInventory } from './model.ts'
143 143
144 export interface Settings {144 export interface Settings {
145- /** Absolute path of the catalog folder. Default: `~/.skilly/catalog`. */145+ /** Absolute path of the catalog folder. Default: `~/.panoplie/catalog`. */
146 catalogPath: string146 catalogPath: string
147 /** Commit, pull and push the catalog automatically after changes (debounced). */147 /** Commit, pull and push the catalog automatically after changes (debounced). */
148 autoSync: boolean148 autoSync: boolean
@@ -179,7 +179,7 @@ export interface ImportResult {
179 179
180 export interface ScopeState {180 export interface ScopeState {
181 scope: string181 scope: string
182- /** Null when the scope has no skilly.json yet. */182+ /** Null when the scope has no panoplie.json yet. */
183 config: ScopeConfig | null183 config: ScopeConfig | null
184 inventory: ScopeInventory184 inventory: ScopeInventory
185 /** Inventory item id -> status. */185 /** Inventory item id -> status. */
@@ -202,7 +202,7 @@ export interface ApplyResponse {
202 export interface RemoveFromGlobalBody {202 export interface RemoveFromGlobalBody {
203 /** Home inventory item ids (skill, rule or agent). */203 /** Home inventory item ids (skill, rule or agent). */
204 itemIds: string[]204 itemIds: string[]
205- /** Project ids in which to enable the items (added to their skilly.json `add`, then applied). */205+ /** Project ids in which to enable the items (added to their panoplie.json `add`, then applied). */
206 enableIn: string[]206 enableIn: string[]
207 }207 }
208 208
modified packages/core/src/catalog-model.ts +10 -10
@@ -1,5 +1,5 @@
11 /**
2- * Data model for the catalog, presets, scope configuration (skilly.json),
2+ * Data model for the catalog, presets, scope configuration (panoplie.json),
33 * lock file and sync plans. Shared by core modules, server and UI.
44 *
55 * Catalog layout on disk (a plain folder, usually a git repository):
@@ -10,7 +10,7 @@
1010 * <catalog>/mcp/<name>.yaml McpTemplate
1111 * <catalog>/hooks/<name>.yaml HookTemplate
1212 * <catalog>/presets/<name>.yaml Preset
13- * <catalog>/.skilly/index.json CatalogIndex (tags, origins)
13+ * <catalog>/.panoplie/index.json CatalogIndex (tags, origins)
1414 *
1515 * Names are kebab-case: /^[a-z0-9][a-z0-9._-]*$/ (presets too).
1616 */
@@ -30,7 +30,7 @@ export interface CatalogRef {
3030 export type CatalogKey = string
3131
3232 export interface ItemOrigin {
33- /** `import`: copied from Home or a project; `marketplace`: imported from a marketplace source; `created`: written in skilly. */
33+ /** `import`: copied from Home or a project; `marketplace`: imported from a marketplace source; `created`: written in panoplie. */
3434 kind: 'import' | 'marketplace' | 'created'
3535 /** Source path (import) or marketplace source id (marketplace). */
3636 source?: string
@@ -134,7 +134,7 @@ export interface CatalogSnapshot {
134134 errors: { path: string; message: string }[]
135135 }
136136
137-/** Contents of `skilly.json` in a project, or `~/.skilly/home.json` for Home. */
137+/** Contents of `panoplie.json` in a project, or `~/.panoplie/home.json` for Home. */
138138 export interface ScopeConfig {
139139 version: 1
140140 targets: AgentId[]
@@ -153,10 +153,10 @@ export interface LockFileEntry {
153153 sha256: string
154154 }
155155
156-/** A key skilly owns inside a shared config file (settings.json, .mcp.json, config.toml, hooks.json, AGENTS.md block). */
156+/** A key panoplie owns inside a shared config file (settings.json, .mcp.json, config.toml, hooks.json, AGENTS.md block). */
157157 export interface LockConfigEntry {
158158 file: string
159- /** Dotted key path, e.g. `mcpServers.github`, `skillOverrides.seo-audit`, `mcp_servers.github`, `hooks.PreToolUse[skilly:fmt]`, `agents-md-block:typescript-strict`. */
159+ /** Dotted key path, e.g. `mcpServers.github`, `skillOverrides.seo-audit`, `mcp_servers.github`, `hooks.PreToolUse[panoplie:fmt]`, `agents-md-block:typescript-strict`. */
160160 key: string
161161 sha256: string
162162 }
@@ -174,7 +174,7 @@ export interface LockEntry {
174174 via: string[]
175175 }
176176
177-/** `skilly.lock` in a project, `~/.skilly/home.lock` for Home. */
177+/** `panoplie.lock` in a project, `~/.panoplie/home.lock` for Home. */
178178 export interface LockFile {
179179 version: 1
180180 /** Catalog git commit at the last sync, when the catalog is a repo. */
@@ -188,7 +188,7 @@ export type PlanActionKind = 'add' | 'update' | 'remove' | 'unchanged' | 'confli
188188 export type ConflictKind =
189189 /** A managed file was edited in the scope since the last sync. */
190190 | 'modified-locally'
191- /** The target path exists but skilly does not own it (not in the lock). */
191+ /** The target path exists but panoplie does not own it (not in the lock). */
192192 | 'exists-unmanaged'
193193 /** The item is in the lock but no longer in the catalog. */
194194 | 'missing-in-catalog'
@@ -237,7 +237,7 @@ export interface ApplyResult {
237237
238238 /** Status of an inventory item relative to the catalog and the lock, computed by `annotateInventory`. */
239239 export interface ItemStatus {
240- /** True when the item was written by skilly (present in the lock). */
240+ /** True when the item was written by panoplie (present in the lock). */
241241 managed: boolean
242242 catalogRef?: CatalogRef
243243 state:
@@ -256,7 +256,7 @@ export interface SyncTarget {
256256 root: string
257257 homeDir: string
258258 codexHome?: string
259- /** Where scope config and lock are stored: project root for projects, `~/.skilly` for Home. */
259+ /** Where scope config and lock are stored: project root for projects, `~/.panoplie` for Home. */
260260 configDir: string
261261 }
262262
@@ -1,5 +1,5 @@
1 /**1 /**
2- * Data model for the catalog, presets, scope configuration (skilly.json),2+ * Data model for the catalog, presets, scope configuration (panoplie.json),
3 * lock file and sync plans. Shared by core modules, server and UI.3 * lock file and sync plans. Shared by core modules, server and UI.
4 *4 *
5 * Catalog layout on disk (a plain folder, usually a git repository):5 * Catalog layout on disk (a plain folder, usually a git repository):
@@ -10,7 +10,7 @@
10 * <catalog>/mcp/<name>.yaml McpTemplate10 * <catalog>/mcp/<name>.yaml McpTemplate
11 * <catalog>/hooks/<name>.yaml HookTemplate11 * <catalog>/hooks/<name>.yaml HookTemplate
12 * <catalog>/presets/<name>.yaml Preset12 * <catalog>/presets/<name>.yaml Preset
13- * <catalog>/.skilly/index.json CatalogIndex (tags, origins)13+ * <catalog>/.panoplie/index.json CatalogIndex (tags, origins)
14 *14 *
15 * Names are kebab-case: /^[a-z0-9][a-z0-9._-]*$/ (presets too).15 * Names are kebab-case: /^[a-z0-9][a-z0-9._-]*$/ (presets too).
16 */16 */
@@ -30,7 +30,7 @@ export interface CatalogRef {
30 export type CatalogKey = string30 export type CatalogKey = string
31 31
32 export interface ItemOrigin {32 export interface ItemOrigin {
33- /** `import`: copied from Home or a project; `marketplace`: imported from a marketplace source; `created`: written in skilly. */33+ /** `import`: copied from Home or a project; `marketplace`: imported from a marketplace source; `created`: written in panoplie. */
34 kind: 'import' | 'marketplace' | 'created'34 kind: 'import' | 'marketplace' | 'created'
35 /** Source path (import) or marketplace source id (marketplace). */35 /** Source path (import) or marketplace source id (marketplace). */
36 source?: string36 source?: string
@@ -134,7 +134,7 @@ export interface CatalogSnapshot {
134 errors: { path: string; message: string }[]134 errors: { path: string; message: string }[]
135 }135 }
136 136
137-/** Contents of `skilly.json` in a project, or `~/.skilly/home.json` for Home. */137+/** Contents of `panoplie.json` in a project, or `~/.panoplie/home.json` for Home. */
138 export interface ScopeConfig {138 export interface ScopeConfig {
139 version: 1139 version: 1
140 targets: AgentId[]140 targets: AgentId[]
@@ -153,10 +153,10 @@ export interface LockFileEntry {
153 sha256: string153 sha256: string
154 }154 }
155 155
156-/** A key skilly owns inside a shared config file (settings.json, .mcp.json, config.toml, hooks.json, AGENTS.md block). */156+/** A key panoplie owns inside a shared config file (settings.json, .mcp.json, config.toml, hooks.json, AGENTS.md block). */
157 export interface LockConfigEntry {157 export interface LockConfigEntry {
158 file: string158 file: string
159- /** Dotted key path, e.g. `mcpServers.github`, `skillOverrides.seo-audit`, `mcp_servers.github`, `hooks.PreToolUse[skilly:fmt]`, `agents-md-block:typescript-strict`. */159+ /** Dotted key path, e.g. `mcpServers.github`, `skillOverrides.seo-audit`, `mcp_servers.github`, `hooks.PreToolUse[panoplie:fmt]`, `agents-md-block:typescript-strict`. */
160 key: string160 key: string
161 sha256: string161 sha256: string
162 }162 }
@@ -174,7 +174,7 @@ export interface LockEntry {
174 via: string[]174 via: string[]
175 }175 }
176 176
177-/** `skilly.lock` in a project, `~/.skilly/home.lock` for Home. */177+/** `panoplie.lock` in a project, `~/.panoplie/home.lock` for Home. */
178 export interface LockFile {178 export interface LockFile {
179 version: 1179 version: 1
180 /** Catalog git commit at the last sync, when the catalog is a repo. */180 /** Catalog git commit at the last sync, when the catalog is a repo. */
@@ -188,7 +188,7 @@ export type PlanActionKind = 'add' | 'update' | 'remove' | 'unchanged' | 'confli
188 export type ConflictKind =188 export type ConflictKind =
189 /** A managed file was edited in the scope since the last sync. */189 /** A managed file was edited in the scope since the last sync. */
190 | 'modified-locally'190 | 'modified-locally'
191- /** The target path exists but skilly does not own it (not in the lock). */191+ /** The target path exists but panoplie does not own it (not in the lock). */
192 | 'exists-unmanaged'192 | 'exists-unmanaged'
193 /** The item is in the lock but no longer in the catalog. */193 /** The item is in the lock but no longer in the catalog. */
194 | 'missing-in-catalog'194 | 'missing-in-catalog'
@@ -237,7 +237,7 @@ export interface ApplyResult {
237 237
238 /** Status of an inventory item relative to the catalog and the lock, computed by `annotateInventory`. */238 /** Status of an inventory item relative to the catalog and the lock, computed by `annotateInventory`. */
239 export interface ItemStatus {239 export interface ItemStatus {
240- /** True when the item was written by skilly (present in the lock). */240+ /** True when the item was written by panoplie (present in the lock). */
241 managed: boolean241 managed: boolean
242 catalogRef?: CatalogRef242 catalogRef?: CatalogRef
243 state:243 state:
@@ -256,7 +256,7 @@ export interface SyncTarget {
256 root: string256 root: string
257 homeDir: string257 homeDir: string
258 codexHome?: string258 codexHome?: string
259- /** Where scope config and lock are stored: project root for projects, `~/.skilly` for Home. */259+ /** Where scope config and lock are stored: project root for projects, `~/.panoplie` for Home. */
260 configDir: string260 configDir: string
261 }261 }
262 262
modified packages/core/src/catalog/git.ts +19 -13
@@ -4,6 +4,7 @@ import path from 'node:path'
44 import type { GitStatus } from '../catalog-model.ts'
55 import type { CatalogGit, OpenCatalogGit } from '../contracts.ts'
66 import { pathExists, stripBom } from '../util.ts'
7+import { LEGACY_CATALOG_META_DIR } from '../legacy.ts'
78
89 interface GitResult {
910 code: number
@@ -22,7 +23,7 @@ class GitError extends Error {
2223
2324 /**
2425 * Line endings are never converted (Git for Windows ships `core.autocrlf=true`): the catalog files must keep
25- * the exact bytes of the repository, otherwise item hashes (recorded in the committed `skilly.lock`) and
26+ * the exact bytes of the repository, otherwise item hashes (recorded in the committed `panoplie.lock`) and
2627 * rendered files would differ from one machine to another.
2728 */
2829 const NO_EOL_CONVERSION = ['-c', 'core.autocrlf=false', '-c', 'core.eol=lf']
@@ -88,8 +89,8 @@ class GitCatalog implements CatalogGit {
8889 if (i > 0 && line.slice(i + 1).trim()) keys.add(line.slice(0, i).toLowerCase())
8990 }
9091 const args: string[] = []
91- if (!keys.has('user.name')) args.push('-c', 'user.name=skilly')
92- if (!keys.has('user.email')) args.push('-c', 'user.email=skilly@localhost')
92+ if (!keys.has('user.name')) args.push('-c', 'user.name=panoplie')
93+ if (!keys.has('user.email')) args.push('-c', 'user.email=panoplie@localhost')
9394 return args
9495 }
9596
@@ -182,7 +183,7 @@ class GitCatalog implements CatalogGit {
182183 this.identityArgs(),
183184 ])
184185 if (!s.trim() && merge.code !== 0) return undefined
185- await this.git([...identity, 'commit', '-m', message || 'skilly sync'])
186+ await this.git([...identity, 'commit', '-m', message || 'panoplie sync'])
186187 return (await this.currentCommit()) ?? undefined
187188 }
188189
@@ -221,15 +222,20 @@ class GitCatalog implements CatalogGit {
221222 return this.status()
222223 }
223224
224- /** Removes `.skilly/index.json` when it holds no item metadata (the file written by `CatalogStore.init`). */
225+ /**
226+ * Removes `.panoplie/index.json` when it holds no item metadata (the file written by `CatalogStore.init`).
227+ * Same for the index written under the previous name (skilly).
228+ */
225229 private async dropEmptyIndex(): Promise<void> {
226- const file = path.join(this.root, '.skilly', 'index.json')
227- try {
228- const index = JSON.parse(stripBom(await readFile(file, 'utf8'))) as { items?: unknown } | null
229- const items = index?.items
230- if (items && typeof items === 'object' && Object.keys(items).length === 0) await rm(file, { force: true })
231- } catch {
232- // Missing or not ours: left as is (git reports a clear error if it is in the way).
230+ for (const dir of ['.panoplie', LEGACY_CATALOG_META_DIR]) {
231+ const file = path.join(this.root, dir, 'index.json')
232+ try {
233+ const index = JSON.parse(stripBom(await readFile(file, 'utf8'))) as { items?: unknown } | null
234+ const items = index?.items
235+ if (items && typeof items === 'object' && Object.keys(items).length === 0) await rm(file, { force: true })
236+ } catch {
237+ // Missing or not ours: left as is (git reports a clear error if it is in the way).
238+ }
233239 }
234240 }
235241
@@ -245,7 +251,7 @@ class GitCatalog implements CatalogGit {
245251 return this.status()
246252 }
247253
248- async sync(message = 'skilly sync'): Promise<GitStatus> {
254+ async sync(message = 'panoplie sync'): Promise<GitStatus> {
249255 await this.requireRepo()
250256 await this.commitAll(message)
251257 if (!(await this.remoteName())) throw new Error('No git remote configured')
@@ -4,6 +4,7 @@ import path from 'node:path'
4 import type { GitStatus } from '../catalog-model.ts'4 import type { GitStatus } from '../catalog-model.ts'
5 import type { CatalogGit, OpenCatalogGit } from '../contracts.ts'5 import type { CatalogGit, OpenCatalogGit } from '../contracts.ts'
6 import { pathExists, stripBom } from '../util.ts'6 import { pathExists, stripBom } from '../util.ts'
7+import { LEGACY_CATALOG_META_DIR } from '../legacy.ts'
7 8
8 interface GitResult {9 interface GitResult {
9 code: number10 code: number
@@ -22,7 +23,7 @@ class GitError extends Error {
22 23
23 /**24 /**
24 * Line endings are never converted (Git for Windows ships `core.autocrlf=true`): the catalog files must keep25 * Line endings are never converted (Git for Windows ships `core.autocrlf=true`): the catalog files must keep
25- * the exact bytes of the repository, otherwise item hashes (recorded in the committed `skilly.lock`) and26+ * the exact bytes of the repository, otherwise item hashes (recorded in the committed `panoplie.lock`) and
26 * rendered files would differ from one machine to another.27 * rendered files would differ from one machine to another.
27 */28 */
28 const NO_EOL_CONVERSION = ['-c', 'core.autocrlf=false', '-c', 'core.eol=lf']29 const NO_EOL_CONVERSION = ['-c', 'core.autocrlf=false', '-c', 'core.eol=lf']
@@ -88,8 +89,8 @@ class GitCatalog implements CatalogGit {
88 if (i > 0 && line.slice(i + 1).trim()) keys.add(line.slice(0, i).toLowerCase())89 if (i > 0 && line.slice(i + 1).trim()) keys.add(line.slice(0, i).toLowerCase())
89 }90 }
90 const args: string[] = []91 const args: string[] = []
91- if (!keys.has('user.name')) args.push('-c', 'user.name=skilly')92+ if (!keys.has('user.name')) args.push('-c', 'user.name=panoplie')
92- if (!keys.has('user.email')) args.push('-c', 'user.email=skilly@localhost')93+ if (!keys.has('user.email')) args.push('-c', 'user.email=panoplie@localhost')
93 return args94 return args
94 }95 }
95 96
@@ -182,7 +183,7 @@ class GitCatalog implements CatalogGit {
182 this.identityArgs(),183 this.identityArgs(),
183 ])184 ])
184 if (!s.trim() && merge.code !== 0) return undefined185 if (!s.trim() && merge.code !== 0) return undefined
185- await this.git([...identity, 'commit', '-m', message || 'skilly sync'])186+ await this.git([...identity, 'commit', '-m', message || 'panoplie sync'])
186 return (await this.currentCommit()) ?? undefined187 return (await this.currentCommit()) ?? undefined
187 }188 }
188 189
@@ -221,15 +222,20 @@ class GitCatalog implements CatalogGit {
221 return this.status()222 return this.status()
222 }223 }
223 224
224- /** Removes `.skilly/index.json` when it holds no item metadata (the file written by `CatalogStore.init`). */225+ /**
226+ * Removes `.panoplie/index.json` when it holds no item metadata (the file written by `CatalogStore.init`).
227+ * Same for the index written under the previous name (skilly).
228+ */
225 private async dropEmptyIndex(): Promise<void> {229 private async dropEmptyIndex(): Promise<void> {
226- const file = path.join(this.root, '.skilly', 'index.json')230+ for (const dir of ['.panoplie', LEGACY_CATALOG_META_DIR]) {
227- try {231+ const file = path.join(this.root, dir, 'index.json')
228- const index = JSON.parse(stripBom(await readFile(file, 'utf8'))) as { items?: unknown } | null232+ try {
229- const items = index?.items233+ const index = JSON.parse(stripBom(await readFile(file, 'utf8'))) as { items?: unknown } | null
230- if (items && typeof items === 'object' && Object.keys(items).length === 0) await rm(file, { force: true })234+ const items = index?.items
231- } catch {235+ if (items && typeof items === 'object' && Object.keys(items).length === 0) await rm(file, { force: true })
232- // Missing or not ours: left as is (git reports a clear error if it is in the way).236+ } catch {
237+ // Missing or not ours: left as is (git reports a clear error if it is in the way).
238+ }
233 }239 }
234 }240 }
235 241
@@ -245,7 +251,7 @@ class GitCatalog implements CatalogGit {
245 return this.status()251 return this.status()
246 }252 }
247 253
248- async sync(message = 'skilly sync'): Promise<GitStatus> {254+ async sync(message = 'panoplie sync'): Promise<GitStatus> {
249 await this.requireRepo()255 await this.requireRepo()
250 await this.commitAll(message)256 await this.commitAll(message)
251 if (!(await this.remoteName())) throw new Error('No git remote configured')257 if (!(await this.remoteName())) throw new Error('No git remote configured')
modified packages/core/src/catalog/index.ts +13 -5
@@ -1,4 +1,4 @@
1-import { mkdir, readdir, readFile, rm, stat } from 'node:fs/promises'
1+import { mkdir, readdir, readFile, rm, rmdir, stat } from 'node:fs/promises'
22 import path from 'node:path'
33 import { stringify } from 'yaml'
44 import {
@@ -17,6 +17,7 @@ import {
1717 type SaveItemInput,
1818 } from '../catalog-model.ts'
1919 import type { CatalogStore, OpenCatalog } from '../contracts.ts'
20+import { LEGACY_CATALOG_META_DIR } from '../legacy.ts'
2021 import type { HookDetails, InventoryItem, McpDetails } from '../model.ts'
2122 import { hashPath, isDirectory, listDirs, listFilesRecursive, parseFrontmatter, pathExists, pathFingerprint, readText } from '../util.ts'
2223 import { atomicWrite, copyDir, movePath, tmpSuffix } from './fsutil.ts'
@@ -91,19 +92,24 @@ class Catalog implements CatalogStore {
9192 }
9293
9394 private get indexPath(): string {
94- return path.join(this.root, '.skilly', 'index.json')
95+ return path.join(this.root, '.panoplie', 'index.json')
96+ }
97+
98+ /** Index written under the previous name (skilly): read while the new one is missing, removed on the next write. */
99+ private get legacyIndexPath(): string {
100+ return path.join(this.root, LEGACY_CATALOG_META_DIR, 'index.json')
95101 }
96102
97103 // ---- index ----
98104
99105 private async readIndex(): Promise<CatalogIndex> {
100- const text = await readText(this.indexPath)
106+ const text = (await readText(this.indexPath)) ?? (await readText(this.legacyIndexPath))
101107 if (text === undefined) return { version: 1, items: {} }
102108 let raw: unknown
103109 try {
104110 raw = JSON.parse(text.replace(/^/, ''))
105111 } catch (e) {
106- throw new Error(`Invalid .skilly/index.json: ${(e as Error).message}`)
112+ throw new Error(`Invalid .panoplie/index.json: ${(e as Error).message}`)
107113 }
108114 const items = (raw as Partial<CatalogIndex> | null)?.items
109115 return { version: 1, items: items && typeof items === 'object' ? items : {} }
@@ -114,6 +120,8 @@ class Catalog implements CatalogStore {
114120 const index = await this.readIndex()
115121 fn(index)
116122 await atomicWrite(this.indexPath, JSON.stringify(index, null, 2) + '\n')
123+ await rm(this.legacyIndexPath, { force: true })
124+ await rmdir(path.dirname(this.legacyIndexPath)).catch(() => {})
117125 }
118126 const next = this.indexLock.then(run, run)
119127 this.indexLock = next.catch(() => {})
@@ -130,7 +138,7 @@ class Catalog implements CatalogStore {
130138 for (const t of CATALOG_ITEM_TYPES) await mkdir(this.typeDir(t), { recursive: true })
131139 await mkdir(path.join(this.root, 'presets'), { recursive: true })
132140 await mkdir(path.dirname(this.indexPath), { recursive: true })
133- if (!(await pathExists(this.indexPath))) {
141+ if (!(await pathExists(this.indexPath)) && !(await pathExists(this.legacyIndexPath))) {
134142 await atomicWrite(this.indexPath, JSON.stringify({ version: 1, items: {} }, null, 2) + '\n')
135143 }
136144 }
@@ -1,4 +1,4 @@
1-import { mkdir, readdir, readFile, rm, stat } from 'node:fs/promises'1+import { mkdir, readdir, readFile, rm, rmdir, stat } from 'node:fs/promises'
2 import path from 'node:path'2 import path from 'node:path'
3 import { stringify } from 'yaml'3 import { stringify } from 'yaml'
4 import {4 import {
@@ -17,6 +17,7 @@ import {
17 type SaveItemInput,17 type SaveItemInput,
18 } from '../catalog-model.ts'18 } from '../catalog-model.ts'
19 import type { CatalogStore, OpenCatalog } from '../contracts.ts'19 import type { CatalogStore, OpenCatalog } from '../contracts.ts'
20+import { LEGACY_CATALOG_META_DIR } from '../legacy.ts'
20 import type { HookDetails, InventoryItem, McpDetails } from '../model.ts'21 import type { HookDetails, InventoryItem, McpDetails } from '../model.ts'
21 import { hashPath, isDirectory, listDirs, listFilesRecursive, parseFrontmatter, pathExists, pathFingerprint, readText } from '../util.ts'22 import { hashPath, isDirectory, listDirs, listFilesRecursive, parseFrontmatter, pathExists, pathFingerprint, readText } from '../util.ts'
22 import { atomicWrite, copyDir, movePath, tmpSuffix } from './fsutil.ts'23 import { atomicWrite, copyDir, movePath, tmpSuffix } from './fsutil.ts'
@@ -91,19 +92,24 @@ class Catalog implements CatalogStore {
91 }92 }
92 93
93 private get indexPath(): string {94 private get indexPath(): string {
94- return path.join(this.root, '.skilly', 'index.json')95+ return path.join(this.root, '.panoplie', 'index.json')
96+ }
97+
98+ /** Index written under the previous name (skilly): read while the new one is missing, removed on the next write. */
99+ private get legacyIndexPath(): string {
100+ return path.join(this.root, LEGACY_CATALOG_META_DIR, 'index.json')
95 }101 }
96 102
97 // ---- index ----103 // ---- index ----
98 104
99 private async readIndex(): Promise<CatalogIndex> {105 private async readIndex(): Promise<CatalogIndex> {
100- const text = await readText(this.indexPath)106+ const text = (await readText(this.indexPath)) ?? (await readText(this.legacyIndexPath))
101 if (text === undefined) return { version: 1, items: {} }107 if (text === undefined) return { version: 1, items: {} }
102 let raw: unknown108 let raw: unknown
103 try {109 try {
104 raw = JSON.parse(text.replace(/^/, ''))110 raw = JSON.parse(text.replace(/^/, ''))
105 } catch (e) {111 } catch (e) {
106- throw new Error(`Invalid .skilly/index.json: ${(e as Error).message}`)112+ throw new Error(`Invalid .panoplie/index.json: ${(e as Error).message}`)
107 }113 }
108 const items = (raw as Partial<CatalogIndex> | null)?.items114 const items = (raw as Partial<CatalogIndex> | null)?.items
109 return { version: 1, items: items && typeof items === 'object' ? items : {} }115 return { version: 1, items: items && typeof items === 'object' ? items : {} }
@@ -114,6 +120,8 @@ class Catalog implements CatalogStore {
114 const index = await this.readIndex()120 const index = await this.readIndex()
115 fn(index)121 fn(index)
116 await atomicWrite(this.indexPath, JSON.stringify(index, null, 2) + '\n')122 await atomicWrite(this.indexPath, JSON.stringify(index, null, 2) + '\n')
123+ await rm(this.legacyIndexPath, { force: true })
124+ await rmdir(path.dirname(this.legacyIndexPath)).catch(() => {})
117 }125 }
118 const next = this.indexLock.then(run, run)126 const next = this.indexLock.then(run, run)
119 this.indexLock = next.catch(() => {})127 this.indexLock = next.catch(() => {})
@@ -130,7 +138,7 @@ class Catalog implements CatalogStore {
130 for (const t of CATALOG_ITEM_TYPES) await mkdir(this.typeDir(t), { recursive: true })138 for (const t of CATALOG_ITEM_TYPES) await mkdir(this.typeDir(t), { recursive: true })
131 await mkdir(path.join(this.root, 'presets'), { recursive: true })139 await mkdir(path.join(this.root, 'presets'), { recursive: true })
132 await mkdir(path.dirname(this.indexPath), { recursive: true })140 await mkdir(path.dirname(this.indexPath), { recursive: true })
133- if (!(await pathExists(this.indexPath))) {141+ if (!(await pathExists(this.indexPath)) && !(await pathExists(this.legacyIndexPath))) {
134 await atomicWrite(this.indexPath, JSON.stringify({ version: 1, items: {} }, null, 2) + '\n')142 await atomicWrite(this.indexPath, JSON.stringify({ version: 1, items: {} }, null, 2) + '\n')
135 }143 }
136 }144 }
modified packages/core/src/contracts.ts +2 -2
@@ -28,7 +28,7 @@ import type { InventoryItem, ScopeInventory } from './model.ts'
2828 export interface CatalogStore {
2929 readonly root: string
3030 exists(): Promise<boolean>
31- /** Creates the folder layout and `.skilly/index.json` if missing (idempotent). Does not run git. */
31+ /** Creates the folder layout and `.panoplie/index.json` if missing (idempotent). Does not run git. */
3232 init(): Promise<void>
3333 snapshot(): Promise<CatalogSnapshot>
3434 /** `known`: the item as listed by a fresh snapshot, reused instead of hashing the item again. */
@@ -107,7 +107,7 @@ export interface SyncEngine {
107107
108108 /** packages/core/src/marketplace — browse sources and fetch skills. */
109109 export interface Marketplace {
110- /** Built-in + user sources (user sources stored by the server in ~/.skilly/config.json and passed in). */
110+ /** Built-in + user sources (user sources stored by the server in ~/.panoplie/config.json and passed in). */
111111 sources(): MarketplaceSource[]
112112 /** Clones or updates the source in the cache (`git clone --depth 1` / `git pull`). */
113113 refresh(sourceId: string): Promise<MarketplaceSource>
@@ -28,7 +28,7 @@ import type { InventoryItem, ScopeInventory } from './model.ts'
28 export interface CatalogStore {28 export interface CatalogStore {
29 readonly root: string29 readonly root: string
30 exists(): Promise<boolean>30 exists(): Promise<boolean>
31- /** Creates the folder layout and `.skilly/index.json` if missing (idempotent). Does not run git. */31+ /** Creates the folder layout and `.panoplie/index.json` if missing (idempotent). Does not run git. */
32 init(): Promise<void>32 init(): Promise<void>
33 snapshot(): Promise<CatalogSnapshot>33 snapshot(): Promise<CatalogSnapshot>
34 /** `known`: the item as listed by a fresh snapshot, reused instead of hashing the item again. */34 /** `known`: the item as listed by a fresh snapshot, reused instead of hashing the item again. */
@@ -107,7 +107,7 @@ export interface SyncEngine {
107 107
108 /** packages/core/src/marketplace — browse sources and fetch skills. */108 /** packages/core/src/marketplace — browse sources and fetch skills. */
109 export interface Marketplace {109 export interface Marketplace {
110- /** Built-in + user sources (user sources stored by the server in ~/.skilly/config.json and passed in). */110+ /** Built-in + user sources (user sources stored by the server in ~/.panoplie/config.json and passed in). */
111 sources(): MarketplaceSource[]111 sources(): MarketplaceSource[]
112 /** Clones or updates the source in the cache (`git clone --depth 1` / `git pull`). */112 /** Clones or updates the source in the cache (`git clone --depth 1` / `git pull`). */
113 refresh(sourceId: string): Promise<MarketplaceSource>113 refresh(sourceId: string): Promise<MarketplaceSource>
modified packages/core/src/index.ts +1 -0
@@ -8,3 +8,4 @@ export { openCatalogGit } from './catalog/git.ts'
88 export { syncEngine } from './sync/index.ts'
99 export { createMarketplace } from './marketplace/index.ts'
1010 export { normalizePath, hashPath } from './util.ts'
11+export { migrateLegacyConfigDir } from './legacy.ts'
@@ -8,3 +8,4 @@ export { openCatalogGit } from './catalog/git.ts'
8 export { syncEngine } from './sync/index.ts'8 export { syncEngine } from './sync/index.ts'
9 export { createMarketplace } from './marketplace/index.ts'9 export { createMarketplace } from './marketplace/index.ts'
10 export { normalizePath, hashPath } from './util.ts'10 export { normalizePath, hashPath } from './util.ts'
11+export { migrateLegacyConfigDir } from './legacy.ts'
added packages/core/src/legacy.ts +57 -0
new file mode 100644
@@ -0,0 +1,57 @@
1+/**
2+ * Migration from the previous name of the project, skilly. Existing installs keep working:
3+ *
4+ * ~/.skilly/ moved to ~/.panoplie/ on startup (migrateLegacyConfigDir)
5+ * skilly.json / skilly.lock read when the new files are missing, renamed on the next write (sync/config.ts)
6+ * <catalog>/.skilly/index.json read when the new index is missing, removed on the next write (catalog/index.ts)
7+ * <!-- skilly:begin --> AGENTS.md markers still recognized, rewritten on the next change (sync/merge/agents-md.ts)
8+ *
9+ * Everything that reads a legacy name goes through the constants below, so this can be removed in one go later.
10+ */
11+
12+import { readFile, rename } from 'node:fs/promises'
13+import os from 'node:os'
14+import path from 'node:path'
15+import { pathExists } from './util.ts'
16+import { isRecord, writeFileAtomic } from './sync/helpers.ts'
17+
18+export const LEGACY_CONFIG_FILE = 'skilly.json'
19+export const LEGACY_LOCK_FILE = 'skilly.lock'
20+export const LEGACY_CATALOG_META_DIR = '.skilly'
21+export const LEGACY_AGENTS_MD_NAME = 'skilly'
22+
23+export function legacyConfigDir(home = os.homedir()): string {
24+ return path.join(home, '.skilly')
25+}
26+
27+/**
28+ * Moves the legacy machine folder to `configDir` when `configDir` does not exist yet. A catalog path set
29+ * inside the legacy folder is moved along. Returns true when a migration happened.
30+ */
31+export async function migrateLegacyConfigDir(configDir: string, legacyDir = legacyConfigDir()): Promise<boolean> {
32+ if ((await pathExists(configDir)) || !(await pathExists(legacyDir))) return false
33+ try {
34+ await rename(legacyDir, configDir)
35+ } catch (e) {
36+ const reason = e instanceof Error ? e.message : String(e)
37+ throw new Error(
38+ `Could not move ${legacyDir} to ${configDir} (${reason}). Close any running instance and try again, or move the folder by hand.`,
39+ )
40+ }
41+ await relocateCatalogPath(path.join(configDir, 'config.json'), legacyDir, configDir)
42+ return true
43+}
44+
45+async function relocateCatalogPath(file: string, legacyDir: string, configDir: string): Promise<void> {
46+ let raw: unknown
47+ try {
48+ raw = JSON.parse((await readFile(file, 'utf8')).replace(/^/, ''))
49+ } catch {
50+ return
51+ }
52+ if (!isRecord(raw) || !isRecord(raw.settings) || typeof raw.settings.catalogPath !== 'string') return
53+ const rel = path.relative(legacyDir, raw.settings.catalogPath)
54+ if (rel.startsWith('..') || path.isAbsolute(rel)) return
55+ raw.settings.catalogPath = path.join(configDir, rel)
56+ await writeFileAtomic(file, `${JSON.stringify(raw, null, 2)}\n`)
57+}
new file mode 100644
@@ -0,0 +1,57 @@
1+/**
2+ * Migration from the previous name of the project, skilly. Existing installs keep working:
3+ *
4+ * ~/.skilly/ moved to ~/.panoplie/ on startup (migrateLegacyConfigDir)
5+ * skilly.json / skilly.lock read when the new files are missing, renamed on the next write (sync/config.ts)
6+ * <catalog>/.skilly/index.json read when the new index is missing, removed on the next write (catalog/index.ts)
7+ * <!-- skilly:begin --> AGENTS.md markers still recognized, rewritten on the next change (sync/merge/agents-md.ts)
8+ *
9+ * Everything that reads a legacy name goes through the constants below, so this can be removed in one go later.
10+ */
11+
12+import { readFile, rename } from 'node:fs/promises'
13+import os from 'node:os'
14+import path from 'node:path'
15+import { pathExists } from './util.ts'
16+import { isRecord, writeFileAtomic } from './sync/helpers.ts'
17+
18+export const LEGACY_CONFIG_FILE = 'skilly.json'
19+export const LEGACY_LOCK_FILE = 'skilly.lock'
20+export const LEGACY_CATALOG_META_DIR = '.skilly'
21+export const LEGACY_AGENTS_MD_NAME = 'skilly'
22+
23+export function legacyConfigDir(home = os.homedir()): string {
24+ return path.join(home, '.skilly')
25+}
26+
27+/**
28+ * Moves the legacy machine folder to `configDir` when `configDir` does not exist yet. A catalog path set
29+ * inside the legacy folder is moved along. Returns true when a migration happened.
30+ */
31+export async function migrateLegacyConfigDir(configDir: string, legacyDir = legacyConfigDir()): Promise<boolean> {
32+ if ((await pathExists(configDir)) || !(await pathExists(legacyDir))) return false
33+ try {
34+ await rename(legacyDir, configDir)
35+ } catch (e) {
36+ const reason = e instanceof Error ? e.message : String(e)
37+ throw new Error(
38+ `Could not move ${legacyDir} to ${configDir} (${reason}). Close any running instance and try again, or move the folder by hand.`,
39+ )
40+ }
41+ await relocateCatalogPath(path.join(configDir, 'config.json'), legacyDir, configDir)
42+ return true
43+}
44+
45+async function relocateCatalogPath(file: string, legacyDir: string, configDir: string): Promise<void> {
46+ let raw: unknown
47+ try {
48+ raw = JSON.parse((await readFile(file, 'utf8')).replace(/^/, ''))
49+ } catch {
50+ return
51+ }
52+ if (!isRecord(raw) || !isRecord(raw.settings) || typeof raw.settings.catalogPath !== 'string') return
53+ const rel = path.relative(legacyDir, raw.settings.catalogPath)
54+ if (rel.startsWith('..') || path.isAbsolute(rel)) return
55+ raw.settings.catalogPath = path.join(configDir, rel)
56+ await writeFileAtomic(file, `${JSON.stringify(raw, null, 2)}\n`)
57+}
modified packages/core/src/sync/config-entries.ts +4 -4
@@ -1,10 +1,10 @@
11 /**
2- * Keys skilly owns inside shared files, and how to read / write / remove them.
2+ * Keys panoplie owns inside shared files, and how to read / write / remove them.
33 *
44 * Lock key formats (`LockConfigEntry.key`):
55 * `mcpServers.<name>`, `skillOverrides.<name>` JSON object entry (first dot separates the container)
66 * `hooks.<Event>[<name>]`, `deniedMcpServers[<name>]`
7- * JSON array item; found by the hash of what skilly wrote
7+ * JSON array item; found by the hash of what panoplie wrote
88 * (JSON has no comments to mark it)
99 * `mcp_servers.<name>` in a `.toml` file TOML table, edited surgically
1010 * `agents-md-block:<name>` rule section of the AGENTS.md managed block
@@ -32,7 +32,7 @@ export type ConfigLocation =
3232 | { kind: 'toml-table'; file: string; key: string; path: string[] }
3333 | { kind: 'agents-md-section'; file: string; key: string; name: string }
3434
35-/** A value skilly wants at a location. `value` is a string (section text) for AGENTS.md. */
35+/** A value panoplie wants at a location. `value` is a string (section text) for AGENTS.md. */
3636 export interface ConfigWrite {
3737 loc: ConfigLocation
3838 value: unknown
@@ -112,7 +112,7 @@ export function entryMatches(text: string | undefined, loc: ConfigLocation, sha:
112112 }
113113
114114 /**
115- * Writes the value. For array items, `previousSha` identifies the item skilly wrote last time (replaced in place).
115+ * Writes the value. For array items, `previousSha` identifies the item panoplie wrote last time (replaced in place).
116116 * Returns the new text, or undefined when the file should be deleted.
117117 */
118118 export function setEntry(text: string | undefined, write: ConfigWrite, previousSha?: string): string | undefined {
@@ -1,10 +1,10 @@
1 /**1 /**
2- * Keys skilly owns inside shared files, and how to read / write / remove them.2+ * Keys panoplie owns inside shared files, and how to read / write / remove them.
3 *3 *
4 * Lock key formats (`LockConfigEntry.key`):4 * Lock key formats (`LockConfigEntry.key`):
5 * `mcpServers.<name>`, `skillOverrides.<name>` JSON object entry (first dot separates the container)5 * `mcpServers.<name>`, `skillOverrides.<name>` JSON object entry (first dot separates the container)
6 * `hooks.<Event>[<name>]`, `deniedMcpServers[<name>]`6 * `hooks.<Event>[<name>]`, `deniedMcpServers[<name>]`
7- * JSON array item; found by the hash of what skilly wrote7+ * JSON array item; found by the hash of what panoplie wrote
8 * (JSON has no comments to mark it)8 * (JSON has no comments to mark it)
9 * `mcp_servers.<name>` in a `.toml` file TOML table, edited surgically9 * `mcp_servers.<name>` in a `.toml` file TOML table, edited surgically
10 * `agents-md-block:<name>` rule section of the AGENTS.md managed block10 * `agents-md-block:<name>` rule section of the AGENTS.md managed block
@@ -32,7 +32,7 @@ export type ConfigLocation =
32 | { kind: 'toml-table'; file: string; key: string; path: string[] }32 | { kind: 'toml-table'; file: string; key: string; path: string[] }
33 | { kind: 'agents-md-section'; file: string; key: string; name: string }33 | { kind: 'agents-md-section'; file: string; key: string; name: string }
34 34
35-/** A value skilly wants at a location. `value` is a string (section text) for AGENTS.md. */35+/** A value panoplie wants at a location. `value` is a string (section text) for AGENTS.md. */
36 export interface ConfigWrite {36 export interface ConfigWrite {
37 loc: ConfigLocation37 loc: ConfigLocation
38 value: unknown38 value: unknown
@@ -112,7 +112,7 @@ export function entryMatches(text: string | undefined, loc: ConfigLocation, sha:
112 }112 }
113 113
114 /**114 /**
115- * Writes the value. For array items, `previousSha` identifies the item skilly wrote last time (replaced in place).115+ * Writes the value. For array items, `previousSha` identifies the item panoplie wrote last time (replaced in place).
116 * Returns the new text, or undefined when the file should be deleted.116 * Returns the new text, or undefined when the file should be deleted.
117 */117 */
118 export function setEntry(text: string | undefined, write: ConfigWrite, previousSha?: string): string | undefined {118 export function setEntry(text: string | undefined, write: ConfigWrite, previousSha?: string): string | undefined {
modified packages/core/src/sync/config.ts +34 -6
@@ -1,17 +1,43 @@
1-/** Scope configuration (`skilly.json` / `home.json`) and lock (`skilly.lock` / `home.lock`) files. */
1+/** Scope configuration (`panoplie.json` / `home.json`) and lock (`panoplie.lock` / `home.lock`) files. */
22
3+import { rename } from 'node:fs/promises'
34 import path from 'node:path'
45 import { CATALOG_ITEM_TYPES, type CatalogItemType, type LockEntry, type LockFile, type ScopeConfig, type SyncTarget } from '../catalog-model.ts'
56 import { AGENTS, type AgentId } from '../model.ts'
6-import { readJson } from '../util.ts'
7+import { LEGACY_CONFIG_FILE, LEGACY_LOCK_FILE } from '../legacy.ts'
8+import { pathExists, readJson } from '../util.ts'
79 import { isRecord, writeFileAtomic } from './helpers.ts'
810
911 export function configFile(target: SyncTarget): string {
10- return path.join(target.configDir, target.kind === 'home' ? 'home.json' : 'skilly.json')
12+ return path.join(target.configDir, target.kind === 'home' ? 'home.json' : 'panoplie.json')
1113 }
1214
1315 export function lockFile(target: SyncTarget): string {
14- return path.join(target.configDir, target.kind === 'home' ? 'home.lock' : 'skilly.lock')
16+ return path.join(target.configDir, target.kind === 'home' ? 'home.lock' : 'panoplie.lock')
17+}
18+
19+/** Project files written under the previous name (skilly). Home files kept their names. */
20+function legacyFiles(target: SyncTarget): [legacy: string, current: string][] {
21+ if (target.kind === 'home') return []
22+ return [
23+ [path.join(target.configDir, LEGACY_CONFIG_FILE), configFile(target)],
24+ [path.join(target.configDir, LEGACY_LOCK_FILE), lockFile(target)],
25+ ]
26+}
27+
28+/** Reads `file`, or its legacy counterpart when only the legacy one exists. */
29+async function readJsonWithLegacy(target: SyncTarget, file: string): Promise<unknown> {
30+ const raw = await readJson(file)
31+ if (raw !== undefined) return raw
32+ const legacy = legacyFiles(target).find(([, current]) => current === file)?.[0]
33+ return legacy === undefined ? undefined : readJson(legacy)
34+}
35+
36+/** Renames the legacy config and lock before a write, so both files move together. */
37+async function migrateLegacyFiles(target: SyncTarget): Promise<void> {
38+ for (const [legacy, current] of legacyFiles(target)) {
39+ if ((await pathExists(legacy)) && !(await pathExists(current))) await rename(legacy, current)
40+ }
1541 }
1642
1743 export function defaultConfig(): ScopeConfig {
@@ -77,11 +103,12 @@ export function formatJsonFile(value: unknown): string {
77103 }
78104
79105 export async function readConfig(target: SyncTarget): Promise<ScopeConfig | undefined> {
80- const raw = await readJson(configFile(target))
106+ const raw = await readJsonWithLegacy(target, configFile(target))
81107 return raw === undefined ? undefined : normalizeConfig(raw)
82108 }
83109
84110 export async function writeConfig(target: SyncTarget, config: ScopeConfig): Promise<void> {
111+ await migrateLegacyFiles(target)
85112 await writeFileAtomic(configFile(target), formatJsonFile(orderedConfig(config)))
86113 }
87114
@@ -118,7 +145,7 @@ export function sortLockEntries(entries: LockEntry[]): LockEntry[] {
118145 }
119146
120147 export async function readLock(target: SyncTarget): Promise<LockFile | undefined> {
121- const raw = await readJson(lockFile(target))
148+ const raw = await readJsonWithLegacy(target, lockFile(target))
122149 if (raw === undefined) return undefined
123150 const r = isRecord(raw) ? raw : {}
124151 const entries = (Array.isArray(r.entries) ? r.entries : []).map(normalizeEntry).filter((e) => e !== undefined)
@@ -147,5 +174,6 @@ export async function writeLock(target: SyncTarget, lock: LockFile): Promise<voi
147174 via: e.via,
148175 })),
149176 }
177+ await migrateLegacyFiles(target)
150178 await writeFileAtomic(lockFile(target), formatJsonFile(ordered))
151179 }
@@ -1,17 +1,43 @@
1-/** Scope configuration (`skilly.json` / `home.json`) and lock (`skilly.lock` / `home.lock`) files. */1+/** Scope configuration (`panoplie.json` / `home.json`) and lock (`panoplie.lock` / `home.lock`) files. */
2 2
3+import { rename } from 'node:fs/promises'
3 import path from 'node:path'4 import path from 'node:path'
4 import { CATALOG_ITEM_TYPES, type CatalogItemType, type LockEntry, type LockFile, type ScopeConfig, type SyncTarget } from '../catalog-model.ts'5 import { CATALOG_ITEM_TYPES, type CatalogItemType, type LockEntry, type LockFile, type ScopeConfig, type SyncTarget } from '../catalog-model.ts'
5 import { AGENTS, type AgentId } from '../model.ts'6 import { AGENTS, type AgentId } from '../model.ts'
6-import { readJson } from '../util.ts'7+import { LEGACY_CONFIG_FILE, LEGACY_LOCK_FILE } from '../legacy.ts'
8+import { pathExists, readJson } from '../util.ts'
7 import { isRecord, writeFileAtomic } from './helpers.ts'9 import { isRecord, writeFileAtomic } from './helpers.ts'
8 10
9 export function configFile(target: SyncTarget): string {11 export function configFile(target: SyncTarget): string {
10- return path.join(target.configDir, target.kind === 'home' ? 'home.json' : 'skilly.json')12+ return path.join(target.configDir, target.kind === 'home' ? 'home.json' : 'panoplie.json')
11 }13 }
12 14
13 export function lockFile(target: SyncTarget): string {15 export function lockFile(target: SyncTarget): string {
14- return path.join(target.configDir, target.kind === 'home' ? 'home.lock' : 'skilly.lock')16+ return path.join(target.configDir, target.kind === 'home' ? 'home.lock' : 'panoplie.lock')
17+}
18+
19+/** Project files written under the previous name (skilly). Home files kept their names. */
20+function legacyFiles(target: SyncTarget): [legacy: string, current: string][] {
21+ if (target.kind === 'home') return []
22+ return [
23+ [path.join(target.configDir, LEGACY_CONFIG_FILE), configFile(target)],
24+ [path.join(target.configDir, LEGACY_LOCK_FILE), lockFile(target)],
25+ ]
26+}
27+
28+/** Reads `file`, or its legacy counterpart when only the legacy one exists. */
29+async function readJsonWithLegacy(target: SyncTarget, file: string): Promise<unknown> {
30+ const raw = await readJson(file)
31+ if (raw !== undefined) return raw
32+ const legacy = legacyFiles(target).find(([, current]) => current === file)?.[0]
33+ return legacy === undefined ? undefined : readJson(legacy)
34+}
35+
36+/** Renames the legacy config and lock before a write, so both files move together. */
37+async function migrateLegacyFiles(target: SyncTarget): Promise<void> {
38+ for (const [legacy, current] of legacyFiles(target)) {
39+ if ((await pathExists(legacy)) && !(await pathExists(current))) await rename(legacy, current)
40+ }
15 }41 }
16 42
17 export function defaultConfig(): ScopeConfig {43 export function defaultConfig(): ScopeConfig {
@@ -77,11 +103,12 @@ export function formatJsonFile(value: unknown): string {
77 }103 }
78 104
79 export async function readConfig(target: SyncTarget): Promise<ScopeConfig | undefined> {105 export async function readConfig(target: SyncTarget): Promise<ScopeConfig | undefined> {
80- const raw = await readJson(configFile(target))106+ const raw = await readJsonWithLegacy(target, configFile(target))
81 return raw === undefined ? undefined : normalizeConfig(raw)107 return raw === undefined ? undefined : normalizeConfig(raw)
82 }108 }
83 109
84 export async function writeConfig(target: SyncTarget, config: ScopeConfig): Promise<void> {110 export async function writeConfig(target: SyncTarget, config: ScopeConfig): Promise<void> {
111+ await migrateLegacyFiles(target)
85 await writeFileAtomic(configFile(target), formatJsonFile(orderedConfig(config)))112 await writeFileAtomic(configFile(target), formatJsonFile(orderedConfig(config)))
86 }113 }
87 114
@@ -118,7 +145,7 @@ export function sortLockEntries(entries: LockEntry[]): LockEntry[] {
118 }145 }
119 146
120 export async function readLock(target: SyncTarget): Promise<LockFile | undefined> {147 export async function readLock(target: SyncTarget): Promise<LockFile | undefined> {
121- const raw = await readJson(lockFile(target))148+ const raw = await readJsonWithLegacy(target, lockFile(target))
122 if (raw === undefined) return undefined149 if (raw === undefined) return undefined
123 const r = isRecord(raw) ? raw : {}150 const r = isRecord(raw) ? raw : {}
124 const entries = (Array.isArray(r.entries) ? r.entries : []).map(normalizeEntry).filter((e) => e !== undefined)151 const entries = (Array.isArray(r.entries) ? r.entries : []).map(normalizeEntry).filter((e) => e !== undefined)
@@ -147,5 +174,6 @@ export async function writeLock(target: SyncTarget, lock: LockFile): Promise<voi
147 via: e.via,174 via: e.via,
148 })),175 })),
149 }176 }
177+ await migrateLegacyFiles(target)
150 await writeFileAtomic(lockFile(target), formatJsonFile(ordered))178 await writeFileAtomic(lockFile(target), formatJsonFile(ordered))
151 }179 }
modified packages/core/src/sync/helpers.ts +1 -1
@@ -88,7 +88,7 @@ export function hashFiles(files: { rel: string; content: Buffer }[]): string {
8888 /** Writes through a temporary file and a rename, creating parent folders. */
8989 export async function writeFileAtomic(file: string, content: string | Buffer): Promise<void> {
9090 await mkdir(path.dirname(file), { recursive: true })
91- const tmp = `${file}.skilly-${randomBytes(4).toString('hex')}.tmp`
91+ const tmp = `${file}.panoplie-${randomBytes(4).toString('hex')}.tmp`
9292 try {
9393 await writeFile(tmp, content)
9494 await rename(tmp, file)
@@ -88,7 +88,7 @@ export function hashFiles(files: { rel: string; content: Buffer }[]): string {
88 /** Writes through a temporary file and a rename, creating parent folders. */88 /** Writes through a temporary file and a rename, creating parent folders. */
89 export async function writeFileAtomic(file: string, content: string | Buffer): Promise<void> {89 export async function writeFileAtomic(file: string, content: string | Buffer): Promise<void> {
90 await mkdir(path.dirname(file), { recursive: true })90 await mkdir(path.dirname(file), { recursive: true })
91- const tmp = `${file}.skilly-${randomBytes(4).toString('hex')}.tmp`91+ const tmp = `${file}.panoplie-${randomBytes(4).toString('hex')}.tmp`
92 try {92 try {
93 await writeFile(tmp, content)93 await writeFile(tmp, content)
94 await rename(tmp, file)94 await rename(tmp, file)
modified packages/core/src/sync/index.ts +2 -2
@@ -1,12 +1,12 @@
11 /**
22 * Sync engine: scope configuration, rendering per agent, plan / apply, drift annotation, Home cleanup.
33 *
4- * config.ts skilly.json / home.json and skilly.lock / home.lock
4+ * config.ts panoplie.json / home.json and panoplie.lock / home.lock
55 * resolve.ts presets + add - exclude -> catalog refs
66 * layout.ts where each agent reads items (project and Home)
77 * render/ catalog item -> files and config entries, per agent; hidden global items
88 * merge/ surgical edits of shared files (JSON, TOML tables, AGENTS.md block)
9- * config-entries.ts keys skilly owns in shared files (lock key formats)
9+ * config-entries.ts keys panoplie owns in shared files (lock key formats)
1010 * plan.ts desired renders vs lock vs disk -> actions (pure)
1111 * apply.ts executes actions and conflict resolutions, writes the lock
1212 * push.ts push-to-catalog conversions
@@ -1,12 +1,12 @@
1 /**1 /**
2 * Sync engine: scope configuration, rendering per agent, plan / apply, drift annotation, Home cleanup.2 * Sync engine: scope configuration, rendering per agent, plan / apply, drift annotation, Home cleanup.
3 *3 *
4- * config.ts skilly.json / home.json and skilly.lock / home.lock4+ * config.ts panoplie.json / home.json and panoplie.lock / home.lock
5 * resolve.ts presets + add - exclude -> catalog refs5 * resolve.ts presets + add - exclude -> catalog refs
6 * layout.ts where each agent reads items (project and Home)6 * layout.ts where each agent reads items (project and Home)
7 * render/ catalog item -> files and config entries, per agent; hidden global items7 * render/ catalog item -> files and config entries, per agent; hidden global items
8 * merge/ surgical edits of shared files (JSON, TOML tables, AGENTS.md block)8 * merge/ surgical edits of shared files (JSON, TOML tables, AGENTS.md block)
9- * config-entries.ts keys skilly owns in shared files (lock key formats)9+ * config-entries.ts keys panoplie owns in shared files (lock key formats)
10 * plan.ts desired renders vs lock vs disk -> actions (pure)10 * plan.ts desired renders vs lock vs disk -> actions (pure)
11 * apply.ts executes actions and conflict resolutions, writes the lock11 * apply.ts executes actions and conflict resolutions, writes the lock
12 * push.ts push-to-catalog conversions12 * push.ts push-to-catalog conversions
modified packages/core/src/sync/layout.ts +1 -1
@@ -45,7 +45,7 @@ function projectLayout(): Layout {
4545 codexAgentFile: (n) => `.codex/agents/${n}.toml`,
4646 codexHooks: '.codex/hooks.json',
4747 codexConfig: '.codex/config.toml',
48- // Empty folders mean nothing to git: in projects every empty folder skilly leaves is removed.
48+ // Empty folders mean nothing to git: in projects every empty folder panoplie leaves is removed.
4949 keepDirs: [''],
5050 }
5151 }
@@ -45,7 +45,7 @@ function projectLayout(): Layout {
45 codexAgentFile: (n) => `.codex/agents/${n}.toml`,45 codexAgentFile: (n) => `.codex/agents/${n}.toml`,
46 codexHooks: '.codex/hooks.json',46 codexHooks: '.codex/hooks.json',
47 codexConfig: '.codex/config.toml',47 codexConfig: '.codex/config.toml',
48- // Empty folders mean nothing to git: in projects every empty folder skilly leaves is removed.48+ // Empty folders mean nothing to git: in projects every empty folder panoplie leaves is removed.
49 keepDirs: [''],49 keepDirs: [''],
50 }50 }
51 }51 }
modified packages/core/src/sync/merge/agents-md.ts +22 -13
@@ -1,23 +1,27 @@
11 /**
22 * Managed block in AGENTS.md. Content outside the block is never touched.
33 *
4- * <!-- skilly:begin (generated by skilly, do not edit) -->
5- * <!-- skilly:rule typescript-strict -->
4+ * <!-- panoplie:begin (generated by panoplie, do not edit) -->
5+ * <!-- panoplie:rule typescript-strict -->
66 * ## typescript-strict (applies to **\/*.ts, **\/*.vue)
77 *
88 * body
9- * <!-- skilly:end -->
9+ * <!-- panoplie:end -->
1010 *
11- * Each rule section is preceded by an invisible `skilly:rule` marker so that headings inside rule bodies
11+ * Each rule section is preceded by an invisible `panoplie:rule` marker so that headings inside rule bodies
1212 * cannot be mistaken for section boundaries. Sections are sorted by rule name.
1313 */
1414
15+import { LEGACY_AGENTS_MD_NAME } from '../../legacy.ts'
1516 import { detectEol } from '../helpers.ts'
1617
17-export const BLOCK_BEGIN = '<!-- skilly:begin (generated by skilly, do not edit) -->'
18-export const BLOCK_END = '<!-- skilly:end -->'
19-const BEGIN_PREFIX = '<!-- skilly:begin'
20-const SECTION_MARKER = /^<!-- skilly:rule (\S+) -->\s*$/
18+export const BLOCK_BEGIN = '<!-- panoplie:begin (generated by panoplie, do not edit) -->'
19+export const BLOCK_END = '<!-- panoplie:end -->'
20+const BEGIN_PREFIX = '<!-- panoplie:begin'
21+/** Blocks written under the previous name (skilly) are still read; they get the new markers on the next write. */
22+const LEGACY_BEGIN_PREFIX = `<!-- ${LEGACY_AGENTS_MD_NAME}:begin`
23+const LEGACY_BLOCK_END = `<!-- ${LEGACY_AGENTS_MD_NAME}:end -->`
24+const SECTION_MARKER = /^<!-- (?:panoplie|skilly):rule (\S+) -->\s*$/
2125
2226 interface ParsedAgentsMd {
2327 /** Text before the begin marker line (or the whole file when there is no block). */
@@ -41,14 +45,19 @@ export function normalizeSection(text: string): string {
4145
4246 export function parseAgentsMd(text: string | undefined): ParsedAgentsMd {
4347 const source = text ?? ''
44- const begin = findLineStart(source, BEGIN_PREFIX)
48+ let begin = findLineStart(source, BEGIN_PREFIX)
49+ let endMarker = BLOCK_END
50+ if (begin < 0) {
51+ begin = findLineStart(source, LEGACY_BEGIN_PREFIX)
52+ endMarker = LEGACY_BLOCK_END
53+ }
4554 if (begin < 0) return { before: source, after: '', hasBlock: false, sections: new Map() }
4655 const innerStart = lineEnd(source, begin)
47- const end = source.indexOf(BLOCK_END, innerStart)
48- if (end < 0) throw new Error('AGENTS.md: the skilly block has no end marker; fix it by hand')
56+ const end = source.indexOf(endMarker, innerStart)
57+ if (end < 0) throw new Error('AGENTS.md: the panoplie block has no end marker; fix it by hand')
4958 return {
5059 before: source.slice(0, begin),
51- after: source.slice(end + BLOCK_END.length),
60+ after: source.slice(end + endMarker.length),
5261 hasBlock: true,
5362 sections: parseSections(source.slice(innerStart, end)),
5463 }
@@ -88,7 +97,7 @@ function renderAgentsMd(p: ParsedAgentsMd, eol: string): string | undefined {
8897
8998 function renderBlock(sections: Map<string, string>): string {
9099 const names = [...sections.keys()].sort((a, b) => a.localeCompare(b))
91- const body = names.map((n) => `<!-- skilly:rule ${n} -->\n${sections.get(n)}`).join('\n\n')
100+ const body = names.map((n) => `<!-- panoplie:rule ${n} -->\n${sections.get(n)}`).join('\n\n')
92101 return `${BLOCK_BEGIN}\n${body}\n${BLOCK_END}`
93102 }
94103
@@ -1,23 +1,27 @@
1 /**1 /**
2 * Managed block in AGENTS.md. Content outside the block is never touched.2 * Managed block in AGENTS.md. Content outside the block is never touched.
3 *3 *
4- * <!-- skilly:begin (generated by skilly, do not edit) -->4+ * <!-- panoplie:begin (generated by panoplie, do not edit) -->
5- * <!-- skilly:rule typescript-strict -->5+ * <!-- panoplie:rule typescript-strict -->
6 * ## typescript-strict (applies to **\/*.ts, **\/*.vue)6 * ## typescript-strict (applies to **\/*.ts, **\/*.vue)
7 *7 *
8 * body8 * body
9- * <!-- skilly:end -->9+ * <!-- panoplie:end -->
10 *10 *
11- * Each rule section is preceded by an invisible `skilly:rule` marker so that headings inside rule bodies11+ * Each rule section is preceded by an invisible `panoplie:rule` marker so that headings inside rule bodies
12 * cannot be mistaken for section boundaries. Sections are sorted by rule name.12 * cannot be mistaken for section boundaries. Sections are sorted by rule name.
13 */13 */
14 14
15+import { LEGACY_AGENTS_MD_NAME } from '../../legacy.ts'
15 import { detectEol } from '../helpers.ts'16 import { detectEol } from '../helpers.ts'
16 17
17-export const BLOCK_BEGIN = '<!-- skilly:begin (generated by skilly, do not edit) -->'18+export const BLOCK_BEGIN = '<!-- panoplie:begin (generated by panoplie, do not edit) -->'
18-export const BLOCK_END = '<!-- skilly:end -->'19+export const BLOCK_END = '<!-- panoplie:end -->'
19-const BEGIN_PREFIX = '<!-- skilly:begin'20+const BEGIN_PREFIX = '<!-- panoplie:begin'
20-const SECTION_MARKER = /^<!-- skilly:rule (\S+) -->\s*$/21+/** Blocks written under the previous name (skilly) are still read; they get the new markers on the next write. */
22+const LEGACY_BEGIN_PREFIX = `<!-- ${LEGACY_AGENTS_MD_NAME}:begin`
23+const LEGACY_BLOCK_END = `<!-- ${LEGACY_AGENTS_MD_NAME}:end -->`
24+const SECTION_MARKER = /^<!-- (?:panoplie|skilly):rule (\S+) -->\s*$/
21 25
22 interface ParsedAgentsMd {26 interface ParsedAgentsMd {
23 /** Text before the begin marker line (or the whole file when there is no block). */27 /** Text before the begin marker line (or the whole file when there is no block). */
@@ -41,14 +45,19 @@ export function normalizeSection(text: string): string {
41 45
42 export function parseAgentsMd(text: string | undefined): ParsedAgentsMd {46 export function parseAgentsMd(text: string | undefined): ParsedAgentsMd {
43 const source = text ?? ''47 const source = text ?? ''
44- const begin = findLineStart(source, BEGIN_PREFIX)48+ let begin = findLineStart(source, BEGIN_PREFIX)
49+ let endMarker = BLOCK_END
50+ if (begin < 0) {
51+ begin = findLineStart(source, LEGACY_BEGIN_PREFIX)
52+ endMarker = LEGACY_BLOCK_END
53+ }
45 if (begin < 0) return { before: source, after: '', hasBlock: false, sections: new Map() }54 if (begin < 0) return { before: source, after: '', hasBlock: false, sections: new Map() }
46 const innerStart = lineEnd(source, begin)55 const innerStart = lineEnd(source, begin)
47- const end = source.indexOf(BLOCK_END, innerStart)56+ const end = source.indexOf(endMarker, innerStart)
48- if (end < 0) throw new Error('AGENTS.md: the skilly block has no end marker; fix it by hand')57+ if (end < 0) throw new Error('AGENTS.md: the panoplie block has no end marker; fix it by hand')
49 return {58 return {
50 before: source.slice(0, begin),59 before: source.slice(0, begin),
51- after: source.slice(end + BLOCK_END.length),60+ after: source.slice(end + endMarker.length),
52 hasBlock: true,61 hasBlock: true,
53 sections: parseSections(source.slice(innerStart, end)),62 sections: parseSections(source.slice(innerStart, end)),
54 }63 }
@@ -88,7 +97,7 @@ function renderAgentsMd(p: ParsedAgentsMd, eol: string): string | undefined {
88 97
89 function renderBlock(sections: Map<string, string>): string {98 function renderBlock(sections: Map<string, string>): string {
90 const names = [...sections.keys()].sort((a, b) => a.localeCompare(b))99 const names = [...sections.keys()].sort((a, b) => a.localeCompare(b))
91- const body = names.map((n) => `<!-- skilly:rule ${n} -->\n${sections.get(n)}`).join('\n\n')100+ const body = names.map((n) => `<!-- panoplie:rule ${n} -->\n${sections.get(n)}`).join('\n\n')
92 return `${BLOCK_BEGIN}\n${body}\n${BLOCK_END}`101 return `${BLOCK_BEGIN}\n${body}\n${BLOCK_END}`
93 }102 }
94 103
modified packages/core/src/sync/plan.ts +2 -2
@@ -187,7 +187,7 @@ async function classifyDesired(d: DesiredItem, entry: LockEntry | undefined, dis
187187 if (state === 'identical') {
188188 return { action: makeAction('add', d, 'Already present with identical content; taken over', parts), desired: d, wanted: true }
189189 }
190- const action = makeAction('conflict', d, 'The target already exists and is not managed by skilly', parts, {
190+ const action = makeAction('conflict', d, 'The target already exists and is not managed by panoplie', parts, {
191191 conflict: 'exists-unmanaged',
192192 })
193193 return { action, desired: d, wanted: true }
@@ -230,7 +230,7 @@ export function ownedDir(entry: LockEntry): string | undefined {
230230 return top ? path.posix.dirname(top) : undefined
231231 }
232232
233-/** True when every file and key recorded in the lock still holds what skilly wrote (and nothing was added to an owned folder). */
233+/** True when every file and key recorded in the lock still holds what panoplie wrote (and nothing was added to an owned folder). */
234234 export async function diskMatchesLock(entry: LockEntry, disk: DiskReader): Promise<boolean> {
235235 for (const f of entry.files) {
236236 if ((await disk.fileHash(f.path)) !== f.sha256) return false
@@ -187,7 +187,7 @@ async function classifyDesired(d: DesiredItem, entry: LockEntry | undefined, dis
187 if (state === 'identical') {187 if (state === 'identical') {
188 return { action: makeAction('add', d, 'Already present with identical content; taken over', parts), desired: d, wanted: true }188 return { action: makeAction('add', d, 'Already present with identical content; taken over', parts), desired: d, wanted: true }
189 }189 }
190- const action = makeAction('conflict', d, 'The target already exists and is not managed by skilly', parts, {190+ const action = makeAction('conflict', d, 'The target already exists and is not managed by panoplie', parts, {
191 conflict: 'exists-unmanaged',191 conflict: 'exists-unmanaged',
192 })192 })
193 return { action, desired: d, wanted: true }193 return { action, desired: d, wanted: true }
@@ -230,7 +230,7 @@ export function ownedDir(entry: LockEntry): string | undefined {
230 return top ? path.posix.dirname(top) : undefined230 return top ? path.posix.dirname(top) : undefined
231 }231 }
232 232
233-/** True when every file and key recorded in the lock still holds what skilly wrote (and nothing was added to an owned folder). */233+/** True when every file and key recorded in the lock still holds what panoplie wrote (and nothing was added to an owned folder). */
234 export async function diskMatchesLock(entry: LockEntry, disk: DiskReader): Promise<boolean> {234 export async function diskMatchesLock(entry: LockEntry, disk: DiskReader): Promise<boolean> {
235 for (const f of entry.files) {235 for (const f of entry.files) {
236 if ((await disk.fileHash(f.path)) !== f.sha256) return false236 if ((await disk.fileHash(f.path)) !== f.sha256) return false
modified packages/core/src/sync/push.ts +2 -2
@@ -1,7 +1,7 @@
11 /**
22 * push-to-catalog: converts the scope version of an item back to the catalog format and saves it.
33 * Supported: skills, rules, subagents, MCP servers. Hooks and hidden global items cannot be pushed
4- * (a modified hook group cannot be located once it differs from what skilly wrote).
4+ * (a modified hook group cannot be located once it differs from what panoplie wrote).
55 */
66
77 import { readFile } from 'node:fs/promises'
@@ -65,7 +65,7 @@ async function readRequired(ctx: PushContext, rel: string): Promise<string> {
6565
6666 async function skillVersion(ref: CatalogRef, agent: AgentId, ctx: PushContext): Promise<SaveItemInput> {
6767 const dir = agent === 'claude' ? ctx.layout.claudeSkillDir(ref.name) : ctx.layout.codexSkillDir(ref.name)
68- // The Codex on-demand policy file is generated by skilly: it is not part of the skill.
68+ // The Codex on-demand policy file is generated by panoplie: it is not part of the skill.
6969 const skipGenerated = agent === 'codex' && matchesAny(ctx.config.onDemand.skills, ref.name)
7070 const local = (await ctx.disk.listFiles(dir)).filter((rel) => !(skipGenerated && rel === OPENAI_YAML))
7171 if (!local.includes('SKILL.md')) throw new Error(`${dir}/SKILL.md does not exist`)
@@ -1,7 +1,7 @@
1 /**1 /**
2 * push-to-catalog: converts the scope version of an item back to the catalog format and saves it.2 * push-to-catalog: converts the scope version of an item back to the catalog format and saves it.
3 * Supported: skills, rules, subagents, MCP servers. Hooks and hidden global items cannot be pushed3 * Supported: skills, rules, subagents, MCP servers. Hooks and hidden global items cannot be pushed
4- * (a modified hook group cannot be located once it differs from what skilly wrote).4+ * (a modified hook group cannot be located once it differs from what panoplie wrote).
5 */5 */
6 6
7 import { readFile } from 'node:fs/promises'7 import { readFile } from 'node:fs/promises'
@@ -65,7 +65,7 @@ async function readRequired(ctx: PushContext, rel: string): Promise<string> {
65 65
66 async function skillVersion(ref: CatalogRef, agent: AgentId, ctx: PushContext): Promise<SaveItemInput> {66 async function skillVersion(ref: CatalogRef, agent: AgentId, ctx: PushContext): Promise<SaveItemInput> {
67 const dir = agent === 'claude' ? ctx.layout.claudeSkillDir(ref.name) : ctx.layout.codexSkillDir(ref.name)67 const dir = agent === 'claude' ? ctx.layout.claudeSkillDir(ref.name) : ctx.layout.codexSkillDir(ref.name)
68- // The Codex on-demand policy file is generated by skilly: it is not part of the skill.68+ // The Codex on-demand policy file is generated by panoplie: it is not part of the skill.
69 const skipGenerated = agent === 'codex' && matchesAny(ctx.config.onDemand.skills, ref.name)69 const skipGenerated = agent === 'codex' && matchesAny(ctx.config.onDemand.skills, ref.name)
70 const local = (await ctx.disk.listFiles(dir)).filter((rel) => !(skipGenerated && rel === OPENAI_YAML))70 const local = (await ctx.disk.listFiles(dir)).filter((rel) => !(skipGenerated && rel === OPENAI_YAML))
71 if (!local.includes('SKILL.md')) throw new Error(`${dir}/SKILL.md does not exist`)71 if (!local.includes('SKILL.md')) throw new Error(`${dir}/SKILL.md does not exist`)
modified packages/core/src/sync/render/types.ts +1 -1
@@ -10,7 +10,7 @@ export interface RenderedFile {
1010 sha256: string
1111 }
1212
13-/** What skilly wants on disk for one item and one agent. */
13+/** What panoplie wants on disk for one item and one agent. */
1414 export interface RenderedItem {
1515 type: CatalogItemType
1616 name: string
@@ -10,7 +10,7 @@ export interface RenderedFile {
10 sha256: string10 sha256: string
11 }11 }
12 12
13-/** What skilly wants on disk for one item and one agent. */13+/** What panoplie wants on disk for one item and one agent. */
14 export interface RenderedItem {14 export interface RenderedItem {
15 type: CatalogItemType15 type: CatalogItemType
16 name: string16 name: string
modified packages/core/test/catalog-git.test.ts +1 -1
@@ -25,7 +25,7 @@ async function setup(dir: string): Promise<void> {
2525 }
2626
2727 beforeAll(async () => {
28- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-git-'))
28+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-git-'))
2929 remote = path.join(tmp, 'remote.git')
3030 git(tmp, 'init', '--bare', '--initial-branch=main', remote)
3131 a = path.join(tmp, 'a')
@@ -25,7 +25,7 @@ async function setup(dir: string): Promise<void> {
25 }25 }
26 26
27 beforeAll(async () => {27 beforeAll(async () => {
28- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-git-'))28+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-git-'))
29 remote = path.join(tmp, 'remote.git')29 remote = path.join(tmp, 'remote.git')
30 git(tmp, 'init', '--bare', '--initial-branch=main', remote)30 git(tmp, 'init', '--bare', '--initial-branch=main', remote)
31 a = path.join(tmp, 'a')31 a = path.join(tmp, 'a')
modified packages/core/test/catalog.test.ts +5 -5
@@ -14,7 +14,7 @@ let cat: CatalogStore
1414
1515 const roots: string[] = []
1616 beforeEach(async () => {
17- tmp ??= await mkdtemp(path.join(os.tmpdir(), 'skilly-catalog-'))
17+ tmp ??= await mkdtemp(path.join(os.tmpdir(), 'panoplie-catalog-'))
1818 roots.push(tmp)
1919 root = path.join(tmp, `cat${n++}`)
2020 cat = openCatalog(root)
@@ -46,8 +46,8 @@ describe('catalog store', () => {
4646 await cat.init()
4747 await cat.init()
4848 expect(await cat.exists()).toBe(true)
49- expect((await readdir(root)).sort()).toEqual(['.skilly', 'agents', 'hooks', 'mcp', 'presets', 'rules', 'skills'])
50- expect(JSON.parse(await readFile(path.join(root, '.skilly', 'index.json'), 'utf8'))).toEqual({ version: 1, items: {} })
49+ expect((await readdir(root)).sort()).toEqual(['.panoplie', 'agents', 'hooks', 'mcp', 'presets', 'rules', 'skills'])
50+ expect(JSON.parse(await readFile(path.join(root, '.panoplie', 'index.json'), 'utf8'))).toEqual({ version: 1, items: {} })
5151 })
5252
5353 it('validates names', async () => {
@@ -102,7 +102,7 @@ describe('catalog store', () => {
102102 expect(renamed.tags).toEqual(['t'])
103103 expect(renamed.origin?.source).toBe('/x')
104104 expect(await cat.getItem({ type: 'skill', name: 'demo' })).toBeUndefined()
105- const index = JSON.parse(await readFile(path.join(root, '.skilly', 'index.json'), 'utf8'))
105+ const index = JSON.parse(await readFile(path.join(root, '.panoplie', 'index.json'), 'utf8'))
106106 expect(Object.keys(index.items)).toEqual(['skill/demo2'])
107107 await cat.saveItem({ type: 'rule', name: 'r1', content: '---\ndescription: R\n---\nx' })
108108 await cat.saveItem({ type: 'rule', name: 'r2', content: 'y' })
@@ -138,7 +138,7 @@ describe('catalog store', () => {
138138 await put(path.join(root, 'skills', 'nofile', 'README.md'), 'x')
139139 await put(path.join(root, 'presets', 'bad.yaml'), 'includes: nope\n')
140140 await put(path.join(root, 'rules', 'ok.md'), '---\ndescription: fine\n---\n')
141- await put(path.join(root, '.skilly', 'index.json'), '{not json')
141+ await put(path.join(root, '.panoplie', 'index.json'), '{not json')
142142 const snap = await cat.snapshot()
143143 expect(snap.items.map((i) => i.name)).toEqual(['ok'])
144144 expect(snap.items[0]?.description).toBe('fine')
@@ -14,7 +14,7 @@ let cat: CatalogStore
14 14
15 const roots: string[] = []15 const roots: string[] = []
16 beforeEach(async () => {16 beforeEach(async () => {
17- tmp ??= await mkdtemp(path.join(os.tmpdir(), 'skilly-catalog-'))17+ tmp ??= await mkdtemp(path.join(os.tmpdir(), 'panoplie-catalog-'))
18 roots.push(tmp)18 roots.push(tmp)
19 root = path.join(tmp, `cat${n++}`)19 root = path.join(tmp, `cat${n++}`)
20 cat = openCatalog(root)20 cat = openCatalog(root)
@@ -46,8 +46,8 @@ describe('catalog store', () => {
46 await cat.init()46 await cat.init()
47 await cat.init()47 await cat.init()
48 expect(await cat.exists()).toBe(true)48 expect(await cat.exists()).toBe(true)
49- expect((await readdir(root)).sort()).toEqual(['.skilly', 'agents', 'hooks', 'mcp', 'presets', 'rules', 'skills'])49+ expect((await readdir(root)).sort()).toEqual(['.panoplie', 'agents', 'hooks', 'mcp', 'presets', 'rules', 'skills'])
50- expect(JSON.parse(await readFile(path.join(root, '.skilly', 'index.json'), 'utf8'))).toEqual({ version: 1, items: {} })50+ expect(JSON.parse(await readFile(path.join(root, '.panoplie', 'index.json'), 'utf8'))).toEqual({ version: 1, items: {} })
51 })51 })
52 52
53 it('validates names', async () => {53 it('validates names', async () => {
@@ -102,7 +102,7 @@ describe('catalog store', () => {
102 expect(renamed.tags).toEqual(['t'])102 expect(renamed.tags).toEqual(['t'])
103 expect(renamed.origin?.source).toBe('/x')103 expect(renamed.origin?.source).toBe('/x')
104 expect(await cat.getItem({ type: 'skill', name: 'demo' })).toBeUndefined()104 expect(await cat.getItem({ type: 'skill', name: 'demo' })).toBeUndefined()
105- const index = JSON.parse(await readFile(path.join(root, '.skilly', 'index.json'), 'utf8'))105+ const index = JSON.parse(await readFile(path.join(root, '.panoplie', 'index.json'), 'utf8'))
106 expect(Object.keys(index.items)).toEqual(['skill/demo2'])106 expect(Object.keys(index.items)).toEqual(['skill/demo2'])
107 await cat.saveItem({ type: 'rule', name: 'r1', content: '---\ndescription: R\n---\nx' })107 await cat.saveItem({ type: 'rule', name: 'r1', content: '---\ndescription: R\n---\nx' })
108 await cat.saveItem({ type: 'rule', name: 'r2', content: 'y' })108 await cat.saveItem({ type: 'rule', name: 'r2', content: 'y' })
@@ -138,7 +138,7 @@ describe('catalog store', () => {
138 await put(path.join(root, 'skills', 'nofile', 'README.md'), 'x')138 await put(path.join(root, 'skills', 'nofile', 'README.md'), 'x')
139 await put(path.join(root, 'presets', 'bad.yaml'), 'includes: nope\n')139 await put(path.join(root, 'presets', 'bad.yaml'), 'includes: nope\n')
140 await put(path.join(root, 'rules', 'ok.md'), '---\ndescription: fine\n---\n')140 await put(path.join(root, 'rules', 'ok.md'), '---\ndescription: fine\n---\n')
141- await put(path.join(root, '.skilly', 'index.json'), '{not json')141+ await put(path.join(root, '.panoplie', 'index.json'), '{not json')
142 const snap = await cat.snapshot()142 const snap = await cat.snapshot()
143 expect(snap.items.map((i) => i.name)).toEqual(['ok'])143 expect(snap.items.map((i) => i.name)).toEqual(['ok'])
144 expect(snap.items[0]?.description).toBe('fine')144 expect(snap.items[0]?.description).toBe('fine')
modified packages/core/test/claude.test.ts +1 -1
@@ -28,7 +28,7 @@ function find(r: ScanResult, type: string, name: string): InventoryItem {
2828 const SECRET = 'sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAA'
2929
3030 beforeAll(async () => {
31- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-claude-'))
31+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-claude-'))
3232 home = path.join(tmp, 'home')
3333 project = path.join(tmp, 'proj')
3434 ctx = { homeDir: home }
@@ -28,7 +28,7 @@ function find(r: ScanResult, type: string, name: string): InventoryItem {
28 const SECRET = 'sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAA'28 const SECRET = 'sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAA'
29 29
30 beforeAll(async () => {30 beforeAll(async () => {
31- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-claude-'))31+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-claude-'))
32 home = path.join(tmp, 'home')32 home = path.join(tmp, 'home')
33 project = path.join(tmp, 'proj')33 project = path.join(tmp, 'proj')
34 ctx = { homeDir: home }34 ctx = { homeDir: home }
modified packages/core/test/codex.test.ts +3 -3
@@ -30,7 +30,7 @@ function find(r: ScanResult, type: string, name: string): InventoryItem {
3030 const TOKEN = 'abcDEF1234567890ghijKLMN0987654321xyz'
3131
3232 beforeAll(async () => {
33- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-codex-'))
33+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-codex-'))
3434 homeDir = path.join(tmp, 'home')
3535 codexHome = path.join(tmp, 'codex-home')
3636 project = path.join(tmp, 'proj')
@@ -226,7 +226,7 @@ describe('codex scanHome', () => {
226226 })
227227
228228 it('is not detected on an empty home', async () => {
229- const empty = await mkdtemp(path.join(os.tmpdir(), 'skilly-codex-empty-'))
229+ const empty = await mkdtemp(path.join(os.tmpdir(), 'panoplie-codex-empty-'))
230230 try {
231231 const res = await codexScanner.scanHome({ homeDir: empty })
232232 expect(res.detected).toBe(false)
@@ -298,7 +298,7 @@ describe('codex scanProject', () => {
298298 })
299299
300300 it('is not detected on a folder without codex files', async () => {
301- const empty = await mkdtemp(path.join(os.tmpdir(), 'skilly-codex-emptyproj-'))
301+ const empty = await mkdtemp(path.join(os.tmpdir(), 'panoplie-codex-emptyproj-'))
302302 try {
303303 const r = await codexScanner.scanProject(empty, ctx)
304304 expect(r.detected).toBe(false)
@@ -30,7 +30,7 @@ function find(r: ScanResult, type: string, name: string): InventoryItem {
30 const TOKEN = 'abcDEF1234567890ghijKLMN0987654321xyz'30 const TOKEN = 'abcDEF1234567890ghijKLMN0987654321xyz'
31 31
32 beforeAll(async () => {32 beforeAll(async () => {
33- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-codex-'))33+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-codex-'))
34 homeDir = path.join(tmp, 'home')34 homeDir = path.join(tmp, 'home')
35 codexHome = path.join(tmp, 'codex-home')35 codexHome = path.join(tmp, 'codex-home')
36 project = path.join(tmp, 'proj')36 project = path.join(tmp, 'proj')
@@ -226,7 +226,7 @@ describe('codex scanHome', () => {
226 })226 })
227 227
228 it('is not detected on an empty home', async () => {228 it('is not detected on an empty home', async () => {
229- const empty = await mkdtemp(path.join(os.tmpdir(), 'skilly-codex-empty-'))229+ const empty = await mkdtemp(path.join(os.tmpdir(), 'panoplie-codex-empty-'))
230 try {230 try {
231 const res = await codexScanner.scanHome({ homeDir: empty })231 const res = await codexScanner.scanHome({ homeDir: empty })
232 expect(res.detected).toBe(false)232 expect(res.detected).toBe(false)
@@ -298,7 +298,7 @@ describe('codex scanProject', () => {
298 })298 })
299 299
300 it('is not detected on a folder without codex files', async () => {300 it('is not detected on a folder without codex files', async () => {
301- const empty = await mkdtemp(path.join(os.tmpdir(), 'skilly-codex-emptyproj-'))301+ const empty = await mkdtemp(path.join(os.tmpdir(), 'panoplie-codex-emptyproj-'))
302 try {302 try {
303 const r = await codexScanner.scanProject(empty, ctx)303 const r = await codexScanner.scanProject(empty, ctx)
304 expect(r.detected).toBe(false)304 expect(r.detected).toBe(false)
added packages/core/test/legacy.test.ts +100 -0
new file mode 100644
@@ -0,0 +1,100 @@
1+import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
2+import os from 'node:os'
3+import path from 'node:path'
4+import { afterAll, beforeEach, describe, expect, it } from 'vitest'
5+import type { SyncTarget } from '../src/catalog-model.ts'
6+import { openCatalog } from '../src/catalog/index.ts'
7+import { migrateLegacyConfigDir } from '../src/legacy.ts'
8+import { readConfig, readLock, writeConfig, writeLock } from '../src/sync/config.ts'
9+import { getSection, setSection } from '../src/sync/merge/agents-md.ts'
10+import { pathExists } from '../src/util.ts'
11+
12+let tmp: string
13+let n = 0
14+let dir: string
15+
16+beforeEach(async () => {
17+ tmp ??= await mkdtemp(path.join(os.tmpdir(), 'panoplie-legacy-'))
18+ dir = path.join(tmp, `case${n++}`)
19+ await mkdir(dir, { recursive: true })
20+})
21+afterAll(async () => {
22+ if (tmp) await rm(tmp, { recursive: true, force: true })
23+})
24+
25+async function put(file: string, content: string): Promise<void> {
26+ await mkdir(path.dirname(file), { recursive: true })
27+ await writeFile(file, content)
28+}
29+
30+describe('migration from skilly', () => {
31+ it('moves the legacy machine folder and a catalog path inside it', async () => {
32+ const legacy = path.join(dir, '.skilly')
33+ const current = path.join(dir, '.panoplie')
34+ await put(path.join(legacy, 'catalog', 'skills', 'x', 'SKILL.md'), 'x')
35+ await put(path.join(legacy, 'config.json'), JSON.stringify({ version: 1, settings: { catalogPath: path.join(legacy, 'catalog') } }))
36+
37+ expect(await migrateLegacyConfigDir(current, legacy)).toBe(true)
38+ expect(await pathExists(legacy)).toBe(false)
39+ expect(await pathExists(path.join(current, 'catalog', 'skills', 'x', 'SKILL.md'))).toBe(true)
40+ const config = JSON.parse(await readFile(path.join(current, 'config.json'), 'utf8'))
41+ expect(config.settings.catalogPath).toBe(path.join(current, 'catalog'))
42+
43+ // Nothing to do once migrated, or when the new folder already exists.
44+ await mkdir(legacy)
45+ expect(await migrateLegacyConfigDir(current, legacy)).toBe(false)
46+ expect(await pathExists(legacy)).toBe(true)
47+ })
48+
49+ it('keeps a catalog path outside the legacy folder', async () => {
50+ const legacy = path.join(dir, '.skilly')
51+ const elsewhere = path.join(dir, 'my-catalog')
52+ await put(path.join(legacy, 'config.json'), JSON.stringify({ version: 1, settings: { catalogPath: elsewhere } }))
53+ await migrateLegacyConfigDir(path.join(dir, '.panoplie'), legacy)
54+ const config = JSON.parse(await readFile(path.join(dir, '.panoplie', 'config.json'), 'utf8'))
55+ expect(config.settings.catalogPath).toBe(elsewhere)
56+ })
57+
58+ it('reads skilly.json and skilly.lock, then renames both on the next write', async () => {
59+ const target: SyncTarget = { kind: 'project', root: dir, homeDir: dir, configDir: dir }
60+ await put(path.join(dir, 'skilly.json'), JSON.stringify({ presets: ['base'] }))
61+ await put(path.join(dir, 'skilly.lock'), JSON.stringify({ version: 1, syncedAt: 'then', entries: [] }))
62+
63+ expect((await readConfig(target))?.presets).toEqual(['base'])
64+ expect((await readLock(target))?.syncedAt).toBe('then')
65+
66+ const lock = (await readLock(target))!
67+ await writeLock(target, { ...lock, syncedAt: 'now' })
68+ expect(await pathExists(path.join(dir, 'skilly.json'))).toBe(false)
69+ expect(await pathExists(path.join(dir, 'skilly.lock'))).toBe(false)
70+ expect((await readConfig(target))?.presets).toEqual(['base'])
71+ expect((await readLock(target))?.syncedAt).toBe('now')
72+
73+ await writeConfig(target, { ...(await readConfig(target))!, presets: ['other'] })
74+ expect((await readConfig(target))?.presets).toEqual(['other'])
75+ })
76+
77+ it('reads the legacy catalog index and replaces it on the next write', async () => {
78+ const root = path.join(dir, 'catalog')
79+ await put(path.join(root, 'skills', 'demo', 'SKILL.md'), '---\nname: demo\ndescription: A demo\n---\nBody\n')
80+ await put(path.join(root, '.skilly', 'index.json'), JSON.stringify({ version: 1, items: { 'skill/demo': { tags: ['old'] } } }))
81+ const cat = openCatalog(root)
82+ await cat.init()
83+ expect(await pathExists(path.join(root, '.panoplie', 'index.json'))).toBe(false)
84+ expect((await cat.getItem({ type: 'skill', name: 'demo' }))?.item.tags).toEqual(['old'])
85+
86+ await cat.setTags({ type: 'skill', name: 'demo' }, ['old', 'new'])
87+ expect(await pathExists(path.join(root, '.skilly'))).toBe(false)
88+ const index = JSON.parse(await readFile(path.join(root, '.panoplie', 'index.json'), 'utf8'))
89+ expect(index.items['skill/demo'].tags).toEqual(['old', 'new'])
90+ })
91+
92+ it('reads a legacy AGENTS.md block and rewrites it with the new markers', () => {
93+ const text = '# Mine\n\n<!-- skilly:begin (generated by skilly, do not edit) -->\n<!-- skilly:rule ts -->\n## ts\n\nStrict.\n<!-- skilly:end -->\n\nAfter\n'
94+ expect(getSection(text, 'ts')).toBe('## ts\n\nStrict.')
95+ const out = setSection(text, 'ts', '## ts\n\nStricter.')
96+ expect(out).toBe(
97+ '# Mine\n\n<!-- panoplie:begin (generated by panoplie, do not edit) -->\n<!-- panoplie:rule ts -->\n## ts\n\nStricter.\n<!-- panoplie:end -->\n\nAfter\n',
98+ )
99+ })
100+})
new file mode 100644
@@ -0,0 +1,100 @@
1+import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
2+import os from 'node:os'
3+import path from 'node:path'
4+import { afterAll, beforeEach, describe, expect, it } from 'vitest'
5+import type { SyncTarget } from '../src/catalog-model.ts'
6+import { openCatalog } from '../src/catalog/index.ts'
7+import { migrateLegacyConfigDir } from '../src/legacy.ts'
8+import { readConfig, readLock, writeConfig, writeLock } from '../src/sync/config.ts'
9+import { getSection, setSection } from '../src/sync/merge/agents-md.ts'
10+import { pathExists } from '../src/util.ts'
11+
12+let tmp: string
13+let n = 0
14+let dir: string
15+
16+beforeEach(async () => {
17+ tmp ??= await mkdtemp(path.join(os.tmpdir(), 'panoplie-legacy-'))
18+ dir = path.join(tmp, `case${n++}`)
19+ await mkdir(dir, { recursive: true })
20+})
21+afterAll(async () => {
22+ if (tmp) await rm(tmp, { recursive: true, force: true })
23+})
24+
25+async function put(file: string, content: string): Promise<void> {
26+ await mkdir(path.dirname(file), { recursive: true })
27+ await writeFile(file, content)
28+}
29+
30+describe('migration from skilly', () => {
31+ it('moves the legacy machine folder and a catalog path inside it', async () => {
32+ const legacy = path.join(dir, '.skilly')
33+ const current = path.join(dir, '.panoplie')
34+ await put(path.join(legacy, 'catalog', 'skills', 'x', 'SKILL.md'), 'x')
35+ await put(path.join(legacy, 'config.json'), JSON.stringify({ version: 1, settings: { catalogPath: path.join(legacy, 'catalog') } }))
36+
37+ expect(await migrateLegacyConfigDir(current, legacy)).toBe(true)
38+ expect(await pathExists(legacy)).toBe(false)
39+ expect(await pathExists(path.join(current, 'catalog', 'skills', 'x', 'SKILL.md'))).toBe(true)
40+ const config = JSON.parse(await readFile(path.join(current, 'config.json'), 'utf8'))
41+ expect(config.settings.catalogPath).toBe(path.join(current, 'catalog'))
42+
43+ // Nothing to do once migrated, or when the new folder already exists.
44+ await mkdir(legacy)
45+ expect(await migrateLegacyConfigDir(current, legacy)).toBe(false)
46+ expect(await pathExists(legacy)).toBe(true)
47+ })
48+
49+ it('keeps a catalog path outside the legacy folder', async () => {
50+ const legacy = path.join(dir, '.skilly')
51+ const elsewhere = path.join(dir, 'my-catalog')
52+ await put(path.join(legacy, 'config.json'), JSON.stringify({ version: 1, settings: { catalogPath: elsewhere } }))
53+ await migrateLegacyConfigDir(path.join(dir, '.panoplie'), legacy)
54+ const config = JSON.parse(await readFile(path.join(dir, '.panoplie', 'config.json'), 'utf8'))
55+ expect(config.settings.catalogPath).toBe(elsewhere)
56+ })
57+
58+ it('reads skilly.json and skilly.lock, then renames both on the next write', async () => {
59+ const target: SyncTarget = { kind: 'project', root: dir, homeDir: dir, configDir: dir }
60+ await put(path.join(dir, 'skilly.json'), JSON.stringify({ presets: ['base'] }))
61+ await put(path.join(dir, 'skilly.lock'), JSON.stringify({ version: 1, syncedAt: 'then', entries: [] }))
62+
63+ expect((await readConfig(target))?.presets).toEqual(['base'])
64+ expect((await readLock(target))?.syncedAt).toBe('then')
65+
66+ const lock = (await readLock(target))!
67+ await writeLock(target, { ...lock, syncedAt: 'now' })
68+ expect(await pathExists(path.join(dir, 'skilly.json'))).toBe(false)
69+ expect(await pathExists(path.join(dir, 'skilly.lock'))).toBe(false)
70+ expect((await readConfig(target))?.presets).toEqual(['base'])
71+ expect((await readLock(target))?.syncedAt).toBe('now')
72+
73+ await writeConfig(target, { ...(await readConfig(target))!, presets: ['other'] })
74+ expect((await readConfig(target))?.presets).toEqual(['other'])
75+ })
76+
77+ it('reads the legacy catalog index and replaces it on the next write', async () => {
78+ const root = path.join(dir, 'catalog')
79+ await put(path.join(root, 'skills', 'demo', 'SKILL.md'), '---\nname: demo\ndescription: A demo\n---\nBody\n')
80+ await put(path.join(root, '.skilly', 'index.json'), JSON.stringify({ version: 1, items: { 'skill/demo': { tags: ['old'] } } }))
81+ const cat = openCatalog(root)
82+ await cat.init()
83+ expect(await pathExists(path.join(root, '.panoplie', 'index.json'))).toBe(false)
84+ expect((await cat.getItem({ type: 'skill', name: 'demo' }))?.item.tags).toEqual(['old'])
85+
86+ await cat.setTags({ type: 'skill', name: 'demo' }, ['old', 'new'])
87+ expect(await pathExists(path.join(root, '.skilly'))).toBe(false)
88+ const index = JSON.parse(await readFile(path.join(root, '.panoplie', 'index.json'), 'utf8'))
89+ expect(index.items['skill/demo'].tags).toEqual(['old', 'new'])
90+ })
91+
92+ it('reads a legacy AGENTS.md block and rewrites it with the new markers', () => {
93+ const text = '# Mine\n\n<!-- skilly:begin (generated by skilly, do not edit) -->\n<!-- skilly:rule ts -->\n## ts\n\nStrict.\n<!-- skilly:end -->\n\nAfter\n'
94+ expect(getSection(text, 'ts')).toBe('## ts\n\nStrict.')
95+ const out = setSection(text, 'ts', '## ts\n\nStricter.')
96+ expect(out).toBe(
97+ '# Mine\n\n<!-- panoplie:begin (generated by panoplie, do not edit) -->\n<!-- panoplie:rule ts -->\n## ts\n\nStricter.\n<!-- panoplie:end -->\n\nAfter\n',
98+ )
99+ })
100+})
modified packages/core/test/marketplace.test.ts +2 -2
@@ -45,7 +45,7 @@ let cacheDir: string
4545 let sources: MarketplaceSource[]
4646
4747 beforeAll(async () => {
48- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-mkt-'))
48+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-mkt-'))
4949 cacheDir = path.join(tmp, 'cache')
5050 plainRepo = await makeRepo('plain', {
5151 'skills/alpha/SKILL.md': skillMd('alpha', 'Alpha skill'),
@@ -183,7 +183,7 @@ describe('marketplace', () => {
183183 await expect(m.skillDir('plain', 'skills')).rejects.toThrow(/No skill found/)
184184 })
185185
186- it.skipIf(!process.env.SKILLY_NET_TESTS)('clones the real built-in sources', { timeout: 300_000 }, async () => {
186+ it.skipIf(!process.env.PANOPLIE_NET_TESTS)('clones the real built-in sources', { timeout: 300_000 }, async () => {
187187 const netCache = path.join(tmp, 'net-cache')
188188 const m = createMarketplace({ cacheDir: netCache, userSources: [] })
189189 for (const s of m.sources().filter((x) => x.builtIn)) {
@@ -45,7 +45,7 @@ let cacheDir: string
45 let sources: MarketplaceSource[]45 let sources: MarketplaceSource[]
46 46
47 beforeAll(async () => {47 beforeAll(async () => {
48- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-mkt-'))48+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-mkt-'))
49 cacheDir = path.join(tmp, 'cache')49 cacheDir = path.join(tmp, 'cache')
50 plainRepo = await makeRepo('plain', {50 plainRepo = await makeRepo('plain', {
51 'skills/alpha/SKILL.md': skillMd('alpha', 'Alpha skill'),51 'skills/alpha/SKILL.md': skillMd('alpha', 'Alpha skill'),
@@ -183,7 +183,7 @@ describe('marketplace', () => {
183 await expect(m.skillDir('plain', 'skills')).rejects.toThrow(/No skill found/)183 await expect(m.skillDir('plain', 'skills')).rejects.toThrow(/No skill found/)
184 })184 })
185 185
186- it.skipIf(!process.env.SKILLY_NET_TESTS)('clones the real built-in sources', { timeout: 300_000 }, async () => {186+ it.skipIf(!process.env.PANOPLIE_NET_TESTS)('clones the real built-in sources', { timeout: 300_000 }, async () => {
187 const netCache = path.join(tmp, 'net-cache')187 const netCache = path.join(tmp, 'net-cache')
188 const m = createMarketplace({ cacheDir: netCache, userSources: [] })188 const m = createMarketplace({ cacheDir: netCache, userSources: [] })
189 for (const s of m.sources().filter((x) => x.builtIn)) {189 for (const s of m.sources().filter((x) => x.builtIn)) {
modified packages/core/test/sync-home.test.ts +5 -5
@@ -19,7 +19,7 @@ async function put(file: string, content: string): Promise<void> {
1919 }
2020
2121 function homeTarget(home: string, codexHome = path.join(home, '.codex')): SyncTarget {
22- return { kind: 'home', root: home, homeDir: home, codexHome, configDir: path.join(home, '.skilly') }
22+ return { kind: 'home', root: home, homeDir: home, codexHome, configDir: path.join(home, '.panoplie') }
2323 }
2424
2525 const CONFIG: ScopeConfig = {
@@ -33,7 +33,7 @@ const CONFIG: ScopeConfig = {
3333 }
3434
3535 beforeAll(async () => {
36- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-sync-home-'))
36+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-sync-home-'))
3737 catalog = new FakeCatalog(path.join(tmp, 'catalog'))
3838 await catalog.put('skills/nuxt/SKILL.md', '---\nname: nuxt\ndescription: Nuxt\n---\nBody\n')
3939 await catalog.put('rules/vue.md', '---\nname: vue\n---\nUse SFCs.\n')
@@ -57,7 +57,7 @@ describe('Home sync', () => {
5757 await put(path.join(home, '.claude', 'settings.json'), '{\n "theme": "dark"\n}\n')
5858
5959 await syncEngine.writeConfig(target, CONFIG)
60- expect(await pathExists(path.join(home, '.skilly', 'home.json'))).toBe(true)
60+ expect(await pathExists(path.join(home, '.panoplie', 'home.json'))).toBe(true)
6161 const config = (await syncEngine.readConfig(target))!
6262
6363 const plan = await syncEngine.plan(target, config, catalog)
@@ -78,7 +78,7 @@ describe('Home sync', () => {
7878
7979 const result = await syncEngine.apply(target, config, catalog, plan, {})
8080 expect(result.errors).toEqual([])
81- expect(await pathExists(path.join(home, '.skilly', 'home.lock'))).toBe(true)
81+ expect(await pathExists(path.join(home, '.panoplie', 'home.lock'))).toBe(true)
8282 expect(await pathExists(path.join(home, '.claude.json'))).toBe(false)
8383 expect(await pathExists(path.join(home, '.mcp.json'))).toBe(false)
8484
@@ -88,7 +88,7 @@ describe('Home sync', () => {
8888 policy: { allow_implicit_invocation: false },
8989 })
9090 const agentsMd = await readFile(path.join(home, '.codex', 'AGENTS.md'), 'utf8')
91- expect(agentsMd).toMatch(/^# Global\n\n<!-- skilly:begin/)
91+ expect(agentsMd).toMatch(/^# Global\n\n<!-- panoplie:begin/)
9292 expect(await readFile(path.join(home, '.codex', 'agents', 'reviewer.toml'), 'utf8')).toContain('model = "gpt-5"')
9393
9494 const inv = await scanHome({ homeDir: home, codexHome: path.join(home, '.codex') })
@@ -19,7 +19,7 @@ async function put(file: string, content: string): Promise<void> {
19 }19 }
20 20
21 function homeTarget(home: string, codexHome = path.join(home, '.codex')): SyncTarget {21 function homeTarget(home: string, codexHome = path.join(home, '.codex')): SyncTarget {
22- return { kind: 'home', root: home, homeDir: home, codexHome, configDir: path.join(home, '.skilly') }22+ return { kind: 'home', root: home, homeDir: home, codexHome, configDir: path.join(home, '.panoplie') }
23 }23 }
24 24
25 const CONFIG: ScopeConfig = {25 const CONFIG: ScopeConfig = {
@@ -33,7 +33,7 @@ const CONFIG: ScopeConfig = {
33 }33 }
34 34
35 beforeAll(async () => {35 beforeAll(async () => {
36- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-sync-home-'))36+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-sync-home-'))
37 catalog = new FakeCatalog(path.join(tmp, 'catalog'))37 catalog = new FakeCatalog(path.join(tmp, 'catalog'))
38 await catalog.put('skills/nuxt/SKILL.md', '---\nname: nuxt\ndescription: Nuxt\n---\nBody\n')38 await catalog.put('skills/nuxt/SKILL.md', '---\nname: nuxt\ndescription: Nuxt\n---\nBody\n')
39 await catalog.put('rules/vue.md', '---\nname: vue\n---\nUse SFCs.\n')39 await catalog.put('rules/vue.md', '---\nname: vue\n---\nUse SFCs.\n')
@@ -57,7 +57,7 @@ describe('Home sync', () => {
57 await put(path.join(home, '.claude', 'settings.json'), '{\n "theme": "dark"\n}\n')57 await put(path.join(home, '.claude', 'settings.json'), '{\n "theme": "dark"\n}\n')
58 58
59 await syncEngine.writeConfig(target, CONFIG)59 await syncEngine.writeConfig(target, CONFIG)
60- expect(await pathExists(path.join(home, '.skilly', 'home.json'))).toBe(true)60+ expect(await pathExists(path.join(home, '.panoplie', 'home.json'))).toBe(true)
61 const config = (await syncEngine.readConfig(target))!61 const config = (await syncEngine.readConfig(target))!
62 62
63 const plan = await syncEngine.plan(target, config, catalog)63 const plan = await syncEngine.plan(target, config, catalog)
@@ -78,7 +78,7 @@ describe('Home sync', () => {
78 78
79 const result = await syncEngine.apply(target, config, catalog, plan, {})79 const result = await syncEngine.apply(target, config, catalog, plan, {})
80 expect(result.errors).toEqual([])80 expect(result.errors).toEqual([])
81- expect(await pathExists(path.join(home, '.skilly', 'home.lock'))).toBe(true)81+ expect(await pathExists(path.join(home, '.panoplie', 'home.lock'))).toBe(true)
82 expect(await pathExists(path.join(home, '.claude.json'))).toBe(false)82 expect(await pathExists(path.join(home, '.claude.json'))).toBe(false)
83 expect(await pathExists(path.join(home, '.mcp.json'))).toBe(false)83 expect(await pathExists(path.join(home, '.mcp.json'))).toBe(false)
84 84
@@ -88,7 +88,7 @@ describe('Home sync', () => {
88 policy: { allow_implicit_invocation: false },88 policy: { allow_implicit_invocation: false },
89 })89 })
90 const agentsMd = await readFile(path.join(home, '.codex', 'AGENTS.md'), 'utf8')90 const agentsMd = await readFile(path.join(home, '.codex', 'AGENTS.md'), 'utf8')
91- expect(agentsMd).toMatch(/^# Global\n\n<!-- skilly:begin/)91+ expect(agentsMd).toMatch(/^# Global\n\n<!-- panoplie:begin/)
92 expect(await readFile(path.join(home, '.codex', 'agents', 'reviewer.toml'), 'utf8')).toContain('model = "gpt-5"')92 expect(await readFile(path.join(home, '.codex', 'agents', 'reviewer.toml'), 'utf8')).toContain('model = "gpt-5"')
93 93
94 const inv = await scanHome({ homeDir: home, codexHome: path.join(home, '.codex') })94 const inv = await scanHome({ homeDir: home, codexHome: path.join(home, '.codex') })
modified packages/core/test/sync-merge.test.ts +3 -3
@@ -98,9 +98,9 @@ describe('AGENTS.md managed block', () => {
9898 })
9999
100100 it('keeps content after the block untouched', () => {
101- const text = `${user}\n${BLOCK_BEGIN}\n<!-- skilly:rule a -->\n## a\n${BLOCK_END}\n\n## Footer\nkeep me\n`
101+ const text = `${user}\n${BLOCK_BEGIN}\n<!-- panoplie:rule a -->\n## a\n${BLOCK_END}\n\n## Footer\nkeep me\n`
102102 const out = setSection(text, 'a', '## a\n\nnew')
103- expect(out).toBe(`${user}\n${BLOCK_BEGIN}\n<!-- skilly:rule a -->\n## a\n\nnew\n${BLOCK_END}\n\n## Footer\nkeep me\n`)
103+ expect(out).toBe(`${user}\n${BLOCK_BEGIN}\n<!-- panoplie:rule a -->\n## a\n\nnew\n${BLOCK_END}\n\n## Footer\nkeep me\n`)
104104 })
105105 })
106106
@@ -115,7 +115,7 @@ describe('config entries', () => {
115115 expect(parseConfigKey('AGENTS.md', 'agents-md-block:ts')).toMatchObject({ kind: 'agents-md-section', name: 'ts' })
116116 })
117117
118- it('finds, replaces and removes only skilly array items', () => {
118+ it('finds, replaces and removes only panoplie array items', () => {
119119 const loc = jsonArrayItem('s.json', ['hooks', 'Stop'], 'x')
120120 const user = { matcher: 'u', hooks: [{ type: 'command', command: 'user' }] }
121121 const w1 = configWrite(loc, { hooks: [{ type: 'command', command: 'one' }] })
@@ -98,9 +98,9 @@ describe('AGENTS.md managed block', () => {
98 })98 })
99 99
100 it('keeps content after the block untouched', () => {100 it('keeps content after the block untouched', () => {
101- const text = `${user}\n${BLOCK_BEGIN}\n<!-- skilly:rule a -->\n## a\n${BLOCK_END}\n\n## Footer\nkeep me\n`101+ const text = `${user}\n${BLOCK_BEGIN}\n<!-- panoplie:rule a -->\n## a\n${BLOCK_END}\n\n## Footer\nkeep me\n`
102 const out = setSection(text, 'a', '## a\n\nnew')102 const out = setSection(text, 'a', '## a\n\nnew')
103- expect(out).toBe(`${user}\n${BLOCK_BEGIN}\n<!-- skilly:rule a -->\n## a\n\nnew\n${BLOCK_END}\n\n## Footer\nkeep me\n`)103+ expect(out).toBe(`${user}\n${BLOCK_BEGIN}\n<!-- panoplie:rule a -->\n## a\n\nnew\n${BLOCK_END}\n\n## Footer\nkeep me\n`)
104 })104 })
105 })105 })
106 106
@@ -115,7 +115,7 @@ describe('config entries', () => {
115 expect(parseConfigKey('AGENTS.md', 'agents-md-block:ts')).toMatchObject({ kind: 'agents-md-section', name: 'ts' })115 expect(parseConfigKey('AGENTS.md', 'agents-md-block:ts')).toMatchObject({ kind: 'agents-md-section', name: 'ts' })
116 })116 })
117 117
118- it('finds, replaces and removes only skilly array items', () => {118+ it('finds, replaces and removes only panoplie array items', () => {
119 const loc = jsonArrayItem('s.json', ['hooks', 'Stop'], 'x')119 const loc = jsonArrayItem('s.json', ['hooks', 'Stop'], 'x')
120 const user = { matcher: 'u', hooks: [{ type: 'command', command: 'user' }] }120 const user = { matcher: 'u', hooks: [{ type: 'command', command: 'user' }] }
121 const w1 = configWrite(loc, { hooks: [{ type: 'command', command: 'one' }] })121 const w1 = configWrite(loc, { hooks: [{ type: 'command', command: 'one' }] })
modified packages/core/test/sync.test.ts +6 -6
@@ -89,7 +89,7 @@ function find(inv: ScopeInventory, agent: string, type: string, name: string): I
8989 }
9090
9191 beforeAll(async () => {
92- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-sync-'))
92+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-sync-'))
9393 home = path.join(tmp, 'home')
9494 project = path.join(tmp, 'proj')
9595 catalog = new FakeCatalog(path.join(tmp, 'catalog'))
@@ -144,7 +144,7 @@ describe('config and lock files', () => {
144144 it('returns undefined when missing and normalizes defaults', async () => {
145145 expect(await syncEngine.readConfig(target)).toBeUndefined()
146146 expect(await syncEngine.readLock(target)).toBeUndefined()
147- await put(p('skilly.json'), JSON.stringify({ presets: ['base'], add: { skills: ['x'] } }))
147+ await put(p('panoplie.json'), JSON.stringify({ presets: ['base'], add: { skills: ['x'] } }))
148148 expect(await syncEngine.readConfig(target)).toEqual({
149149 version: 1,
150150 targets: ['claude', 'codex'],
@@ -155,7 +155,7 @@ describe('config and lock files', () => {
155155 hideGlobal: { skills: [], mcp: [] },
156156 })
157157 await syncEngine.writeConfig(target, config)
158- const text = await read('skilly.json')
158+ const text = await read('panoplie.json')
159159 expect(text.endsWith('}\n')).toBe(true)
160160 expect(text).toContain('\n "targets": [')
161161 expect(Object.keys(JSON.parse(text))).toEqual(['version', 'targets', 'presets', 'add', 'exclude', 'onDemand', 'hideGlobal'])
@@ -199,7 +199,7 @@ describe('project sync', () => {
199199 expect(w).toMatch(/OTHER_KEY is forwarded as is, not as API_KEY/)
200200 expect(w).toMatch(/Codex cannot hide global items per project; still visible to Codex: skill hyperframes-a/)
201201 expect(await pathExists(p('.claude/skills'))).toBe(false)
202- expect(await pathExists(p('skilly.lock'))).toBe(false)
202+ expect(await pathExists(p('panoplie.lock'))).toBe(false)
203203 expect(await read('.claude/settings.json')).toBe(SETTINGS)
204204 })
205205
@@ -299,9 +299,9 @@ describe('project sync', () => {
299299 expect(status[find(inv, 'claude', 'skill', 'nuxt').id]).toEqual({ managed: true, catalogRef: { type: 'skill', name: 'nuxt' }, state: 'in-sync' })
300300 expect(status[find(inv, 'codex', 'mcp', 'github').id]!.state).toBe('in-sync')
301301 expect(status[find(inv, 'codex', 'agent', 'reviewer').id]!.managed).toBe(true)
302- const skillyHook = inv.items.find((i) => i.agent === 'claude' && i.type === 'hook' && (i.details as any).command === 'npx prettier --write')!
302+ const panoplieHook = inv.items.find((i) => i.agent === 'claude' && i.type === 'hook' && (i.details as any).command === 'npx prettier --write')!
303303 const userHook = inv.items.find((i) => i.agent === 'claude' && i.type === 'hook' && (i.details as any).command === 'echo user')!
304- expect(status[skillyHook.id]).toMatchObject({ managed: true, state: 'in-sync' })
304+ expect(status[panoplieHook.id]).toMatchObject({ managed: true, state: 'in-sync' })
305305 expect(status[userHook.id]).toEqual({ managed: false, state: 'not-in-catalog' })
306306 expect(status[find(inv, 'codex', 'instructions', 'AGENTS.md').id]!.managed).toBe(false)
307307 })
@@ -89,7 +89,7 @@ function find(inv: ScopeInventory, agent: string, type: string, name: string): I
89 }89 }
90 90
91 beforeAll(async () => {91 beforeAll(async () => {
92- tmp = await mkdtemp(path.join(os.tmpdir(), 'skilly-sync-'))92+ tmp = await mkdtemp(path.join(os.tmpdir(), 'panoplie-sync-'))
93 home = path.join(tmp, 'home')93 home = path.join(tmp, 'home')
94 project = path.join(tmp, 'proj')94 project = path.join(tmp, 'proj')
95 catalog = new FakeCatalog(path.join(tmp, 'catalog'))95 catalog = new FakeCatalog(path.join(tmp, 'catalog'))
@@ -144,7 +144,7 @@ describe('config and lock files', () => {
144 it('returns undefined when missing and normalizes defaults', async () => {144 it('returns undefined when missing and normalizes defaults', async () => {
145 expect(await syncEngine.readConfig(target)).toBeUndefined()145 expect(await syncEngine.readConfig(target)).toBeUndefined()
146 expect(await syncEngine.readLock(target)).toBeUndefined()146 expect(await syncEngine.readLock(target)).toBeUndefined()
147- await put(p('skilly.json'), JSON.stringify({ presets: ['base'], add: { skills: ['x'] } }))147+ await put(p('panoplie.json'), JSON.stringify({ presets: ['base'], add: { skills: ['x'] } }))
148 expect(await syncEngine.readConfig(target)).toEqual({148 expect(await syncEngine.readConfig(target)).toEqual({
149 version: 1,149 version: 1,
150 targets: ['claude', 'codex'],150 targets: ['claude', 'codex'],
@@ -155,7 +155,7 @@ describe('config and lock files', () => {
155 hideGlobal: { skills: [], mcp: [] },155 hideGlobal: { skills: [], mcp: [] },
156 })156 })
157 await syncEngine.writeConfig(target, config)157 await syncEngine.writeConfig(target, config)
158- const text = await read('skilly.json')158+ const text = await read('panoplie.json')
159 expect(text.endsWith('}\n')).toBe(true)159 expect(text.endsWith('}\n')).toBe(true)
160 expect(text).toContain('\n "targets": [')160 expect(text).toContain('\n "targets": [')
161 expect(Object.keys(JSON.parse(text))).toEqual(['version', 'targets', 'presets', 'add', 'exclude', 'onDemand', 'hideGlobal'])161 expect(Object.keys(JSON.parse(text))).toEqual(['version', 'targets', 'presets', 'add', 'exclude', 'onDemand', 'hideGlobal'])
@@ -199,7 +199,7 @@ describe('project sync', () => {
199 expect(w).toMatch(/OTHER_KEY is forwarded as is, not as API_KEY/)199 expect(w).toMatch(/OTHER_KEY is forwarded as is, not as API_KEY/)
200 expect(w).toMatch(/Codex cannot hide global items per project; still visible to Codex: skill hyperframes-a/)200 expect(w).toMatch(/Codex cannot hide global items per project; still visible to Codex: skill hyperframes-a/)
201 expect(await pathExists(p('.claude/skills'))).toBe(false)201 expect(await pathExists(p('.claude/skills'))).toBe(false)
202- expect(await pathExists(p('skilly.lock'))).toBe(false)202+ expect(await pathExists(p('panoplie.lock'))).toBe(false)
203 expect(await read('.claude/settings.json')).toBe(SETTINGS)203 expect(await read('.claude/settings.json')).toBe(SETTINGS)
204 })204 })
205 205
@@ -299,9 +299,9 @@ describe('project sync', () => {
299 expect(status[find(inv, 'claude', 'skill', 'nuxt').id]).toEqual({ managed: true, catalogRef: { type: 'skill', name: 'nuxt' }, state: 'in-sync' })299 expect(status[find(inv, 'claude', 'skill', 'nuxt').id]).toEqual({ managed: true, catalogRef: { type: 'skill', name: 'nuxt' }, state: 'in-sync' })
300 expect(status[find(inv, 'codex', 'mcp', 'github').id]!.state).toBe('in-sync')300 expect(status[find(inv, 'codex', 'mcp', 'github').id]!.state).toBe('in-sync')
301 expect(status[find(inv, 'codex', 'agent', 'reviewer').id]!.managed).toBe(true)301 expect(status[find(inv, 'codex', 'agent', 'reviewer').id]!.managed).toBe(true)
302- const skillyHook = inv.items.find((i) => i.agent === 'claude' && i.type === 'hook' && (i.details as any).command === 'npx prettier --write')!302+ const panoplieHook = inv.items.find((i) => i.agent === 'claude' && i.type === 'hook' && (i.details as any).command === 'npx prettier --write')!
303 const userHook = inv.items.find((i) => i.agent === 'claude' && i.type === 'hook' && (i.details as any).command === 'echo user')!303 const userHook = inv.items.find((i) => i.agent === 'claude' && i.type === 'hook' && (i.details as any).command === 'echo user')!
304- expect(status[skillyHook.id]).toMatchObject({ managed: true, state: 'in-sync' })304+ expect(status[panoplieHook.id]).toMatchObject({ managed: true, state: 'in-sync' })
305 expect(status[userHook.id]).toEqual({ managed: false, state: 'not-in-catalog' })305 expect(status[userHook.id]).toEqual({ managed: false, state: 'not-in-catalog' })
306 expect(status[find(inv, 'codex', 'instructions', 'AGENTS.md').id]!.managed).toBe(false)306 expect(status[find(inv, 'codex', 'instructions', 'AGENTS.md').id]!.managed).toBe(false)
307 })307 })
modified packages/server/package.json +2 -2
@@ -1,5 +1,5 @@
11 {
2- "name": "@skilly/server",
2+ "name": "@panoplie/server",
33 "version": "0.0.0",
44 "private": true,
55 "license": "MIT",
@@ -12,7 +12,7 @@
1212 },
1313 "dependencies": {
1414 "@hono/node-server": "^2.1.0",
15- "@skilly/core": "workspace:*",
15+ "@panoplie/core": "workspace:*",
1616 "hono": "^4.13.0"
1717 }
1818 }
@@ -1,5 +1,5 @@
1 {1 {
2- "name": "@skilly/server",2+ "name": "@panoplie/server",
3 "version": "0.0.0",3 "version": "0.0.0",
4 "private": true,4 "private": true,
5 "license": "MIT",5 "license": "MIT",
@@ -12,7 +12,7 @@
12 },12 },
13 "dependencies": {13 "dependencies": {
14 "@hono/node-server": "^2.1.0",14 "@hono/node-server": "^2.1.0",
15- "@skilly/core": "workspace:*",15+ "@panoplie/core": "workspace:*",
16 "hono": "^4.13.0"16 "hono": "^4.13.0"
17 }17 }
18 }18 }
modified packages/server/src/catalog-routes.ts +3 -3
@@ -10,7 +10,7 @@ import {
1010 type SaveItemInput,
1111 type ScopeConfig,
1212 type Settings,
13-} from '@skilly/core'
13+} from '@panoplie/core'
1414 import type { Hono } from 'hono'
1515 import path from 'node:path'
1616 import { HttpError, isDirectory, isStringArray, readJsonBody, type AppContext } from './context.ts'
@@ -80,7 +80,7 @@ async function computeUsage(ctx: AppContext): Promise<Record<string, string[]>>
8080 }
8181
8282 /**
83- * Points the scope configs (`skilly.json` / `home.json`) at the new name of a renamed item or preset, so that a
83+ * Points the scope configs (`panoplie.json` / `home.json`) at the new name of a renamed item or preset, so that a
8484 * rename does not silently disable it. Exact names only: glob patterns are left as is. Nothing is applied.
8585 */
8686 async function renameInScopeConfigs(ctx: AppContext, kind: CatalogItemType | 'preset', from: string, to: string): Promise<void> {
@@ -110,7 +110,7 @@ async function renameInScopeConfigs(ctx: AppContext, kind: CatalogItemType | 'pr
110110 await ctx.syncEngine.writeConfig(scope.target, next)
111111 ctx.invalidateScan(id)
112112 } catch (e) {
113- console.error(`skilly: could not update the config of scope ${id} after a rename:`, e instanceof Error ? e.message : e)
113+ console.error(`panoplie: could not update the config of scope ${id} after a rename:`, e instanceof Error ? e.message : e)
114114 }
115115 }
116116 }
@@ -10,7 +10,7 @@ import {
10 type SaveItemInput,10 type SaveItemInput,
11 type ScopeConfig,11 type ScopeConfig,
12 type Settings,12 type Settings,
13-} from '@skilly/core'13+} from '@panoplie/core'
14 import type { Hono } from 'hono'14 import type { Hono } from 'hono'
15 import path from 'node:path'15 import path from 'node:path'
16 import { HttpError, isDirectory, isStringArray, readJsonBody, type AppContext } from './context.ts'16 import { HttpError, isDirectory, isStringArray, readJsonBody, type AppContext } from './context.ts'
@@ -80,7 +80,7 @@ async function computeUsage(ctx: AppContext): Promise<Record<string, string[]>>
80 }80 }
81 81
82 /**82 /**
83- * Points the scope configs (`skilly.json` / `home.json`) at the new name of a renamed item or preset, so that a83+ * Points the scope configs (`panoplie.json` / `home.json`) at the new name of a renamed item or preset, so that a
84 * rename does not silently disable it. Exact names only: glob patterns are left as is. Nothing is applied.84 * rename does not silently disable it. Exact names only: glob patterns are left as is. Nothing is applied.
85 */85 */
86 async function renameInScopeConfigs(ctx: AppContext, kind: CatalogItemType | 'preset', from: string, to: string): Promise<void> {86 async function renameInScopeConfigs(ctx: AppContext, kind: CatalogItemType | 'preset', from: string, to: string): Promise<void> {
@@ -110,7 +110,7 @@ async function renameInScopeConfigs(ctx: AppContext, kind: CatalogItemType | 'pr
110 await ctx.syncEngine.writeConfig(scope.target, next)110 await ctx.syncEngine.writeConfig(scope.target, next)
111 ctx.invalidateScan(id)111 ctx.invalidateScan(id)
112 } catch (e) {112 } catch (e) {
113- console.error(`skilly: could not update the config of scope ${id} after a rename:`, e instanceof Error ? e.message : e)113+ console.error(`panoplie: could not update the config of scope ${id} after a rename:`, e instanceof Error ? e.message : e)
114 }114 }
115 }115 }
116 }116 }
modified packages/server/src/context.ts +1 -1
@@ -13,7 +13,7 @@ import {
1313 type Settings,
1414 type SyncEngine,
1515 type SyncTarget,
16-} from '@skilly/core'
16+} from '@panoplie/core'
1717 import type { Context } from 'hono'
1818 import type { ProjectStore } from './store.ts'
1919
@@ -13,7 +13,7 @@ import {
13 type Settings,13 type Settings,
14 type SyncEngine,14 type SyncEngine,
15 type SyncTarget,15 type SyncTarget,
16-} from '@skilly/core'16+} from '@panoplie/core'
17 import type { Context } from 'hono'17 import type { Context } from 'hono'
18 import type { ProjectStore } from './store.ts'18 import type { ProjectStore } from './store.ts'
19 19
modified packages/server/src/dev.ts +1 -1
@@ -1,4 +1,4 @@
11 import { startServer } from './index.ts'
22
33 const server = await startServer({ token: 'dev', port: 4300 })
4-console.log(`skilly dev API on ${server.url} (token: dev)`)
4+console.log(`panoplie dev API on ${server.url} (token: dev)`)
@@ -1,4 +1,4 @@
1 import { startServer } from './index.ts'1 import { startServer } from './index.ts'
2 2
3 const server = await startServer({ token: 'dev', port: 4300 })3 const server = await startServer({ token: 'dev', port: 4300 })
4-console.log(`skilly dev API on ${server.url} (token: dev)`)4+console.log(`panoplie dev API on ${server.url} (token: dev)`)
modified packages/server/src/files.ts +1 -1
@@ -1,6 +1,6 @@
11 import { open, realpath, stat } from 'node:fs/promises'
22 import path from 'node:path'
3-import type { FileContent } from '@skilly/core'
3+import type { FileContent } from '@panoplie/core'
44
55 export const MAX_FILE_BYTES = 512 * 1024
66
@@ -1,6 +1,6 @@
1 import { open, realpath, stat } from 'node:fs/promises'1 import { open, realpath, stat } from 'node:fs/promises'
2 import path from 'node:path'2 import path from 'node:path'
3-import type { FileContent } from '@skilly/core'3+import type { FileContent } from '@panoplie/core'
4 4
5 export const MAX_FILE_BYTES = 512 * 10245 export const MAX_FILE_BYTES = 512 * 1024
6 6
modified packages/server/src/fslist.ts +1 -1
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'
22 import { readdir, stat } from 'node:fs/promises'
33 import os from 'node:os'
44 import path from 'node:path'
5-import type { DiscoveredProject, FsDiscovery, FsEntry, FsListing } from '@skilly/core'
5+import type { DiscoveredProject, FsDiscovery, FsEntry, FsListing } from '@panoplie/core'
66
77 const EXCLUDED = new Set(['node_modules', '$recycle.bin', 'system volume information'])
88 const AGENT_MARKERS = ['.claude', '.agents', '.codex', 'AGENTS.md', 'CLAUDE.md', '.mcp.json']
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'
2 import { readdir, stat } from 'node:fs/promises'2 import { readdir, stat } from 'node:fs/promises'
3 import os from 'node:os'3 import os from 'node:os'
4 import path from 'node:path'4 import path from 'node:path'
5-import type { DiscoveredProject, FsDiscovery, FsEntry, FsListing } from '@skilly/core'5+import type { DiscoveredProject, FsDiscovery, FsEntry, FsListing } from '@panoplie/core'
6 6
7 const EXCLUDED = new Set(['node_modules', '$recycle.bin', 'system volume information'])7 const EXCLUDED = new Set(['node_modules', '$recycle.bin', 'system volume information'])
8 const AGENT_MARKERS = ['.claude', '.agents', '.codex', 'AGENTS.md', 'CLAUDE.md', '.mcp.json']8 const AGENT_MARKERS = ['.claude', '.agents', '.codex', 'AGENTS.md', 'CLAUDE.md', '.mcp.json']
modified packages/server/src/index.ts +9 -6
@@ -8,6 +8,7 @@ import { serve } from '@hono/node-server'
88 import {
99 ITEM_TYPES,
1010 defaultScanContext,
11+ migrateLegacyConfigDir,
1112 createMarketplace as realCreateMarketplace,
1213 openCatalog as realOpenCatalog,
1314 openCatalogGit as realOpenCatalogGit,
@@ -24,7 +25,7 @@ import {
2425 type ProjectSummary,
2526 type ScanContext,
2627 type ScopeInventory,
27-} from '@skilly/core'
28+} from '@panoplie/core'
2829 import { Hono } from 'hono'
2930 import { registerCatalogRoutes } from './catalog-routes.ts'
3031 import { HttpError, guardGitConflicts, isDirectory, makeScopeRef, type AppContext } from './context.ts'
@@ -41,7 +42,7 @@ const STARTUP_GRACE_MS = 60_000
4142
4243 export interface AppOptions {
4344 token: string
44- /** Defaults to `~/.skilly`. */
45+ /** Defaults to `~/.panoplie`. */
4546 configDir?: string
4647 scanContext?: ScanContext
4748 /** Sync engine (defaults to the real one); injectable for tests. */
@@ -153,7 +154,7 @@ export function createApp(options: AppOptions): Hono {
153154
154155 export function createAppHandle(options: AppOptions): AppHandle {
155156 const { token } = options
156- const configDir = options.configDir ?? path.join(os.homedir(), '.skilly')
157+ const configDir = options.configDir ?? path.join(os.homedir(), '.panoplie')
157158 const ctx = options.scanContext ?? defaultScanContext()
158159 const store = new ProjectStore(configDir)
159160 const scanCache = new Map<string, { at: number; inventory: ScopeInventory }>()
@@ -175,7 +176,7 @@ export function createAppHandle(options: AppOptions): AppHandle {
175176 const status = await git.status()
176177 if (!status.isRepo) return
177178 if (status.remote) await git.sync()
178- else await git.commitAll('skilly: update catalog')
179+ else await git.commitAll('panoplie: update catalog')
179180 }
180181
181182 const appCtx: AppContext = {
@@ -212,7 +213,7 @@ export function createAppHandle(options: AppOptions): AppHandle {
212213 syncTimer = undefined
213214 syncChain = syncChain
214215 .then(runAutoSync)
215- .catch((e: unknown) => console.error('skilly: catalog auto-sync failed:', e instanceof Error ? e.message : e))
216+ .catch((e: unknown) => console.error('panoplie: catalog auto-sync failed:', e instanceof Error ? e.message : e))
216217 }, options.autoSyncDelayMs ?? 3000)
217218 syncTimer.unref()
218219 })
@@ -229,7 +230,7 @@ export function createAppHandle(options: AppOptions): AppHandle {
229230 })
230231
231232 app.use('/api/*', async (c, next) => {
232- if (!tokenMatches(c.req.header('x-skilly-token'), token)) return c.json(err('Unauthorized'), 401)
233+ if (!tokenMatches(c.req.header('x-panoplie-token'), token)) return c.json(err('Unauthorized'), 401)
233234 await next()
234235 })
235236
@@ -376,6 +377,8 @@ export function createAppHandle(options: AppOptions): AppHandle {
376377 }
377378
378379 export async function startServer(options: ServerOptions): Promise<RunningServer> {
380+ // Callers passing configDir (the CLI) migrate it themselves; the default folder is migrated here.
381+ if (options.configDir === undefined) await migrateLegacyConfigDir(path.join(os.homedir(), '.panoplie'))
379382 let idleTimer: NodeJS.Timeout | undefined
380383 let closed = false
381384 const armWatchdog = (ms: number): void => {
@@ -8,6 +8,7 @@ import { serve } from '@hono/node-server'
8 import {8 import {
9 ITEM_TYPES,9 ITEM_TYPES,
10 defaultScanContext,10 defaultScanContext,
11+ migrateLegacyConfigDir,
11 createMarketplace as realCreateMarketplace,12 createMarketplace as realCreateMarketplace,
12 openCatalog as realOpenCatalog,13 openCatalog as realOpenCatalog,
13 openCatalogGit as realOpenCatalogGit,14 openCatalogGit as realOpenCatalogGit,
@@ -24,7 +25,7 @@ import {
24 type ProjectSummary,25 type ProjectSummary,
25 type ScanContext,26 type ScanContext,
26 type ScopeInventory,27 type ScopeInventory,
27-} from '@skilly/core'28+} from '@panoplie/core'
28 import { Hono } from 'hono'29 import { Hono } from 'hono'
29 import { registerCatalogRoutes } from './catalog-routes.ts'30 import { registerCatalogRoutes } from './catalog-routes.ts'
30 import { HttpError, guardGitConflicts, isDirectory, makeScopeRef, type AppContext } from './context.ts'31 import { HttpError, guardGitConflicts, isDirectory, makeScopeRef, type AppContext } from './context.ts'
@@ -41,7 +42,7 @@ const STARTUP_GRACE_MS = 60_000
41 42
42 export interface AppOptions {43 export interface AppOptions {
43 token: string44 token: string
44- /** Defaults to `~/.skilly`. */45+ /** Defaults to `~/.panoplie`. */
45 configDir?: string46 configDir?: string
46 scanContext?: ScanContext47 scanContext?: ScanContext
47 /** Sync engine (defaults to the real one); injectable for tests. */48 /** Sync engine (defaults to the real one); injectable for tests. */
@@ -153,7 +154,7 @@ export function createApp(options: AppOptions): Hono {
153 154
154 export function createAppHandle(options: AppOptions): AppHandle {155 export function createAppHandle(options: AppOptions): AppHandle {
155 const { token } = options156 const { token } = options
156- const configDir = options.configDir ?? path.join(os.homedir(), '.skilly')157+ const configDir = options.configDir ?? path.join(os.homedir(), '.panoplie')
157 const ctx = options.scanContext ?? defaultScanContext()158 const ctx = options.scanContext ?? defaultScanContext()
158 const store = new ProjectStore(configDir)159 const store = new ProjectStore(configDir)
159 const scanCache = new Map<string, { at: number; inventory: ScopeInventory }>()160 const scanCache = new Map<string, { at: number; inventory: ScopeInventory }>()
@@ -175,7 +176,7 @@ export function createAppHandle(options: AppOptions): AppHandle {
175 const status = await git.status()176 const status = await git.status()
176 if (!status.isRepo) return177 if (!status.isRepo) return
177 if (status.remote) await git.sync()178 if (status.remote) await git.sync()
178- else await git.commitAll('skilly: update catalog')179+ else await git.commitAll('panoplie: update catalog')
179 }180 }
180 181
181 const appCtx: AppContext = {182 const appCtx: AppContext = {
@@ -212,7 +213,7 @@ export function createAppHandle(options: AppOptions): AppHandle {
212 syncTimer = undefined213 syncTimer = undefined
213 syncChain = syncChain214 syncChain = syncChain
214 .then(runAutoSync)215 .then(runAutoSync)
215- .catch((e: unknown) => console.error('skilly: catalog auto-sync failed:', e instanceof Error ? e.message : e))216+ .catch((e: unknown) => console.error('panoplie: catalog auto-sync failed:', e instanceof Error ? e.message : e))
216 }, options.autoSyncDelayMs ?? 3000)217 }, options.autoSyncDelayMs ?? 3000)
217 syncTimer.unref()218 syncTimer.unref()
218 })219 })
@@ -229,7 +230,7 @@ export function createAppHandle(options: AppOptions): AppHandle {
229 })230 })
230 231
231 app.use('/api/*', async (c, next) => {232 app.use('/api/*', async (c, next) => {
232- if (!tokenMatches(c.req.header('x-skilly-token'), token)) return c.json(err('Unauthorized'), 401)233+ if (!tokenMatches(c.req.header('x-panoplie-token'), token)) return c.json(err('Unauthorized'), 401)
233 await next()234 await next()
234 })235 })
235 236
@@ -376,6 +377,8 @@ export function createAppHandle(options: AppOptions): AppHandle {
376 }377 }
377 378
378 export async function startServer(options: ServerOptions): Promise<RunningServer> {379 export async function startServer(options: ServerOptions): Promise<RunningServer> {
380+ // Callers passing configDir (the CLI) migrate it themselves; the default folder is migrated here.
381+ if (options.configDir === undefined) await migrateLegacyConfigDir(path.join(os.homedir(), '.panoplie'))
379 let idleTimer: NodeJS.Timeout | undefined382 let idleTimer: NodeJS.Timeout | undefined
380 let closed = false383 let closed = false
381 const armWatchdog = (ms: number): void => {384 const armWatchdog = (ms: number): void => {
modified packages/server/src/marketplace-routes.ts +1 -1
@@ -1,6 +1,6 @@
11 import { readFile, readdir, rm } from 'node:fs/promises'
22 import path from 'node:path'
3-import type { ImportResult, MarketplaceSkillDetail, MarketplaceSource } from '@skilly/core'
3+import type { ImportResult, MarketplaceSkillDetail, MarketplaceSource } from '@panoplie/core'
44 import type { Hono } from 'hono'
55 import { HttpError, isStringArray, readJsonBody, type AppContext } from './context.ts'
66
@@ -1,6 +1,6 @@
1 import { readFile, readdir, rm } from 'node:fs/promises'1 import { readFile, readdir, rm } from 'node:fs/promises'
2 import path from 'node:path'2 import path from 'node:path'
3-import type { ImportResult, MarketplaceSkillDetail, MarketplaceSource } from '@skilly/core'3+import type { ImportResult, MarketplaceSkillDetail, MarketplaceSource } from '@panoplie/core'
4 import type { Hono } from 'hono'4 import type { Hono } from 'hono'
5 import { HttpError, isStringArray, readJsonBody, type AppContext } from './context.ts'5 import { HttpError, isStringArray, readJsonBody, type AppContext } from './context.ts'
6 6
modified packages/server/src/scope-routes.ts +1 -1
@@ -15,7 +15,7 @@ import {
1515 type ScopeInventory,
1616 type ScopeState,
1717 type SyncPlan,
18-} from '@skilly/core'
18+} from '@panoplie/core'
1919 import type { Hono } from 'hono'
2020 import { HttpError, isStringArray, memoizeCatalogReads, readJsonBody, type AppContext, type ScopeRef } from './context.ts'
2121
@@ -15,7 +15,7 @@ import {
15 type ScopeInventory,15 type ScopeInventory,
16 type ScopeState,16 type ScopeState,
17 type SyncPlan,17 type SyncPlan,
18-} from '@skilly/core'18+} from '@panoplie/core'
19 import type { Hono } from 'hono'19 import type { Hono } from 'hono'
20 import { HttpError, isStringArray, memoizeCatalogReads, readJsonBody, type AppContext, type ScopeRef } from './context.ts'20 import { HttpError, isStringArray, memoizeCatalogReads, readJsonBody, type AppContext, type ScopeRef } from './context.ts'
21 21
modified packages/server/src/store.ts +1 -1
@@ -1,7 +1,7 @@
11 import { createHash } from 'node:crypto'
22 import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
33 import path from 'node:path'
4-import { normalizePath, type MarketplaceSource, type Settings } from '@skilly/core'
4+import { normalizePath, type MarketplaceSource, type Settings } from '@panoplie/core'
55
66 export interface StoredProject {
77 id: string
@@ -1,7 +1,7 @@
1 import { createHash } from 'node:crypto'1 import { createHash } from 'node:crypto'
2 import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'2 import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
3 import path from 'node:path'3 import path from 'node:path'
4-import { normalizePath, type MarketplaceSource, type Settings } from '@skilly/core'4+import { normalizePath, type MarketplaceSource, type Settings } from '@panoplie/core'
5 5
6 export interface StoredProject {6 export interface StoredProject {
7 id: string7 id: string
modified packages/server/test/catalog.test.ts +3 -3
@@ -1,7 +1,7 @@
11 import { execFileSync } from 'node:child_process'
22 import { mkdir, readFile } from 'node:fs/promises'
33 import path from 'node:path'
4-import type { CatalogItem, CatalogItemDetail, CatalogResponse, FileContent, GitStatus, ImportResult, Settings } from '@skilly/core'
4+import type { CatalogItem, CatalogItemDetail, CatalogResponse, FileContent, GitStatus, ImportResult, Settings } from '@panoplie/core'
55 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
66 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'
77
@@ -181,11 +181,11 @@ describe('catalog git', { timeout: 30_000 }, () => {
181181 await env.send('PUT', '/api/settings', { autoSync: true })
182182 await env.send('PUT', '/api/catalog/items/skill/auto1', { content: skillMd('auto1') })
183183 await waitFor(() => git(env.catalogDir, '--no-optional-locks', 'status', '--porcelain').trim() === '')
184- expect(git(env.catalogDir, 'log', '--oneline')).toContain('skilly')
184+ expect(git(env.catalogDir, 'log', '--oneline')).toContain('panoplie')
185185
186186 await env.send('POST', '/api/catalog/git/remote', { url: remote })
187187 await env.send('PUT', '/api/catalog/items/skill/auto2', { content: skillMd('auto2') })
188- await waitFor(() => safe(() => git(remote, 'log', '--oneline', 'main')).includes('skilly'))
188+ await waitFor(() => safe(() => git(remote, 'log', '--oneline', 'main')).includes('panoplie'))
189189 expect(git(remote, 'ls-tree', '-r', '--name-only', 'main')).toContain('skills/auto2/SKILL.md')
190190 })
191191
@@ -1,7 +1,7 @@
1 import { execFileSync } from 'node:child_process'1 import { execFileSync } from 'node:child_process'
2 import { mkdir, readFile } from 'node:fs/promises'2 import { mkdir, readFile } from 'node:fs/promises'
3 import path from 'node:path'3 import path from 'node:path'
4-import type { CatalogItem, CatalogItemDetail, CatalogResponse, FileContent, GitStatus, ImportResult, Settings } from '@skilly/core'4+import type { CatalogItem, CatalogItemDetail, CatalogResponse, FileContent, GitStatus, ImportResult, Settings } from '@panoplie/core'
5 import { afterEach, beforeEach, describe, expect, it } from 'vitest'5 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'6 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'
7 7
@@ -181,11 +181,11 @@ describe('catalog git', { timeout: 30_000 }, () => {
181 await env.send('PUT', '/api/settings', { autoSync: true })181 await env.send('PUT', '/api/settings', { autoSync: true })
182 await env.send('PUT', '/api/catalog/items/skill/auto1', { content: skillMd('auto1') })182 await env.send('PUT', '/api/catalog/items/skill/auto1', { content: skillMd('auto1') })
183 await waitFor(() => git(env.catalogDir, '--no-optional-locks', 'status', '--porcelain').trim() === '')183 await waitFor(() => git(env.catalogDir, '--no-optional-locks', 'status', '--porcelain').trim() === '')
184- expect(git(env.catalogDir, 'log', '--oneline')).toContain('skilly')184+ expect(git(env.catalogDir, 'log', '--oneline')).toContain('panoplie')
185 185
186 await env.send('POST', '/api/catalog/git/remote', { url: remote })186 await env.send('POST', '/api/catalog/git/remote', { url: remote })
187 await env.send('PUT', '/api/catalog/items/skill/auto2', { content: skillMd('auto2') })187 await env.send('PUT', '/api/catalog/items/skill/auto2', { content: skillMd('auto2') })
188- await waitFor(() => safe(() => git(remote, 'log', '--oneline', 'main')).includes('skilly'))188+ await waitFor(() => safe(() => git(remote, 'log', '--oneline', 'main')).includes('panoplie'))
189 expect(git(remote, 'ls-tree', '-r', '--name-only', 'main')).toContain('skills/auto2/SKILL.md')189 expect(git(remote, 'ls-tree', '-r', '--name-only', 'main')).toContain('skills/auto2/SKILL.md')
190 })190 })
191 191
modified packages/server/test/helpers.ts +3 -3
@@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process'
22 import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
33 import os from 'node:os'
44 import path from 'node:path'
5-import type { ApplyResult, ConflictResolution, InventoryItem, ScopeConfig, SyncEngine, SyncPlan, SyncTarget } from '@skilly/core'
5+import type { ApplyResult, ConflictResolution, InventoryItem, ScopeConfig, SyncEngine, SyncPlan, SyncTarget } from '@panoplie/core'
66 import type { Hono } from 'hono'
77 import { createApp, type AppOptions } from '../src/index.ts'
88
@@ -43,7 +43,7 @@ process.env.GIT_COMMITTER_NAME = 'Test'
4343 process.env.GIT_COMMITTER_EMAIL = 'test@example.com'
4444
4545 export async function makeEnv(extra: Partial<AppOptions> = {}): Promise<Env> {
46- const tmp = await realpath(await mkdtemp(path.join(os.tmpdir(), 'skilly-server-')))
46+ const tmp = await realpath(await mkdtemp(path.join(os.tmpdir(), 'panoplie-server-')))
4747 const home = path.join(tmp, 'home')
4848 const configDir = path.join(tmp, 'config')
4949 const project = path.join(tmp, 'work', 'my-project')
@@ -52,7 +52,7 @@ export async function makeEnv(extra: Partial<AppOptions> = {}): Promise<Env> {
5252 const app = createApp({ token: TOKEN, configDir, scanContext: { homeDir: home }, autoSyncDelayMs: 50, ...extra })
5353 const req = (url: string, init: RequestInit = {}) => {
5454 const headers = new Headers(init.headers)
55- headers.set('x-skilly-token', TOKEN)
55+ headers.set('x-panoplie-token', TOKEN)
5656 return Promise.resolve(app.request(url, { ...init, headers }))
5757 }
5858 const send = (method: string, url: string, body?: unknown) =>
@@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process'
2 import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'2 import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
3 import os from 'node:os'3 import os from 'node:os'
4 import path from 'node:path'4 import path from 'node:path'
5-import type { ApplyResult, ConflictResolution, InventoryItem, ScopeConfig, SyncEngine, SyncPlan, SyncTarget } from '@skilly/core'5+import type { ApplyResult, ConflictResolution, InventoryItem, ScopeConfig, SyncEngine, SyncPlan, SyncTarget } from '@panoplie/core'
6 import type { Hono } from 'hono'6 import type { Hono } from 'hono'
7 import { createApp, type AppOptions } from '../src/index.ts'7 import { createApp, type AppOptions } from '../src/index.ts'
8 8
@@ -43,7 +43,7 @@ process.env.GIT_COMMITTER_NAME = 'Test'
43 process.env.GIT_COMMITTER_EMAIL = 'test@example.com'43 process.env.GIT_COMMITTER_EMAIL = 'test@example.com'
44 44
45 export async function makeEnv(extra: Partial<AppOptions> = {}): Promise<Env> {45 export async function makeEnv(extra: Partial<AppOptions> = {}): Promise<Env> {
46- const tmp = await realpath(await mkdtemp(path.join(os.tmpdir(), 'skilly-server-')))46+ const tmp = await realpath(await mkdtemp(path.join(os.tmpdir(), 'panoplie-server-')))
47 const home = path.join(tmp, 'home')47 const home = path.join(tmp, 'home')
48 const configDir = path.join(tmp, 'config')48 const configDir = path.join(tmp, 'config')
49 const project = path.join(tmp, 'work', 'my-project')49 const project = path.join(tmp, 'work', 'my-project')
@@ -52,7 +52,7 @@ export async function makeEnv(extra: Partial<AppOptions> = {}): Promise<Env> {
52 const app = createApp({ token: TOKEN, configDir, scanContext: { homeDir: home }, autoSyncDelayMs: 50, ...extra })52 const app = createApp({ token: TOKEN, configDir, scanContext: { homeDir: home }, autoSyncDelayMs: 50, ...extra })
53 const req = (url: string, init: RequestInit = {}) => {53 const req = (url: string, init: RequestInit = {}) => {
54 const headers = new Headers(init.headers)54 const headers = new Headers(init.headers)
55- headers.set('x-skilly-token', TOKEN)55+ headers.set('x-panoplie-token', TOKEN)
56 return Promise.resolve(app.request(url, { ...init, headers }))56 return Promise.resolve(app.request(url, { ...init, headers }))
57 }57 }
58 const send = (method: string, url: string, body?: unknown) =>58 const send = (method: string, url: string, body?: unknown) =>
modified packages/server/test/integration.test.ts +1 -1
@@ -1,7 +1,7 @@
11 import { execFileSync } from 'node:child_process'
22 import { readFile, writeFile } from 'node:fs/promises'
33 import path from 'node:path'
4-import { syncEngine, type ApplyResponse, type ProjectSummary, type ScopeConfig, type ScopeState } from '@skilly/core'
4+import { syncEngine, type ApplyResponse, type ProjectSummary, type ScopeConfig, type ScopeState } from '@panoplie/core'
55 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
66 import { makeEnv, skillMd, type Env } from './helpers.ts'
77
@@ -1,7 +1,7 @@
1 import { execFileSync } from 'node:child_process'1 import { execFileSync } from 'node:child_process'
2 import { readFile, writeFile } from 'node:fs/promises'2 import { readFile, writeFile } from 'node:fs/promises'
3 import path from 'node:path'3 import path from 'node:path'
4-import { syncEngine, type ApplyResponse, type ProjectSummary, type ScopeConfig, type ScopeState } from '@skilly/core'4+import { syncEngine, type ApplyResponse, type ProjectSummary, type ScopeConfig, type ScopeState } from '@panoplie/core'
5 import { afterEach, beforeEach, describe, expect, it } from 'vitest'5 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6 import { makeEnv, skillMd, type Env } from './helpers.ts'6 import { makeEnv, skillMd, type Env } from './helpers.ts'
7 7
modified packages/server/test/marketplace.test.ts +1 -1
@@ -1,6 +1,6 @@
11 import { mkdir } from 'node:fs/promises'
22 import path from 'node:path'
3-import type { ImportResult, MarketplaceSkill, MarketplaceSkillDetail, MarketplaceSource } from '@skilly/core'
3+import type { ImportResult, MarketplaceSkill, MarketplaceSkillDetail, MarketplaceSource } from '@panoplie/core'
44 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
55 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'
66
@@ -1,6 +1,6 @@
1 import { mkdir } from 'node:fs/promises'1 import { mkdir } from 'node:fs/promises'
2 import path from 'node:path'2 import path from 'node:path'
3-import type { ImportResult, MarketplaceSkill, MarketplaceSkillDetail, MarketplaceSource } from '@skilly/core'3+import type { ImportResult, MarketplaceSkill, MarketplaceSkillDetail, MarketplaceSource } from '@panoplie/core'
4 import { afterEach, beforeEach, describe, expect, it } from 'vitest'4 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'5 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'
6 6
modified packages/server/test/onboarding.test.ts +1 -1
@@ -1,7 +1,7 @@
11 import { execFileSync } from 'node:child_process'
22 import { mkdir, readFile } from 'node:fs/promises'
33 import path from 'node:path'
4-import type { CatalogResponse, FsDiscovery, Settings } from '@skilly/core'
4+import type { CatalogResponse, FsDiscovery, Settings } from '@panoplie/core'
55 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
66 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'
77
@@ -1,7 +1,7 @@
1 import { execFileSync } from 'node:child_process'1 import { execFileSync } from 'node:child_process'
2 import { mkdir, readFile } from 'node:fs/promises'2 import { mkdir, readFile } from 'node:fs/promises'
3 import path from 'node:path'3 import path from 'node:path'
4-import type { CatalogResponse, FsDiscovery, Settings } from '@skilly/core'4+import type { CatalogResponse, FsDiscovery, Settings } from '@panoplie/core'
5 import { afterEach, beforeEach, describe, expect, it } from 'vitest'5 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'6 import { fakeEngine, git, makeEnv, put, skillMd, type Env } from './helpers.ts'
7 7
modified packages/server/test/scopes.test.ts +2 -2
@@ -1,4 +1,4 @@
1-import type { ApplyResponse, CatalogItem, ProjectSummary, RemoveFromGlobalResult, ScopeConfig, ScopeState } from '@skilly/core'
1+import type { ApplyResponse, CatalogItem, ProjectSummary, RemoveFromGlobalResult, ScopeConfig, ScopeState } from '@panoplie/core'
22 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
33 import { fakeEngine, makeEnv, put, skillMd, type Env, type FakeEngine } from './helpers.ts'
44
@@ -53,7 +53,7 @@ describe('scope state and config', () => {
5353 expect(write.args[0]).toMatchObject({ kind: 'project', root: env.project, configDir: env.project, homeDir: env.home })
5454 })
5555
56- it('uses the skilly config dir as target for Home', async () => {
56+ it('uses the panoplie config dir as target for Home', async () => {
5757 await env.send('PUT', '/api/scopes/home/config', config())
5858 const write = engine.calls.find((c) => c.method === 'writeConfig')!
5959 expect(write.args[0]).toMatchObject({ kind: 'home', root: env.home, configDir: env.configDir })
@@ -1,4 +1,4 @@
1-import type { ApplyResponse, CatalogItem, ProjectSummary, RemoveFromGlobalResult, ScopeConfig, ScopeState } from '@skilly/core'1+import type { ApplyResponse, CatalogItem, ProjectSummary, RemoveFromGlobalResult, ScopeConfig, ScopeState } from '@panoplie/core'
2 import { afterEach, beforeEach, describe, expect, it } from 'vitest'2 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
3 import { fakeEngine, makeEnv, put, skillMd, type Env, type FakeEngine } from './helpers.ts'3 import { fakeEngine, makeEnv, put, skillMd, type Env, type FakeEngine } from './helpers.ts'
4 4
@@ -53,7 +53,7 @@ describe('scope state and config', () => {
53 expect(write.args[0]).toMatchObject({ kind: 'project', root: env.project, configDir: env.project, homeDir: env.home })53 expect(write.args[0]).toMatchObject({ kind: 'project', root: env.project, configDir: env.project, homeDir: env.home })
54 })54 })
55 55
56- it('uses the skilly config dir as target for Home', async () => {56+ it('uses the panoplie config dir as target for Home', async () => {
57 await env.send('PUT', '/api/scopes/home/config', config())57 await env.send('PUT', '/api/scopes/home/config', config())
58 const write = engine.calls.find((c) => c.method === 'writeConfig')!58 const write = engine.calls.find((c) => c.method === 'writeConfig')!
59 expect(write.args[0]).toMatchObject({ kind: 'home', root: env.home, configDir: env.configDir })59 expect(write.args[0]).toMatchObject({ kind: 'home', root: env.home, configDir: env.configDir })
modified packages/server/test/server.test.ts +4 -4
@@ -1,7 +1,7 @@
11 import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'node:fs/promises'
22 import os from 'node:os'
33 import path from 'node:path'
4-import type { FileContent, FsListing, ProjectSummary } from '@skilly/core'
4+import type { FileContent, FsListing, ProjectSummary } from '@panoplie/core'
55 import type { Hono } from 'hono'
66 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
77 import { createApp, startServer } from '../src/index.ts'
@@ -15,7 +15,7 @@ let app: Hono
1515
1616 function req(url: string, init: RequestInit = {}, token: string | null = TOKEN) {
1717 const headers = new Headers(init.headers)
18- if (token) headers.set('x-skilly-token', token)
18+ if (token) headers.set('x-panoplie-token', token)
1919 return app.request(url, { ...init, headers })
2020 }
2121
@@ -24,7 +24,7 @@ function post(url: string, body: unknown) {
2424 }
2525
2626 beforeEach(async () => {
27- tmp = await realpath(await mkdtemp(path.join(os.tmpdir(), 'skilly-server-')))
27+ tmp = await realpath(await mkdtemp(path.join(os.tmpdir(), 'panoplie-server-')))
2828 home = path.join(tmp, 'home')
2929 configDir = path.join(tmp, 'config')
3030 project = path.join(tmp, 'work', 'my-project')
@@ -216,7 +216,7 @@ describe('startServer', () => {
216216 })
217217 try {
218218 expect(server.port).toBeGreaterThan(0)
219- const headers = { 'x-skilly-token': TOKEN }
219+ const headers = { 'x-panoplie-token': TOKEN }
220220 const res = await fetch(`${server.url}/api/health`, { headers })
221221 expect(res.status).toBe(200)
222222 expect(idle).toBe(0) // startup grace is at least 60 s
@@ -1,7 +1,7 @@
1 import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'node:fs/promises'1 import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'node:fs/promises'
2 import os from 'node:os'2 import os from 'node:os'
3 import path from 'node:path'3 import path from 'node:path'
4-import type { FileContent, FsListing, ProjectSummary } from '@skilly/core'4+import type { FileContent, FsListing, ProjectSummary } from '@panoplie/core'
5 import type { Hono } from 'hono'5 import type { Hono } from 'hono'
6 import { afterEach, beforeEach, describe, expect, it } from 'vitest'6 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
7 import { createApp, startServer } from '../src/index.ts'7 import { createApp, startServer } from '../src/index.ts'
@@ -15,7 +15,7 @@ let app: Hono
15 15
16 function req(url: string, init: RequestInit = {}, token: string | null = TOKEN) {16 function req(url: string, init: RequestInit = {}, token: string | null = TOKEN) {
17 const headers = new Headers(init.headers)17 const headers = new Headers(init.headers)
18- if (token) headers.set('x-skilly-token', token)18+ if (token) headers.set('x-panoplie-token', token)
19 return app.request(url, { ...init, headers })19 return app.request(url, { ...init, headers })
20 }20 }
21 21
@@ -24,7 +24,7 @@ function post(url: string, body: unknown) {
24 }24 }
25 25
26 beforeEach(async () => {26 beforeEach(async () => {
27- tmp = await realpath(await mkdtemp(path.join(os.tmpdir(), 'skilly-server-')))27+ tmp = await realpath(await mkdtemp(path.join(os.tmpdir(), 'panoplie-server-')))
28 home = path.join(tmp, 'home')28 home = path.join(tmp, 'home')
29 configDir = path.join(tmp, 'config')29 configDir = path.join(tmp, 'config')
30 project = path.join(tmp, 'work', 'my-project')30 project = path.join(tmp, 'work', 'my-project')
@@ -216,7 +216,7 @@ describe('startServer', () => {
216 })216 })
217 try {217 try {
218 expect(server.port).toBeGreaterThan(0)218 expect(server.port).toBeGreaterThan(0)
219- const headers = { 'x-skilly-token': TOKEN }219+ const headers = { 'x-panoplie-token': TOKEN }
220 const res = await fetch(`${server.url}/api/health`, { headers })220 const res = await fetch(`${server.url}/api/health`, { headers })
221 expect(res.status).toBe(200)221 expect(res.status).toBe(200)
222 expect(idle).toBe(0) // startup grace is at least 60 s222 expect(idle).toBe(0) // startup grace is at least 60 s
modified packages/ui/app/assets/css/main.css +1 -1
@@ -75,7 +75,7 @@
7575 --text-sm--line-height: 1.45;
7676 }
7777
78-/* ------------------------------------------------------------------ skilly tokens (source of truth: design/BRIEF.md 1.2) */
78+/* ------------------------------------------------------------------ panoplie tokens (source of truth: design/BRIEF.md 1.2) */
7979 :root,
8080 .light {
8181 --sk-bg: #f5f4f1;
@@ -75,7 +75,7 @@
75 --text-sm--line-height: 1.45;75 --text-sm--line-height: 1.45;
76 }76 }
77 77
78-/* ------------------------------------------------------------------ skilly tokens (source of truth: design/BRIEF.md 1.2) */78+/* ------------------------------------------------------------------ panoplie tokens (source of truth: design/BRIEF.md 1.2) */
79 :root,79 :root,
80 .light {80 .light {
81 --sk-bg: #f5f4f1;81 --sk-bg: #f5f4f1;
modified packages/ui/app/components/AddToCatalogModal.vue +4 -4
@@ -1,9 +1,9 @@
11 <script setup lang="ts">
2-import type { ImportResult, InventoryItem, ItemStatus } from '@skilly/core'
2+import type { ImportResult, InventoryItem, ItemStatus } from '@panoplie/core'
33
44 /**
55 * "Add to catalog": copies local items into the catalog (tags, "overwrite" when a different version exists).
6- * The scope page then keeps them enabled where they came from, managed by skilly (`done` carries the result).
6+ * The scope page then keeps them enabled where they came from, managed by panoplie (`done` carries the result).
77 */
88 const props = defineProps<{ scope: string, items: InventoryItem[], statuses: Record<string, ItemStatus> }>()
99 const open = defineModel<boolean>('open', { default: false })
@@ -67,7 +67,7 @@ async function submit() {
6767 })
6868 toast.add({
6969 title: `${result.value.imported.length} added to the catalog`,
70- description: result.value.imported.length ? 'They stay here, now managed by skilly. Apply to take them over.' : undefined,
70+ description: result.value.imported.length ? 'They stay here, now managed by panoplie. Apply to take them over.' : undefined,
7171 icon: 'i-lucide-check',
7272 color: 'success'
7373 })
@@ -81,7 +81,7 @@ async function submit() {
8181 </script>
8282
8383 <template>
84- <SkModal v-model:open="open" :width="items.some(i => statuses[i.id]?.state === 'differs-from-catalog') ? 'lg' : 'md'" title="Add to catalog" description="Copy these items into the catalog so they can be reused in other projects. They stay enabled here, managed by skilly.">
84+ <SkModal v-model:open="open" :width="items.some(i => statuses[i.id]?.state === 'differs-from-catalog') ? 'lg' : 'md'" title="Add to catalog" description="Copy these items into the catalog so they can be reused in other projects. They stay enabled here, managed by panoplie.">
8585 <div v-if="result" class="flex flex-col gap-3">
8686 <SkNote v-if="result.imported.length" tone="ok">
8787 <span class="font-medium">{{ result.imported.length }} added</span>
@@ -1,9 +1,9 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ImportResult, InventoryItem, ItemStatus } from '@skilly/core'2+import type { ImportResult, InventoryItem, ItemStatus } from '@panoplie/core'
3 3
4 /**4 /**
5 * "Add to catalog": copies local items into the catalog (tags, "overwrite" when a different version exists).5 * "Add to catalog": copies local items into the catalog (tags, "overwrite" when a different version exists).
6- * The scope page then keeps them enabled where they came from, managed by skilly (`done` carries the result).6+ * The scope page then keeps them enabled where they came from, managed by panoplie (`done` carries the result).
7 */7 */
8 const props = defineProps<{ scope: string, items: InventoryItem[], statuses: Record<string, ItemStatus> }>()8 const props = defineProps<{ scope: string, items: InventoryItem[], statuses: Record<string, ItemStatus> }>()
9 const open = defineModel<boolean>('open', { default: false })9 const open = defineModel<boolean>('open', { default: false })
@@ -67,7 +67,7 @@ async function submit() {
67 })67 })
68 toast.add({68 toast.add({
69 title: `${result.value.imported.length} added to the catalog`,69 title: `${result.value.imported.length} added to the catalog`,
70- description: result.value.imported.length ? 'They stay here, now managed by skilly. Apply to take them over.' : undefined,70+ description: result.value.imported.length ? 'They stay here, now managed by panoplie. Apply to take them over.' : undefined,
71 icon: 'i-lucide-check',71 icon: 'i-lucide-check',
72 color: 'success'72 color: 'success'
73 })73 })
@@ -81,7 +81,7 @@ async function submit() {
81 </script>81 </script>
82 82
83 <template>83 <template>
84- <SkModal v-model:open="open" :width="items.some(i => statuses[i.id]?.state === 'differs-from-catalog') ? 'lg' : 'md'" title="Add to catalog" description="Copy these items into the catalog so they can be reused in other projects. They stay enabled here, managed by skilly.">84+ <SkModal v-model:open="open" :width="items.some(i => statuses[i.id]?.state === 'differs-from-catalog') ? 'lg' : 'md'" title="Add to catalog" description="Copy these items into the catalog so they can be reused in other projects. They stay enabled here, managed by panoplie.">
85 <div v-if="result" class="flex flex-col gap-3">85 <div v-if="result" class="flex flex-col gap-3">
86 <SkNote v-if="result.imported.length" tone="ok">86 <SkNote v-if="result.imported.length" tone="ok">
87 <span class="font-medium">{{ result.imported.length }} added</span>87 <span class="font-medium">{{ result.imported.length }} added</span>
modified packages/ui/app/components/AppHeader.vue +2 -2
@@ -45,9 +45,9 @@ async function onPill() {
4545 <header
4646 class="sticky top-0 z-30 flex min-w-0 items-center gap-2.5 px-[18px] py-3 backdrop-blur-[8px] bg-[color-mix(in_oklch,var(--sk-bg)_82%,transparent)]"
4747 >
48- <NuxtLink to="/" class="flex shrink-0 items-center gap-2 text-sk-text hover:text-sk-text" aria-label="skilly">
48+ <NuxtLink to="/" class="flex shrink-0 items-center gap-2 text-sk-text hover:text-sk-text" aria-label="panoplie">
4949 <SkLogo />
50- <span class="text-base font-semibold">skilly</span>
50+ <span class="text-base font-semibold">panoplie</span>
5151 </NuxtLink>
5252
5353 <span class="min-w-0 flex-1" />
@@ -45,9 +45,9 @@ async function onPill() {
45 <header45 <header
46 class="sticky top-0 z-30 flex min-w-0 items-center gap-2.5 px-[18px] py-3 backdrop-blur-[8px] bg-[color-mix(in_oklch,var(--sk-bg)_82%,transparent)]"46 class="sticky top-0 z-30 flex min-w-0 items-center gap-2.5 px-[18px] py-3 backdrop-blur-[8px] bg-[color-mix(in_oklch,var(--sk-bg)_82%,transparent)]"
47 >47 >
48- <NuxtLink to="/" class="flex shrink-0 items-center gap-2 text-sk-text hover:text-sk-text" aria-label="skilly">48+ <NuxtLink to="/" class="flex shrink-0 items-center gap-2 text-sk-text hover:text-sk-text" aria-label="panoplie">
49 <SkLogo />49 <SkLogo />
50- <span class="text-base font-semibold">skilly</span>50+ <span class="text-base font-semibold">panoplie</span>
51 </NuxtLink>51 </NuxtLink>
52 52
53 <span class="min-w-0 flex-1" />53 <span class="min-w-0 flex-1" />
modified packages/ui/app/components/CatalogBrowser.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogItem, CatalogItemType } from '@skilly/core'
2+import type { CatalogItem, CatalogItemType } from '@panoplie/core'
33
44 /**
55 * Catalog items screen: header, left column (search, type chips, tag chips, list) and the detail panel.
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItem, CatalogItemType } from '@skilly/core'2+import type { CatalogItem, CatalogItemType } from '@panoplie/core'
3 3
4 /**4 /**
5 * Catalog items screen: header, left column (search, type chips, tag chips, list) and the detail panel.5 * Catalog items screen: header, left column (search, type chips, tag chips, list) and the detail panel.
modified packages/ui/app/components/CatalogHeader.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogItemType } from '@skilly/core'
2+import type { CatalogItemType } from '@panoplie/core'
33
44 /**
55 * Catalog header: title, "Your shared library" and the New split button (the main action creates the current type,
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItemType } from '@skilly/core'2+import type { CatalogItemType } from '@panoplie/core'
3 3
4 /**4 /**
5 * Catalog header: title, "Your shared library" and the New split button (the main action creates the current type,5 * Catalog header: title, "Your shared library" and the New split button (the main action creates the current type,
modified packages/ui/app/components/CatalogItemDiff.vue +2 -2
@@ -1,8 +1,8 @@
11 <script setup lang="ts">
2-import type { CatalogItemDetail, FileContent, InventoryItem, SkillDetails } from '@skilly/core'
2+import type { CatalogItemDetail, FileContent, InventoryItem, SkillDetails } from '@panoplie/core'
33
44 /**
5- * Diff of a local item (not managed by skilly) against the catalog item of the same name, shown before an import
5+ * Diff of a local item (not managed by panoplie) against the catalog item of the same name, shown before an import
66 * replaces the catalog version. Compares the main file (SKILL.md, rule or subagent markdown) and, for skill folders,
77 * the file lists. A legacy command (single markdown file) is compared with the catalog SKILL.md it would become. Codex subagents (TOML rendering) and entries of shared configuration files have no standalone diff.
88 */
@@ -1,8 +1,8 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItemDetail, FileContent, InventoryItem, SkillDetails } from '@skilly/core'2+import type { CatalogItemDetail, FileContent, InventoryItem, SkillDetails } from '@panoplie/core'
3 3
4 /**4 /**
5- * Diff of a local item (not managed by skilly) against the catalog item of the same name, shown before an import5+ * Diff of a local item (not managed by panoplie) against the catalog item of the same name, shown before an import
6 * replaces the catalog version. Compares the main file (SKILL.md, rule or subagent markdown) and, for skill folders,6 * replaces the catalog version. Compares the main file (SKILL.md, rule or subagent markdown) and, for skill folders,
7 * the file lists. A legacy command (single markdown file) is compared with the catalog SKILL.md it would become. Codex subagents (TOML rendering) and entries of shared configuration files have no standalone diff.7 * the file lists. A legacy command (single markdown file) is compared with the catalog SKILL.md it would become. Codex subagents (TOML rendering) and entries of shared configuration files have no standalone diff.
8 */8 */
modified packages/ui/app/components/CatalogItemEditor.vue +3 -3
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogItemDetail, CatalogItemType, HookTemplate, McpTemplate, SaveItemInput } from '@skilly/core'
2+import type { CatalogItemDetail, CatalogItemType, HookTemplate, McpTemplate, SaveItemInput } from '@panoplie/core'
33
44 /**
55 * Detail panel of a catalog item: read view, then edit mode (Markdown split editor for skill / rule / subagent,
@@ -262,7 +262,7 @@ const FORM_VIEWS = [
262262
263263 const originLabel = computed(() => {
264264 const o = detail.value?.item.origin
265- if (!o || o.kind === 'created') return 'Created in skilly'
265+ if (!o || o.kind === 'created') return 'Created in panoplie'
266266 if (o.kind === 'import') return 'Imported'
267267 return 'Marketplace'
268268 })
@@ -417,7 +417,7 @@ const TA = 'block w-full resize-none border-0 bg-sk-bg px-5 py-[18px] font-mono
417417 <p v-if="envNames.length" class="m-0 text-pretty text-sk-muted">
418418 The value of {{ envNames.join(', ') }} is read from your environment. It is never stored in the catalog.
419419 </p>
420- <p v-if="type === 'hook'" class="m-0 text-xs text-sk-faint">skilly never runs the command: the agent does.</p>
420+ <p v-if="type === 'hook'" class="m-0 text-xs text-sk-faint">panoplie never runs the command: the agent does.</p>
421421 </template>
422422
423423 <div v-if="type === 'skill' && existingFiles.length" class="flex flex-col gap-2 pt-1">
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItemDetail, CatalogItemType, HookTemplate, McpTemplate, SaveItemInput } from '@skilly/core'2+import type { CatalogItemDetail, CatalogItemType, HookTemplate, McpTemplate, SaveItemInput } from '@panoplie/core'
3 3
4 /**4 /**
5 * Detail panel of a catalog item: read view, then edit mode (Markdown split editor for skill / rule / subagent,5 * Detail panel of a catalog item: read view, then edit mode (Markdown split editor for skill / rule / subagent,
@@ -262,7 +262,7 @@ const FORM_VIEWS = [
262 262
263 const originLabel = computed(() => {263 const originLabel = computed(() => {
264 const o = detail.value?.item.origin264 const o = detail.value?.item.origin
265- if (!o || o.kind === 'created') return 'Created in skilly'265+ if (!o || o.kind === 'created') return 'Created in panoplie'
266 if (o.kind === 'import') return 'Imported'266 if (o.kind === 'import') return 'Imported'
267 return 'Marketplace'267 return 'Marketplace'
268 })268 })
@@ -417,7 +417,7 @@ const TA = 'block w-full resize-none border-0 bg-sk-bg px-5 py-[18px] font-mono
417 <p v-if="envNames.length" class="m-0 text-pretty text-sk-muted">417 <p v-if="envNames.length" class="m-0 text-pretty text-sk-muted">
418 The value of {{ envNames.join(', ') }} is read from your environment. It is never stored in the catalog.418 The value of {{ envNames.join(', ') }} is read from your environment. It is never stored in the catalog.
419 </p>419 </p>
420- <p v-if="type === 'hook'" class="m-0 text-xs text-sk-faint">skilly never runs the command: the agent does.</p>420+ <p v-if="type === 'hook'" class="m-0 text-xs text-sk-faint">panoplie never runs the command: the agent does.</p>
421 </template>421 </template>
422 422
423 <div v-if="type === 'skill' && existingFiles.length" class="flex flex-col gap-2 pt-1">423 <div v-if="type === 'skill' && existingFiles.length" class="flex flex-col gap-2 pt-1">
modified packages/ui/app/components/CatalogSetupCard.vue +1 -1
@@ -32,7 +32,7 @@ async function create() {
3232 <h2 class="m-0 text-base font-semibold">No catalog yet</h2>
3333 <p class="m-0 max-w-md text-pretty text-sk-muted">
3434 The catalog is a git repository holding your skills, rules, MCP servers, hooks, subagents and presets.
35- Create one at the location below, or point skilly to an existing repository in Settings.
35+ Create one at the location below, or point panoplie to an existing repository in Settings.
3636 </p>
3737 <p class="m-0 max-w-full truncate rounded-[7px] border border-sk-line2 bg-sk-bg px-3 py-1.5 font-mono text-xs" :title="path">{{ path || '...' }}</p>
3838 <div class="flex gap-2">
@@ -32,7 +32,7 @@ async function create() {
32 <h2 class="m-0 text-base font-semibold">No catalog yet</h2>32 <h2 class="m-0 text-base font-semibold">No catalog yet</h2>
33 <p class="m-0 max-w-md text-pretty text-sk-muted">33 <p class="m-0 max-w-md text-pretty text-sk-muted">
34 The catalog is a git repository holding your skills, rules, MCP servers, hooks, subagents and presets.34 The catalog is a git repository holding your skills, rules, MCP servers, hooks, subagents and presets.
35- Create one at the location below, or point skilly to an existing repository in Settings.35+ Create one at the location below, or point panoplie to an existing repository in Settings.
36 </p>36 </p>
37 <p class="m-0 max-w-full truncate rounded-[7px] border border-sk-line2 bg-sk-bg px-3 py-1.5 font-mono text-xs" :title="path">{{ path || '...' }}</p>37 <p class="m-0 max-w-full truncate rounded-[7px] border border-sk-line2 bg-sk-bg px-3 py-1.5 font-mono text-xs" :title="path">{{ path || '...' }}</p>
38 <div class="flex gap-2">38 <div class="flex gap-2">
modified packages/ui/app/components/EnvRows.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { McpEnvValue } from '@skilly/core'
2+import type { McpEnvValue } from '@panoplie/core'
33
44 /** Editable key -> (env var | literal) rows. Used for MCP `env` and `headers`. */
55 const props = defineProps<{ modelValue: Record<string, McpEnvValue> | undefined, keyLabel: string, keyPlaceholder: string }>()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { McpEnvValue } from '@skilly/core'2+import type { McpEnvValue } from '@panoplie/core'
3 3
4 /** Editable key -> (env var | literal) rows. Used for MCP `env` and `headers`. */4 /** Editable key -> (env var | literal) rows. Used for MCP `env` and `headers`. */
5 const props = defineProps<{ modelValue: Record<string, McpEnvValue> | undefined, keyLabel: string, keyPlaceholder: string }>()5 const props = defineProps<{ modelValue: Record<string, McpEnvValue> | undefined, keyLabel: string, keyPlaceholder: string }>()
modified packages/ui/app/components/FolderBrowser.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { FsListing } from '@skilly/core'
2+import type { FsListing } from '@panoplie/core'
33
44 /** Folder browser backed by /api/fs/list. `selected` is the highlighted folder, falling back to the folder being browsed. */
55 const props = defineProps<{ start?: string, height?: string }>()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { FsListing } from '@skilly/core'2+import type { FsListing } from '@panoplie/core'
3 3
4 /** Folder browser backed by /api/fs/list. `selected` is the highlighted folder, falling back to the folder being browsed. */4 /** Folder browser backed by /api/fs/list. `selected` is the highlighted folder, falling back to the folder being browsed. */
5 const props = defineProps<{ start?: string, height?: string }>()5 const props = defineProps<{ start?: string, height?: string }>()
modified packages/ui/app/components/HookForm.vue +2 -2
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { AgentId, HookTemplate } from '@skilly/core'
2+import type { AgentId, HookTemplate } from '@panoplie/core'
33
44 /** Form for a hook template. The parent owns the object (v-model) and renders the YAML. */
55 const t = defineModel<HookTemplate>({ required: true })
@@ -37,7 +37,7 @@ function toggleAgent(a: AgentId, on: boolean | 'indeterminate') {
3737 </CatalogField>
3838 </div>
3939
40- <CatalogField label="Command" required hint="Run by the agent. skilly never executes it.">
40+ <CatalogField label="Command" required hint="Run by the agent. panoplie never executes it.">
4141 <UInput :model-value="t.handler.command" class="w-full" placeholder="prettier --write $FILE" :ui="{ base: 'font-mono text-xs' }" @update:model-value="(v: string) => patchHandler({ command: v })" />
4242 </CatalogField>
4343
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { AgentId, HookTemplate } from '@skilly/core'2+import type { AgentId, HookTemplate } from '@panoplie/core'
3 3
4 /** Form for a hook template. The parent owns the object (v-model) and renders the YAML. */4 /** Form for a hook template. The parent owns the object (v-model) and renders the YAML. */
5 const t = defineModel<HookTemplate>({ required: true })5 const t = defineModel<HookTemplate>({ required: true })
@@ -37,7 +37,7 @@ function toggleAgent(a: AgentId, on: boolean | 'indeterminate') {
37 </CatalogField>37 </CatalogField>
38 </div>38 </div>
39 39
40- <CatalogField label="Command" required hint="Run by the agent. skilly never executes it.">40+ <CatalogField label="Command" required hint="Run by the agent. panoplie never executes it.">
41 <UInput :model-value="t.handler.command" class="w-full" placeholder="prettier --write $FILE" :ui="{ base: 'font-mono text-xs' }" @update:model-value="(v: string) => patchHandler({ command: v })" />41 <UInput :model-value="t.handler.command" class="w-full" placeholder="prettier --write $FILE" :ui="{ base: 'font-mono text-xs' }" @update:model-value="(v: string) => patchHandler({ command: v })" />
42 </CatalogField>42 </CatalogField>
43 43
modified packages/ui/app/components/ItemPicker.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogItem, CatalogItemType } from '@skilly/core'
2+import type { CatalogItem, CatalogItemType } from '@panoplie/core'
33
44 /**
55 * Checkbox picker over the catalog, grouped by type, with a filter.
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItem, CatalogItemType } from '@skilly/core'2+import type { CatalogItem, CatalogItemType } from '@panoplie/core'
3 3
4 /**4 /**
5 * Checkbox picker over the catalog, grouped by type, with a filter.5 * Checkbox picker over the catalog, grouped by type, with a filter.
modified packages/ui/app/components/MarkdownView.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { FileContent } from '@skilly/core'
2+import type { FileContent } from '@panoplie/core'
33
44 const props = defineProps<{
55 /** Absolute path of the file to display. */
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { FileContent } from '@skilly/core'2+import type { FileContent } from '@panoplie/core'
3 3
4 const props = defineProps<{4 const props = defineProps<{
5 /** Absolute path of the file to display. */5 /** Absolute path of the file to display. */
modified packages/ui/app/components/McpForm.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { McpTemplate } from '@skilly/core'
2+import type { McpTemplate } from '@panoplie/core'
33
44 /** Form for an MCP server template. The parent owns the object (v-model) and renders the YAML. */
55 const t = defineModel<McpTemplate>({ required: true })
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { McpTemplate } from '@skilly/core'2+import type { McpTemplate } from '@panoplie/core'
3 3
4 /** Form for an MCP server template. The parent owns the object (v-model) and renders the YAML. */4 /** Form for an MCP server template. The parent owns the object (v-model) and renders the YAML. */
5 const t = defineModel<McpTemplate>({ required: true })5 const t = defineModel<McpTemplate>({ required: true })
modified packages/ui/app/components/PresetEditor.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogItemType, Preset } from '@skilly/core'
2+import type { CatalogItemType, Preset } from '@panoplie/core'
33
44 /** Preset editor: name, description, included presets, items; resolved preview on the right. `name` is undefined for a new preset. */
55 const props = defineProps<{ name?: string }>()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItemType, Preset } from '@skilly/core'2+import type { CatalogItemType, Preset } from '@panoplie/core'
3 3
4 /** Preset editor: name, description, included presets, items; resolved preview on the right. `name` is undefined for a new preset. */4 /** Preset editor: name, description, included presets, items; resolved preview on the right. `name` is undefined for a new preset. */
5 const props = defineProps<{ name?: string }>()5 const props = defineProps<{ name?: string }>()
modified packages/ui/app/components/RemoveFromGlobalModal.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { InventoryItem, ItemStatus, RemoveFromGlobalResult } from '@skilly/core'
2+import type { InventoryItem, ItemStatus, RemoveFromGlobalResult } from '@panoplie/core'
33
44 /**
55 * "Move {name} to projects" (mockup 7, "Remove from global"): three numbered steps adapted to the real API.
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { InventoryItem, ItemStatus, RemoveFromGlobalResult } from '@skilly/core'2+import type { InventoryItem, ItemStatus, RemoveFromGlobalResult } from '@panoplie/core'
3 3
4 /**4 /**
5 * "Move {name} to projects" (mockup 7, "Remove from global"): three numbered steps adapted to the real API.5 * "Move {name} to projects" (mockup 7, "Remove from global"): three numbered steps adapted to the real API.
modified packages/ui/app/components/dashboard/DashboardRecent.vue +2 -2
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ProjectSummary } from '@skilly/core'
2+import type { ProjectSummary } from '@panoplie/core'
33 import type { ScopeSummary } from '../../composables/useScopeSummaries'
44
55 /** Dashboard "Recent projects": the last projects opened (or the last added), with their sync status. */
@@ -46,7 +46,7 @@ const statusOf = (p: ProjectSummary) => (p.exists ? props.summaries[p.id]?.statu
4646 <USkeleton v-else class="h-4 w-20" />
4747 </SkRow>
4848 <SkEmpty v-if="!projects.length" title="No project yet">
49- A project is a folder you work in. skilly shows what Claude Code and Codex load there and writes the items you enable.
49+ A project is a folder you work in. panoplie shows what Claude Code and Codex load there and writes the items you enable.
5050 <template #actions><SkButton variant="primary" label="Add project" @click="addOpen = true" /></template>
5151 </SkEmpty>
5252 </SkPanel>
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ProjectSummary } from '@skilly/core'2+import type { ProjectSummary } from '@panoplie/core'
3 import type { ScopeSummary } from '../../composables/useScopeSummaries'3 import type { ScopeSummary } from '../../composables/useScopeSummaries'
4 4
5 /** Dashboard "Recent projects": the last projects opened (or the last added), with their sync status. */5 /** Dashboard "Recent projects": the last projects opened (or the last added), with their sync status. */
@@ -46,7 +46,7 @@ const statusOf = (p: ProjectSummary) => (p.exists ? props.summaries[p.id]?.statu
46 <USkeleton v-else class="h-4 w-20" />46 <USkeleton v-else class="h-4 w-20" />
47 </SkRow>47 </SkRow>
48 <SkEmpty v-if="!projects.length" title="No project yet">48 <SkEmpty v-if="!projects.length" title="No project yet">
49- A project is a folder you work in. skilly shows what Claude Code and Codex load there and writes the items you enable.49+ A project is a folder you work in. panoplie shows what Claude Code and Codex load there and writes the items you enable.
50 <template #actions><SkButton variant="primary" label="Add project" @click="addOpen = true" /></template>50 <template #actions><SkButton variant="primary" label="Add project" @click="addOpen = true" /></template>
51 </SkEmpty>51 </SkEmpty>
52 </SkPanel>52 </SkPanel>
modified packages/ui/app/components/dev/DevKitSheet.vue +2 -2
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ItemStatus, PlanActionKind } from '@skilly/core'
2+import type { ItemStatus, PlanActionKind } from '@panoplie/core'
33
44 /** Every Sk primitive on one sheet. Rendered once per theme by /dev/kit. Keep in sync when adding a primitive. */
55 const toast = useToast()
@@ -285,7 +285,7 @@ const typeOptions = [
285285 <SkButton label="Open modal" @click="modalOpen = true" />
286286 <SkButton label="Open drawer" @click="drawerOpen = true" />
287287 <SkButton label="Show toast" @click="toast.add({ title: 'Applied 4 changes · 2 files written' })" />
288- <UDropdownMenu :items="[[{ label: 'Rescan' }, { label: 'Copy path' }], [{ label: 'Remove from skilly', color: 'error' }]]">
288+ <UDropdownMenu :items="[[{ label: 'Rescan' }, { label: 'Copy path' }], [{ label: 'Remove from panoplie', color: 'error' }]]">
289289 <SkButton icon="i-lucide-ellipsis" label="Menu" />
290290 </UDropdownMenu>
291291 <UTooltip text="A tooltip"><SkButton variant="ghost" label="Hover me" /></UTooltip>
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ItemStatus, PlanActionKind } from '@skilly/core'2+import type { ItemStatus, PlanActionKind } from '@panoplie/core'
3 3
4 /** Every Sk primitive on one sheet. Rendered once per theme by /dev/kit. Keep in sync when adding a primitive. */4 /** Every Sk primitive on one sheet. Rendered once per theme by /dev/kit. Keep in sync when adding a primitive. */
5 const toast = useToast()5 const toast = useToast()
@@ -285,7 +285,7 @@ const typeOptions = [
285 <SkButton label="Open modal" @click="modalOpen = true" />285 <SkButton label="Open modal" @click="modalOpen = true" />
286 <SkButton label="Open drawer" @click="drawerOpen = true" />286 <SkButton label="Open drawer" @click="drawerOpen = true" />
287 <SkButton label="Show toast" @click="toast.add({ title: 'Applied 4 changes · 2 files written' })" />287 <SkButton label="Show toast" @click="toast.add({ title: 'Applied 4 changes · 2 files written' })" />
288- <UDropdownMenu :items="[[{ label: 'Rescan' }, { label: 'Copy path' }], [{ label: 'Remove from skilly', color: 'error' }]]">288+ <UDropdownMenu :items="[[{ label: 'Rescan' }, { label: 'Copy path' }], [{ label: 'Remove from panoplie', color: 'error' }]]">
289 <SkButton icon="i-lucide-ellipsis" label="Menu" />289 <SkButton icon="i-lucide-ellipsis" label="Menu" />
290 </UDropdownMenu>290 </UDropdownMenu>
291 <UTooltip text="A tooltip"><SkButton variant="ghost" label="Hover me" /></UTooltip>291 <UTooltip text="A tooltip"><SkButton variant="ghost" label="Hover me" /></UTooltip>
modified packages/ui/app/components/home/HomeSuggestions.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { InventoryItem, ItemStatus } from '@skilly/core'
2+import type { InventoryItem, ItemStatus } from '@panoplie/core'
33 import type { ProjectSpecific } from '../../utils/inventoryView'
44
55 /**
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { InventoryItem, ItemStatus } from '@skilly/core'2+import type { InventoryItem, ItemStatus } from '@panoplie/core'
3 import type { ProjectSpecific } from '../../utils/inventoryView'3 import type { ProjectSpecific } from '../../utils/inventoryView'
4 4
5 /**5 /**
modified packages/ui/app/components/projects/ProjectsTable.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ProjectSummary } from '@skilly/core'
2+import type { ProjectSummary } from '@panoplie/core'
33 import type { Tone } from '../../utils/status'
44
55 /**
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ProjectSummary } from '@skilly/core'2+import type { ProjectSummary } from '@panoplie/core'
3 import type { Tone } from '../../utils/status'3 import type { Tone } from '../../utils/status'
4 4
5 /**5 /**
modified packages/ui/app/components/scope/ScopeAddPresetsModal.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { Preset } from '@skilly/core'
2+import type { Preset } from '@panoplie/core'
33
44 /** "Add presets to {scope}" modal (mockup 11): search, checkbox list of presets, "Create a new preset" link. */
55 const props = defineProps<{ presets: Preset[], enabled: string[], scopeName: string, catalogExists: boolean }>()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { Preset } from '@skilly/core'2+import type { Preset } from '@panoplie/core'
3 3
4 /** "Add presets to {scope}" modal (mockup 11): search, checkbox list of presets, "Create a new preset" link. */4 /** "Add presets to {scope}" modal (mockup 11): search, checkbox list of presets, "Create a new preset" link. */
5 const props = defineProps<{ presets: Preset[], enabled: string[], scopeName: string, catalogExists: boolean }>()5 const props = defineProps<{ presets: Preset[], enabled: string[], scopeName: string, catalogExists: boolean }>()
modified packages/ui/app/components/scope/ScopeConflictChoice.vue +2 -2
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ConflictResolution, PlanAction } from '@skilly/core'
2+import type { ConflictResolution, PlanAction } from '@panoplie/core'
33
44 /**
55 * The three ways to resolve a conflict, as a segmented choice. Picking one only stages it: the Review & apply
@@ -15,7 +15,7 @@ const options = computed(() => [
1515 ])
1616
1717 const EFFECT: Record<ConflictResolution, string> = {
18- 'keep-local': 'The files stay as they are. skilly stops overwriting this item until you resolve it.',
18+ 'keep-local': 'The files stay as they are. panoplie stops overwriting this item until you resolve it.',
1919 'take-catalog': 'The catalog version replaces the local one on Apply.',
2020 'push-to-catalog': 'The local version is copied into the catalog on Apply; other scopes that use it will get an update.'
2121 }
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ConflictResolution, PlanAction } from '@skilly/core'2+import type { ConflictResolution, PlanAction } from '@panoplie/core'
3 3
4 /**4 /**
5 * The three ways to resolve a conflict, as a segmented choice. Picking one only stages it: the Review & apply5 * The three ways to resolve a conflict, as a segmented choice. Picking one only stages it: the Review & apply
@@ -15,7 +15,7 @@ const options = computed(() => [
15 ])15 ])
16 16
17 const EFFECT: Record<ConflictResolution, string> = {17 const EFFECT: Record<ConflictResolution, string> = {
18- 'keep-local': 'The files stay as they are. skilly stops overwriting this item until you resolve it.',18+ 'keep-local': 'The files stay as they are. panoplie stops overwriting this item until you resolve it.',
19 'take-catalog': 'The catalog version replaces the local one on Apply.',19 'take-catalog': 'The catalog version replaces the local one on Apply.',
20 'push-to-catalog': 'The local version is copied into the catalog on Apply; other scopes that use it will get an update.'20 'push-to-catalog': 'The local version is copied into the catalog on Apply; other scopes that use it will get an update.'
21 }21 }
modified packages/ui/app/components/scope/ScopeConflictCompare.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { FileContent, PlanAction } from '@skilly/core'
2+import type { FileContent, PlanAction } from '@panoplie/core'
33
44 /**
55 * Diff of a conflicting item against its catalog version (design/BRIEF.md 4.2). Only standalone files have one
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { FileContent, PlanAction } from '@skilly/core'2+import type { FileContent, PlanAction } from '@panoplie/core'
3 3
4 /**4 /**
5 * Diff of a conflicting item against its catalog version (design/BRIEF.md 4.2). Only standalone files have one5 * Diff of a conflicting item against its catalog version (design/BRIEF.md 4.2). Only standalone files have one
modified packages/ui/app/components/scope/ScopeInheritedSection.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { AgentId, ItemType } from '@skilly/core'
2+import type { AgentId, ItemType } from '@panoplie/core'
33
44 /**
55 * "Inherited from Home" (projects only, mockup 9): Home items this project also sees, with hide switches.
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { AgentId, ItemType } from '@skilly/core'2+import type { AgentId, ItemType } from '@panoplie/core'
3 3
4 /**4 /**
5 * "Inherited from Home" (projects only, mockup 9): Home items this project also sees, with hide switches.5 * "Inherited from Home" (projects only, mockup 9): Home items this project also sees, with hide switches.
modified packages/ui/app/components/scope/ScopeInstructionsTab.vue +2 -2
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { InstructionsDetails, InventoryItem } from '@skilly/core'
2+import type { InstructionsDetails, InventoryItem } from '@panoplie/core'
33
44 /**
55 * Instructions tab (mockup 12): CLAUDE.md / AGENTS.md of the project (or of Global: ~/.claude, ~/.codex), read-only, with loaded / not loaded,
@@ -73,7 +73,7 @@ const loadedText = (f: InstructionFile) => (f.loadedBy.length ? `Loaded by ${f.l
7373 </template>
7474
7575 <SkPanel v-else>
76- <SkEmpty>No CLAUDE.md or AGENTS.md {{ home ? 'in ~/.claude or ~/.codex' : 'in this project' }}. Instruction files are written by you: skilly only reads them.</SkEmpty>
76+ <SkEmpty>No CLAUDE.md or AGENTS.md {{ home ? 'in ~/.claude or ~/.codex' : 'in this project' }}. Instruction files are written by you: panoplie only reads them.</SkEmpty>
7777 </SkPanel>
7878 </div>
7979 </template>
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { InstructionsDetails, InventoryItem } from '@skilly/core'2+import type { InstructionsDetails, InventoryItem } from '@panoplie/core'
3 3
4 /**4 /**
5 * Instructions tab (mockup 12): CLAUDE.md / AGENTS.md of the project (or of Global: ~/.claude, ~/.codex), read-only, with loaded / not loaded,5 * Instructions tab (mockup 12): CLAUDE.md / AGENTS.md of the project (or of Global: ~/.claude, ~/.codex), read-only, with loaded / not loaded,
@@ -73,7 +73,7 @@ const loadedText = (f: InstructionFile) => (f.loadedBy.length ? `Loaded by ${f.l
73 </template>73 </template>
74 74
75 <SkPanel v-else>75 <SkPanel v-else>
76- <SkEmpty>No CLAUDE.md or AGENTS.md {{ home ? 'in ~/.claude or ~/.codex' : 'in this project' }}. Instruction files are written by you: skilly only reads them.</SkEmpty>76+ <SkEmpty>No CLAUDE.md or AGENTS.md {{ home ? 'in ~/.claude or ~/.codex' : 'in this project' }}. Instruction files are written by you: panoplie only reads them.</SkEmpty>
77 </SkPanel>77 </SkPanel>
78 </div>78 </div>
79 </template>79 </template>
modified packages/ui/app/components/scope/ScopeInstructionsViewer.vue +3 -3
@@ -1,9 +1,9 @@
11 <script setup lang="ts">
2-import type { FileContent } from '@skilly/core'
2+import type { FileContent } from '@panoplie/core'
33
44 /**
55 * Read-only view of an instruction file (CLAUDE.md, AGENTS.md...) in a mono block.
6- * The block managed by skilly (between `<!-- skilly:begin -->` and `<!-- skilly:end -->`) is highlighted.
6+ * The block managed by panoplie (between `<!-- panoplie:begin -->` and `<!-- panoplie:end -->`) is highlighted.
77 */
88 const props = defineProps<{ path: string }>()
99 const { request } = useApi()
@@ -38,7 +38,7 @@ const hasManaged = computed(() => segments.value.some(s => s.managed))
3838 <SkNote v-if="file.truncated">Only the first part of this file is shown ({{ formatBytes(file.sizeBytes) }} in total).</SkNote>
3939 <p v-if="hasManaged" class="m-0 flex items-center gap-2 text-xs text-sk-muted">
4040 <span class="inline-block h-3 w-1 rounded-full bg-sk-accent" aria-hidden="true" />
41- The highlighted block is generated by skilly from your enabled rules. Edit the rules, not the block.
41+ The highlighted block is generated by panoplie from your enabled rules. Edit the rules, not the block.
4242 </p>
4343 <p v-if="!file.content.trim()" class="m-0 py-6 text-center text-sk-faint">This file is empty.</p>
4444 <pre v-else class="m-0 overflow-auto font-mono text-xs leading-[1.7] break-words whitespace-pre-wrap text-sk-text"><template v-for="(s, i) in segments" :key="i"><span v-if="s.managed" class="-mx-3 block rounded-[6px] border-l-2 border-sk-accent bg-sk-accent-bg px-[10px]">{{ s.text }}</span><template v-else>{{ s.text }}</template></template></pre>
@@ -1,9 +1,9 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { FileContent } from '@skilly/core'2+import type { FileContent } from '@panoplie/core'
3 3
4 /**4 /**
5 * Read-only view of an instruction file (CLAUDE.md, AGENTS.md...) in a mono block.5 * Read-only view of an instruction file (CLAUDE.md, AGENTS.md...) in a mono block.
6- * The block managed by skilly (between `<!-- skilly:begin -->` and `<!-- skilly:end -->`) is highlighted.6+ * The block managed by panoplie (between `<!-- panoplie:begin -->` and `<!-- panoplie:end -->`) is highlighted.
7 */7 */
8 const props = defineProps<{ path: string }>()8 const props = defineProps<{ path: string }>()
9 const { request } = useApi()9 const { request } = useApi()
@@ -38,7 +38,7 @@ const hasManaged = computed(() => segments.value.some(s => s.managed))
38 <SkNote v-if="file.truncated">Only the first part of this file is shown ({{ formatBytes(file.sizeBytes) }} in total).</SkNote>38 <SkNote v-if="file.truncated">Only the first part of this file is shown ({{ formatBytes(file.sizeBytes) }} in total).</SkNote>
39 <p v-if="hasManaged" class="m-0 flex items-center gap-2 text-xs text-sk-muted">39 <p v-if="hasManaged" class="m-0 flex items-center gap-2 text-xs text-sk-muted">
40 <span class="inline-block h-3 w-1 rounded-full bg-sk-accent" aria-hidden="true" />40 <span class="inline-block h-3 w-1 rounded-full bg-sk-accent" aria-hidden="true" />
41- The highlighted block is generated by skilly from your enabled rules. Edit the rules, not the block.41+ The highlighted block is generated by panoplie from your enabled rules. Edit the rules, not the block.
42 </p>42 </p>
43 <p v-if="!file.content.trim()" class="m-0 py-6 text-center text-sk-faint">This file is empty.</p>43 <p v-if="!file.content.trim()" class="m-0 py-6 text-center text-sk-faint">This file is empty.</p>
44 <pre v-else class="m-0 overflow-auto font-mono text-xs leading-[1.7] break-words whitespace-pre-wrap text-sk-text"><template v-for="(s, i) in segments" :key="i"><span v-if="s.managed" class="-mx-3 block rounded-[6px] border-l-2 border-sk-accent bg-sk-accent-bg px-[10px]">{{ s.text }}</span><template v-else>{{ s.text }}</template></template></pre>44 <pre v-else class="m-0 overflow-auto font-mono text-xs leading-[1.7] break-words whitespace-pre-wrap text-sk-text"><template v-for="(s, i) in segments" :key="i"><span v-if="s.managed" class="-mx-3 block rounded-[6px] border-l-2 border-sk-accent bg-sk-accent-bg px-[10px]">{{ s.text }}</span><template v-else>{{ s.text }}</template></template></pre>
modified packages/ui/app/components/scope/ScopeItemDrawer.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { SkillDetails } from '@skilly/core'
2+import type { SkillDetails } from '@panoplie/core'
33
44 /**
55 * Item drawer (brief 3, "Item drawer"): metadata grid, warnings, then the file content (rendered markdown) or the
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { SkillDetails } from '@skilly/core'2+import type { SkillDetails } from '@panoplie/core'
3 3
4 /**4 /**
5 * Item drawer (brief 3, "Item drawer"): metadata grid, warnings, then the file content (rendered markdown) or the5 * Item drawer (brief 3, "Item drawer"): metadata grid, warnings, then the file content (rendered markdown) or the
modified packages/ui/app/components/scope/ScopeItemRow.vue +2 -2
@@ -3,7 +3,7 @@ import type { RowAction, ScopeRow } from '../../utils/scopeRows'
33
44 /**
55 * One row of the Items list (mockup 9 and 10): switch (managed items), name + type + origin (and the path on disk
6- * for items skilly doesn't manage), status cell with its inline action, Automatic / On demand for skills, a "…" menu
6+ * for items panoplie doesn't manage), status cell with its inline action, Automatic / On demand for skills, a "…" menu
77 * (Global: "Move to projects…") and the inline resolve box. Clicking the name opens the item drawer.
88 * Editing only changes the config (autosaved); files change on Apply.
99 */
@@ -11,7 +11,7 @@ const props = defineProps<{
1111 row: ScopeRow
1212 root: string
1313 home?: boolean
14- /** Path on disk, shown under the origin for items skilly doesn't manage. */
14+ /** Path on disk, shown under the origin for items panoplie doesn't manage. */
1515 location?: string
1616 /** "Project-specific" hint (Global). */
1717 flag?: string
@@ -3,7 +3,7 @@ import type { RowAction, ScopeRow } from '../../utils/scopeRows'
3 3
4 /**4 /**
5 * One row of the Items list (mockup 9 and 10): switch (managed items), name + type + origin (and the path on disk5 * One row of the Items list (mockup 9 and 10): switch (managed items), name + type + origin (and the path on disk
6- * for items skilly doesn't manage), status cell with its inline action, Automatic / On demand for skills, a "…" menu6+ * for items panoplie doesn't manage), status cell with its inline action, Automatic / On demand for skills, a "…" menu
7 * (Global: "Move to projects…") and the inline resolve box. Clicking the name opens the item drawer.7 * (Global: "Move to projects…") and the inline resolve box. Clicking the name opens the item drawer.
8 * Editing only changes the config (autosaved); files change on Apply.8 * Editing only changes the config (autosaved); files change on Apply.
9 */9 */
@@ -11,7 +11,7 @@ const props = defineProps<{
11 row: ScopeRow11 row: ScopeRow
12 root: string12 root: string
13 home?: boolean13 home?: boolean
14- /** Path on disk, shown under the origin for items skilly doesn't manage. */14+ /** Path on disk, shown under the origin for items panoplie doesn't manage. */
15 location?: string15 location?: string
16 /** "Project-specific" hint (Global). */16 /** "Project-specific" hint (Global). */
17 flag?: string17 flag?: string
modified packages/ui/app/components/scope/ScopeItemsSection.vue +4 -4
@@ -1,10 +1,10 @@
11 <script setup lang="ts">
2-import type { CatalogItemType, InventoryItem } from '@skilly/core'
2+import type { CatalogItemType, InventoryItem } from '@panoplie/core'
33 import type { HomeNames, ScopeRow } from '../../utils/scopeRows'
44 import type { ProjectSpecific } from '../../utils/inventoryView'
55
66 /**
7- * The single "Items" list of a project or of Global (mockup 9-10, merged with the old Files tab): what skilly manages
7+ * The single "Items" list of a project or of Global (mockup 9-10, merged with the old Files tab): what panoplie manages
88 * (switch, status, mode, inline resolution) and what is only on disk (local and plugin items, with "Import" to the
99 * catalog and, in Global, "Move to projects…"), grouped by type. Filters: type, origin (managed / on disk only), search.
1010 * Works without a config: the scope then only lists what is on disk.
@@ -76,7 +76,7 @@ const typeOptions = computed(() => [
7676 ])
7777 const originOptions = computed(() => [
7878 { value: 'all' as const, label: 'All' },
79- { value: 'managed' as const, label: 'Managed by skilly', count: rows.value.filter(isManagedRow).length },
79+ { value: 'managed' as const, label: 'Managed by panoplie', count: rows.value.filter(isManagedRow).length },
8080 { value: 'local' as const, label: 'Not managed', count: rows.value.filter(r => !isManagedRow(r)).length }
8181 ])
8282
@@ -199,7 +199,7 @@ function confirmPicker() {
199199 <template>
200200 <SkSection :title="home ? 'Items in Global' : 'Items in this project'">
201201 <template #actions>
202- <SkButton v-if="manageable.length" :label="`Manage all in catalog (${manageable.length})`" title="These items are identical to the catalog version: let skilly manage them here" @click="scope.manage(manageable)" />
202+ <SkButton v-if="manageable.length" :label="`Manage all in catalog (${manageable.length})`" title="These items are identical to the catalog version: let panoplie manage them here" @click="scope.manage(manageable)" />
203203 <SkButton v-if="importable.length" :label="`Import all to catalog (${importable.length})`" @click="scope.askImport(importable)" />
204204 <SkButton v-if="managed" label="Add items" :disabled="!pickable.length" @click="openPicker" />
205205 </template>
@@ -1,10 +1,10 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItemType, InventoryItem } from '@skilly/core'2+import type { CatalogItemType, InventoryItem } from '@panoplie/core'
3 import type { HomeNames, ScopeRow } from '../../utils/scopeRows'3 import type { HomeNames, ScopeRow } from '../../utils/scopeRows'
4 import type { ProjectSpecific } from '../../utils/inventoryView'4 import type { ProjectSpecific } from '../../utils/inventoryView'
5 5
6 /**6 /**
7- * The single "Items" list of a project or of Global (mockup 9-10, merged with the old Files tab): what skilly manages7+ * The single "Items" list of a project or of Global (mockup 9-10, merged with the old Files tab): what panoplie manages
8 * (switch, status, mode, inline resolution) and what is only on disk (local and plugin items, with "Import" to the8 * (switch, status, mode, inline resolution) and what is only on disk (local and plugin items, with "Import" to the
9 * catalog and, in Global, "Move to projects…"), grouped by type. Filters: type, origin (managed / on disk only), search.9 * catalog and, in Global, "Move to projects…"), grouped by type. Filters: type, origin (managed / on disk only), search.
10 * Works without a config: the scope then only lists what is on disk.10 * Works without a config: the scope then only lists what is on disk.
@@ -76,7 +76,7 @@ const typeOptions = computed(() => [
76 ])76 ])
77 const originOptions = computed(() => [77 const originOptions = computed(() => [
78 { value: 'all' as const, label: 'All' },78 { value: 'all' as const, label: 'All' },
79- { value: 'managed' as const, label: 'Managed by skilly', count: rows.value.filter(isManagedRow).length },79+ { value: 'managed' as const, label: 'Managed by panoplie', count: rows.value.filter(isManagedRow).length },
80 { value: 'local' as const, label: 'Not managed', count: rows.value.filter(r => !isManagedRow(r)).length }80 { value: 'local' as const, label: 'Not managed', count: rows.value.filter(r => !isManagedRow(r)).length }
81 ])81 ])
82 82
@@ -199,7 +199,7 @@ function confirmPicker() {
199 <template>199 <template>
200 <SkSection :title="home ? 'Items in Global' : 'Items in this project'">200 <SkSection :title="home ? 'Items in Global' : 'Items in this project'">
201 <template #actions>201 <template #actions>
202- <SkButton v-if="manageable.length" :label="`Manage all in catalog (${manageable.length})`" title="These items are identical to the catalog version: let skilly manage them here" @click="scope.manage(manageable)" />202+ <SkButton v-if="manageable.length" :label="`Manage all in catalog (${manageable.length})`" title="These items are identical to the catalog version: let panoplie manage them here" @click="scope.manage(manageable)" />
203 <SkButton v-if="importable.length" :label="`Import all to catalog (${importable.length})`" @click="scope.askImport(importable)" />203 <SkButton v-if="importable.length" :label="`Import all to catalog (${importable.length})`" @click="scope.askImport(importable)" />
204 <SkButton v-if="managed" label="Add items" :disabled="!pickable.length" @click="openPicker" />204 <SkButton v-if="managed" label="Add items" :disabled="!pickable.length" @click="openPicker" />
205 </template>205 </template>
modified packages/ui/app/components/scope/ScopeItemsTab.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { InventoryItem } from '@skilly/core'
2+import type { InventoryItem } from '@panoplie/core'
33 import type { ProjectSpecific } from '../../utils/inventoryView'
44
55 /**
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { InventoryItem } from '@skilly/core'2+import type { InventoryItem } from '@panoplie/core'
3 import type { ProjectSpecific } from '../../utils/inventoryView'3 import type { ProjectSpecific } from '../../utils/inventoryView'
4 4
5 /**5 /**
modified packages/ui/app/components/scope/ScopeManageCard.vue +5 -5
@@ -1,7 +1,7 @@
11 <script setup lang="ts">
2-import type { AgentId } from '@skilly/core'
2+import type { AgentId } from '@panoplie/core'
33
4-/** Card above the Items list of a scope without skilly.json / home.json: choose the target agents and start managing. */
4+/** Card above the Items list of a scope without panoplie.json / home.json: choose the target agents and start managing. */
55 const scope = useScopeContext()
66 const home = scope.kind === 'home'
77 const AGENT_LIST: AgentId[] = ['claude', 'codex']
@@ -23,13 +23,13 @@ function start() {
2323 <template>
2424 <SkPanel padded class="flex flex-col gap-5">
2525 <div class="flex flex-col gap-1">
26- <h2 class="m-0 text-[15px] font-semibold">{{ home ? 'skilly does not manage Global yet' : 'skilly does not manage this project yet' }}</h2>
26+ <h2 class="m-0 text-[15px] font-semibold">{{ home ? 'panoplie does not manage Global yet' : 'panoplie does not manage this project yet' }}</h2>
2727 <p class="m-0 max-w-2xl text-pretty text-sk-muted">
2828 The list below is what the agents load here today. Start managing to enable presets and catalog items, switch
2929 skills to on demand and keep the files in sync with the catalog.
3030 {{ home
31- ? 'Settings are stored in ~/.skilly/home.json (not in a repository). Nothing is written into ~/.claude, ~/.agents or ~/.codex until you apply.'
32- : 'A skilly.json file is created at the project root: commit it with the project. Nothing else is written until you apply.' }}
31+ ? 'Settings are stored in ~/.panoplie/home.json (not in a repository). Nothing is written into ~/.claude, ~/.agents or ~/.codex until you apply.'
32+ : 'A panoplie.json file is created at the project root: commit it with the project. Nothing else is written until you apply.' }}
3333 </p>
3434 </div>
3535
@@ -1,7 +1,7 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { AgentId } from '@skilly/core'2+import type { AgentId } from '@panoplie/core'
3 3
4-/** Card above the Items list of a scope without skilly.json / home.json: choose the target agents and start managing. */4+/** Card above the Items list of a scope without panoplie.json / home.json: choose the target agents and start managing. */
5 const scope = useScopeContext()5 const scope = useScopeContext()
6 const home = scope.kind === 'home'6 const home = scope.kind === 'home'
7 const AGENT_LIST: AgentId[] = ['claude', 'codex']7 const AGENT_LIST: AgentId[] = ['claude', 'codex']
@@ -23,13 +23,13 @@ function start() {
23 <template>23 <template>
24 <SkPanel padded class="flex flex-col gap-5">24 <SkPanel padded class="flex flex-col gap-5">
25 <div class="flex flex-col gap-1">25 <div class="flex flex-col gap-1">
26- <h2 class="m-0 text-[15px] font-semibold">{{ home ? 'skilly does not manage Global yet' : 'skilly does not manage this project yet' }}</h2>26+ <h2 class="m-0 text-[15px] font-semibold">{{ home ? 'panoplie does not manage Global yet' : 'panoplie does not manage this project yet' }}</h2>
27 <p class="m-0 max-w-2xl text-pretty text-sk-muted">27 <p class="m-0 max-w-2xl text-pretty text-sk-muted">
28 The list below is what the agents load here today. Start managing to enable presets and catalog items, switch28 The list below is what the agents load here today. Start managing to enable presets and catalog items, switch
29 skills to on demand and keep the files in sync with the catalog.29 skills to on demand and keep the files in sync with the catalog.
30 {{ home30 {{ home
31- ? 'Settings are stored in ~/.skilly/home.json (not in a repository). Nothing is written into ~/.claude, ~/.agents or ~/.codex until you apply.'31+ ? 'Settings are stored in ~/.panoplie/home.json (not in a repository). Nothing is written into ~/.claude, ~/.agents or ~/.codex until you apply.'
32- : 'A skilly.json file is created at the project root: commit it with the project. Nothing else is written until you apply.' }}32+ : 'A panoplie.json file is created at the project root: commit it with the project. Nothing else is written until you apply.' }}
33 </p>33 </p>
34 </div>34 </div>
35 35
modified packages/ui/app/components/scope/ScopePage.vue +8 -8
@@ -3,7 +3,7 @@
33 * Project page, also used for Global (the Home scope, `~`) (design/BRIEF.md 2.0, 2.4, 4.1), full width: header, tab
44 * segment (Items · Instructions), tab content, the sticky "pending changes" bar and the overlays (item drawer, review
55 * drawer, import and move modals). Global has the same tabs as a project; it differs by its "loaded in every project"
6- * banner, its "to tidy up" panel and the absence of "Remove from skilly".
6+ * banner, its "to tidy up" panel and the absence of "Remove from panoplie".
77 * State lives in `createScope` and is shared with the scope components through `useScopeContext()`.
88 */
99 const props = defineProps<{ kind: 'home' | 'project', projectId?: string }>()
@@ -67,7 +67,7 @@ async function confirmRemove() {
6767 try {
6868 await remove(props.projectId)
6969 removeOpen.value = false
70- toast.add({ title: 'Project removed from skilly', description: 'No file was deleted.', icon: 'i-lucide-check', color: 'success' })
70+ toast.add({ title: 'Project removed from panoplie', description: 'No file was deleted.', icon: 'i-lucide-check', color: 'success' })
7171 await router.push('/projects')
7272 } catch {
7373 // The error toast comes from useApi.
@@ -79,7 +79,7 @@ async function confirmRemove() {
7979 const menu = computed(() => [[
8080 { label: 'Rescan', icon: 'i-lucide-refresh-cw', onSelect: () => scope.rescan() },
8181 { label: 'Copy path', icon: 'i-lucide-copy', onSelect: copyPath },
82- ...(home ? [] : [{ label: 'Remove from skilly', icon: 'i-lucide-trash-2', color: 'error' as const, onSelect: () => { removeOpen.value = true } }])
82+ ...(home ? [] : [{ label: 'Remove from panoplie', icon: 'i-lucide-trash-2', color: 'error' as const, onSelect: () => { removeOpen.value = true } }])
8383 ]])
8484
8585 const agentsText = computed(() => {
@@ -126,11 +126,11 @@ const barVisible = computed(() => !!config.value && (pending.value.length > 0 ||
126126 <template v-else-if="error && !inventory">
127127 <SkNote v-if="folderMissing" tone="accent">
128128 <p class="m-0 font-semibold">This folder no longer exists on disk</p>
129- <p class="m-0 mt-1">{{ rootPath }} was moved or deleted. Remove the project from skilly, or retry once the folder is back.</p>
129+ <p class="m-0 mt-1">{{ rootPath }} was moved or deleted. Remove the project from panoplie, or retry once the folder is back.</p>
130130 <template #aside>
131131 <div class="flex gap-2">
132132 <SkButton size="sm" label="Retry" @click="scope.load()" />
133- <SkButton size="sm" variant="danger" label="Remove from skilly" @click="removeOpen = true" />
133+ <SkButton size="sm" variant="danger" label="Remove from panoplie" @click="removeOpen = true" />
134134 </div>
135135 </template>
136136 </SkNote>
@@ -150,7 +150,7 @@ const barVisible = computed(() => !!config.value && (pending.value.length > 0 ||
150150
151151 <SkNote v-if="folderMissing" tone="accent">
152152 <p class="m-0 font-semibold">This folder no longer exists on disk</p>
153- <p class="m-0 mt-1">The inventory below may be empty or outdated. You can remove the project from skilly.</p>
153+ <p class="m-0 mt-1">The inventory below may be empty or outdated. You can remove the project from panoplie.</p>
154154 </SkNote>
155155
156156 <SkNote v-if="inventory.errors.length" tone="accent">
@@ -183,14 +183,14 @@ const barVisible = computed(() => !!config.value && (pending.value.length > 0 ||
183183 <AddToCatalogModal v-model:open="importOpen" :scope="scopeId" :items="importItems" :statuses="statuses" @done="scope.afterImport" />
184184 <RemoveFromGlobalModal v-model:open="moveOpen" :items="moveItems" :statuses="statuses" @done="scope.afterCatalogChange" />
185185
186- <SkModal v-model:open="removeOpen" title="Remove project from skilly" description="This only forgets the project in skilly." width="sm">
186+ <SkModal v-model:open="removeOpen" title="Remove project from panoplie" description="This only forgets the project in panoplie." width="sm">
187187 <p class="m-0">
188188 <strong>{{ title }}</strong> will disappear from the project list. <strong>No file is deleted</strong>:
189189 the folder and its agent configuration stay untouched. You can add it again at any time.
190190 </p>
191191 <template #footer>
192192 <SkButton label="Cancel" @click="removeOpen = false" />
193- <SkButton variant="primary" label="Remove from skilly" :loading="removing" @click="confirmRemove" />
193+ <SkButton variant="primary" label="Remove from panoplie" :loading="removing" @click="confirmRemove" />
194194 </template>
195195 </SkModal>
196196 </template>
@@ -3,7 +3,7 @@
3 * Project page, also used for Global (the Home scope, `~`) (design/BRIEF.md 2.0, 2.4, 4.1), full width: header, tab3 * Project page, also used for Global (the Home scope, `~`) (design/BRIEF.md 2.0, 2.4, 4.1), full width: header, tab
4 * segment (Items · Instructions), tab content, the sticky "pending changes" bar and the overlays (item drawer, review4 * segment (Items · Instructions), tab content, the sticky "pending changes" bar and the overlays (item drawer, review
5 * drawer, import and move modals). Global has the same tabs as a project; it differs by its "loaded in every project"5 * drawer, import and move modals). Global has the same tabs as a project; it differs by its "loaded in every project"
6- * banner, its "to tidy up" panel and the absence of "Remove from skilly".6+ * banner, its "to tidy up" panel and the absence of "Remove from panoplie".
7 * State lives in `createScope` and is shared with the scope components through `useScopeContext()`.7 * State lives in `createScope` and is shared with the scope components through `useScopeContext()`.
8 */8 */
9 const props = defineProps<{ kind: 'home' | 'project', projectId?: string }>()9 const props = defineProps<{ kind: 'home' | 'project', projectId?: string }>()
@@ -67,7 +67,7 @@ async function confirmRemove() {
67 try {67 try {
68 await remove(props.projectId)68 await remove(props.projectId)
69 removeOpen.value = false69 removeOpen.value = false
70- toast.add({ title: 'Project removed from skilly', description: 'No file was deleted.', icon: 'i-lucide-check', color: 'success' })70+ toast.add({ title: 'Project removed from panoplie', description: 'No file was deleted.', icon: 'i-lucide-check', color: 'success' })
71 await router.push('/projects')71 await router.push('/projects')
72 } catch {72 } catch {
73 // The error toast comes from useApi.73 // The error toast comes from useApi.
@@ -79,7 +79,7 @@ async function confirmRemove() {
79 const menu = computed(() => [[79 const menu = computed(() => [[
80 { label: 'Rescan', icon: 'i-lucide-refresh-cw', onSelect: () => scope.rescan() },80 { label: 'Rescan', icon: 'i-lucide-refresh-cw', onSelect: () => scope.rescan() },
81 { label: 'Copy path', icon: 'i-lucide-copy', onSelect: copyPath },81 { label: 'Copy path', icon: 'i-lucide-copy', onSelect: copyPath },
82- ...(home ? [] : [{ label: 'Remove from skilly', icon: 'i-lucide-trash-2', color: 'error' as const, onSelect: () => { removeOpen.value = true } }])82+ ...(home ? [] : [{ label: 'Remove from panoplie', icon: 'i-lucide-trash-2', color: 'error' as const, onSelect: () => { removeOpen.value = true } }])
83 ]])83 ]])
84 84
85 const agentsText = computed(() => {85 const agentsText = computed(() => {
@@ -126,11 +126,11 @@ const barVisible = computed(() => !!config.value && (pending.value.length > 0 ||
126 <template v-else-if="error && !inventory">126 <template v-else-if="error && !inventory">
127 <SkNote v-if="folderMissing" tone="accent">127 <SkNote v-if="folderMissing" tone="accent">
128 <p class="m-0 font-semibold">This folder no longer exists on disk</p>128 <p class="m-0 font-semibold">This folder no longer exists on disk</p>
129- <p class="m-0 mt-1">{{ rootPath }} was moved or deleted. Remove the project from skilly, or retry once the folder is back.</p>129+ <p class="m-0 mt-1">{{ rootPath }} was moved or deleted. Remove the project from panoplie, or retry once the folder is back.</p>
130 <template #aside>130 <template #aside>
131 <div class="flex gap-2">131 <div class="flex gap-2">
132 <SkButton size="sm" label="Retry" @click="scope.load()" />132 <SkButton size="sm" label="Retry" @click="scope.load()" />
133- <SkButton size="sm" variant="danger" label="Remove from skilly" @click="removeOpen = true" />133+ <SkButton size="sm" variant="danger" label="Remove from panoplie" @click="removeOpen = true" />
134 </div>134 </div>
135 </template>135 </template>
136 </SkNote>136 </SkNote>
@@ -150,7 +150,7 @@ const barVisible = computed(() => !!config.value && (pending.value.length > 0 ||
150 150
151 <SkNote v-if="folderMissing" tone="accent">151 <SkNote v-if="folderMissing" tone="accent">
152 <p class="m-0 font-semibold">This folder no longer exists on disk</p>152 <p class="m-0 font-semibold">This folder no longer exists on disk</p>
153- <p class="m-0 mt-1">The inventory below may be empty or outdated. You can remove the project from skilly.</p>153+ <p class="m-0 mt-1">The inventory below may be empty or outdated. You can remove the project from panoplie.</p>
154 </SkNote>154 </SkNote>
155 155
156 <SkNote v-if="inventory.errors.length" tone="accent">156 <SkNote v-if="inventory.errors.length" tone="accent">
@@ -183,14 +183,14 @@ const barVisible = computed(() => !!config.value && (pending.value.length > 0 ||
183 <AddToCatalogModal v-model:open="importOpen" :scope="scopeId" :items="importItems" :statuses="statuses" @done="scope.afterImport" />183 <AddToCatalogModal v-model:open="importOpen" :scope="scopeId" :items="importItems" :statuses="statuses" @done="scope.afterImport" />
184 <RemoveFromGlobalModal v-model:open="moveOpen" :items="moveItems" :statuses="statuses" @done="scope.afterCatalogChange" />184 <RemoveFromGlobalModal v-model:open="moveOpen" :items="moveItems" :statuses="statuses" @done="scope.afterCatalogChange" />
185 185
186- <SkModal v-model:open="removeOpen" title="Remove project from skilly" description="This only forgets the project in skilly." width="sm">186+ <SkModal v-model:open="removeOpen" title="Remove project from panoplie" description="This only forgets the project in panoplie." width="sm">
187 <p class="m-0">187 <p class="m-0">
188 <strong>{{ title }}</strong> will disappear from the project list. <strong>No file is deleted</strong>:188 <strong>{{ title }}</strong> will disappear from the project list. <strong>No file is deleted</strong>:
189 the folder and its agent configuration stay untouched. You can add it again at any time.189 the folder and its agent configuration stay untouched. You can add it again at any time.
190 </p>190 </p>
191 <template #footer>191 <template #footer>
192 <SkButton label="Cancel" @click="removeOpen = false" />192 <SkButton label="Cancel" @click="removeOpen = false" />
193- <SkButton variant="primary" label="Remove from skilly" :loading="removing" @click="confirmRemove" />193+ <SkButton variant="primary" label="Remove from panoplie" :loading="removing" @click="confirmRemove" />
194 </template>194 </template>
195 </SkModal>195 </SkModal>
196 </template>196 </template>
modified packages/ui/app/components/scope/ScopeResolveBox.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ConflictResolution, PlanAction } from '@skilly/core'
2+import type { ConflictResolution, PlanAction } from '@panoplie/core'
33
44 /** Inline drift resolution under an item row (mockup 10): headline, diff, and a staged choice per conflicting agent. */
55 defineProps<{ conflicts: PlanAction[], root: string, home?: boolean }>()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ConflictResolution, PlanAction } from '@skilly/core'2+import type { ConflictResolution, PlanAction } from '@panoplie/core'
3 3
4 /** Inline drift resolution under an item row (mockup 10): headline, diff, and a staged choice per conflicting agent. */4 /** Inline drift resolution under an item row (mockup 10): headline, diff, and a staged choice per conflicting agent. */
5 defineProps<{ conflicts: PlanAction[], root: string, home?: boolean }>()5 defineProps<{ conflicts: PlanAction[], root: string, home?: boolean }>()
modified packages/ui/app/components/scope/ScopeReviewDrawer.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ApplyResponse, ConflictResolution, PlanAction, PlanActionKind, ScopeState, SyncPlan } from '@skilly/core'
2+import type { ApplyResponse, ConflictResolution, PlanAction, PlanActionKind, ScopeState, SyncPlan } from '@panoplie/core'
33 import type { HomeNames } from '../../utils/scopeRows'
44
55 /**
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ApplyResponse, ConflictResolution, PlanAction, PlanActionKind, ScopeState, SyncPlan } from '@skilly/core'2+import type { ApplyResponse, ConflictResolution, PlanAction, PlanActionKind, ScopeState, SyncPlan } from '@panoplie/core'
3 import type { HomeNames } from '../../utils/scopeRows'3 import type { HomeNames } from '../../utils/scopeRows'
4 4
5 /**5 /**
modified packages/ui/app/components/scope/ScopeScanErrors.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ScanError } from '@skilly/core'
2+import type { ScanError } from '@panoplie/core'
33
44 /** Details of the items the scanners could not read (inside a clay SkNote). */
55 defineProps<{ errors: ScanError[], root: string }>()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ScanError } from '@skilly/core'2+import type { ScanError } from '@panoplie/core'
3 3
4 /** Details of the items the scanners could not read (inside a clay SkNote). */4 /** Details of the items the scanners could not read (inside a clay SkNote). */
5 defineProps<{ errors: ScanError[], root: string }>()5 defineProps<{ errors: ScanError[], root: string }>()
modified packages/ui/app/components/scope/ScopeTargets.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { AgentId } from '@skilly/core'
2+import type { AgentId } from '@panoplie/core'
33
44 /** "Writes for" + agent logos with a popover to change the target agents (ScopeConfig.targets). */
55 const scope = useScopeContext()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { AgentId } from '@skilly/core'2+import type { AgentId } from '@panoplie/core'
3 3
4 /** "Writes for" + agent logos with a popover to change the target agents (ScopeConfig.targets). */4 /** "Writes for" + agent logos with a popover to change the target agents (ScopeConfig.targets). */
5 const scope = useScopeContext()5 const scope = useScopeContext()
modified packages/ui/app/components/sk/SkAgentBadge.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { AgentId } from '@skilly/core'
2+import type { AgentId } from '@panoplie/core'
33
44 /**
55 * Agents have no colour in the design: plain muted text ("text", default) or a neutral mono tag ("tag").
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { AgentId } from '@skilly/core'2+import type { AgentId } from '@panoplie/core'
3 3
4 /**4 /**
5 * Agents have no colour in the design: plain muted text ("text", default) or a neutral mono tag ("tag").5 * Agents have no colour in the design: plain muted text ("text", default) or a neutral mono tag ("tag").
modified packages/ui/app/components/sk/SkAgentIcons.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { AgentId } from '@skilly/core'
2+import type { AgentId } from '@panoplie/core'
33
44 /** Agent logos (Claude Code, Codex) for at-a-glance lists; the name stays available as tooltip and accessible label. */
55 withDefaults(defineProps<{ agents: AgentId[], empty?: string }>(), { empty: '—' })
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { AgentId } from '@skilly/core'2+import type { AgentId } from '@panoplie/core'
3 3
4 /** Agent logos (Claude Code, Codex) for at-a-glance lists; the name stays available as tooltip and accessible label. */4 /** Agent logos (Claude Code, Codex) for at-a-glance lists; the name stays available as tooltip and accessible label. */
5 withDefaults(defineProps<{ agents: AgentId[], empty?: string }>(), { empty: '—' })5 withDefaults(defineProps<{ agents: AgentId[], empty?: string }>(), { empty: '—' })
modified packages/ui/app/components/sk/SkItemChip.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ItemType } from '@skilly/core'
2+import type { ItemType } from '@panoplie/core'
33
44 /** Item chip: name + mono type (preset cards, "Used in"). */
55 defineProps<{ name: string, type?: ItemType }>()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ItemType } from '@skilly/core'2+import type { ItemType } from '@panoplie/core'
3 3
4 /** Item chip: name + mono type (preset cards, "Used in"). */4 /** Item chip: name + mono type (preset cards, "Used in"). */
5 defineProps<{ name: string, type?: ItemType }>()5 defineProps<{ name: string, type?: ItemType }>()
modified packages/ui/app/components/sk/SkLogo.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-/** skilly logo mark: a 2x2 grid (design/BRIEF.md 1.9). public/favicon.svg draws the same mark. */
2+/** panoplie logo mark: a 2x2 grid (design/BRIEF.md 1.9). public/favicon.svg draws the same mark. */
33 withDefaults(defineProps<{ size?: number }>(), { size: 26 })
44 </script>
55
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-/** skilly logo mark: a 2x2 grid (design/BRIEF.md 1.9). public/favicon.svg draws the same mark. */2+/** panoplie logo mark: a 2x2 grid (design/BRIEF.md 1.9). public/favicon.svg draws the same mark. */
3 withDefaults(defineProps<{ size?: number }>(), { size: 26 })3 withDefaults(defineProps<{ size?: number }>(), { size: 26 })
4 </script>4 </script>
5 5
modified packages/ui/app/components/sk/SkStatus.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ItemStatus } from '@skilly/core'
2+import type { ItemStatus } from '@panoplie/core'
33 import type { Tone } from '../../utils/status'
44
55 /**
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ItemStatus } from '@skilly/core'2+import type { ItemStatus } from '@panoplie/core'
3 import type { Tone } from '../../utils/status'3 import type { Tone } from '../../utils/status'
4 4
5 /**5 /**
modified packages/ui/app/components/sk/SkTypeTag.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ItemType } from '@skilly/core'
2+import type { ItemType } from '@panoplie/core'
33
44 /** Item type as a mono text tag: Skill, Rule, MCP, Hook, Subagent (no icons in the design). `label` overrides the text. */
55 defineProps<{ type: ItemType, label?: string }>()
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ItemType } from '@skilly/core'2+import type { ItemType } from '@panoplie/core'
3 3
4 /** Item type as a mono text tag: Skill, Rule, MCP, Hook, Subagent (no icons in the design). `label` overrides the text. */4 /** Item type as a mono text tag: Skill, Rule, MCP, Hook, Subagent (no icons in the design). `label` overrides the text. */
5 defineProps<{ type: ItemType, label?: string }>()5 defineProps<{ type: ItemType, label?: string }>()
modified packages/ui/app/composables/useApi.ts +4 -4
@@ -1,4 +1,4 @@
1-import type { ApiError } from '@skilly/core'
1+import type { ApiError } from '@panoplie/core'
22
33 export class ApiRequestError extends Error {
44 status: number
@@ -25,14 +25,14 @@ export function useApi() {
2525 method: opts.method ?? 'GET',
2626 body: opts.body as Record<string, unknown> | undefined,
2727 query: opts.query,
28- headers: { 'x-skilly-token': getToken() }
28+ headers: { 'x-panoplie-token': getToken() }
2929 })
3030 } catch (e: any) {
3131 const status: number = e?.response?.status ?? e?.statusCode ?? 0
3232 const data = e?.data as Partial<ApiError> | undefined
3333 const message = data?.error
34- ?? (status === 401 ? 'Unauthorized: the session token is missing or invalid. Relaunch skilly.' : undefined)
35- ?? (status === 0 ? 'Cannot reach the skilly server.' : e?.message ?? 'Request failed')
34+ ?? (status === 401 ? 'Unauthorized: the session token is missing or invalid. Relaunch panoplie.' : undefined)
35+ ?? (status === 0 ? 'Cannot reach the panoplie server.' : e?.message ?? 'Request failed')
3636 if (!opts.silent) {
3737 toast.add({ title: 'Request failed', description: message, color: 'error', icon: 'i-lucide-circle-alert' })
3838 }
@@ -1,4 +1,4 @@
1-import type { ApiError } from '@skilly/core'1+import type { ApiError } from '@panoplie/core'
2 2
3 export class ApiRequestError extends Error {3 export class ApiRequestError extends Error {
4 status: number4 status: number
@@ -25,14 +25,14 @@ export function useApi() {
25 method: opts.method ?? 'GET',25 method: opts.method ?? 'GET',
26 body: opts.body as Record<string, unknown> | undefined,26 body: opts.body as Record<string, unknown> | undefined,
27 query: opts.query,27 query: opts.query,
28- headers: { 'x-skilly-token': getToken() }28+ headers: { 'x-panoplie-token': getToken() }
29 })29 })
30 } catch (e: any) {30 } catch (e: any) {
31 const status: number = e?.response?.status ?? e?.statusCode ?? 031 const status: number = e?.response?.status ?? e?.statusCode ?? 0
32 const data = e?.data as Partial<ApiError> | undefined32 const data = e?.data as Partial<ApiError> | undefined
33 const message = data?.error33 const message = data?.error
34- ?? (status === 401 ? 'Unauthorized: the session token is missing or invalid. Relaunch skilly.' : undefined)34+ ?? (status === 401 ? 'Unauthorized: the session token is missing or invalid. Relaunch panoplie.' : undefined)
35- ?? (status === 0 ? 'Cannot reach the skilly server.' : e?.message ?? 'Request failed')35+ ?? (status === 0 ? 'Cannot reach the panoplie server.' : e?.message ?? 'Request failed')
36 if (!opts.silent) {36 if (!opts.silent) {
37 toast.add({ title: 'Request failed', description: message, color: 'error', icon: 'i-lucide-circle-alert' })37 toast.add({ title: 'Request failed', description: message, color: 'error', icon: 'i-lucide-circle-alert' })
38 }38 }
modified packages/ui/app/composables/useCatalog.ts +4 -4
@@ -1,11 +1,11 @@
1-import type { CatalogItem, CatalogResponse, GitStatus, Preset } from '@skilly/core'
1+import type { CatalogItem, CatalogResponse, GitStatus, Preset } from '@panoplie/core'
22
33 /** Shared catalog snapshot (items, presets, usage, git status). */
44 export function useCatalog() {
55 const { request } = useApi()
6- const data = useState<CatalogResponse | null>('skilly-catalog', () => null)
7- const loading = useState('skilly-catalog-loading', () => false)
8- const error = useState<string | null>('skilly-catalog-error', () => null)
6+ const data = useState<CatalogResponse | null>('panoplie-catalog', () => null)
7+ const loading = useState('panoplie-catalog-loading', () => false)
8+ const error = useState<string | null>('panoplie-catalog-error', () => null)
99
1010 async function refresh() {
1111 loading.value = true
@@ -1,11 +1,11 @@
1-import type { CatalogItem, CatalogResponse, GitStatus, Preset } from '@skilly/core'1+import type { CatalogItem, CatalogResponse, GitStatus, Preset } from '@panoplie/core'
2 2
3 /** Shared catalog snapshot (items, presets, usage, git status). */3 /** Shared catalog snapshot (items, presets, usage, git status). */
4 export function useCatalog() {4 export function useCatalog() {
5 const { request } = useApi()5 const { request } = useApi()
6- const data = useState<CatalogResponse | null>('skilly-catalog', () => null)6+ const data = useState<CatalogResponse | null>('panoplie-catalog', () => null)
7- const loading = useState('skilly-catalog-loading', () => false)7+ const loading = useState('panoplie-catalog-loading', () => false)
8- const error = useState<string | null>('skilly-catalog-error', () => null)8+ const error = useState<string | null>('panoplie-catalog-error', () => null)
9 9
10 async function refresh() {10 async function refresh() {
11 loading.value = true11 loading.value = true
modified packages/ui/app/composables/useCatalogGit.ts +3 -3
@@ -1,4 +1,4 @@
1-import type { GitStatus } from '@skilly/core'
1+import type { GitStatus } from '@panoplie/core'
22
33 export type GitAction = 'remote' | 'commit' | 'pull' | 'push' | 'sync' | 'resolve'
44
@@ -16,8 +16,8 @@ export function useCatalogGit() {
1616 get: () => route.path === '/sync',
1717 set: (v: boolean) => { if (v && route.path !== '/sync') navigateTo('/sync') }
1818 })
19- const busy = useState<GitAction | null>('skilly-git-busy', () => null)
20- const error = useState<string | null>('skilly-git-error', () => null)
19+ const busy = useState<GitAction | null>('panoplie-git-busy', () => null)
20+ const error = useState<string | null>('panoplie-git-error', () => null)
2121
2222 async function run(action: GitAction, body?: unknown, successTitle?: string): Promise<boolean> {
2323 busy.value = action
@@ -1,4 +1,4 @@
1-import type { GitStatus } from '@skilly/core'1+import type { GitStatus } from '@panoplie/core'
2 2
3 export type GitAction = 'remote' | 'commit' | 'pull' | 'push' | 'sync' | 'resolve'3 export type GitAction = 'remote' | 'commit' | 'pull' | 'push' | 'sync' | 'resolve'
4 4
@@ -16,8 +16,8 @@ export function useCatalogGit() {
16 get: () => route.path === '/sync',16 get: () => route.path === '/sync',
17 set: (v: boolean) => { if (v && route.path !== '/sync') navigateTo('/sync') }17 set: (v: boolean) => { if (v && route.path !== '/sync') navigateTo('/sync') }
18 })18 })
19- const busy = useState<GitAction | null>('skilly-git-busy', () => null)19+ const busy = useState<GitAction | null>('panoplie-git-busy', () => null)
20- const error = useState<string | null>('skilly-git-error', () => null)20+ const error = useState<string | null>('panoplie-git-error', () => null)
21 21
22 async function run(action: GitAction, body?: unknown, successTitle?: string): Promise<boolean> {22 async function run(action: GitAction, body?: unknown, successTitle?: string): Promise<boolean> {
23 busy.value = action23 busy.value = action
modified packages/ui/app/composables/useProjects.ts +8 -8
@@ -1,12 +1,12 @@
1-import type { ProjectSummary, ScopeInventory } from '@skilly/core'
1+import type { ProjectSummary, ScopeInventory } from '@panoplie/core'
22
33 /** Shared list of added projects (sidebar, dashboard, scope pages). */
44 export function useProjects() {
55 const { request } = useApi()
6- const projects = useState<ProjectSummary[]>('skilly-projects', () => [])
7- const loading = useState('skilly-projects-loading', () => false)
8- const loaded = useState('skilly-projects-loaded', () => false)
9- const error = useState<string | null>('skilly-projects-error', () => null)
6+ const projects = useState<ProjectSummary[]>('panoplie-projects', () => [])
7+ const loading = useState('panoplie-projects-loading', () => false)
8+ const loaded = useState('panoplie-projects-loaded', () => false)
9+ const error = useState<string | null>('panoplie-projects-error', () => null)
1010
1111 async function refresh() {
1212 loading.value = true
@@ -38,9 +38,9 @@ export function useProjects() {
3838 /** Shared Home (Global scope) inventory: onboarding, inherited items of a project, the move modal. */
3939 export function useHomeInventory() {
4040 const { request } = useApi()
41- const home = useState<ScopeInventory | null>('skilly-home', () => null)
42- const loading = useState('skilly-home-loading', () => false)
43- const error = useState<string | null>('skilly-home-error', () => null)
41+ const home = useState<ScopeInventory | null>('panoplie-home', () => null)
42+ const loading = useState('panoplie-home-loading', () => false)
43+ const error = useState<string | null>('panoplie-home-error', () => null)
4444
4545 async function refresh() {
4646 loading.value = true
@@ -1,12 +1,12 @@
1-import type { ProjectSummary, ScopeInventory } from '@skilly/core'1+import type { ProjectSummary, ScopeInventory } from '@panoplie/core'
2 2
3 /** Shared list of added projects (sidebar, dashboard, scope pages). */3 /** Shared list of added projects (sidebar, dashboard, scope pages). */
4 export function useProjects() {4 export function useProjects() {
5 const { request } = useApi()5 const { request } = useApi()
6- const projects = useState<ProjectSummary[]>('skilly-projects', () => [])6+ const projects = useState<ProjectSummary[]>('panoplie-projects', () => [])
7- const loading = useState('skilly-projects-loading', () => false)7+ const loading = useState('panoplie-projects-loading', () => false)
8- const loaded = useState('skilly-projects-loaded', () => false)8+ const loaded = useState('panoplie-projects-loaded', () => false)
9- const error = useState<string | null>('skilly-projects-error', () => null)9+ const error = useState<string | null>('panoplie-projects-error', () => null)
10 10
11 async function refresh() {11 async function refresh() {
12 loading.value = true12 loading.value = true
@@ -38,9 +38,9 @@ export function useProjects() {
38 /** Shared Home (Global scope) inventory: onboarding, inherited items of a project, the move modal. */38 /** Shared Home (Global scope) inventory: onboarding, inherited items of a project, the move modal. */
39 export function useHomeInventory() {39 export function useHomeInventory() {
40 const { request } = useApi()40 const { request } = useApi()
41- const home = useState<ScopeInventory | null>('skilly-home', () => null)41+ const home = useState<ScopeInventory | null>('panoplie-home', () => null)
42- const loading = useState('skilly-home-loading', () => false)42+ const loading = useState('panoplie-home-loading', () => false)
43- const error = useState<string | null>('skilly-home-error', () => null)43+ const error = useState<string | null>('panoplie-home-error', () => null)
44 44
45 async function refresh() {45 async function refresh() {
46 loading.value = true46 loading.value = true
modified packages/ui/app/composables/useScopeState.ts +4 -4
@@ -1,4 +1,4 @@
1-import type { AgentId, CatalogItemType, ConflictResolution, ImportResult, InventoryItem, PlanAction, ScopeConfig, ScopeState, SyncPlan } from '@skilly/core'
1+import type { AgentId, CatalogItemType, ConflictResolution, ImportResult, InventoryItem, PlanAction, ScopeConfig, ScopeState, SyncPlan } from '@panoplie/core'
22 import type { InjectionKey } from 'vue'
33
44 /**
@@ -7,7 +7,7 @@ import type { InjectionKey } from 'vue'
77 * (item drawer, review drawer, import and remove-from-global modals).
88 *
99 * `ScopePage` creates it and provides it; the scope components read it with `useScopeContext()`.
10- * Nothing is written to disk by this composable except `skilly.json` / `home.json` (the config); files change on Apply only.
10+ * Nothing is written to disk by this composable except `panoplie.json` / `home.json` (the config); files change on Apply only.
1111 */
1212 export function createScope(kind: 'home' | 'project', projectId: () => string | undefined) {
1313 const { request } = useApi()
@@ -194,7 +194,7 @@ export function createScope(kind: 'home' | 'project', projectId: () => string |
194194 await flush()
195195 }
196196
197- /** After "Add to catalog" from this scope: the imported items stay here, now managed by skilly. */
197+ /** After "Add to catalog" from this scope: the imported items stay here, now managed by panoplie. */
198198 async function afterImport(res: ImportResult | null) {
199199 await catalog.refresh()
200200 if (res && manage(res.imported.map(i => ({ type: i.type, name: i.name })))) await flush()
@@ -223,7 +223,7 @@ export function createScope(kind: 'home' | 'project', projectId: () => string |
223223
224224 export type ScopeContext = ReturnType<typeof createScope>
225225
226-const SCOPE_KEY: InjectionKey<ScopeContext> = Symbol('skilly-scope')
226+const SCOPE_KEY: InjectionKey<ScopeContext> = Symbol('panoplie-scope')
227227
228228 export function provideScope(scope: ScopeContext) {
229229 provide(SCOPE_KEY, scope)
@@ -1,4 +1,4 @@
1-import type { AgentId, CatalogItemType, ConflictResolution, ImportResult, InventoryItem, PlanAction, ScopeConfig, ScopeState, SyncPlan } from '@skilly/core'1+import type { AgentId, CatalogItemType, ConflictResolution, ImportResult, InventoryItem, PlanAction, ScopeConfig, ScopeState, SyncPlan } from '@panoplie/core'
2 import type { InjectionKey } from 'vue'2 import type { InjectionKey } from 'vue'
3 3
4 /**4 /**
@@ -7,7 +7,7 @@ import type { InjectionKey } from 'vue'
7 * (item drawer, review drawer, import and remove-from-global modals).7 * (item drawer, review drawer, import and remove-from-global modals).
8 *8 *
9 * `ScopePage` creates it and provides it; the scope components read it with `useScopeContext()`.9 * `ScopePage` creates it and provides it; the scope components read it with `useScopeContext()`.
10- * Nothing is written to disk by this composable except `skilly.json` / `home.json` (the config); files change on Apply only.10+ * Nothing is written to disk by this composable except `panoplie.json` / `home.json` (the config); files change on Apply only.
11 */11 */
12 export function createScope(kind: 'home' | 'project', projectId: () => string | undefined) {12 export function createScope(kind: 'home' | 'project', projectId: () => string | undefined) {
13 const { request } = useApi()13 const { request } = useApi()
@@ -194,7 +194,7 @@ export function createScope(kind: 'home' | 'project', projectId: () => string |
194 await flush()194 await flush()
195 }195 }
196 196
197- /** After "Add to catalog" from this scope: the imported items stay here, now managed by skilly. */197+ /** After "Add to catalog" from this scope: the imported items stay here, now managed by panoplie. */
198 async function afterImport(res: ImportResult | null) {198 async function afterImport(res: ImportResult | null) {
199 await catalog.refresh()199 await catalog.refresh()
200 if (res && manage(res.imported.map(i => ({ type: i.type, name: i.name })))) await flush()200 if (res && manage(res.imported.map(i => ({ type: i.type, name: i.name })))) await flush()
@@ -223,7 +223,7 @@ export function createScope(kind: 'home' | 'project', projectId: () => string |
223 223
224 export type ScopeContext = ReturnType<typeof createScope>224 export type ScopeContext = ReturnType<typeof createScope>
225 225
226-const SCOPE_KEY: InjectionKey<ScopeContext> = Symbol('skilly-scope')226+const SCOPE_KEY: InjectionKey<ScopeContext> = Symbol('panoplie-scope')
227 227
228 export function provideScope(scope: ScopeContext) {228 export function provideScope(scope: ScopeContext) {
229 provide(SCOPE_KEY, scope)229 provide(SCOPE_KEY, scope)
modified packages/ui/app/composables/useScopeSummaries.ts +4 -4
@@ -1,4 +1,4 @@
1-import type { AgentId, ScopeState } from '@skilly/core'
1+import type { AgentId, ScopeState } from '@panoplie/core'
22 import type { Tone } from '../utils/status'
33
44 export interface ScopeSummary {
@@ -23,9 +23,9 @@ export function useScopeSummaries() {
2323 const { request } = useApi()
2424 const { projects } = useProjects()
2525 const catalog = useCatalog()
26- const summaries = useState<Record<string, ScopeSummary | null>>('skilly-scope-summaries', () => ({}))
27- const homeState = useState<ScopeState | null>('skilly-home-state', () => null)
28- const loading = useState('skilly-scope-summaries-loading', () => false)
26+ const summaries = useState<Record<string, ScopeSummary | null>>('panoplie-scope-summaries', () => ({}))
27+ const homeState = useState<ScopeState | null>('panoplie-home-state', () => null)
28+ const loading = useState('panoplie-scope-summaries-loading', () => false)
2929
3030 function summarize(s: ScopeState): ScopeSummary {
3131 const cfg = s.config
@@ -1,4 +1,4 @@
1-import type { AgentId, ScopeState } from '@skilly/core'1+import type { AgentId, ScopeState } from '@panoplie/core'
2 import type { Tone } from '../utils/status'2 import type { Tone } from '../utils/status'
3 3
4 export interface ScopeSummary {4 export interface ScopeSummary {
@@ -23,9 +23,9 @@ export function useScopeSummaries() {
23 const { request } = useApi()23 const { request } = useApi()
24 const { projects } = useProjects()24 const { projects } = useProjects()
25 const catalog = useCatalog()25 const catalog = useCatalog()
26- const summaries = useState<Record<string, ScopeSummary | null>>('skilly-scope-summaries', () => ({}))26+ const summaries = useState<Record<string, ScopeSummary | null>>('panoplie-scope-summaries', () => ({}))
27- const homeState = useState<ScopeState | null>('skilly-home-state', () => null)27+ const homeState = useState<ScopeState | null>('panoplie-home-state', () => null)
28- const loading = useState('skilly-scope-summaries-loading', () => false)28+ const loading = useState('panoplie-scope-summaries-loading', () => false)
29 29
30 function summarize(s: ScopeState): ScopeSummary {30 function summarize(s: ScopeState): ScopeSummary {
31 const cfg = s.config31 const cfg = s.config
modified packages/ui/app/composables/useShell.ts +2 -2
@@ -2,10 +2,10 @@
22
33 /** "Add project" modal (mounted once in the default layout). */
44 export function useAddProjectModal() {
5- return useState('skilly-add-open', () => false)
5+ return useState('panoplie-add-open', () => false)
66 }
77
88 /** Ctrl+K quick switcher (mounted once in the default layout). */
99 export function useSwitcher() {
10- return useState('skilly-switcher-open', () => false)
10+ return useState('panoplie-switcher-open', () => false)
1111 }
@@ -2,10 +2,10 @@
2 2
3 /** "Add project" modal (mounted once in the default layout). */3 /** "Add project" modal (mounted once in the default layout). */
4 export function useAddProjectModal() {4 export function useAddProjectModal() {
5- return useState('skilly-add-open', () => false)5+ return useState('panoplie-add-open', () => false)
6 }6 }
7 7
8 /** Ctrl+K quick switcher (mounted once in the default layout). */8 /** Ctrl+K quick switcher (mounted once in the default layout). */
9 export function useSwitcher() {9 export function useSwitcher() {
10- return useState('skilly-switcher-open', () => false)10+ return useState('panoplie-switcher-open', () => false)
11 }11 }
modified packages/ui/app/pages/catalog/[type]/[name].vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogItemType } from '@skilly/core'
2+import type { CatalogItemType } from '@panoplie/core'
33
44 const route = useRoute()
55 const selected = computed(() => ({ type: String(route.params.type) as CatalogItemType, name: String(route.params.name) }))
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItemType } from '@skilly/core'2+import type { CatalogItemType } from '@panoplie/core'
3 3
4 const route = useRoute()4 const route = useRoute()
5 const selected = computed(() => ({ type: String(route.params.type) as CatalogItemType, name: String(route.params.name) }))5 const selected = computed(() => ({ type: String(route.params.type) as CatalogItemType, name: String(route.params.name) }))
modified packages/ui/app/pages/catalog/new.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogItemType } from '@skilly/core'
2+import type { CatalogItemType } from '@panoplie/core'
33
44 const route = useRoute()
55 const type = computed<CatalogItemType>(() => (CAT_TYPES.includes(route.query.type as CatalogItemType) ? route.query.type as CatalogItemType : 'skill'))
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItemType } from '@skilly/core'2+import type { CatalogItemType } from '@panoplie/core'
3 3
4 const route = useRoute()4 const route = useRoute()
5 const type = computed<CatalogItemType>(() => (CAT_TYPES.includes(route.query.type as CatalogItemType) ? route.query.type as CatalogItemType : 'skill'))5 const type = computed<CatalogItemType>(() => (CAT_TYPES.includes(route.query.type as CatalogItemType) ? route.query.type as CatalogItemType : 'skill'))
modified packages/ui/app/pages/index.vue +4 -4
@@ -2,16 +2,16 @@
22 import type { DashboardAction, OverviewCard } from '../utils/dashboard'
33
44 /**
5- * Dashboard (`/`): what skilly does, what needs attention ("To do now"), a "Getting started" checklist until it is
5+ * Dashboard (`/`): what panoplie does, what needs attention ("To do now"), a "Getting started" checklist until it is
66 * done, entry points per area and the recent projects. First run (no catalog and no project yet) goes to the
7- * onboarding instead, unless the user skipped or finished it once (`skilly-onboarding-done`).
7+ * onboarding instead, unless the user skipped or finished it once (`panoplie-onboarding-done`).
88 */
99 const { projects, refresh: refreshProjects } = useProjects()
1010 const catalog = useCatalog()
1111
1212 await Promise.all([refreshProjects(), catalog.refresh()])
1313 let onboarded = false
14-try { onboarded = !!localStorage.getItem('skilly-onboarding-done') } catch { /* private mode */ }
14+try { onboarded = !!localStorage.getItem('panoplie-onboarding-done') } catch { /* private mode */ }
1515 if (!catalog.exists.value && !projects.value.length && !onboarded) await navigateTo('/onboarding', { replace: true })
1616
1717 const { summaries, homeState, load } = useScopeSummaries()
@@ -42,7 +42,7 @@ const todos = computed(() => dashboardTodos({
4242
4343 /* ---------------------------------------------------------------- getting started */
4444
45-const START_HIDDEN = 'skilly-dashboard-start-hidden'
45+const START_HIDDEN = 'panoplie-dashboard-start-hidden'
4646 const startHidden = ref(false)
4747 try { startHidden.value = !!localStorage.getItem(START_HIDDEN) } catch { /* private mode */ }
4848 function hideStart() {
@@ -2,16 +2,16 @@
2 import type { DashboardAction, OverviewCard } from '../utils/dashboard'2 import type { DashboardAction, OverviewCard } from '../utils/dashboard'
3 3
4 /**4 /**
5- * Dashboard (`/`): what skilly does, what needs attention ("To do now"), a "Getting started" checklist until it is5+ * Dashboard (`/`): what panoplie does, what needs attention ("To do now"), a "Getting started" checklist until it is
6 * done, entry points per area and the recent projects. First run (no catalog and no project yet) goes to the6 * done, entry points per area and the recent projects. First run (no catalog and no project yet) goes to the
7- * onboarding instead, unless the user skipped or finished it once (`skilly-onboarding-done`).7+ * onboarding instead, unless the user skipped or finished it once (`panoplie-onboarding-done`).
8 */8 */
9 const { projects, refresh: refreshProjects } = useProjects()9 const { projects, refresh: refreshProjects } = useProjects()
10 const catalog = useCatalog()10 const catalog = useCatalog()
11 11
12 await Promise.all([refreshProjects(), catalog.refresh()])12 await Promise.all([refreshProjects(), catalog.refresh()])
13 let onboarded = false13 let onboarded = false
14-try { onboarded = !!localStorage.getItem('skilly-onboarding-done') } catch { /* private mode */ }14+try { onboarded = !!localStorage.getItem('panoplie-onboarding-done') } catch { /* private mode */ }
15 if (!catalog.exists.value && !projects.value.length && !onboarded) await navigateTo('/onboarding', { replace: true })15 if (!catalog.exists.value && !projects.value.length && !onboarded) await navigateTo('/onboarding', { replace: true })
16 16
17 const { summaries, homeState, load } = useScopeSummaries()17 const { summaries, homeState, load } = useScopeSummaries()
@@ -42,7 +42,7 @@ const todos = computed(() => dashboardTodos({
42 42
43 /* ---------------------------------------------------------------- getting started */43 /* ---------------------------------------------------------------- getting started */
44 44
45-const START_HIDDEN = 'skilly-dashboard-start-hidden'45+const START_HIDDEN = 'panoplie-dashboard-start-hidden'
46 const startHidden = ref(false)46 const startHidden = ref(false)
47 try { startHidden.value = !!localStorage.getItem(START_HIDDEN) } catch { /* private mode */ }47 try { startHidden.value = !!localStorage.getItem(START_HIDDEN) } catch { /* private mode */ }
48 function hideStart() {48 function hideStart() {
modified packages/ui/app/pages/marketplaces/index.vue +2 -2
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { ImportResult, MarketplaceSkill, MarketplaceSkillDetail, MarketplaceSource } from '@skilly/core'
2+import type { ImportResult, MarketplaceSkill, MarketplaceSkillDetail, MarketplaceSource } from '@panoplie/core'
33
44 /**
55 * Marketplaces (brief 3.1): two panes like the Catalog. Left: sources (built-in + yours). Right: the skills of the
@@ -330,7 +330,7 @@ const plural = (n: number, w: string) => `${n} ${w}${n === 1 ? '' : 's'}`
330330
331331 <SkNote v-if="fetching" tone="info">
332332 <p class="m-0 font-medium">{{ refreshing ? 'Fetching the repository…' : 'Loading the skills…' }} <span class="font-mono text-xs text-sk-faint">{{ elapsed }}s</span></p>
333- <p class="m-0 text-xs text-sk-muted">The first load clones the repository, which can take a minute for large ones. You can keep using skilly meanwhile.</p>
333+ <p class="m-0 text-xs text-sk-muted">The first load clones the repository, which can take a minute for large ones. You can keep using panoplie meanwhile.</p>
334334 </SkNote>
335335
336336 <div class="flex flex-wrap items-center gap-2.5">
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { ImportResult, MarketplaceSkill, MarketplaceSkillDetail, MarketplaceSource } from '@skilly/core'2+import type { ImportResult, MarketplaceSkill, MarketplaceSkillDetail, MarketplaceSource } from '@panoplie/core'
3 3
4 /**4 /**
5 * Marketplaces (brief 3.1): two panes like the Catalog. Left: sources (built-in + yours). Right: the skills of the5 * Marketplaces (brief 3.1): two panes like the Catalog. Left: sources (built-in + yours). Right: the skills of the
@@ -330,7 +330,7 @@ const plural = (n: number, w: string) => `${n} ${w}${n === 1 ? '' : 's'}`
330 330
331 <SkNote v-if="fetching" tone="info">331 <SkNote v-if="fetching" tone="info">
332 <p class="m-0 font-medium">{{ refreshing ? 'Fetching the repository…' : 'Loading the skills…' }} <span class="font-mono text-xs text-sk-faint">{{ elapsed }}s</span></p>332 <p class="m-0 font-medium">{{ refreshing ? 'Fetching the repository…' : 'Loading the skills…' }} <span class="font-mono text-xs text-sk-faint">{{ elapsed }}s</span></p>
333- <p class="m-0 text-xs text-sk-muted">The first load clones the repository, which can take a minute for large ones. You can keep using skilly meanwhile.</p>333+ <p class="m-0 text-xs text-sk-muted">The first load clones the repository, which can take a minute for large ones. You can keep using panoplie meanwhile.</p>
334 </SkNote>334 </SkNote>
335 335
336 <div class="flex flex-wrap items-center gap-2.5">336 <div class="flex flex-wrap items-center gap-2.5">
modified packages/ui/app/pages/onboarding.vue +9 -9
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogResponse, DiscoveredProject, FsDiscovery, FsListing, Settings } from '@skilly/core'
2+import type { CatalogResponse, DiscoveredProject, FsDiscovery, FsListing, Settings } from '@panoplie/core'
33
44 /**
55 * Onboarding (design/BRIEF.md 2.1 + 4.8), bare layout. Five steps: Welcome, Catalog (restore = git clone, or start
@@ -188,7 +188,7 @@ async function addSelected(): Promise<boolean> {
188188
189189 /* ------------------------------------------------ Navigation */
190190
191-const SKIP_KEY = 'skilly-onboarding-done'
191+const SKIP_KEY = 'panoplie-onboarding-done'
192192 function finish(to = '/') {
193193 try { localStorage.setItem(SKIP_KEY, '1') } catch { /* private mode */ }
194194 return router.push(to)
@@ -202,7 +202,7 @@ const canContinue = computed(() => {
202202 const nextLabel = computed(() => {
203203 if (step.value === 1 && mode.value === 'fresh' && !catalogReady.value) return 'Skip for now'
204204 if (step.value === 3) return selectedCandidates.value.length ? `Add ${selectedCandidates.value.length} and continue` : 'Skip for now'
205- if (step.value === 4) return 'Open skilly'
205+ if (step.value === 4) return 'Open panoplie'
206206 return 'Continue'
207207 })
208208
@@ -229,7 +229,7 @@ const summary = computed(() => [
229229 <SkPanel hero class="grid w-full max-w-[860px] md:min-h-[520px] md:grid-cols-[200px_minmax(0,1fr)]">
230230 <!-- Steps -->
231231 <aside class="hidden flex-col gap-5 border-r border-sk-line2 bg-sk-panel p-[18px] md:flex">
232- <div class="flex items-center gap-2"><SkLogo /><span class="text-base font-semibold">skilly</span></div>
232+ <div class="flex items-center gap-2"><SkLogo /><span class="text-base font-semibold">panoplie</span></div>
233233 <ol class="m-0 flex list-none flex-col gap-3 p-0">
234234 <li v-for="(s, i) in STEPS" :key="s" class="flex items-center gap-2.5" :aria-current="i === step ? 'step' : undefined">
235235 <span
@@ -248,7 +248,7 @@ const summary = computed(() => [
248248 <div class="flex flex-1 flex-col gap-4">
249249 <!-- 0 Welcome -->
250250 <template v-if="step === 0">
251- <h1 class="m-0 mt-3 font-mono text-[26px] leading-tight font-normal tracking-[-0.01em]">Welcome to skilly</h1>
251+ <h1 class="m-0 mt-3 font-mono text-[26px] leading-tight font-normal tracking-[-0.01em]">Welcome to panoplie</h1>
252252 <p class="m-0 max-w-[460px] text-pretty text-sk-muted">Manage the skills, rules, MCP servers, hooks and subagents your coding agents use, across all your projects, from one place.</p>
253253 <div class="mt-3 grid gap-3 sm:grid-cols-2">
254254 <button type="button" class="cursor-pointer rounded-[10px] border border-sk-line bg-sk-panel p-4 text-left transition-colors hover:border-sk-faint" @click="choose('restore')">
@@ -266,7 +266,7 @@ const summary = computed(() => [
266266 <template v-else-if="step === 1">
267267 <SkSectionLabel>Step 1 of 3</SkSectionLabel>
268268 <h1 class="m-0 font-mono text-[22px] font-normal tracking-[-0.01em]">{{ mode === 'restore' ? 'Connect your catalog' : 'Create your catalog' }}</h1>
269- <p class="m-0 text-sk-muted">{{ mode === 'restore' ? 'skilly clones the repository and restores your items and presets on this machine.' : 'An empty catalog in a git repository. Add a remote later from the Sync page to share it.' }}</p>
269+ <p class="m-0 text-sk-muted">{{ mode === 'restore' ? 'panoplie clones the repository and restores your items and presets on this machine.' : 'An empty catalog in a git repository. Add a remote later from the Sync page to share it.' }}</p>
270270
271271 <div class="flex flex-col gap-1.5">
272272 <span class="font-medium">Catalog folder</span>
@@ -317,7 +317,7 @@ const summary = computed(() => [
317317 <template v-else-if="step === 2">
318318 <SkSectionLabel>Step 2 of 3</SkSectionLabel>
319319 <h1 class="m-0 font-mono text-[22px] font-normal tracking-[-0.01em]">Agents on this machine</h1>
320- <p class="m-0 text-sk-muted">What skilly found in your home folder. Each project chooses the agents it writes for.</p>
320+ <p class="m-0 text-sk-muted">What panoplie found in your home folder. Each project chooses the agents it writes for.</p>
321321 <SkPanel>
322322 <SkRow v-for="a in agentRows" :key="a.id" cols="minmax(0,1fr) auto" class="!py-3">
323323 <div class="min-w-0">
@@ -327,7 +327,7 @@ const summary = computed(() => [
327327 <SkStatus :tone="a.found ? 'ok' : 'faint'" :label="a.found ? `Detected · ${a.count} item${a.count === 1 ? '' : 's'}` : 'Not found'" />
328328 </SkRow>
329329 </SkPanel>
330- <p v-if="home && !home.agentsDetected.length" class="m-0 text-xs text-sk-faint">Nothing detected yet. You can still manage projects; skilly writes the agent folders when you apply.</p>
330+ <p v-if="home && !home.agentsDetected.length" class="m-0 text-xs text-sk-faint">Nothing detected yet. You can still manage projects; panoplie writes the agent folders when you apply.</p>
331331 </template>
332332
333333 <!-- 3 Projects -->
@@ -358,7 +358,7 @@ const summary = computed(() => [
358358 <!-- 4 Ready -->
359359 <template v-else>
360360 <h1 class="m-0 mt-3 font-mono text-[26px] leading-tight font-normal tracking-[-0.01em]">You're set up</h1>
361- <p class="m-0 max-w-[480px] text-pretty text-sk-muted">skilly writes the enabled items into each project. The files are committed with the project, so it works without skilly too.</p>
361+ <p class="m-0 max-w-[480px] text-pretty text-sk-muted">panoplie writes the enabled items into each project. The files are committed with the project, so it works without panoplie too.</p>
362362 <SkPanel>
363363 <SkRow v-for="[k, v] in summary" :key="k" cols="110px minmax(0,1fr)">
364364 <span class="text-sk-muted">{{ k }}</span>
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogResponse, DiscoveredProject, FsDiscovery, FsListing, Settings } from '@skilly/core'2+import type { CatalogResponse, DiscoveredProject, FsDiscovery, FsListing, Settings } from '@panoplie/core'
3 3
4 /**4 /**
5 * Onboarding (design/BRIEF.md 2.1 + 4.8), bare layout. Five steps: Welcome, Catalog (restore = git clone, or start5 * Onboarding (design/BRIEF.md 2.1 + 4.8), bare layout. Five steps: Welcome, Catalog (restore = git clone, or start
@@ -188,7 +188,7 @@ async function addSelected(): Promise<boolean> {
188 188
189 /* ------------------------------------------------ Navigation */189 /* ------------------------------------------------ Navigation */
190 190
191-const SKIP_KEY = 'skilly-onboarding-done'191+const SKIP_KEY = 'panoplie-onboarding-done'
192 function finish(to = '/') {192 function finish(to = '/') {
193 try { localStorage.setItem(SKIP_KEY, '1') } catch { /* private mode */ }193 try { localStorage.setItem(SKIP_KEY, '1') } catch { /* private mode */ }
194 return router.push(to)194 return router.push(to)
@@ -202,7 +202,7 @@ const canContinue = computed(() => {
202 const nextLabel = computed(() => {202 const nextLabel = computed(() => {
203 if (step.value === 1 && mode.value === 'fresh' && !catalogReady.value) return 'Skip for now'203 if (step.value === 1 && mode.value === 'fresh' && !catalogReady.value) return 'Skip for now'
204 if (step.value === 3) return selectedCandidates.value.length ? `Add ${selectedCandidates.value.length} and continue` : 'Skip for now'204 if (step.value === 3) return selectedCandidates.value.length ? `Add ${selectedCandidates.value.length} and continue` : 'Skip for now'
205- if (step.value === 4) return 'Open skilly'205+ if (step.value === 4) return 'Open panoplie'
206 return 'Continue'206 return 'Continue'
207 })207 })
208 208
@@ -229,7 +229,7 @@ const summary = computed(() => [
229 <SkPanel hero class="grid w-full max-w-[860px] md:min-h-[520px] md:grid-cols-[200px_minmax(0,1fr)]">229 <SkPanel hero class="grid w-full max-w-[860px] md:min-h-[520px] md:grid-cols-[200px_minmax(0,1fr)]">
230 <!-- Steps -->230 <!-- Steps -->
231 <aside class="hidden flex-col gap-5 border-r border-sk-line2 bg-sk-panel p-[18px] md:flex">231 <aside class="hidden flex-col gap-5 border-r border-sk-line2 bg-sk-panel p-[18px] md:flex">
232- <div class="flex items-center gap-2"><SkLogo /><span class="text-base font-semibold">skilly</span></div>232+ <div class="flex items-center gap-2"><SkLogo /><span class="text-base font-semibold">panoplie</span></div>
233 <ol class="m-0 flex list-none flex-col gap-3 p-0">233 <ol class="m-0 flex list-none flex-col gap-3 p-0">
234 <li v-for="(s, i) in STEPS" :key="s" class="flex items-center gap-2.5" :aria-current="i === step ? 'step' : undefined">234 <li v-for="(s, i) in STEPS" :key="s" class="flex items-center gap-2.5" :aria-current="i === step ? 'step' : undefined">
235 <span235 <span
@@ -248,7 +248,7 @@ const summary = computed(() => [
248 <div class="flex flex-1 flex-col gap-4">248 <div class="flex flex-1 flex-col gap-4">
249 <!-- 0 Welcome -->249 <!-- 0 Welcome -->
250 <template v-if="step === 0">250 <template v-if="step === 0">
251- <h1 class="m-0 mt-3 font-mono text-[26px] leading-tight font-normal tracking-[-0.01em]">Welcome to skilly</h1>251+ <h1 class="m-0 mt-3 font-mono text-[26px] leading-tight font-normal tracking-[-0.01em]">Welcome to panoplie</h1>
252 <p class="m-0 max-w-[460px] text-pretty text-sk-muted">Manage the skills, rules, MCP servers, hooks and subagents your coding agents use, across all your projects, from one place.</p>252 <p class="m-0 max-w-[460px] text-pretty text-sk-muted">Manage the skills, rules, MCP servers, hooks and subagents your coding agents use, across all your projects, from one place.</p>
253 <div class="mt-3 grid gap-3 sm:grid-cols-2">253 <div class="mt-3 grid gap-3 sm:grid-cols-2">
254 <button type="button" class="cursor-pointer rounded-[10px] border border-sk-line bg-sk-panel p-4 text-left transition-colors hover:border-sk-faint" @click="choose('restore')">254 <button type="button" class="cursor-pointer rounded-[10px] border border-sk-line bg-sk-panel p-4 text-left transition-colors hover:border-sk-faint" @click="choose('restore')">
@@ -266,7 +266,7 @@ const summary = computed(() => [
266 <template v-else-if="step === 1">266 <template v-else-if="step === 1">
267 <SkSectionLabel>Step 1 of 3</SkSectionLabel>267 <SkSectionLabel>Step 1 of 3</SkSectionLabel>
268 <h1 class="m-0 font-mono text-[22px] font-normal tracking-[-0.01em]">{{ mode === 'restore' ? 'Connect your catalog' : 'Create your catalog' }}</h1>268 <h1 class="m-0 font-mono text-[22px] font-normal tracking-[-0.01em]">{{ mode === 'restore' ? 'Connect your catalog' : 'Create your catalog' }}</h1>
269- <p class="m-0 text-sk-muted">{{ mode === 'restore' ? 'skilly clones the repository and restores your items and presets on this machine.' : 'An empty catalog in a git repository. Add a remote later from the Sync page to share it.' }}</p>269+ <p class="m-0 text-sk-muted">{{ mode === 'restore' ? 'panoplie clones the repository and restores your items and presets on this machine.' : 'An empty catalog in a git repository. Add a remote later from the Sync page to share it.' }}</p>
270 270
271 <div class="flex flex-col gap-1.5">271 <div class="flex flex-col gap-1.5">
272 <span class="font-medium">Catalog folder</span>272 <span class="font-medium">Catalog folder</span>
@@ -317,7 +317,7 @@ const summary = computed(() => [
317 <template v-else-if="step === 2">317 <template v-else-if="step === 2">
318 <SkSectionLabel>Step 2 of 3</SkSectionLabel>318 <SkSectionLabel>Step 2 of 3</SkSectionLabel>
319 <h1 class="m-0 font-mono text-[22px] font-normal tracking-[-0.01em]">Agents on this machine</h1>319 <h1 class="m-0 font-mono text-[22px] font-normal tracking-[-0.01em]">Agents on this machine</h1>
320- <p class="m-0 text-sk-muted">What skilly found in your home folder. Each project chooses the agents it writes for.</p>320+ <p class="m-0 text-sk-muted">What panoplie found in your home folder. Each project chooses the agents it writes for.</p>
321 <SkPanel>321 <SkPanel>
322 <SkRow v-for="a in agentRows" :key="a.id" cols="minmax(0,1fr) auto" class="!py-3">322 <SkRow v-for="a in agentRows" :key="a.id" cols="minmax(0,1fr) auto" class="!py-3">
323 <div class="min-w-0">323 <div class="min-w-0">
@@ -327,7 +327,7 @@ const summary = computed(() => [
327 <SkStatus :tone="a.found ? 'ok' : 'faint'" :label="a.found ? `Detected · ${a.count} item${a.count === 1 ? '' : 's'}` : 'Not found'" />327 <SkStatus :tone="a.found ? 'ok' : 'faint'" :label="a.found ? `Detected · ${a.count} item${a.count === 1 ? '' : 's'}` : 'Not found'" />
328 </SkRow>328 </SkRow>
329 </SkPanel>329 </SkPanel>
330- <p v-if="home && !home.agentsDetected.length" class="m-0 text-xs text-sk-faint">Nothing detected yet. You can still manage projects; skilly writes the agent folders when you apply.</p>330+ <p v-if="home && !home.agentsDetected.length" class="m-0 text-xs text-sk-faint">Nothing detected yet. You can still manage projects; panoplie writes the agent folders when you apply.</p>
331 </template>331 </template>
332 332
333 <!-- 3 Projects -->333 <!-- 3 Projects -->
@@ -358,7 +358,7 @@ const summary = computed(() => [
358 <!-- 4 Ready -->358 <!-- 4 Ready -->
359 <template v-else>359 <template v-else>
360 <h1 class="m-0 mt-3 font-mono text-[26px] leading-tight font-normal tracking-[-0.01em]">You're set up</h1>360 <h1 class="m-0 mt-3 font-mono text-[26px] leading-tight font-normal tracking-[-0.01em]">You're set up</h1>
361- <p class="m-0 max-w-[480px] text-pretty text-sk-muted">skilly writes the enabled items into each project. The files are committed with the project, so it works without skilly too.</p>361+ <p class="m-0 max-w-[480px] text-pretty text-sk-muted">panoplie writes the enabled items into each project. The files are committed with the project, so it works without panoplie too.</p>
362 <SkPanel>362 <SkPanel>
363 <SkRow v-for="[k, v] in summary" :key="k" cols="110px minmax(0,1fr)">363 <SkRow v-for="[k, v] in summary" :key="k" cols="110px minmax(0,1fr)">
364 <span class="text-sk-muted">{{ k }}</span>364 <span class="text-sk-muted">{{ k }}</span>
modified packages/ui/app/pages/presets/index.vue +1 -1
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { CatalogItemType, Preset } from '@skilly/core'
2+import type { CatalogItemType, Preset } from '@panoplie/core'
33
44 const { data, loading, error, refresh, exists, presets, presetUsedBy } = useCatalog()
55
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { CatalogItemType, Preset } from '@skilly/core'2+import type { CatalogItemType, Preset } from '@panoplie/core'
3 3
4 const { data, loading, error, refresh, exists, presets, presetUsedBy } = useCatalog()4 const { data, loading, error, refresh, exists, presets, presetUsedBy } = useCatalog()
5 5
modified packages/ui/app/pages/projects/index.vue +1 -1
@@ -4,7 +4,7 @@ const addOpen = useAddProjectModal()
44
55 <template>
66 <SkPage>
7- <SkPageHeader title="Projects" subtitle="Folders managed by skilly. Enabled items are written into each project.">
7+ <SkPageHeader title="Projects" subtitle="Folders managed by panoplie. Enabled items are written into each project.">
88 <template #actions>
99 <SkButton variant="primary" label="Add project" @click="addOpen = true" />
1010 </template>
@@ -4,7 +4,7 @@ const addOpen = useAddProjectModal()
4 4
5 <template>5 <template>
6 <SkPage>6 <SkPage>
7- <SkPageHeader title="Projects" subtitle="Folders managed by skilly. Enabled items are written into each project.">7+ <SkPageHeader title="Projects" subtitle="Folders managed by panoplie. Enabled items are written into each project.">
8 <template #actions>8 <template #actions>
9 <SkButton variant="primary" label="Add project" @click="addOpen = true" />9 <SkButton variant="primary" label="Add project" @click="addOpen = true" />
10 </template>10 </template>
modified packages/ui/app/pages/settings.vue +2 -2
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { Settings } from '@skilly/core'
2+import type { Settings } from '@panoplie/core'
33
44 /** Settings (brief 3.1): catalog location + automatic sync, appearance, run the setup again, about. */
55 const { request } = useApi()
@@ -112,7 +112,7 @@ const APPEARANCE = [
112112 <SkMeta label="Version" mono>{{ version ?? '-' }}</SkMeta>
113113 <SkMeta label="Projects" mono>{{ projects.length }}</SkMeta>
114114 </SkMetaGrid>
115- <p class="m-0 text-xs text-sk-faint">skilly runs locally. The catalog only leaves this machine through the git remote you set.</p>
115+ <p class="m-0 text-xs text-sk-faint">panoplie runs locally. The catalog only leaves this machine through the git remote you set.</p>
116116 </SkPanel>
117117 </template>
118118
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { Settings } from '@skilly/core'2+import type { Settings } from '@panoplie/core'
3 3
4 /** Settings (brief 3.1): catalog location + automatic sync, appearance, run the setup again, about. */4 /** Settings (brief 3.1): catalog location + automatic sync, appearance, run the setup again, about. */
5 const { request } = useApi()5 const { request } = useApi()
@@ -112,7 +112,7 @@ const APPEARANCE = [
112 <SkMeta label="Version" mono>{{ version ?? '-' }}</SkMeta>112 <SkMeta label="Version" mono>{{ version ?? '-' }}</SkMeta>
113 <SkMeta label="Projects" mono>{{ projects.length }}</SkMeta>113 <SkMeta label="Projects" mono>{{ projects.length }}</SkMeta>
114 </SkMetaGrid>114 </SkMetaGrid>
115- <p class="m-0 text-xs text-sk-faint">skilly runs locally. The catalog only leaves this machine through the git remote you set.</p>115+ <p class="m-0 text-xs text-sk-faint">panoplie runs locally. The catalog only leaves this machine through the git remote you set.</p>
116 </SkPanel>116 </SkPanel>
117 </template>117 </template>
118 118
modified packages/ui/app/pages/sync.vue +2 -2
@@ -1,5 +1,5 @@
11 <script setup lang="ts">
2-import type { Settings } from '@skilly/core'
2+import type { Settings } from '@panoplie/core'
33
44 /**
55 * Sync (brief 3.1): the catalog git repository in one page. Status, remote, commit, pull / push / sync, conflicts
@@ -224,7 +224,7 @@ const noRemoteHint = 'Set a remote first'
224224 </div>
225225 </SkRowCard>
226226 </SkTray>
227- <p class="m-0 text-xs text-sk-muted">Keep mine keeps the version on this machine. Use remote takes the version from the remote. Once every conflict is resolved, skilly commits the merge.</p>
227+ <p class="m-0 text-xs text-sk-muted">Keep mine keeps the version on this machine. Use remote takes the version from the remote. Once every conflict is resolved, panoplie commits the merge.</p>
228228 </SkPanel>
229229
230230 <!-- Changes -->
@@ -1,5 +1,5 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2-import type { Settings } from '@skilly/core'2+import type { Settings } from '@panoplie/core'
3 3
4 /**4 /**
5 * Sync (brief 3.1): the catalog git repository in one page. Status, remote, commit, pull / push / sync, conflicts5 * Sync (brief 3.1): the catalog git repository in one page. Status, remote, commit, pull / push / sync, conflicts
@@ -224,7 +224,7 @@ const noRemoteHint = 'Set a remote first'
224 </div>224 </div>
225 </SkRowCard>225 </SkRowCard>
226 </SkTray>226 </SkTray>
227- <p class="m-0 text-xs text-sk-muted">Keep mine keeps the version on this machine. Use remote takes the version from the remote. Once every conflict is resolved, skilly commits the merge.</p>227+ <p class="m-0 text-xs text-sk-muted">Keep mine keeps the version on this machine. Use remote takes the version from the remote. Once every conflict is resolved, panoplie commits the merge.</p>
228 </SkPanel>228 </SkPanel>
229 229
230 <!-- Changes -->230 <!-- Changes -->
modified packages/ui/app/plugins/02.heartbeat.client.ts +1 -1
@@ -3,7 +3,7 @@ export default defineNuxtPlugin(() => {
33 const ping = () => {
44 fetch('/api/heartbeat', {
55 method: 'POST',
6- headers: { 'x-skilly-token': getToken() },
6+ headers: { 'x-panoplie-token': getToken() },
77 keepalive: true
88 }).catch(() => {})
99 }
@@ -3,7 +3,7 @@ export default defineNuxtPlugin(() => {
3 const ping = () => {3 const ping = () => {
4 fetch('/api/heartbeat', {4 fetch('/api/heartbeat', {
5 method: 'POST',5 method: 'POST',
6- headers: { 'x-skilly-token': getToken() },6+ headers: { 'x-panoplie-token': getToken() },
7 keepalive: true7 keepalive: true
8 }).catch(() => {})8 }).catch(() => {})
9 }9 }
modified packages/ui/app/utils/catalog.ts +2 -2
@@ -1,4 +1,4 @@
1-import type { CatalogItemType, HookTemplate, ItemStatus, McpEnvValue, McpTemplate } from '@skilly/core'
1+import type { CatalogItemType, HookTemplate, ItemStatus, McpEnvValue, McpTemplate } from '@panoplie/core'
22 import { splitFrontmatter } from './markdown'
33
44 export const CAT_TYPES: CatalogItemType[] = ['skill', 'rule', 'agent', 'mcp', 'hook']
@@ -23,7 +23,7 @@ export const STATUS_META: Record<ItemStatus['state'], { label: string, color: 's
2323 'modified': { label: 'modified', color: 'warning', icon: 'i-lucide-pencil', hint: 'Edited in this scope since the last sync.' },
2424 'catalog-updated': { label: 'catalog updated', color: 'info', icon: 'i-lucide-arrow-down-to-line', hint: 'The catalog has a newer version.' },
2525 'not-in-catalog': { label: 'not in catalog', color: 'neutral', icon: 'i-lucide-circle-dashed', hint: 'Local item, absent from the catalog.' },
26- 'differs-from-catalog': { label: 'differs from catalog', color: 'warning', icon: 'i-lucide-git-compare', hint: 'Same name as a catalog item, different content, not managed by skilly.' }
26+ 'differs-from-catalog': { label: 'differs from catalog', color: 'warning', icon: 'i-lucide-git-compare', hint: 'Same name as a catalog item, different content, not managed by panoplie.' }
2727 }
2828
2929 /** `*` wildcard match on names, same semantics as the config globs. */
@@ -1,4 +1,4 @@
1-import type { CatalogItemType, HookTemplate, ItemStatus, McpEnvValue, McpTemplate } from '@skilly/core'1+import type { CatalogItemType, HookTemplate, ItemStatus, McpEnvValue, McpTemplate } from '@panoplie/core'
2 import { splitFrontmatter } from './markdown'2 import { splitFrontmatter } from './markdown'
3 3
4 export const CAT_TYPES: CatalogItemType[] = ['skill', 'rule', 'agent', 'mcp', 'hook']4 export const CAT_TYPES: CatalogItemType[] = ['skill', 'rule', 'agent', 'mcp', 'hook']
@@ -23,7 +23,7 @@ export const STATUS_META: Record<ItemStatus['state'], { label: string, color: 's
23 'modified': { label: 'modified', color: 'warning', icon: 'i-lucide-pencil', hint: 'Edited in this scope since the last sync.' },23 'modified': { label: 'modified', color: 'warning', icon: 'i-lucide-pencil', hint: 'Edited in this scope since the last sync.' },
24 'catalog-updated': { label: 'catalog updated', color: 'info', icon: 'i-lucide-arrow-down-to-line', hint: 'The catalog has a newer version.' },24 'catalog-updated': { label: 'catalog updated', color: 'info', icon: 'i-lucide-arrow-down-to-line', hint: 'The catalog has a newer version.' },
25 'not-in-catalog': { label: 'not in catalog', color: 'neutral', icon: 'i-lucide-circle-dashed', hint: 'Local item, absent from the catalog.' },25 'not-in-catalog': { label: 'not in catalog', color: 'neutral', icon: 'i-lucide-circle-dashed', hint: 'Local item, absent from the catalog.' },
26- 'differs-from-catalog': { label: 'differs from catalog', color: 'warning', icon: 'i-lucide-git-compare', hint: 'Same name as a catalog item, different content, not managed by skilly.' }26+ 'differs-from-catalog': { label: 'differs from catalog', color: 'warning', icon: 'i-lucide-git-compare', hint: 'Same name as a catalog item, different content, not managed by panoplie.' }
27 }27 }
28 28
29 /** `*` wildcard match on names, same semantics as the config globs. */29 /** `*` wildcard match on names, same semantics as the config globs. */
modified packages/ui/app/utils/dashboard.ts +3 -3
@@ -1,4 +1,4 @@
1-import type { GitStatus, InventoryItem, ItemStatus } from '@skilly/core'
1+import type { GitStatus, InventoryItem, ItemStatus } from '@panoplie/core'
22 import type { Tone } from './status'
33 import { canMoveOutOfHome, projectSpecific } from './inventoryView'
44 import { GLOBAL_PATH, scopeLink } from './catalog'
@@ -144,14 +144,14 @@ export function startSteps(input: {
144144 {
145145 id: 'project',
146146 title: 'Add a project',
147- hint: 'Pick a folder: skilly shows what Claude Code and Codex load there.',
147+ hint: 'Pick a folder: panoplie shows what Claude Code and Codex load there.',
148148 done: input.projects.length > 0,
149149 action: { label: 'Add project', run: 'add-project' }
150150 },
151151 {
152152 id: 'manage',
153153 title: 'Choose what a project uses',
154- hint: 'Enable presets and catalog items, then review and apply: skilly writes the files.',
154+ hint: 'Enable presets and catalog items, then review and apply: panoplie writes the files.',
155155 done: input.managedProjects > 0,
156156 action: { label: 'Open', to: first ? scopeLink(first.id) : '/projects' }
157157 },
@@ -1,4 +1,4 @@
1-import type { GitStatus, InventoryItem, ItemStatus } from '@skilly/core'1+import type { GitStatus, InventoryItem, ItemStatus } from '@panoplie/core'
2 import type { Tone } from './status'2 import type { Tone } from './status'
3 import { canMoveOutOfHome, projectSpecific } from './inventoryView'3 import { canMoveOutOfHome, projectSpecific } from './inventoryView'
4 import { GLOBAL_PATH, scopeLink } from './catalog'4 import { GLOBAL_PATH, scopeLink } from './catalog'
@@ -144,14 +144,14 @@ export function startSteps(input: {
144 {144 {
145 id: 'project',145 id: 'project',
146 title: 'Add a project',146 title: 'Add a project',
147- hint: 'Pick a folder: skilly shows what Claude Code and Codex load there.',147+ hint: 'Pick a folder: panoplie shows what Claude Code and Codex load there.',
148 done: input.projects.length > 0,148 done: input.projects.length > 0,
149 action: { label: 'Add project', run: 'add-project' }149 action: { label: 'Add project', run: 'add-project' }
150 },150 },
151 {151 {
152 id: 'manage',152 id: 'manage',
153 title: 'Choose what a project uses',153 title: 'Choose what a project uses',
154- hint: 'Enable presets and catalog items, then review and apply: skilly writes the files.',154+ hint: 'Enable presets and catalog items, then review and apply: panoplie writes the files.',
155 done: input.managedProjects > 0,155 done: input.managedProjects > 0,
156 action: { label: 'Open', to: first ? scopeLink(first.id) : '/projects' }156 action: { label: 'Open', to: first ? scopeLink(first.id) : '/projects' }
157 },157 },
modified packages/ui/app/utils/format.ts +1 -1
@@ -1,4 +1,4 @@
1-import type { AgentId, ItemType } from '@skilly/core'
1+import type { AgentId, ItemType } from '@panoplie/core'
22
33 export const AGENT_LABELS: Record<AgentId, string> = { claude: 'Claude Code', codex: 'Codex' }
44
@@ -1,4 +1,4 @@
1-import type { AgentId, ItemType } from '@skilly/core'1+import type { AgentId, ItemType } from '@panoplie/core'
2 2
3 export const AGENT_LABELS: Record<AgentId, string> = { claude: 'Claude Code', codex: 'Codex' }3 export const AGENT_LABELS: Record<AgentId, string> = { claude: 'Claude Code', codex: 'Codex' }
4 4
modified packages/ui/app/utils/inventoryView.ts +9 -5
@@ -1,4 +1,4 @@
1-import type { AgentId, CatalogItemType, HookDetails, InstructionsDetails, InventoryItem, ItemStatus } from '@skilly/core'
1+import type { AgentId, CatalogItemType, HookDetails, InstructionsDetails, InventoryItem, ItemStatus } from '@panoplie/core'
22 import type { Tone } from './status'
33
44 /**
@@ -56,7 +56,7 @@ export function homeStateMeta(state: ItemStatus['state'] | undefined): { tone: T
5656 /** Types that "Remove from global" supports. MCP servers and hooks of Home are read-only in v1. */
5757 export const MOVABLE_TYPES: ReadonlySet<string> = new Set(['skill', 'rule', 'agent'])
5858
59-/** True when the item can be moved out of Home (skills, rules, subagents not provided by a plugin nor managed by skilly). */
59+/** True when the item can be moved out of Home (skills, rules, subagents not provided by a plugin nor managed by panoplie). */
6060 export function canMoveOutOfHome(i: InventoryItem, status?: ItemStatus): boolean {
6161 return MOVABLE_TYPES.has(i.type) && !i.source.plugin && !status?.managed
6262 }
@@ -99,12 +99,16 @@ export function agentsOf(items: { agent: AgentId }[]): AgentId[] {
9999 return (['claude', 'codex'] as AgentId[]).filter(a => set.has(a))
100100 }
101101
102-/** Splits an instruction file into plain and skilly-managed segments (the AGENTS.md block between skilly:begin and skilly:end). */
102+/**
103+ * Splits an instruction file into plain and panoplie-managed segments (the AGENTS.md block between panoplie:begin
104+ * and panoplie:end). Blocks written under the previous name (skilly:begin / skilly:end) are recognized too.
105+ */
103106 export function splitManagedBlock(text: string): { text: string, managed: boolean }[] {
104107 const lines = text.split('\n')
105- const begin = lines.findIndex(l => l.startsWith('<!-- skilly:begin'))
108+ const begin = lines.findIndex(l => /^<!-- (panoplie|skilly):begin/.test(l))
106109 if (begin === -1) return [{ text, managed: false }]
107- let end = lines.findIndex((l, idx) => idx > begin && l.startsWith('<!-- skilly:end'))
110+ const name = lines[begin]!.startsWith('<!-- skilly:') ? 'skilly' : 'panoplie'
111+ let end = lines.findIndex((l, idx) => idx > begin && l.startsWith(`<!-- ${name}:end`))
108112 if (end === -1) end = lines.length - 1
109113 const out: { text: string, managed: boolean }[] = []
110114 if (begin > 0) out.push({ text: lines.slice(0, begin).join('\n') + '\n', managed: false })
@@ -1,4 +1,4 @@
1-import type { AgentId, CatalogItemType, HookDetails, InstructionsDetails, InventoryItem, ItemStatus } from '@skilly/core'1+import type { AgentId, CatalogItemType, HookDetails, InstructionsDetails, InventoryItem, ItemStatus } from '@panoplie/core'
2 import type { Tone } from './status'2 import type { Tone } from './status'
3 3
4 /**4 /**
@@ -56,7 +56,7 @@ export function homeStateMeta(state: ItemStatus['state'] | undefined): { tone: T
56 /** Types that "Remove from global" supports. MCP servers and hooks of Home are read-only in v1. */56 /** Types that "Remove from global" supports. MCP servers and hooks of Home are read-only in v1. */
57 export const MOVABLE_TYPES: ReadonlySet<string> = new Set(['skill', 'rule', 'agent'])57 export const MOVABLE_TYPES: ReadonlySet<string> = new Set(['skill', 'rule', 'agent'])
58 58
59-/** True when the item can be moved out of Home (skills, rules, subagents not provided by a plugin nor managed by skilly). */59+/** True when the item can be moved out of Home (skills, rules, subagents not provided by a plugin nor managed by panoplie). */
60 export function canMoveOutOfHome(i: InventoryItem, status?: ItemStatus): boolean {60 export function canMoveOutOfHome(i: InventoryItem, status?: ItemStatus): boolean {
61 return MOVABLE_TYPES.has(i.type) && !i.source.plugin && !status?.managed61 return MOVABLE_TYPES.has(i.type) && !i.source.plugin && !status?.managed
62 }62 }
@@ -99,12 +99,16 @@ export function agentsOf(items: { agent: AgentId }[]): AgentId[] {
99 return (['claude', 'codex'] as AgentId[]).filter(a => set.has(a))99 return (['claude', 'codex'] as AgentId[]).filter(a => set.has(a))
100 }100 }
101 101
102-/** Splits an instruction file into plain and skilly-managed segments (the AGENTS.md block between skilly:begin and skilly:end). */102+/**
103+ * Splits an instruction file into plain and panoplie-managed segments (the AGENTS.md block between panoplie:begin
104+ * and panoplie:end). Blocks written under the previous name (skilly:begin / skilly:end) are recognized too.
105+ */
103 export function splitManagedBlock(text: string): { text: string, managed: boolean }[] {106 export function splitManagedBlock(text: string): { text: string, managed: boolean }[] {
104 const lines = text.split('\n')107 const lines = text.split('\n')
105- const begin = lines.findIndex(l => l.startsWith('<!-- skilly:begin'))108+ const begin = lines.findIndex(l => /^<!-- (panoplie|skilly):begin/.test(l))
106 if (begin === -1) return [{ text, managed: false }]109 if (begin === -1) return [{ text, managed: false }]
107- let end = lines.findIndex((l, idx) => idx > begin && l.startsWith('<!-- skilly:end'))110+ const name = lines[begin]!.startsWith('<!-- skilly:') ? 'skilly' : 'panoplie'
111+ let end = lines.findIndex((l, idx) => idx > begin && l.startsWith(`<!-- ${name}:end`))
108 if (end === -1) end = lines.length - 1112 if (end === -1) end = lines.length - 1
109 const out: { text: string, managed: boolean }[] = []113 const out: { text: string, managed: boolean }[] = []
110 if (begin > 0) out.push({ text: lines.slice(0, begin).join('\n') + '\n', managed: false })114 if (begin > 0) out.push({ text: lines.slice(0, begin).join('\n') + '\n', managed: false })
modified packages/ui/app/utils/planView.ts +1 -1
@@ -1,4 +1,4 @@
1-import type { PlanAction, ScopeState } from '@skilly/core'
1+import type { PlanAction, ScopeState } from '@panoplie/core'
22
33 /** Plan helpers shared by the Projects table, the pending bar and the review drawer. */
44
@@ -1,4 +1,4 @@
1-import type { PlanAction, ScopeState } from '@skilly/core'1+import type { PlanAction, ScopeState } from '@panoplie/core'
2 2
3 /** Plan helpers shared by the Projects table, the pending bar and the review drawer. */3 /** Plan helpers shared by the Projects table, the pending bar and the review drawer. */
4 4
modified packages/ui/app/utils/recent.ts +1 -1
@@ -1,5 +1,5 @@
11 /** Recently opened projects (dashboard "Recent projects"), most recent first. A UI preference kept in localStorage. */
2-const KEY = 'skilly-recent-projects'
2+const KEY = 'panoplie-recent-projects'
33 const MAX = 8
44
55 export function recentProjectIds(): string[] {
@@ -1,5 +1,5 @@
1 /** Recently opened projects (dashboard "Recent projects"), most recent first. A UI preference kept in localStorage. */1 /** Recently opened projects (dashboard "Recent projects"), most recent first. A UI preference kept in localStorage. */
2-const KEY = 'skilly-recent-projects'2+const KEY = 'panoplie-recent-projects'
3 const MAX = 83 const MAX = 8
4 4
5 export function recentProjectIds(): string[] {5 export function recentProjectIds(): string[] {
modified packages/ui/app/utils/resolve.ts +1 -1
@@ -1,4 +1,4 @@
1-import type { CatalogItemType, Preset } from '@skilly/core'
1+import type { CatalogItemType, Preset } from '@panoplie/core'
22
33 export interface ResolvedEntry {
44 type: CatalogItemType
@@ -1,4 +1,4 @@
1-import type { CatalogItemType, Preset } from '@skilly/core'1+import type { CatalogItemType, Preset } from '@panoplie/core'
2 2
3 export interface ResolvedEntry {3 export interface ResolvedEntry {
4 type: CatalogItemType4 type: CatalogItemType
modified packages/ui/app/utils/scopeConfig.ts +1 -1
@@ -1,4 +1,4 @@
1-import type { AgentId, CatalogItemType, PlanAction, Preset, ScopeConfig } from '@skilly/core'
1+import type { AgentId, CatalogItemType, PlanAction, Preset, ScopeConfig } from '@panoplie/core'
22 import { resolveItems } from './resolve'
33
44 /** Types Global's config (home.json) can hold: its MCP servers and hooks are read-only in v1. */
@@ -1,4 +1,4 @@
1-import type { AgentId, CatalogItemType, PlanAction, Preset, ScopeConfig } from '@skilly/core'1+import type { AgentId, CatalogItemType, PlanAction, Preset, ScopeConfig } from '@panoplie/core'
2 import { resolveItems } from './resolve'2 import { resolveItems } from './resolve'
3 3
4 /** Types Global's config (home.json) can hold: its MCP servers and hooks are read-only in v1. */4 /** Types Global's config (home.json) can hold: its MCP servers and hooks are read-only in v1. */
modified packages/ui/app/utils/scopeRows.ts +6 -6
@@ -1,19 +1,19 @@
11 import type {
22 AgentId, CatalogItem, CatalogItemType, ConflictKind, ItemStatus, PlanAction, Preset, ScopeConfig, ScopeInventory, SyncPlan
3-} from '@skilly/core'
3+} from '@panoplie/core'
44 import { ITEM_STATE_META, ITEM_STATE_SHORT, type Tone } from './status'
55 import { resolveItems } from './resolve'
66 import { itemDisplayName } from './inventoryView'
77 import { HOME_CONFIG_TYPES } from './scopeConfig'
88
99 /**
10- * View model of the single "Items" list of a project or of Global: what skilly manages (config entries, removals)
10+ * View model of the single "Items" list of a project or of Global: what panoplie manages (config entries, removals)
1111 * and what is only on disk (local and plugin items), in one list. One row per logical item (`type/name`; hooks: one
1212 * per inventory entry), with one status per agent. Built from the local config copy (null when the scope is not
1313 * managed yet), the sync plan and the inventory. Pure and unit-tested.
1414 */
1515
16-/** `manage`: the local item is identical to a catalog item; add it to the config so skilly manages it. */
16+/** `manage`: the local item is identical to a catalog item; add it to the config so panoplie manages it. */
1717 export type RowAction = 'resolve' | 'review' | 'import' | 'manage'
1818
1919 export interface AgentRowStatus {
@@ -132,7 +132,7 @@ export interface BuildRowsInput {
132132 statuses: Record<string, ItemStatus>
133133 /** Names of the Home skills / MCP servers, to tell "hide global" actions from item actions. */
134134 homeNames?: HomeNames
135- /** Also list the items on disk that skilly does not manage (local and plugin items). */
135+ /** Also list the items on disk that panoplie does not manage (local and plugin items). */
136136 includeLocal: boolean
137137 /** Global: its config only takes skills, rules and subagents, so its MCP servers and hooks can't be managed. */
138138 home?: boolean
@@ -215,7 +215,7 @@ export function buildRows(input: BuildRowsInput): ScopeRow[] {
215215 const groups = new Map<string, ScopeRow>()
216216 for (const it of inventory?.items ?? []) {
217217 const st = statuses[it.id]
218- // Written by skilly: already an entry or a removal row when there is a config.
218+ // Written by panoplie: already an entry or a removal row when there is a config.
219219 if (it.type === 'instructions' || (config && st?.managed)) continue
220220 const t = it.type as CatalogItemType
221221 const plugin = it.source.plugin
@@ -239,7 +239,7 @@ export function buildRows(input: BuildRowsInput): ScopeRow[] {
239239 const row = groups.get(key) ?? {
240240 key, kind: 'local' as const, type: t, name: it.name, label: itemDisplayName(it), itemIds: [], plugin,
241241 presets: [], added: false, excluded: false, excludedExact: false, inCatalog: !plugin && inCatalog(t, it.name),
242- origin: plugin ? `Plugin · ${plugin}` : 'Local · not managed by skilly',
242+ origin: plugin ? `Plugin · ${plugin}` : 'Local · not managed by panoplie',
243243 statuses: [], conflicts: [], viaPreset: false, onDemand: { on: false, exact: true }
244244 }
245245 row.itemIds.push(it.id)
@@ -1,19 +1,19 @@
1 import type {1 import type {
2 AgentId, CatalogItem, CatalogItemType, ConflictKind, ItemStatus, PlanAction, Preset, ScopeConfig, ScopeInventory, SyncPlan2 AgentId, CatalogItem, CatalogItemType, ConflictKind, ItemStatus, PlanAction, Preset, ScopeConfig, ScopeInventory, SyncPlan
3-} from '@skilly/core'3+} from '@panoplie/core'
4 import { ITEM_STATE_META, ITEM_STATE_SHORT, type Tone } from './status'4 import { ITEM_STATE_META, ITEM_STATE_SHORT, type Tone } from './status'
5 import { resolveItems } from './resolve'5 import { resolveItems } from './resolve'
6 import { itemDisplayName } from './inventoryView'6 import { itemDisplayName } from './inventoryView'
7 import { HOME_CONFIG_TYPES } from './scopeConfig'7 import { HOME_CONFIG_TYPES } from './scopeConfig'
8 8
9 /**9 /**
10- * View model of the single "Items" list of a project or of Global: what skilly manages (config entries, removals)10+ * View model of the single "Items" list of a project or of Global: what panoplie manages (config entries, removals)
11 * and what is only on disk (local and plugin items), in one list. One row per logical item (`type/name`; hooks: one11 * and what is only on disk (local and plugin items), in one list. One row per logical item (`type/name`; hooks: one
12 * per inventory entry), with one status per agent. Built from the local config copy (null when the scope is not12 * per inventory entry), with one status per agent. Built from the local config copy (null when the scope is not
13 * managed yet), the sync plan and the inventory. Pure and unit-tested.13 * managed yet), the sync plan and the inventory. Pure and unit-tested.
14 */14 */
15 15
16-/** `manage`: the local item is identical to a catalog item; add it to the config so skilly manages it. */16+/** `manage`: the local item is identical to a catalog item; add it to the config so panoplie manages it. */
17 export type RowAction = 'resolve' | 'review' | 'import' | 'manage'17 export type RowAction = 'resolve' | 'review' | 'import' | 'manage'
18 18
19 export interface AgentRowStatus {19 export interface AgentRowStatus {
@@ -132,7 +132,7 @@ export interface BuildRowsInput {
132 statuses: Record<string, ItemStatus>132 statuses: Record<string, ItemStatus>
133 /** Names of the Home skills / MCP servers, to tell "hide global" actions from item actions. */133 /** Names of the Home skills / MCP servers, to tell "hide global" actions from item actions. */
134 homeNames?: HomeNames134 homeNames?: HomeNames
135- /** Also list the items on disk that skilly does not manage (local and plugin items). */135+ /** Also list the items on disk that panoplie does not manage (local and plugin items). */
136 includeLocal: boolean136 includeLocal: boolean
137 /** Global: its config only takes skills, rules and subagents, so its MCP servers and hooks can't be managed. */137 /** Global: its config only takes skills, rules and subagents, so its MCP servers and hooks can't be managed. */
138 home?: boolean138 home?: boolean
@@ -215,7 +215,7 @@ export function buildRows(input: BuildRowsInput): ScopeRow[] {
215 const groups = new Map<string, ScopeRow>()215 const groups = new Map<string, ScopeRow>()
216 for (const it of inventory?.items ?? []) {216 for (const it of inventory?.items ?? []) {
217 const st = statuses[it.id]217 const st = statuses[it.id]
218- // Written by skilly: already an entry or a removal row when there is a config.218+ // Written by panoplie: already an entry or a removal row when there is a config.
219 if (it.type === 'instructions' || (config && st?.managed)) continue219 if (it.type === 'instructions' || (config && st?.managed)) continue
220 const t = it.type as CatalogItemType220 const t = it.type as CatalogItemType
221 const plugin = it.source.plugin221 const plugin = it.source.plugin
@@ -239,7 +239,7 @@ export function buildRows(input: BuildRowsInput): ScopeRow[] {
239 const row = groups.get(key) ?? {239 const row = groups.get(key) ?? {
240 key, kind: 'local' as const, type: t, name: it.name, label: itemDisplayName(it), itemIds: [], plugin,240 key, kind: 'local' as const, type: t, name: it.name, label: itemDisplayName(it), itemIds: [], plugin,
241 presets: [], added: false, excluded: false, excludedExact: false, inCatalog: !plugin && inCatalog(t, it.name),241 presets: [], added: false, excluded: false, excludedExact: false, inCatalog: !plugin && inCatalog(t, it.name),
242- origin: plugin ? `Plugin · ${plugin}` : 'Local · not managed by skilly',242+ origin: plugin ? `Plugin · ${plugin}` : 'Local · not managed by panoplie',
243 statuses: [], conflicts: [], viaPreset: false, onDemand: { on: false, exact: true }243 statuses: [], conflicts: [], viaPreset: false, onDemand: { on: false, exact: true }
244 }244 }
245 row.itemIds.push(it.id)245 row.itemIds.push(it.id)
modified packages/ui/app/utils/status.ts +3 -3
@@ -1,4 +1,4 @@
1-import type { ConflictKind, GitStatus, ItemStatus, ItemType, PlanActionKind } from '@skilly/core'
1+import type { ConflictKind, GitStatus, ItemStatus, ItemType, PlanActionKind } from '@panoplie/core'
22
33 /**
44 * Status vocabulary shared by every screen (design/BRIEF.md 1.2 "Status colour semantics" and 3, 4.1, 4.2).
@@ -34,7 +34,7 @@ export const ITEM_STATE_META: Record<ItemStatus['state'], StatusMeta> = {
3434 'in-sync': { tone: 'ok', label: 'Up to date', hint: 'Identical to the catalog version.' },
3535 'catalog-updated': { tone: 'info', label: 'Update available', hint: 'The catalog has a newer version.' },
3636 'modified': { tone: 'accent', label: 'Drift', hint: 'Edited in this scope since the last sync.' },
37- 'differs-from-catalog': { tone: 'accent', label: 'Differs from catalog', hint: 'Same name as a catalog item, different content, not managed by skilly.' },
37+ 'differs-from-catalog': { tone: 'accent', label: 'Differs from catalog', hint: 'Same name as a catalog item, different content, not managed by panoplie.' },
3838 'not-in-catalog': { tone: 'faint', label: 'Local only', hint: 'Local item, absent from the catalog.' }
3939 }
4040
@@ -75,7 +75,7 @@ export function planActionMeta(kind: PlanActionKind, opts: { catalogUpdated?: bo
7575 /** Headline per conflict kind (brief 4.2). */
7676 export const CONFLICT_HEADLINE: Record<ConflictKind, string> = {
7777 'modified-locally': 'Edited in this project since the last sync',
78- 'exists-unmanaged': 'A file already exists here and is not managed by skilly',
78+ 'exists-unmanaged': 'A file already exists here and is not managed by panoplie',
7979 'missing-in-catalog': 'No longer in the catalog'
8080 }
8181
@@ -1,4 +1,4 @@
1-import type { ConflictKind, GitStatus, ItemStatus, ItemType, PlanActionKind } from '@skilly/core'1+import type { ConflictKind, GitStatus, ItemStatus, ItemType, PlanActionKind } from '@panoplie/core'
2 2
3 /**3 /**
4 * Status vocabulary shared by every screen (design/BRIEF.md 1.2 "Status colour semantics" and 3, 4.1, 4.2).4 * Status vocabulary shared by every screen (design/BRIEF.md 1.2 "Status colour semantics" and 3, 4.1, 4.2).
@@ -34,7 +34,7 @@ export const ITEM_STATE_META: Record<ItemStatus['state'], StatusMeta> = {
34 'in-sync': { tone: 'ok', label: 'Up to date', hint: 'Identical to the catalog version.' },34 'in-sync': { tone: 'ok', label: 'Up to date', hint: 'Identical to the catalog version.' },
35 'catalog-updated': { tone: 'info', label: 'Update available', hint: 'The catalog has a newer version.' },35 'catalog-updated': { tone: 'info', label: 'Update available', hint: 'The catalog has a newer version.' },
36 'modified': { tone: 'accent', label: 'Drift', hint: 'Edited in this scope since the last sync.' },36 'modified': { tone: 'accent', label: 'Drift', hint: 'Edited in this scope since the last sync.' },
37- 'differs-from-catalog': { tone: 'accent', label: 'Differs from catalog', hint: 'Same name as a catalog item, different content, not managed by skilly.' },37+ 'differs-from-catalog': { tone: 'accent', label: 'Differs from catalog', hint: 'Same name as a catalog item, different content, not managed by panoplie.' },
38 'not-in-catalog': { tone: 'faint', label: 'Local only', hint: 'Local item, absent from the catalog.' }38 'not-in-catalog': { tone: 'faint', label: 'Local only', hint: 'Local item, absent from the catalog.' }
39 }39 }
40 40
@@ -75,7 +75,7 @@ export function planActionMeta(kind: PlanActionKind, opts: { catalogUpdated?: bo
75 /** Headline per conflict kind (brief 4.2). */75 /** Headline per conflict kind (brief 4.2). */
76 export const CONFLICT_HEADLINE: Record<ConflictKind, string> = {76 export const CONFLICT_HEADLINE: Record<ConflictKind, string> = {
77 'modified-locally': 'Edited in this project since the last sync',77 'modified-locally': 'Edited in this project since the last sync',
78- 'exists-unmanaged': 'A file already exists here and is not managed by skilly',78+ 'exists-unmanaged': 'A file already exists here and is not managed by panoplie',
79 'missing-in-catalog': 'No longer in the catalog'79 'missing-in-catalog': 'No longer in the catalog'
80 }80 }
81 81
modified packages/ui/app/utils/token.ts +1 -1
@@ -1,4 +1,4 @@
1-const STORAGE_KEY = 'skilly-token'
1+const STORAGE_KEY = 'panoplie-token'
22
33 /** Reads the session token from sessionStorage, falling back to `dev` in development. */
44 export function getToken(): string {
@@ -1,4 +1,4 @@
1-const STORAGE_KEY = 'skilly-token'1+const STORAGE_KEY = 'panoplie-token'
2 2
3 /** Reads the session token from sessionStorage, falling back to `dev` in development. */3 /** Reads the session token from sessionStorage, falling back to `dev` in development. */
4 export function getToken(): string {4 export function getToken(): string {
modified packages/ui/nuxt.config.ts +1 -1
@@ -6,7 +6,7 @@ export default defineNuxtConfig({
66 devtools: { enabled: false },
77 app: {
88 head: {
9- title: 'skilly',
9+ title: 'panoplie',
1010 link: [{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }]
1111 }
1212 },
@@ -6,7 +6,7 @@ export default defineNuxtConfig({
6 devtools: { enabled: false },6 devtools: { enabled: false },
7 app: {7 app: {
8 head: {8 head: {
9- title: 'skilly',9+ title: 'panoplie',
10 link: [{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }]10 link: [{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }]
11 }11 }
12 },12 },
modified packages/ui/package.json +2 -2
@@ -1,5 +1,5 @@
11 {
2- "name": "@skilly/ui",
2+ "name": "@panoplie/ui",
33 "version": "0.0.0",
44 "private": true,
55 "license": "MIT",
@@ -15,7 +15,7 @@
1515 "@iconify-json/lucide": "^1.2.0",
1616 "@iconify-json/simple-icons": "^1.2.100",
1717 "@nuxt/ui": "^4.12.0",
18- "@skilly/core": "workspace:*",
18+ "@panoplie/core": "workspace:*",
1919 "marked": "^18.1.1",
2020 "nuxt": "^4.6.0",
2121 "tailwindcss": "^4.1.0",
@@ -1,5 +1,5 @@
1 {1 {
2- "name": "@skilly/ui",2+ "name": "@panoplie/ui",
3 "version": "0.0.0",3 "version": "0.0.0",
4 "private": true,4 "private": true,
5 "license": "MIT",5 "license": "MIT",
@@ -15,7 +15,7 @@
15 "@iconify-json/lucide": "^1.2.0",15 "@iconify-json/lucide": "^1.2.0",
16 "@iconify-json/simple-icons": "^1.2.100",16 "@iconify-json/simple-icons": "^1.2.100",
17 "@nuxt/ui": "^4.12.0",17 "@nuxt/ui": "^4.12.0",
18- "@skilly/core": "workspace:*",18+ "@panoplie/core": "workspace:*",
19 "marked": "^18.1.1",19 "marked": "^18.1.1",
20 "nuxt": "^4.6.0",20 "nuxt": "^4.6.0",
21 "tailwindcss": "^4.1.0",21 "tailwindcss": "^4.1.0",
modified packages/ui/public/favicon.svg +1 -1
@@ -1,5 +1,5 @@
11 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2- <!-- The skilly mark (SkLogo): a 2x2 grid on ink, one clay square -->
2+ <!-- The panoplie mark (SkLogo): a 2x2 grid on ink, one clay square -->
33 <rect width="32" height="32" rx="8" fill="#1c1b19"/>
44 <rect x="6" y="6" width="8.5" height="8.5" rx="1.5" fill="#ffffff"/>
55 <rect x="6" y="17.5" width="8.5" height="8.5" rx="1.5" fill="#e2774c"/>
@@ -1,5 +1,5 @@
1 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">1 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2- <!-- The skilly mark (SkLogo): a 2x2 grid on ink, one clay square -->2+ <!-- The panoplie mark (SkLogo): a 2x2 grid on ink, one clay square -->
3 <rect width="32" height="32" rx="8" fill="#1c1b19"/>3 <rect width="32" height="32" rx="8" fill="#1c1b19"/>
4 <rect x="6" y="6" width="8.5" height="8.5" rx="1.5" fill="#ffffff"/>4 <rect x="6" y="6" width="8.5" height="8.5" rx="1.5" fill="#ffffff"/>
5 <rect x="6" y="17.5" width="8.5" height="8.5" rx="1.5" fill="#e2774c"/>5 <rect x="6" y="17.5" width="8.5" height="8.5" rx="1.5" fill="#e2774c"/>
modified packages/ui/test/dashboard.test.ts +1 -1
@@ -1,5 +1,5 @@
11 import { describe, expect, it } from 'vitest'
2-import type { GitStatus } from '@skilly/core'
2+import type { GitStatus } from '@panoplie/core'
33 import { dashboardTodos, startSteps, type ScopeHealth } from '../app/utils/dashboard'
44
55 const git = (over: Partial<GitStatus> = {}): GitStatus => ({
@@ -1,5 +1,5 @@
1 import { describe, expect, it } from 'vitest'1 import { describe, expect, it } from 'vitest'
2-import type { GitStatus } from '@skilly/core'2+import type { GitStatus } from '@panoplie/core'
3 import { dashboardTodos, startSteps, type ScopeHealth } from '../app/utils/dashboard'3 import { dashboardTodos, startSteps, type ScopeHealth } from '../app/utils/dashboard'
4 4
5 const git = (over: Partial<GitStatus> = {}): GitStatus => ({5 const git = (over: Partial<GitStatus> = {}): GitStatus => ({
modified packages/ui/test/scope-views.test.ts +2 -2
@@ -1,5 +1,5 @@
11 import { describe, expect, it } from 'vitest'
2-import type { InventoryItem, ScopeConfig, SyncPlan } from '@skilly/core'
2+import type { InventoryItem, ScopeConfig, SyncPlan } from '@panoplie/core'
33 import { buildRows, matchIn, mergeStatuses } from '../app/utils/scopeRows'
44 import { disableInConfig, manageInConfig, undoInConfig, undoKind } from '../app/utils/scopeConfig'
55 import { projectSpecific, splitManagedBlock, homeDirOf } from '../app/utils/inventoryView'
@@ -114,7 +114,7 @@ describe('inventory view helpers', () => {
114114 expect(homeDirOf(item, '/h')).toBe('~/.claude')
115115 })
116116 it('splits the managed AGENTS.md block', () => {
117- const segs = splitManagedBlock('a\n<!-- skilly:begin (x) -->\nr\n<!-- skilly:end -->\nb')
117+ const segs = splitManagedBlock('a\n<!-- panoplie:begin (x) -->\nr\n<!-- panoplie:end -->\nb')
118118 expect(segs.map(s => s.managed)).toEqual([false, true, false])
119119 })
120120 })
@@ -1,5 +1,5 @@
1 import { describe, expect, it } from 'vitest'1 import { describe, expect, it } from 'vitest'
2-import type { InventoryItem, ScopeConfig, SyncPlan } from '@skilly/core'2+import type { InventoryItem, ScopeConfig, SyncPlan } from '@panoplie/core'
3 import { buildRows, matchIn, mergeStatuses } from '../app/utils/scopeRows'3 import { buildRows, matchIn, mergeStatuses } from '../app/utils/scopeRows'
4 import { disableInConfig, manageInConfig, undoInConfig, undoKind } from '../app/utils/scopeConfig'4 import { disableInConfig, manageInConfig, undoInConfig, undoKind } from '../app/utils/scopeConfig'
5 import { projectSpecific, splitManagedBlock, homeDirOf } from '../app/utils/inventoryView'5 import { projectSpecific, splitManagedBlock, homeDirOf } from '../app/utils/inventoryView'
@@ -114,7 +114,7 @@ describe('inventory view helpers', () => {
114 expect(homeDirOf(item, '/h')).toBe('~/.claude')114 expect(homeDirOf(item, '/h')).toBe('~/.claude')
115 })115 })
116 it('splits the managed AGENTS.md block', () => {116 it('splits the managed AGENTS.md block', () => {
117- const segs = splitManagedBlock('a\n<!-- skilly:begin (x) -->\nr\n<!-- skilly:end -->\nb')117+ const segs = splitManagedBlock('a\n<!-- panoplie:begin (x) -->\nr\n<!-- panoplie:end -->\nb')
118 expect(segs.map(s => s.managed)).toEqual([false, true, false])118 expect(segs.map(s => s.managed)).toEqual([false, true, false])
119 })119 })
120 })120 })
modified pnpm-lock.yaml +4 -4
@@ -23,10 +23,10 @@ importers:
2323
2424 packages/cli:
2525 dependencies:
26- '@skilly/core':
26+ '@panoplie/core':
2727 specifier: workspace:*
2828 version: link:../core
29- '@skilly/server':
29+ '@panoplie/server':
3030 specifier: workspace:*
3131 version: link:../server
3232
@@ -47,7 +47,7 @@ importers:
4747 '@hono/node-server':
4848 specifier: ^2.1.0
4949 version: 2.1.5(hono@4.13.13)
50- '@skilly/core':
50+ '@panoplie/core':
5151 specifier: workspace:*
5252 version: link:../core
5353 hono:
@@ -71,7 +71,7 @@ importers:
7171 '@nuxt/ui':
7272 specifier: ^4.12.0
7373 version: 4.12.0(5b14c4ff6cb0908ddbfab51a24207b59)
74- '@skilly/core':
74+ '@panoplie/core':
7575 specifier: workspace:*
7676 version: link:../core
7777 marked:
@@ -23,10 +23,10 @@ importers:
23 23
24 packages/cli:24 packages/cli:
25 dependencies:25 dependencies:
26- '@skilly/core':26+ '@panoplie/core':
27 specifier: workspace:*27 specifier: workspace:*
28 version: link:../core28 version: link:../core
29- '@skilly/server':29+ '@panoplie/server':
30 specifier: workspace:*30 specifier: workspace:*
31 version: link:../server31 version: link:../server
32 32
@@ -47,7 +47,7 @@ importers:
47 '@hono/node-server':47 '@hono/node-server':
48 specifier: ^2.1.048 specifier: ^2.1.0
49 version: 2.1.5(hono@4.13.13)49 version: 2.1.5(hono@4.13.13)
50- '@skilly/core':50+ '@panoplie/core':
51 specifier: workspace:*51 specifier: workspace:*
52 version: link:../core52 version: link:../core
53 hono:53 hono:
@@ -71,7 +71,7 @@ importers:
71 '@nuxt/ui':71 '@nuxt/ui':
72 specifier: ^4.12.072 specifier: ^4.12.0
73 version: 4.12.0(5b14c4ff6cb0908ddbfab51a24207b59)73 version: 4.12.0(5b14c4ff6cb0908ddbfab51a24207b59)
74- '@skilly/core':74+ '@panoplie/core':
75 specifier: workspace:*75 specifier: workspace:*
76 version: link:../core76 version: link:../core
77 marked:77 marked:
modified site/public/index.html +18 -18
@@ -3,18 +3,18 @@
33 <head>
44 <meta charset="utf-8">
55 <meta name="viewport" content="width=device-width, initial-scale=1">
6- <title>skilly: stop copying agent skills between projects</title>
6+ <title>panoplie: stop copying agent skills between projects</title>
77 <meta name="description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs. Open source.">
8- <link rel="canonical" href="https://skilly.eventuallycoding.com/">
8+ <link rel="canonical" href="https://panoplie.eventuallycoding.com/">
99 <meta name="color-scheme" content="light dark">
1010 <meta name="theme-color" content="#f5f4f1" media="(prefers-color-scheme: light)">
1111 <meta name="theme-color" content="#121110" media="(prefers-color-scheme: dark)">
1212
1313 <meta property="og:type" content="website">
14- <meta property="og:url" content="https://skilly.eventuallycoding.com/">
15- <meta property="og:title" content="skilly: stop copying agent skills between projects">
14+ <meta property="og:url" content="https://panoplie.eventuallycoding.com/">
15+ <meta property="og:title" content="panoplie: stop copying agent skills between projects">
1616 <meta property="og:description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs.">
17- <meta property="og:image" content="https://skilly.eventuallycoding.com/screenshots/project.png">
17+ <meta property="og:image" content="https://panoplie.eventuallycoding.com/screenshots/project.png">
1818 <meta name="twitter:card" content="summary_large_image">
1919
2020 <link rel="icon" type="image/svg+xml" href="favicon.svg">
@@ -28,16 +28,16 @@
2828 <a class="skip" href="#main">Skip to content</a>
2929
3030 <header class="top">
31- <a class="brand" href="./" aria-label="skilly, home">
31+ <a class="brand" href="./" aria-label="panoplie, home">
3232 <span class="mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
33- <span>skilly</span>
33+ <span>panoplie</span>
3434 </a>
3535 <nav class="top-nav" aria-label="Sections">
3636 <a href="#tour">Tour</a>
3737 <a href="#how">How it works</a>
3838 <a href="#try">Try it</a>
3939 </nav>
40- <a class="btn btn-ink btn-sm" href="https://rickub.com/hlassiege/skilly"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a>
40+ <a class="btn btn-ink btn-sm" href="https://rickub.com/hlassiege/panoplie"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a>
4141 </header>
4242
4343 <main id="main">
@@ -47,7 +47,7 @@
4747 <p class="lead">One catalog for your skills, rules and MCP servers.</p>
4848 <div class="actions">
4949 <a class="btn btn-ink" href="#try">Try it</a>
50- <a class="btn btn-line" href="https://rickub.com/hlassiege/skilly"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a>
50+ <a class="btn btn-line" href="https://rickub.com/hlassiege/panoplie"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a>
5151 </div>
5252 <p class="perk">
5353 <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>
@@ -186,15 +186,15 @@
186186 </section>
187187
188188 <section class="tour" id="tour" aria-labelledby="tour-title">
189- <h2 class="sr-only" id="tour-title">A tour of skilly</h2>
189+ <h2 class="sr-only" id="tour-title">A tour of panoplie</h2>
190190 <div class="tour-tabs" role="tablist" aria-label="Screens" aria-orientation="vertical">
191191 <button role="tab" id="tab-project" aria-controls="shot-project" aria-selected="true">
192192 <span class="tab-title">Pick what each project uses</span>
193- <span class="tab-desc">Add a preset or a single skill. skilly writes the files.</span>
193+ <span class="tab-desc">Add a preset or a single skill. panoplie writes the files.</span>
194194 </button>
195195 <button role="tab" id="tab-review" aria-controls="shot-review" aria-selected="false" tabindex="-1">
196196 <span class="tab-title">See every change first</span>
197- <span class="tab-desc">Nothing is written until you apply. Edited a file by hand? skilly asks first.</span>
197+ <span class="tab-desc">Nothing is written until you apply. Edited a file by hand? panoplie asks first.</span>
198198 </button>
199199 <button role="tab" id="tab-global" aria-controls="shot-global" aria-selected="false" tabindex="-1">
200200 <span class="tab-title">Slim down your global config</span>
@@ -214,7 +214,7 @@
214214 </button>
215215 </div>
216216 <div class="tour-shots">
217- <div class="frame" id="shot-project" role="tabpanel" aria-labelledby="tab-project"><img src="screenshots/project.png" width="2880" height="1800" alt="A project in skilly with its presets and items" fetchpriority="high"></div>
217+ <div class="frame" id="shot-project" role="tabpanel" aria-labelledby="tab-project"><img src="screenshots/project.png" width="2880" height="1800" alt="A project in panoplie with its presets and items" fetchpriority="high"></div>
218218 <div class="frame" id="shot-review" role="tabpanel" aria-labelledby="tab-review"><img src="screenshots/review.png" width="2880" height="1800" alt="The review screen listing the files that will change" loading="lazy"></div>
219219 <div class="frame" id="shot-global" role="tabpanel" aria-labelledby="tab-global"><img src="screenshots/global.png" width="2880" height="1800" alt="Global items with suggestions to move them to projects" loading="lazy"></div>
220220 <div class="frame" id="shot-catalog" role="tabpanel" aria-labelledby="tab-catalog"><img src="screenshots/catalog.png" width="2880" height="1800" alt="The catalog with a skill opened in the editor" loading="lazy"></div>
@@ -243,7 +243,7 @@
243243
244244 <section class="how" id="how" aria-labelledby="how-title">
245245 <h2 id="how-title">One catalog, plain files in each project</h2>
246- <p class="section-lead">Your projects keep working without skilly.</p>
246+ <p class="section-lead">Your projects keep working without panoplie.</p>
247247
248248 <div class="flow" data-flow>
249249 <div class="panel cat">
@@ -311,18 +311,18 @@
311311 <section class="try" id="try" aria-labelledby="try-title">
312312 <h2 id="try-title">Try it</h2>
313313 <div class="code">
314- <pre><code id="install-cmd">git clone https://rickub.com/hlassiege/skilly.git
315-cd skilly
314+ <pre><code id="install-cmd">git clone https://rickub.com/hlassiege/panoplie.git
315+cd panoplie
316316 pnpm install &amp;&amp; pnpm build &amp;&amp; pnpm start</code></pre>
317317 <button class="btn btn-line btn-sm copy" type="button" data-copy="install-cmd">Copy</button>
318318 </div>
319- <p class="note"><a href="https://rickub.com/hlassiege/skilly">Read the README</a> for everything else.</p>
319+ <p class="note"><a href="https://rickub.com/hlassiege/panoplie">Read the README</a> for everything else.</p>
320320 </section>
321321 </main>
322322
323323 <footer class="foot">
324324 <p>Open source, MIT. Made by <a href="https://eventuallycoding.com">Hugo Lassiège</a>.</p>
325- <a href="https://rickub.com/hlassiege/skilly">Source code</a>
325+ <a href="https://rickub.com/hlassiege/panoplie">Source code</a>
326326 </footer>
327327 </body>
328328 </html>
@@ -3,18 +3,18 @@
3 <head>3 <head>
4 <meta charset="utf-8">4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6- <title>skilly: stop copying agent skills between projects</title>6+ <title>panoplie: stop copying agent skills between projects</title>
7 <meta name="description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs. Open source.">7 <meta name="description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs. Open source.">
8- <link rel="canonical" href="https://skilly.eventuallycoding.com/">8+ <link rel="canonical" href="https://panoplie.eventuallycoding.com/">
9 <meta name="color-scheme" content="light dark">9 <meta name="color-scheme" content="light dark">
10 <meta name="theme-color" content="#f5f4f1" media="(prefers-color-scheme: light)">10 <meta name="theme-color" content="#f5f4f1" media="(prefers-color-scheme: light)">
11 <meta name="theme-color" content="#121110" media="(prefers-color-scheme: dark)">11 <meta name="theme-color" content="#121110" media="(prefers-color-scheme: dark)">
12 12
13 <meta property="og:type" content="website">13 <meta property="og:type" content="website">
14- <meta property="og:url" content="https://skilly.eventuallycoding.com/">14+ <meta property="og:url" content="https://panoplie.eventuallycoding.com/">
15- <meta property="og:title" content="skilly: stop copying agent skills between projects">15+ <meta property="og:title" content="panoplie: stop copying agent skills between projects">
16 <meta property="og:description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs.">16 <meta property="og:description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs.">
17- <meta property="og:image" content="https://skilly.eventuallycoding.com/screenshots/project.png">17+ <meta property="og:image" content="https://panoplie.eventuallycoding.com/screenshots/project.png">
18 <meta name="twitter:card" content="summary_large_image">18 <meta name="twitter:card" content="summary_large_image">
19 19
20 <link rel="icon" type="image/svg+xml" href="favicon.svg">20 <link rel="icon" type="image/svg+xml" href="favicon.svg">
@@ -28,16 +28,16 @@
28 <a class="skip" href="#main">Skip to content</a>28 <a class="skip" href="#main">Skip to content</a>
29 29
30 <header class="top">30 <header class="top">
31- <a class="brand" href="./" aria-label="skilly, home">31+ <a class="brand" href="./" aria-label="panoplie, home">
32 <span class="mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>32 <span class="mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
33- <span>skilly</span>33+ <span>panoplie</span>
34 </a>34 </a>
35 <nav class="top-nav" aria-label="Sections">35 <nav class="top-nav" aria-label="Sections">
36 <a href="#tour">Tour</a>36 <a href="#tour">Tour</a>
37 <a href="#how">How it works</a>37 <a href="#how">How it works</a>
38 <a href="#try">Try it</a>38 <a href="#try">Try it</a>
39 </nav>39 </nav>
40- <a class="btn btn-ink btn-sm" href="https://rickub.com/hlassiege/skilly"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a>40+ <a class="btn btn-ink btn-sm" href="https://rickub.com/hlassiege/panoplie"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a>
41 </header>41 </header>
42 42
43 <main id="main">43 <main id="main">
@@ -47,7 +47,7 @@
47 <p class="lead">One catalog for your skills, rules and MCP servers.</p>47 <p class="lead">One catalog for your skills, rules and MCP servers.</p>
48 <div class="actions">48 <div class="actions">
49 <a class="btn btn-ink" href="#try">Try it</a>49 <a class="btn btn-ink" href="#try">Try it</a>
50- <a class="btn btn-line" href="https://rickub.com/hlassiege/skilly"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a>50+ <a class="btn btn-line" href="https://rickub.com/hlassiege/panoplie"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a>
51 </div>51 </div>
52 <p class="perk">52 <p class="perk">
53 <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>53 <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>
@@ -186,15 +186,15 @@
186 </section>186 </section>
187 187
188 <section class="tour" id="tour" aria-labelledby="tour-title">188 <section class="tour" id="tour" aria-labelledby="tour-title">
189- <h2 class="sr-only" id="tour-title">A tour of skilly</h2>189+ <h2 class="sr-only" id="tour-title">A tour of panoplie</h2>
190 <div class="tour-tabs" role="tablist" aria-label="Screens" aria-orientation="vertical">190 <div class="tour-tabs" role="tablist" aria-label="Screens" aria-orientation="vertical">
191 <button role="tab" id="tab-project" aria-controls="shot-project" aria-selected="true">191 <button role="tab" id="tab-project" aria-controls="shot-project" aria-selected="true">
192 <span class="tab-title">Pick what each project uses</span>192 <span class="tab-title">Pick what each project uses</span>
193- <span class="tab-desc">Add a preset or a single skill. skilly writes the files.</span>193+ <span class="tab-desc">Add a preset or a single skill. panoplie writes the files.</span>
194 </button>194 </button>
195 <button role="tab" id="tab-review" aria-controls="shot-review" aria-selected="false" tabindex="-1">195 <button role="tab" id="tab-review" aria-controls="shot-review" aria-selected="false" tabindex="-1">
196 <span class="tab-title">See every change first</span>196 <span class="tab-title">See every change first</span>
197- <span class="tab-desc">Nothing is written until you apply. Edited a file by hand? skilly asks first.</span>197+ <span class="tab-desc">Nothing is written until you apply. Edited a file by hand? panoplie asks first.</span>
198 </button>198 </button>
199 <button role="tab" id="tab-global" aria-controls="shot-global" aria-selected="false" tabindex="-1">199 <button role="tab" id="tab-global" aria-controls="shot-global" aria-selected="false" tabindex="-1">
200 <span class="tab-title">Slim down your global config</span>200 <span class="tab-title">Slim down your global config</span>
@@ -214,7 +214,7 @@
214 </button>214 </button>
215 </div>215 </div>
216 <div class="tour-shots">216 <div class="tour-shots">
217- <div class="frame" id="shot-project" role="tabpanel" aria-labelledby="tab-project"><img src="screenshots/project.png" width="2880" height="1800" alt="A project in skilly with its presets and items" fetchpriority="high"></div>217+ <div class="frame" id="shot-project" role="tabpanel" aria-labelledby="tab-project"><img src="screenshots/project.png" width="2880" height="1800" alt="A project in panoplie with its presets and items" fetchpriority="high"></div>
218 <div class="frame" id="shot-review" role="tabpanel" aria-labelledby="tab-review"><img src="screenshots/review.png" width="2880" height="1800" alt="The review screen listing the files that will change" loading="lazy"></div>218 <div class="frame" id="shot-review" role="tabpanel" aria-labelledby="tab-review"><img src="screenshots/review.png" width="2880" height="1800" alt="The review screen listing the files that will change" loading="lazy"></div>
219 <div class="frame" id="shot-global" role="tabpanel" aria-labelledby="tab-global"><img src="screenshots/global.png" width="2880" height="1800" alt="Global items with suggestions to move them to projects" loading="lazy"></div>219 <div class="frame" id="shot-global" role="tabpanel" aria-labelledby="tab-global"><img src="screenshots/global.png" width="2880" height="1800" alt="Global items with suggestions to move them to projects" loading="lazy"></div>
220 <div class="frame" id="shot-catalog" role="tabpanel" aria-labelledby="tab-catalog"><img src="screenshots/catalog.png" width="2880" height="1800" alt="The catalog with a skill opened in the editor" loading="lazy"></div>220 <div class="frame" id="shot-catalog" role="tabpanel" aria-labelledby="tab-catalog"><img src="screenshots/catalog.png" width="2880" height="1800" alt="The catalog with a skill opened in the editor" loading="lazy"></div>
@@ -243,7 +243,7 @@
243 243
244 <section class="how" id="how" aria-labelledby="how-title">244 <section class="how" id="how" aria-labelledby="how-title">
245 <h2 id="how-title">One catalog, plain files in each project</h2>245 <h2 id="how-title">One catalog, plain files in each project</h2>
246- <p class="section-lead">Your projects keep working without skilly.</p>246+ <p class="section-lead">Your projects keep working without panoplie.</p>
247 247
248 <div class="flow" data-flow>248 <div class="flow" data-flow>
249 <div class="panel cat">249 <div class="panel cat">
@@ -311,18 +311,18 @@
311 <section class="try" id="try" aria-labelledby="try-title">311 <section class="try" id="try" aria-labelledby="try-title">
312 <h2 id="try-title">Try it</h2>312 <h2 id="try-title">Try it</h2>
313 <div class="code">313 <div class="code">
314- <pre><code id="install-cmd">git clone https://rickub.com/hlassiege/skilly.git314+ <pre><code id="install-cmd">git clone https://rickub.com/hlassiege/panoplie.git
315-cd skilly315+cd panoplie
316 pnpm install &amp;&amp; pnpm build &amp;&amp; pnpm start</code></pre>316 pnpm install &amp;&amp; pnpm build &amp;&amp; pnpm start</code></pre>
317 <button class="btn btn-line btn-sm copy" type="button" data-copy="install-cmd">Copy</button>317 <button class="btn btn-line btn-sm copy" type="button" data-copy="install-cmd">Copy</button>
318 </div>318 </div>
319- <p class="note"><a href="https://rickub.com/hlassiege/skilly">Read the README</a> for everything else.</p>319+ <p class="note"><a href="https://rickub.com/hlassiege/panoplie">Read the README</a> for everything else.</p>
320 </section>320 </section>
321 </main>321 </main>
322 322
323 <footer class="foot">323 <footer class="foot">
324 <p>Open source, MIT. Made by <a href="https://eventuallycoding.com">Hugo Lassiège</a>.</p>324 <p>Open source, MIT. Made by <a href="https://eventuallycoding.com">Hugo Lassiège</a>.</p>
325- <a href="https://rickub.com/hlassiege/skilly">Source code</a>325+ <a href="https://rickub.com/hlassiege/panoplie">Source code</a>
326 </footer>326 </footer>
327 </body>327 </body>
328 </html>328 </html>
modified site/public/styles.css +1 -1
@@ -1,4 +1,4 @@
1-/* skilly site. Tokens mirror the app (packages/ui/app/assets/css/main.css) so screenshots sit at home. */
1+/* panoplie site. Tokens mirror the app (packages/ui/app/assets/css/main.css) so screenshots sit at home. */
22
33 @font-face { font-family: 'Instrument Sans'; font-weight: 400; font-display: swap; src: url(fonts/instrument-sans-latin-400-normal.woff2) format('woff2'); }
44 @font-face { font-family: 'Instrument Sans'; font-weight: 500; font-display: swap; src: url(fonts/instrument-sans-latin-500-normal.woff2) format('woff2'); }
@@ -1,4 +1,4 @@
1-/* skilly site. Tokens mirror the app (packages/ui/app/assets/css/main.css) so screenshots sit at home. */1+/* panoplie site. Tokens mirror the app (packages/ui/app/assets/css/main.css) so screenshots sit at home. */
2 2
3 @font-face { font-family: 'Instrument Sans'; font-weight: 400; font-display: swap; src: url(fonts/instrument-sans-latin-400-normal.woff2) format('woff2'); }3 @font-face { font-family: 'Instrument Sans'; font-weight: 400; font-display: swap; src: url(fonts/instrument-sans-latin-400-normal.woff2) format('woff2'); }
4 @font-face { font-family: 'Instrument Sans'; font-weight: 500; font-display: swap; src: url(fonts/instrument-sans-latin-500-normal.woff2) format('woff2'); }4 @font-face { font-family: 'Instrument Sans'; font-weight: 500; font-display: swap; src: url(fonts/instrument-sans-latin-500-normal.woff2) format('woff2'); }