updose 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,7 @@ npx updose <command>
31
31
  - [`skills.json` Reference](#skillsjson-reference)
32
32
  - [Example: Single-Target Boilerplate (Claude)](#example-single-target-boilerplate-claude)
33
33
  - [Example: Multi-Target Boilerplate](#example-multi-target-boilerplate)
34
+ - [Monorepo Support](#monorepo-support)
34
35
  - [Publishing](#publishing)
35
36
  - [License](#license)
36
37
 
@@ -43,6 +44,8 @@ npx updose search react
43
44
  # Install a boilerplate from a GitHub repository
44
45
  npx updose add owner/repo-name
45
46
 
47
+ # Install from a subdirectory within a monorepo
48
+ npx updose add owner/repo-name/nextjs
46
49
  ```
47
50
 
48
51
  ## Commands
@@ -51,21 +54,29 @@ npx updose add owner/repo-name
51
54
 
52
55
  Install a boilerplate from a GitHub repository into your project.
53
56
 
57
+ The `<repo>` argument accepts two formats:
58
+
59
+ - **`owner/repo`** — installs from the repository root (standard boilerplate)
60
+ - **`owner/repo/dir`** — installs from a subdirectory within the repository (monorepo boilerplate). The `dir` can be nested (e.g., `owner/repo/templates/v2`).
61
+
54
62
  ```bash
55
- npx updose add owner/repo-name
56
- npx updose add owner/repo-name -y # Skip all prompts (install all targets, append main docs, overwrite others)
57
- npx updose add owner/repo-name --dry-run # Preview what would be installed without writing any files
63
+ npx updose add owner/repo-name # Install from repository root
64
+ npx updose add owner/repo-name/nextjs # Install from "nextjs" subdirectory
65
+ npx updose add owner/repo-name/templates/v2 # Install from nested subdirectory
66
+ npx updose add owner/repo-name -y # Skip all prompts
67
+ npx updose add owner/repo-name/nextjs --dry-run # Preview monorepo install
58
68
  ```
59
69
 
60
70
  **What happens when you run `add`:**
61
71
 
62
- 1. Fetches the boilerplate's `updose.json` manifest from the GitHub repository
63
- 2. If the boilerplate supports multiple targets (e.g., both Claude and Gemini), prompts you to choose which targets to install. With `-y`, all targets are installed automatically.
64
- 3. Downloads the file tree from the repository and filters files for the selected target(s)
65
- 4. Installs each file into your project. If a file already exists, the prompt depends on the file type:
72
+ 1. Parses the `<repo>` argument to determine the repository (`owner/repo`) and optional subdirectory
73
+ 2. Fetches the boilerplate's `updose.json` manifest from the repository root or the specified subdirectory
74
+ 3. If the boilerplate supports multiple targets (e.g., both Claude and Gemini), prompts you to choose which targets to install. With `-y`, all targets are installed automatically.
75
+ 4. Downloads the file tree from the repository and filters files for the selected target(s). When a subdirectory is specified, only files under that subdirectory are considered.
76
+ 5. Installs each file into your project. If a file already exists, the prompt depends on the file type:
66
77
  - **Main docs** (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`): **Append** / **Overwrite** / **Skip**
67
78
  - **Other files** (rules, commands, agents, etc.): **Overwrite** / **Skip**
68
- 5. If a `skills.json` file exists in the boilerplate, installs each declared skill via [skills.sh](https://skills.sh/). Skills are installed for the selected targets (`-a`), copied into the project (`--copy`), and auto-confirmed (`-y`)
79
+ 6. If a `skills.json` file exists in the boilerplate (or its subdirectory), installs each declared skill via [skills.sh](https://skills.sh/). Skills are installed for the selected targets (`-a`), copied into the project (`--copy`), and auto-confirmed (`-y`)
69
80
 
70
81
  | Option | Description |
71
82
  |-------- |------------- |
@@ -138,6 +149,17 @@ mkdir my-boilerplate && cd my-boilerplate
138
149
  npx updose init
139
150
  ```
140
151
 
152
+ To scaffold inside a subdirectory (for monorepo setups), use the `--dir` option:
153
+
154
+ ```bash
155
+ npx updose init --dir nextjs # Creates boilerplate in the "nextjs" subdirectory
156
+ npx updose init --dir templates/v2 # Nested subdirectory is also supported
157
+ ```
158
+
159
+ | Option | Description |
160
+ |-------- |------------- |
161
+ | `--dir <dir>` | Create the boilerplate inside the specified subdirectory instead of the repository root. The directory is created if it doesn't exist. |
162
+
141
163
  **What happens when you run `init`:**
142
164
 
143
165
  1. Prompts you for boilerplate configuration (see below)
@@ -145,11 +167,13 @@ npx updose init
145
167
  3. If a file already exists, asks whether to **Overwrite** or **Skip**
146
168
  4. Shows next steps for publishing
147
169
 
170
+ When `--dir` is used, all generated files are placed inside the specified subdirectory instead of the repository root.
171
+
148
172
  **Interactive prompts:**
149
173
 
150
174
  | Prompt | Description | Default |
151
175
  |-------- |------------- |--------- |
152
- | **Name** | Boilerplate name | Current directory name |
176
+ | **Name** | Boilerplate name | Without `--dir`: current directory name (e.g., `my-boilerplate`). With `--dir`: `<repo>/<dir>` (e.g., `my-boilerplate/nextjs`) |
153
177
  | **Description** | Short description (optional) | — |
154
178
  | **Author** | GitHub username | Auto-detected from `git config github.user` or `gh api user`. If neither is available, you enter it manually. |
155
179
  | **Targets** | Which AI tools to support (multiselect) | All selected (`claude`, `codex`, `gemini`) |
@@ -172,7 +196,7 @@ Plus target-specific directories based on your selection:
172
196
  | Codex | `codex/AGENTS.md` |
173
197
  | Gemini | `gemini/GEMINI.md`, `gemini/skills/` |
174
198
 
175
- **Example — scaffolding with all targets selected:**
199
+ **Example — scaffolding with all targets selected (no `--dir`):**
176
200
 
177
201
  ```
178
202
  my-boilerplate/
@@ -191,27 +215,57 @@ my-boilerplate/
191
215
  └── skills/
192
216
  ```
193
217
 
218
+ **Example — scaffolding with `--dir nextjs`:**
219
+
220
+ ```
221
+ my-monorepo/
222
+ ├── nextjs/ ← created by --dir
223
+ │ ├── updose.json
224
+ │ ├── skills.json
225
+ │ ├── README.md
226
+ │ ├── claude/
227
+ │ │ ├── CLAUDE.md
228
+ │ │ ├── rules/
229
+ │ │ ├── agents/
230
+ │ │ └── skills/
231
+ │ ├── codex/
232
+ │ │ └── AGENTS.md
233
+ │ └── gemini/
234
+ │ ├── GEMINI.md
235
+ │ └── skills/
236
+ ├── remix/ ← another boilerplate in the same repo
237
+ │ └── ...
238
+ └── README.md ← repo-level README (not managed by updose)
239
+ ```
240
+
194
241
  After scaffolding, follow the next steps printed by the command:
195
242
 
196
243
  1. Edit your boilerplate files in each target directory
197
244
  2. Push to GitHub
198
- 3. Publish with `npx updose publish`
199
- 4. Others can install with `npx updose add <author>/<name>`
245
+ 3. Publish with `npx updose publish` (or `npx updose publish --dir nextjs` for monorepo)
246
+ 4. Others can install with `npx updose add <author>/<name>` (or `npx updose add <author>/<repo>/nextjs` for monorepo)
200
247
 
201
248
  ### `updose publish`
202
249
 
203
250
  Publish your boilerplate to the marketplace so others can find and install it.
204
251
 
205
252
  ```bash
206
- npx updose publish
253
+ npx updose publish # Publish from repository root
254
+ npx updose publish --dir nextjs # Publish from a subdirectory (monorepo)
207
255
  ```
208
256
 
257
+ | Option | Description |
258
+ |-------- |------------- |
259
+ | `--dir <dir>` | Read `updose.json` from the specified subdirectory instead of the repository root. Use this when publishing a monorepo boilerplate. |
260
+
209
261
  **What happens when you run `publish`:**
210
262
 
211
- 1. Reads and parses `updose.json` from the current directory. If the file is missing, shows an error and suggests running `updose init` first.
263
+ 1. Reads and parses `updose.json` from the current directory (or the subdirectory specified by `--dir`). If the file is missing, shows an error and suggests running `updose init` first.
212
264
  2. Validates the manifest structure (name, author, version, targets are required)
213
265
  3. Detects the GitHub repository by running `git remote get-url origin`. Supports both HTTPS (`https://github.com/owner/repo.git`) and SSH (`git@github.com:owner/repo.git`) formats.
214
- 4. Validates that `author` and `name` in `updose.json` match the repository owner and name (case-insensitive). If they don't match, shows an error and exits.
266
+ 4. Validates that `author` and `name` in `updose.json` match the expected name:
267
+ - Without `--dir`: `name` must match the repository name (e.g., `react-starter`)
268
+ - With `--dir`: `name` must match `<repo>/<dir>` (e.g., `my-monorepo/nextjs`)
215
269
  5. Authenticates via GitHub — if no valid token exists, automatically runs the `login` flow (see below)
216
270
  6. Verifies the repository actually exists on GitHub. If the repo has not been pushed yet, shows an error and exits.
217
271
  7. Displays a publication summary for review:
@@ -225,13 +279,27 @@ Publishing:
225
279
  Tags: react, typescript, web
226
280
  ```
227
281
 
282
+ When `--dir` is used, the summary also includes a `Directory` field:
283
+
284
+ ```
285
+ Publishing:
286
+ Name: my-monorepo/nextjs
287
+ Version: 1.0.0
288
+ Repository: example-user/my-monorepo
289
+ Directory: nextjs
290
+ Targets: claude
291
+ Tags: react, nextjs
292
+ ```
293
+
228
294
  8. Asks for confirmation: **"Publish to registry?"** (defaults to yes). If declined, displays "Publish cancelled." and exits.
229
295
  9. Registers the boilerplate in the marketplace registry
230
- 10. On success, displays: `Users can now install with: npx updose add owner/repo`
296
+ 10. On success, displays the install command:
297
+ - Without `--dir`: `Users can now install with: npx updose add owner/repo`
298
+ - With `--dir`: `Users can now install with: npx updose add owner/repo/dir`
231
299
 
232
300
  **Prerequisites:**
233
301
 
234
- - A valid `updose.json` in the current directory (run `updose init` to create one)
302
+ - A valid `updose.json` in the current directory or specified subdirectory (run `updose init` to create one)
235
303
  - A GitHub remote (`origin`) configured and pushed to GitHub
236
304
  - GitHub authentication (handled automatically if not already logged in)
237
305
 
@@ -388,7 +456,7 @@ The manifest file that describes your boilerplate.
388
456
 
389
457
  | Field | Required | Description |
390
458
  |------- |---------- |------------- |
391
- | `name` | Yes | The boilerplate name. Must match the GitHub repository name. |
459
+ | `name` | Yes | The boilerplate name. Must match the GitHub repository name (e.g., `react-starter`). For monorepo boilerplates, must be `<repo>/<dir>` (e.g., `my-starters/nextjs`). |
392
460
  | `author` | Yes | Author name. Must match the GitHub repository owner. |
393
461
  | `version` | Yes | Version string following [semver](https://semver.org/) (e.g., `1.0.0`). |
394
462
  | `targets` | Yes | Array of supported targets: `"claude"`, `"codex"`, and/or `"gemini"`. |
@@ -497,16 +565,104 @@ boilerplate-multi-target/
497
565
 
498
566
  When a user installs a multi-target boilerplate, they are prompted to choose which targets to install. With `-y`, all targets are installed automatically.
499
567
 
568
+ ### Monorepo Support
569
+
570
+ A single GitHub repository can contain multiple boilerplates, each in its own subdirectory. This is useful when you want to publish several related boilerplates (e.g., framework-specific starters) from one repo.
571
+
572
+ **How it works:**
573
+
574
+ - Each subdirectory is an independent boilerplate with its own `updose.json`, `skills.json`, and target directories
575
+ - The `name` field in `updose.json` must be `<repo>/<dir>` (e.g., `my-starters/nextjs`)
576
+ - Users install with `npx updose add owner/repo/dir` instead of `npx updose add owner/repo`
577
+
578
+ **Monorepo directory structure:**
579
+
580
+ ```
581
+ my-starters/ ← GitHub repository root
582
+ ├── README.md ← repo-level README (not managed by updose)
583
+ ├── nextjs/ ← boilerplate for Next.js
584
+ │ ├── updose.json ← name: "my-starters/nextjs"
585
+ │ ├── skills.json
586
+ │ ├── claude/
587
+ │ │ ├── CLAUDE.md
588
+ │ │ └── rules/
589
+ │ │ └── nextjs-conventions.md
590
+ │ └── gemini/
591
+ │ └── GEMINI.md
592
+ ├── remix/ ← boilerplate for Remix
593
+ │ ├── updose.json ← name: "my-starters/remix"
594
+ │ ├── skills.json
595
+ │ └── claude/
596
+ │ ├── CLAUDE.md
597
+ │ └── rules/
598
+ │ └── remix-conventions.md
599
+ └── sveltekit/ ← boilerplate for SvelteKit
600
+ ├── updose.json ← name: "my-starters/sveltekit"
601
+ ├── skills.json
602
+ └── claude/
603
+ └── CLAUDE.md
604
+ ```
605
+
606
+ **`updose.json` for a monorepo boilerplate (`nextjs/updose.json`):**
607
+
608
+ ```json
609
+ {
610
+ "name": "my-starters/nextjs",
611
+ "author": "example-user",
612
+ "version": "1.0.0",
613
+ "description": "Next.js boilerplate for Claude and Gemini",
614
+ "targets": ["claude", "gemini"],
615
+ "tags": ["nextjs", "react", "web"]
616
+ }
617
+ ```
618
+
619
+ > Note: The `name` field uses the format `<repo>/<dir>` (e.g., `my-starters/nextjs`), not just the directory name.
620
+
621
+ **Workflow for creating a monorepo boilerplate:**
622
+
623
+ ```bash
624
+ # 1. Scaffold each boilerplate in its own subdirectory
625
+ npx updose init --dir nextjs
626
+ npx updose init --dir remix
627
+ npx updose init --dir sveltekit
628
+
629
+ # 2. Edit each boilerplate's files
630
+ # (edit nextjs/claude/CLAUDE.md, remix/claude/CLAUDE.md, etc.)
631
+
632
+ # 3. Push to GitHub
633
+ git add . && git commit -m "Add boilerplates" && git push
634
+
635
+ # 4. Publish each boilerplate separately
636
+ npx updose publish --dir nextjs
637
+ npx updose publish --dir remix
638
+ npx updose publish --dir sveltekit
639
+ ```
640
+
641
+ **Users install each boilerplate independently:**
642
+
643
+ ```bash
644
+ npx updose add example-user/my-starters/nextjs
645
+ npx updose add example-user/my-starters/remix
646
+ ```
647
+
500
648
  ## Publishing
501
649
 
502
650
  To share your boilerplate with others through the marketplace:
503
651
 
504
- 1. Scaffold with `npx updose init`
652
+ 1. Scaffold with `npx updose init` (or `npx updose init --dir <dir>` for monorepo)
505
653
  2. Add your content (rules, commands, agents, skills, etc.)
506
654
  3. Push to a GitHub repository
507
- 4. Run `npx updose publish`
655
+ 4. Run `npx updose publish` (or `npx updose publish --dir <dir>` for monorepo)
656
+
657
+ After publishing, anyone can install your boilerplate:
508
658
 
509
- After publishing, anyone can install your boilerplate with `npx updose add your-username/my-boilerplate`.
659
+ ```bash
660
+ # Standard boilerplate
661
+ npx updose add your-username/my-boilerplate
662
+
663
+ # Monorepo boilerplate
664
+ npx updose add your-username/my-monorepo/nextjs
665
+ ```
510
666
 
511
667
  ## License
512
668
 
package/dist/index.cjs CHANGED
@@ -51,18 +51,18 @@ async function searchBoilerplates(query, filters) {
51
51
  }
52
52
  return await res.json();
53
53
  }
54
- async function recordDownload(repo) {
54
+ async function recordDownload(repo, dir) {
55
55
  await fetch(`${API_BASE_URL}/download`, {
56
56
  method: "POST",
57
57
  headers: {
58
58
  "Content-Type": "application/json",
59
59
  "User-Agent": USER_AGENT
60
60
  },
61
- body: JSON.stringify({ repo }),
61
+ body: JSON.stringify({ repo, dir: dir ?? null }),
62
62
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
63
63
  });
64
64
  }
65
- async function registerBoilerplate(repo, manifest, githubToken) {
65
+ async function registerBoilerplate(repo, manifest, githubToken, dir) {
66
66
  const res = await fetch(`${API_BASE_URL}/register`, {
67
67
  method: "POST",
68
68
  headers: {
@@ -70,7 +70,7 @@ async function registerBoilerplate(repo, manifest, githubToken) {
70
70
  Authorization: `Bearer ${githubToken}`,
71
71
  "User-Agent": USER_AGENT
72
72
  },
73
- body: JSON.stringify({ repo, manifest }),
73
+ body: JSON.stringify({ repo, manifest, dir: dir ?? null }),
74
74
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
75
75
  });
76
76
  if (!res.ok) {
@@ -308,6 +308,18 @@ function optionalStringArray(obj, key) {
308
308
  // src/core/github.ts
309
309
  var GITHUB_RAW = "https://raw.githubusercontent.com";
310
310
  var FETCH_TIMEOUT_MS2 = 3e4;
311
+ function parseRepoInput(input) {
312
+ const parts = input.split("/");
313
+ if (parts.length < 2 || !parts[0] || !parts[1]) {
314
+ throw new Error(
315
+ `Invalid repository format: "${input}". Expected "owner/repo" or "owner/repo/dir".`
316
+ );
317
+ }
318
+ const repo = `${parts[0]}/${parts[1]}`;
319
+ const raw = parts.length > 2 ? parts.slice(2).join("/").replace(/\/+$/, "") : void 0;
320
+ const dir = raw || void 0;
321
+ return { repo, dir };
322
+ }
311
323
  function parseRepo(repo) {
312
324
  const parts = repo.split("/");
313
325
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
@@ -387,11 +399,12 @@ async function fetchFile(repo, path) {
387
399
  }
388
400
  return res.text();
389
401
  }
390
- async function fetchManifest(repo) {
391
- const content = await fetchFile(repo, MANIFEST_FILENAME);
402
+ async function fetchManifest(repo, dir) {
403
+ const path = dir ? `${dir}/${MANIFEST_FILENAME}` : MANIFEST_FILENAME;
404
+ const content = await fetchFile(repo, path);
392
405
  if (content === null) {
393
406
  throw new Error(
394
- `No ${MANIFEST_FILENAME} found in ${repo}. Is this an updose boilerplate?`
407
+ `No ${MANIFEST_FILENAME} found in ${dir ? `${repo}/${dir}` : repo}. Is this an updose boilerplate?`
395
408
  );
396
409
  }
397
410
  let raw;
@@ -429,8 +442,9 @@ async function fetchRepoTree(repo) {
429
442
  }
430
443
  return data.tree.filter((entry) => entry.type === "blob");
431
444
  }
432
- async function fetchSkillsJson(repo) {
433
- return fetchFile(repo, SKILLS_FILENAME);
445
+ async function fetchSkillsJson(repo, dir) {
446
+ const path = dir ? `${dir}/${SKILLS_FILENAME}` : SKILLS_FILENAME;
447
+ return fetchFile(repo, path);
434
448
  }
435
449
 
436
450
  // src/core/installer.ts
@@ -613,15 +627,16 @@ async function settledPool(tasks, limit) {
613
627
  );
614
628
  return results;
615
629
  }
616
- async function addCommand(repo, options) {
630
+ async function addCommand(repoInput, options) {
617
631
  try {
632
+ const { repo, dir } = parseRepoInput(repoInput);
618
633
  const cwd = process.cwd();
619
634
  const skipPrompts = options.yes ?? false;
620
635
  const dryRun = options.dryRun ?? false;
621
636
  const manifestSpinner = createSpinner("Fetching updose.json...").start();
622
637
  let manifest;
623
638
  try {
624
- manifest = await fetchManifest(repo);
639
+ manifest = await fetchManifest(repo, dir);
625
640
  manifestSpinner.success(
626
641
  `Found ${manifest.name} by ${manifest.author} (v${manifest.version})`
627
642
  );
@@ -654,7 +669,7 @@ async function addCommand(repo, options) {
654
669
  const filesByTarget = /* @__PURE__ */ new Map();
655
670
  for (const target of selectedTargets) {
656
671
  const sourceDir = getSourceDir(target);
657
- const prefix = `${sourceDir}/`;
672
+ const prefix = dir ? `${dir}/${sourceDir}/` : `${sourceDir}/`;
658
673
  const files = tree.filter((entry) => {
659
674
  if (!entry.path.startsWith(prefix)) return false;
660
675
  const relativePath = entry.path.slice(prefix.length);
@@ -687,7 +702,7 @@ async function addCommand(repo, options) {
687
702
  dryRunCount++;
688
703
  }
689
704
  }
690
- const skillsContent2 = await fetchSkillsJson(repo);
705
+ const skillsContent2 = await fetchSkillsJson(repo, dir);
691
706
  if (skillsContent2 !== null) {
692
707
  try {
693
708
  const skillsManifest = parseSkills(
@@ -746,7 +761,7 @@ async function addCommand(repo, options) {
746
761
  }
747
762
  }
748
763
  let skillsInstalled = 0;
749
- const skillsContent = await fetchSkillsJson(repo);
764
+ const skillsContent = await fetchSkillsJson(repo, dir);
750
765
  if (skillsContent === null) {
751
766
  info("No skills.json found \u2014 skipping skills installation.");
752
767
  } else {
@@ -794,7 +809,7 @@ async function addCommand(repo, options) {
794
809
  const summary = skillsInstalled > 0 ? `${installed} file(s) + ${skillsInstalled} skill(s)` : `${installed} file(s)`;
795
810
  success(`Done! ${summary} installed, ${skipped} skipped.`);
796
811
  if (installed + skillsInstalled > 0) {
797
- await recordDownload(repo).catch(() => {
812
+ await recordDownload(repo, dir).catch(() => {
798
813
  });
799
814
  }
800
815
  } catch (err) {
@@ -807,6 +822,7 @@ async function addCommand(repo, options) {
807
822
 
808
823
  // src/commands/init.ts
809
824
  var import_node_child_process2 = require("child_process");
825
+ var import_promises2 = require("fs/promises");
810
826
  var import_node_path4 = require("path");
811
827
  var import_prompts4 = __toESM(require("prompts"), 1);
812
828
  var DEFAULT_VERSION = "0.1.0";
@@ -912,10 +928,16 @@ function buildFileList(name, description, author, targets) {
912
928
  }
913
929
  return files;
914
930
  }
915
- async function initCommand() {
931
+ async function initCommand(options) {
916
932
  try {
917
933
  const cwd = process.cwd();
918
- const defaultName = (0, import_node_path4.basename)(cwd);
934
+ const dir = options.dir;
935
+ const baseDir = dir ? (0, import_node_path4.join)(cwd, dir) : cwd;
936
+ if (dir) {
937
+ await (0, import_promises2.mkdir)(baseDir, { recursive: true });
938
+ }
939
+ const repoName = (0, import_node_path4.basename)(cwd);
940
+ const defaultName = dir ? `${repoName}/${dir}` : repoName;
919
941
  const gitUser = getGitHubUsername();
920
942
  info("Scaffolding a new updose boilerplate...\n");
921
943
  let cancelled = false;
@@ -972,7 +994,7 @@ async function initCommand() {
972
994
  let created = 0;
973
995
  let skipped = 0;
974
996
  for (const file of files) {
975
- const destPath = (0, import_node_path4.join)(cwd, file.path);
997
+ const destPath = (0, import_node_path4.join)(baseDir, file.path);
976
998
  const exists = await fileExists(destPath);
977
999
  if (exists) {
978
1000
  const { action } = await (0, import_prompts4.default)({
@@ -998,14 +1020,21 @@ async function initCommand() {
998
1020
  success(`Boilerplate scaffolded! (${created} created, ${skipped} skipped)`);
999
1021
  console.log();
1000
1022
  info("Next steps:");
1001
- console.log(
1002
- ` 1. Edit your boilerplate files in ${targets.map((t) => `${t}/`).join(", ")}`
1003
- );
1023
+ const editDirs = targets.map((t) => dir ? `${dir}/${t}/` : `${t}/`);
1024
+ console.log(` 1. Edit your boilerplate files in ${editDirs.join(", ")}`);
1004
1025
  console.log(" 2. Push to GitHub");
1005
- console.log(" 3. Publish with: npx updose publish");
1006
1026
  console.log(
1007
- ` 4. Others can install with: npx updose add ${author}/${name}`
1027
+ ` 3. Publish with: npx updose publish${dir ? ` --dir ${dir}` : ""}`
1008
1028
  );
1029
+ if (dir) {
1030
+ console.log(
1031
+ ` 4. Others can install with: npx updose add ${author}/${repoName}/${dir}`
1032
+ );
1033
+ } else {
1034
+ console.log(
1035
+ ` 4. Others can install with: npx updose add ${author}/${name}`
1036
+ );
1037
+ }
1009
1038
  } catch (err) {
1010
1039
  error(
1011
1040
  err instanceof Error ? err.message : "An unexpected error occurred during init."
@@ -1015,7 +1044,7 @@ async function initCommand() {
1015
1044
  }
1016
1045
 
1017
1046
  // src/auth/github-oauth.ts
1018
- var import_promises2 = require("fs/promises");
1047
+ var import_promises3 = require("fs/promises");
1019
1048
  var import_node_os = require("os");
1020
1049
  var import_node_path5 = require("path");
1021
1050
  var import_chalk2 = __toESM(require("chalk"), 1);
@@ -1032,7 +1061,7 @@ var AUTH_FILE_MODE = 384;
1032
1061
  var MAX_POLL_INTERVAL_SEC = 60;
1033
1062
  async function getStoredToken() {
1034
1063
  try {
1035
- const content = await (0, import_promises2.readFile)(AUTH_FILE, "utf-8");
1064
+ const content = await (0, import_promises3.readFile)(AUTH_FILE, "utf-8");
1036
1065
  const data = JSON.parse(content);
1037
1066
  return data.github_token ?? null;
1038
1067
  } catch {
@@ -1041,7 +1070,7 @@ async function getStoredToken() {
1041
1070
  }
1042
1071
  async function getStoredAuth() {
1043
1072
  try {
1044
- const content = await (0, import_promises2.readFile)(AUTH_FILE, "utf-8");
1073
+ const content = await (0, import_promises3.readFile)(AUTH_FILE, "utf-8");
1045
1074
  return JSON.parse(content);
1046
1075
  } catch {
1047
1076
  return null;
@@ -1097,8 +1126,8 @@ async function login() {
1097
1126
  throw new Error("GitHub authorization failed");
1098
1127
  }
1099
1128
  const username = await fetchUsername(token);
1100
- await (0, import_promises2.mkdir)(AUTH_DIR, { recursive: true });
1101
- await (0, import_promises2.writeFile)(
1129
+ await (0, import_promises3.mkdir)(AUTH_DIR, { recursive: true });
1130
+ await (0, import_promises3.writeFile)(
1102
1131
  AUTH_FILE,
1103
1132
  JSON.stringify(
1104
1133
  { github_token: token, github_username: username },
@@ -1183,7 +1212,7 @@ function sleep(ms) {
1183
1212
  }
1184
1213
  async function logout() {
1185
1214
  try {
1186
- await (0, import_promises2.unlink)(AUTH_FILE);
1215
+ await (0, import_promises3.unlink)(AUTH_FILE);
1187
1216
  return true;
1188
1217
  } catch {
1189
1218
  return false;
@@ -1218,20 +1247,32 @@ async function logoutCommand() {
1218
1247
 
1219
1248
  // src/commands/publish.ts
1220
1249
  var import_node_child_process3 = require("child_process");
1221
- var import_promises3 = require("fs/promises");
1250
+ var import_node_fs = require("fs");
1251
+ var import_promises4 = require("fs/promises");
1222
1252
  var import_node_path6 = require("path");
1223
1253
  var import_chalk3 = __toESM(require("chalk"), 1);
1224
1254
  var FETCH_TIMEOUT_MS3 = 1e4;
1225
- async function publishCommand() {
1255
+ async function publishCommand(options) {
1226
1256
  const cwd = process.cwd();
1257
+ const dir = options.dir;
1258
+ const manifestDir = dir ? (0, import_node_path6.join)(cwd, dir) : cwd;
1259
+ if (dir && !(0, import_node_fs.existsSync)(manifestDir)) {
1260
+ error(`Directory "${dir}" does not exist.`);
1261
+ process.exitCode = 1;
1262
+ return;
1263
+ }
1227
1264
  let raw;
1228
1265
  try {
1229
- const content = await (0, import_promises3.readFile)((0, import_node_path6.join)(cwd, MANIFEST_FILENAME), "utf-8");
1266
+ const content = await (0, import_promises4.readFile)(
1267
+ (0, import_node_path6.join)(manifestDir, MANIFEST_FILENAME),
1268
+ "utf-8"
1269
+ );
1230
1270
  raw = JSON.parse(content);
1231
1271
  } catch (err) {
1232
1272
  if (err.code === "ENOENT") {
1273
+ const location = dir ? `"${dir}"` : "current directory";
1233
1274
  error(
1234
- `No ${MANIFEST_FILENAME} found in current directory. Run \`updose init\` first.`
1275
+ `No ${MANIFEST_FILENAME} found in ${location}. Run \`updose init\` first.`
1235
1276
  );
1236
1277
  } else {
1237
1278
  error(
@@ -1265,9 +1306,10 @@ async function publishCommand() {
1265
1306
  process.exitCode = 1;
1266
1307
  return;
1267
1308
  }
1268
- if (manifest.name.toLowerCase() !== repoName.toLowerCase()) {
1309
+ const expectedName = dir ? `${repoName}/${dir}` : repoName;
1310
+ if (manifest.name.toLowerCase() !== expectedName.toLowerCase()) {
1269
1311
  error(
1270
- `Manifest name "${manifest.name}" does not match repository name "${repoName}".`
1312
+ `Manifest name "${manifest.name}" does not match expected name "${expectedName}".`
1271
1313
  );
1272
1314
  process.exitCode = 1;
1273
1315
  return;
@@ -1317,6 +1359,9 @@ Make sure you have pushed your code to GitHub.`
1317
1359
  console.log(` Name: ${manifest.name}`);
1318
1360
  console.log(` Version: ${manifest.version}`);
1319
1361
  console.log(` Repository: ${repo}`);
1362
+ if (dir) {
1363
+ console.log(` Directory: ${dir}`);
1364
+ }
1320
1365
  console.log(` Targets: ${manifest.targets.join(", ")}`);
1321
1366
  if (manifest.tags?.length) {
1322
1367
  console.log(` Tags: ${manifest.tags.join(", ")}`);
@@ -1338,11 +1383,15 @@ Make sure you have pushed your code to GitHub.`
1338
1383
  targets: manifest.targets,
1339
1384
  tags: manifest.tags
1340
1385
  },
1341
- token
1386
+ token,
1387
+ dir
1342
1388
  );
1343
1389
  spinner.success("Published successfully!");
1344
1390
  console.log();
1345
- info(`Users can now install with: ${import_chalk3.default.cyan(`npx updose add ${repo}`)}`);
1391
+ const installPath = dir ? `${repo}/${dir}` : repo;
1392
+ info(
1393
+ `Users can now install with: ${import_chalk3.default.cyan(`npx updose add ${installPath}`)}`
1394
+ );
1346
1395
  } catch (err) {
1347
1396
  spinner.fail("Publication failed");
1348
1397
  error(err.message);
@@ -1421,17 +1470,18 @@ function formatResult(bp) {
1421
1470
  if (bp.tags.length > 0) {
1422
1471
  console.log(` ${bp.tags.map((t) => import_chalk4.default.dim(`#${t}`)).join(" ")}`);
1423
1472
  }
1424
- console.log(` ${import_chalk4.default.dim(bp.repo)}`);
1473
+ const repoPath = bp.dir ? `${bp.repo}/${bp.dir}` : bp.repo;
1474
+ console.log(` ${import_chalk4.default.dim(repoPath)}`);
1425
1475
  console.log();
1426
1476
  }
1427
1477
 
1428
1478
  // src/index.ts
1429
1479
  var program = new import_commander.Command();
1430
- program.name("updose").description("AI coding tool boilerplate marketplace").version("0.2.0");
1480
+ program.name("updose").description("AI coding tool boilerplate marketplace").version("0.3.0");
1431
1481
  program.command("add <repo>").description("Install a boilerplate").option("-y, --yes", "Skip all prompts and use defaults").option("--dry-run", "Preview install without writing files").action(addCommand);
1432
1482
  program.command("search [query]").description("Search for boilerplates").option("--target <target>", "Filter by target (claude, codex, gemini)").option("--tag <tag>", "Filter by tag").option("--author <author>", "Filter by author").action(searchCommand);
1433
- program.command("init").description("Scaffold a new boilerplate repository").action(initCommand);
1434
- program.command("publish").description("Publish your boilerplate to the registry").action(publishCommand);
1483
+ program.command("init").description("Scaffold a new boilerplate repository").option("--dir <dir>", "Create boilerplate in a subdirectory").action(initCommand);
1484
+ program.command("publish").description("Publish your boilerplate to the registry").option("--dir <dir>", "Publish from a subdirectory").action(publishCommand);
1435
1485
  program.command("login").description("Log in to GitHub").action(loginCommand);
1436
1486
  program.command("logout").description("Log out from GitHub").action(logoutCommand);
1437
1487
  program.parse();
package/dist/index.js CHANGED
@@ -29,18 +29,18 @@ async function searchBoilerplates(query, filters) {
29
29
  }
30
30
  return await res.json();
31
31
  }
32
- async function recordDownload(repo) {
32
+ async function recordDownload(repo, dir) {
33
33
  await fetch(`${API_BASE_URL}/download`, {
34
34
  method: "POST",
35
35
  headers: {
36
36
  "Content-Type": "application/json",
37
37
  "User-Agent": USER_AGENT
38
38
  },
39
- body: JSON.stringify({ repo }),
39
+ body: JSON.stringify({ repo, dir: dir ?? null }),
40
40
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
41
41
  });
42
42
  }
43
- async function registerBoilerplate(repo, manifest, githubToken) {
43
+ async function registerBoilerplate(repo, manifest, githubToken, dir) {
44
44
  const res = await fetch(`${API_BASE_URL}/register`, {
45
45
  method: "POST",
46
46
  headers: {
@@ -48,7 +48,7 @@ async function registerBoilerplate(repo, manifest, githubToken) {
48
48
  Authorization: `Bearer ${githubToken}`,
49
49
  "User-Agent": USER_AGENT
50
50
  },
51
- body: JSON.stringify({ repo, manifest }),
51
+ body: JSON.stringify({ repo, manifest, dir: dir ?? null }),
52
52
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
53
53
  });
54
54
  if (!res.ok) {
@@ -286,6 +286,18 @@ function optionalStringArray(obj, key) {
286
286
  // src/core/github.ts
287
287
  var GITHUB_RAW = "https://raw.githubusercontent.com";
288
288
  var FETCH_TIMEOUT_MS2 = 3e4;
289
+ function parseRepoInput(input) {
290
+ const parts = input.split("/");
291
+ if (parts.length < 2 || !parts[0] || !parts[1]) {
292
+ throw new Error(
293
+ `Invalid repository format: "${input}". Expected "owner/repo" or "owner/repo/dir".`
294
+ );
295
+ }
296
+ const repo = `${parts[0]}/${parts[1]}`;
297
+ const raw = parts.length > 2 ? parts.slice(2).join("/").replace(/\/+$/, "") : void 0;
298
+ const dir = raw || void 0;
299
+ return { repo, dir };
300
+ }
289
301
  function parseRepo(repo) {
290
302
  const parts = repo.split("/");
291
303
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
@@ -365,11 +377,12 @@ async function fetchFile(repo, path) {
365
377
  }
366
378
  return res.text();
367
379
  }
368
- async function fetchManifest(repo) {
369
- const content = await fetchFile(repo, MANIFEST_FILENAME);
380
+ async function fetchManifest(repo, dir) {
381
+ const path = dir ? `${dir}/${MANIFEST_FILENAME}` : MANIFEST_FILENAME;
382
+ const content = await fetchFile(repo, path);
370
383
  if (content === null) {
371
384
  throw new Error(
372
- `No ${MANIFEST_FILENAME} found in ${repo}. Is this an updose boilerplate?`
385
+ `No ${MANIFEST_FILENAME} found in ${dir ? `${repo}/${dir}` : repo}. Is this an updose boilerplate?`
373
386
  );
374
387
  }
375
388
  let raw;
@@ -407,8 +420,9 @@ async function fetchRepoTree(repo) {
407
420
  }
408
421
  return data.tree.filter((entry) => entry.type === "blob");
409
422
  }
410
- async function fetchSkillsJson(repo) {
411
- return fetchFile(repo, SKILLS_FILENAME);
423
+ async function fetchSkillsJson(repo, dir) {
424
+ const path = dir ? `${dir}/${SKILLS_FILENAME}` : SKILLS_FILENAME;
425
+ return fetchFile(repo, path);
412
426
  }
413
427
 
414
428
  // src/core/installer.ts
@@ -591,15 +605,16 @@ async function settledPool(tasks, limit) {
591
605
  );
592
606
  return results;
593
607
  }
594
- async function addCommand(repo, options) {
608
+ async function addCommand(repoInput, options) {
595
609
  try {
610
+ const { repo, dir } = parseRepoInput(repoInput);
596
611
  const cwd = process.cwd();
597
612
  const skipPrompts = options.yes ?? false;
598
613
  const dryRun = options.dryRun ?? false;
599
614
  const manifestSpinner = createSpinner("Fetching updose.json...").start();
600
615
  let manifest;
601
616
  try {
602
- manifest = await fetchManifest(repo);
617
+ manifest = await fetchManifest(repo, dir);
603
618
  manifestSpinner.success(
604
619
  `Found ${manifest.name} by ${manifest.author} (v${manifest.version})`
605
620
  );
@@ -632,7 +647,7 @@ async function addCommand(repo, options) {
632
647
  const filesByTarget = /* @__PURE__ */ new Map();
633
648
  for (const target of selectedTargets) {
634
649
  const sourceDir = getSourceDir(target);
635
- const prefix = `${sourceDir}/`;
650
+ const prefix = dir ? `${dir}/${sourceDir}/` : `${sourceDir}/`;
636
651
  const files = tree.filter((entry) => {
637
652
  if (!entry.path.startsWith(prefix)) return false;
638
653
  const relativePath = entry.path.slice(prefix.length);
@@ -665,7 +680,7 @@ async function addCommand(repo, options) {
665
680
  dryRunCount++;
666
681
  }
667
682
  }
668
- const skillsContent2 = await fetchSkillsJson(repo);
683
+ const skillsContent2 = await fetchSkillsJson(repo, dir);
669
684
  if (skillsContent2 !== null) {
670
685
  try {
671
686
  const skillsManifest = parseSkills(
@@ -724,7 +739,7 @@ async function addCommand(repo, options) {
724
739
  }
725
740
  }
726
741
  let skillsInstalled = 0;
727
- const skillsContent = await fetchSkillsJson(repo);
742
+ const skillsContent = await fetchSkillsJson(repo, dir);
728
743
  if (skillsContent === null) {
729
744
  info("No skills.json found \u2014 skipping skills installation.");
730
745
  } else {
@@ -772,7 +787,7 @@ async function addCommand(repo, options) {
772
787
  const summary = skillsInstalled > 0 ? `${installed} file(s) + ${skillsInstalled} skill(s)` : `${installed} file(s)`;
773
788
  success(`Done! ${summary} installed, ${skipped} skipped.`);
774
789
  if (installed + skillsInstalled > 0) {
775
- await recordDownload(repo).catch(() => {
790
+ await recordDownload(repo, dir).catch(() => {
776
791
  });
777
792
  }
778
793
  } catch (err) {
@@ -785,6 +800,7 @@ async function addCommand(repo, options) {
785
800
 
786
801
  // src/commands/init.ts
787
802
  import { execSync } from "child_process";
803
+ import { mkdir as mkdir2 } from "fs/promises";
788
804
  import { basename, join as join2 } from "path";
789
805
  import prompts2 from "prompts";
790
806
  var DEFAULT_VERSION = "0.1.0";
@@ -890,10 +906,16 @@ function buildFileList(name, description, author, targets) {
890
906
  }
891
907
  return files;
892
908
  }
893
- async function initCommand() {
909
+ async function initCommand(options) {
894
910
  try {
895
911
  const cwd = process.cwd();
896
- const defaultName = basename(cwd);
912
+ const dir = options.dir;
913
+ const baseDir = dir ? join2(cwd, dir) : cwd;
914
+ if (dir) {
915
+ await mkdir2(baseDir, { recursive: true });
916
+ }
917
+ const repoName = basename(cwd);
918
+ const defaultName = dir ? `${repoName}/${dir}` : repoName;
897
919
  const gitUser = getGitHubUsername();
898
920
  info("Scaffolding a new updose boilerplate...\n");
899
921
  let cancelled = false;
@@ -950,7 +972,7 @@ async function initCommand() {
950
972
  let created = 0;
951
973
  let skipped = 0;
952
974
  for (const file of files) {
953
- const destPath = join2(cwd, file.path);
975
+ const destPath = join2(baseDir, file.path);
954
976
  const exists = await fileExists(destPath);
955
977
  if (exists) {
956
978
  const { action } = await prompts2({
@@ -976,14 +998,21 @@ async function initCommand() {
976
998
  success(`Boilerplate scaffolded! (${created} created, ${skipped} skipped)`);
977
999
  console.log();
978
1000
  info("Next steps:");
979
- console.log(
980
- ` 1. Edit your boilerplate files in ${targets.map((t) => `${t}/`).join(", ")}`
981
- );
1001
+ const editDirs = targets.map((t) => dir ? `${dir}/${t}/` : `${t}/`);
1002
+ console.log(` 1. Edit your boilerplate files in ${editDirs.join(", ")}`);
982
1003
  console.log(" 2. Push to GitHub");
983
- console.log(" 3. Publish with: npx updose publish");
984
1004
  console.log(
985
- ` 4. Others can install with: npx updose add ${author}/${name}`
1005
+ ` 3. Publish with: npx updose publish${dir ? ` --dir ${dir}` : ""}`
986
1006
  );
1007
+ if (dir) {
1008
+ console.log(
1009
+ ` 4. Others can install with: npx updose add ${author}/${repoName}/${dir}`
1010
+ );
1011
+ } else {
1012
+ console.log(
1013
+ ` 4. Others can install with: npx updose add ${author}/${name}`
1014
+ );
1015
+ }
987
1016
  } catch (err) {
988
1017
  error(
989
1018
  err instanceof Error ? err.message : "An unexpected error occurred during init."
@@ -993,7 +1022,7 @@ async function initCommand() {
993
1022
  }
994
1023
 
995
1024
  // src/auth/github-oauth.ts
996
- import { mkdir as mkdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "fs/promises";
1025
+ import { mkdir as mkdir3, readFile as readFile2, unlink, writeFile as writeFile2 } from "fs/promises";
997
1026
  import { homedir } from "os";
998
1027
  import { join as join3 } from "path";
999
1028
  import chalk2 from "chalk";
@@ -1075,7 +1104,7 @@ async function login() {
1075
1104
  throw new Error("GitHub authorization failed");
1076
1105
  }
1077
1106
  const username = await fetchUsername(token);
1078
- await mkdir2(AUTH_DIR, { recursive: true });
1107
+ await mkdir3(AUTH_DIR, { recursive: true });
1079
1108
  await writeFile2(
1080
1109
  AUTH_FILE,
1081
1110
  JSON.stringify(
@@ -1196,20 +1225,32 @@ async function logoutCommand() {
1196
1225
 
1197
1226
  // src/commands/publish.ts
1198
1227
  import { execSync as execSync2 } from "child_process";
1228
+ import { existsSync } from "fs";
1199
1229
  import { readFile as readFile3 } from "fs/promises";
1200
1230
  import { join as join4 } from "path";
1201
1231
  import chalk3 from "chalk";
1202
1232
  var FETCH_TIMEOUT_MS3 = 1e4;
1203
- async function publishCommand() {
1233
+ async function publishCommand(options) {
1204
1234
  const cwd = process.cwd();
1235
+ const dir = options.dir;
1236
+ const manifestDir = dir ? join4(cwd, dir) : cwd;
1237
+ if (dir && !existsSync(manifestDir)) {
1238
+ error(`Directory "${dir}" does not exist.`);
1239
+ process.exitCode = 1;
1240
+ return;
1241
+ }
1205
1242
  let raw;
1206
1243
  try {
1207
- const content = await readFile3(join4(cwd, MANIFEST_FILENAME), "utf-8");
1244
+ const content = await readFile3(
1245
+ join4(manifestDir, MANIFEST_FILENAME),
1246
+ "utf-8"
1247
+ );
1208
1248
  raw = JSON.parse(content);
1209
1249
  } catch (err) {
1210
1250
  if (err.code === "ENOENT") {
1251
+ const location = dir ? `"${dir}"` : "current directory";
1211
1252
  error(
1212
- `No ${MANIFEST_FILENAME} found in current directory. Run \`updose init\` first.`
1253
+ `No ${MANIFEST_FILENAME} found in ${location}. Run \`updose init\` first.`
1213
1254
  );
1214
1255
  } else {
1215
1256
  error(
@@ -1243,9 +1284,10 @@ async function publishCommand() {
1243
1284
  process.exitCode = 1;
1244
1285
  return;
1245
1286
  }
1246
- if (manifest.name.toLowerCase() !== repoName.toLowerCase()) {
1287
+ const expectedName = dir ? `${repoName}/${dir}` : repoName;
1288
+ if (manifest.name.toLowerCase() !== expectedName.toLowerCase()) {
1247
1289
  error(
1248
- `Manifest name "${manifest.name}" does not match repository name "${repoName}".`
1290
+ `Manifest name "${manifest.name}" does not match expected name "${expectedName}".`
1249
1291
  );
1250
1292
  process.exitCode = 1;
1251
1293
  return;
@@ -1295,6 +1337,9 @@ Make sure you have pushed your code to GitHub.`
1295
1337
  console.log(` Name: ${manifest.name}`);
1296
1338
  console.log(` Version: ${manifest.version}`);
1297
1339
  console.log(` Repository: ${repo}`);
1340
+ if (dir) {
1341
+ console.log(` Directory: ${dir}`);
1342
+ }
1298
1343
  console.log(` Targets: ${manifest.targets.join(", ")}`);
1299
1344
  if (manifest.tags?.length) {
1300
1345
  console.log(` Tags: ${manifest.tags.join(", ")}`);
@@ -1316,11 +1361,15 @@ Make sure you have pushed your code to GitHub.`
1316
1361
  targets: manifest.targets,
1317
1362
  tags: manifest.tags
1318
1363
  },
1319
- token
1364
+ token,
1365
+ dir
1320
1366
  );
1321
1367
  spinner.success("Published successfully!");
1322
1368
  console.log();
1323
- info(`Users can now install with: ${chalk3.cyan(`npx updose add ${repo}`)}`);
1369
+ const installPath = dir ? `${repo}/${dir}` : repo;
1370
+ info(
1371
+ `Users can now install with: ${chalk3.cyan(`npx updose add ${installPath}`)}`
1372
+ );
1324
1373
  } catch (err) {
1325
1374
  spinner.fail("Publication failed");
1326
1375
  error(err.message);
@@ -1399,17 +1448,18 @@ function formatResult(bp) {
1399
1448
  if (bp.tags.length > 0) {
1400
1449
  console.log(` ${bp.tags.map((t) => chalk4.dim(`#${t}`)).join(" ")}`);
1401
1450
  }
1402
- console.log(` ${chalk4.dim(bp.repo)}`);
1451
+ const repoPath = bp.dir ? `${bp.repo}/${bp.dir}` : bp.repo;
1452
+ console.log(` ${chalk4.dim(repoPath)}`);
1403
1453
  console.log();
1404
1454
  }
1405
1455
 
1406
1456
  // src/index.ts
1407
1457
  var program = new Command();
1408
- program.name("updose").description("AI coding tool boilerplate marketplace").version("0.2.0");
1458
+ program.name("updose").description("AI coding tool boilerplate marketplace").version("0.3.0");
1409
1459
  program.command("add <repo>").description("Install a boilerplate").option("-y, --yes", "Skip all prompts and use defaults").option("--dry-run", "Preview install without writing files").action(addCommand);
1410
1460
  program.command("search [query]").description("Search for boilerplates").option("--target <target>", "Filter by target (claude, codex, gemini)").option("--tag <tag>", "Filter by tag").option("--author <author>", "Filter by author").action(searchCommand);
1411
- program.command("init").description("Scaffold a new boilerplate repository").action(initCommand);
1412
- program.command("publish").description("Publish your boilerplate to the registry").action(publishCommand);
1461
+ program.command("init").description("Scaffold a new boilerplate repository").option("--dir <dir>", "Create boilerplate in a subdirectory").action(initCommand);
1462
+ program.command("publish").description("Publish your boilerplate to the registry").option("--dir <dir>", "Publish from a subdirectory").action(publishCommand);
1413
1463
  program.command("login").description("Log in to GitHub").action(loginCommand);
1414
1464
  program.command("logout").description("Log out from GitHub").action(logoutCommand);
1415
1465
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "updose",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "AI coding tool boilerplate marketplace",
6
6
  "main": "dist/index.cjs",