updose 0.3.0 → 0.4.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
@@ -130,9 +130,9 @@ npx updose search --author james --target claude # james's Claude boilerpla
130
130
  npx updose search --tag typescript --target codex # TypeScript boilerplates for Codex
131
131
  ```
132
132
 
133
- At least one of the query or filter options must be provided. Running `npx updose search` with no arguments will show an error.
133
+ Running `npx updose search` with no arguments returns popular boilerplates.
134
134
 
135
- Results display the boilerplate name, version, author, description, rating, download count, supported targets, and tags.
135
+ Results display the boilerplate name, version, author, description, download count, supported targets, and tags.
136
136
 
137
137
  | Option | Description |
138
138
  |-------- |------------- |
package/dist/index.cjs CHANGED
@@ -25,6 +25,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  // src/index.ts
26
26
  var import_commander = require("commander");
27
27
 
28
+ // src/commands/add.ts
29
+ var import_node_crypto = require("crypto");
30
+
28
31
  // src/constants.ts
29
32
  var USER_AGENT = "updose-cli";
30
33
  var MANIFEST_FILENAME = "updose.json";
@@ -51,14 +54,18 @@ async function searchBoilerplates(query, filters) {
51
54
  }
52
55
  return await res.json();
53
56
  }
54
- async function recordDownload(repo, dir) {
57
+ async function recordDownload(repo, dir, projectHash) {
55
58
  await fetch(`${API_BASE_URL}/download`, {
56
59
  method: "POST",
57
60
  headers: {
58
61
  "Content-Type": "application/json",
59
62
  "User-Agent": USER_AGENT
60
63
  },
61
- body: JSON.stringify({ repo, dir: dir ?? null }),
64
+ body: JSON.stringify({
65
+ repo,
66
+ dir: dir ?? null,
67
+ ...projectHash ? { project_hash: projectHash } : {}
68
+ }),
62
69
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
63
70
  });
64
71
  }
@@ -357,35 +364,10 @@ function handleHttpError(res) {
357
364
  );
358
365
  }
359
366
  }
360
- var branchCache = /* @__PURE__ */ new Map();
361
- async function getDefaultBranch(repo) {
362
- const cached = branchCache.get(repo);
363
- if (cached) return cached;
364
- const { owner, name } = parseRepo(repo);
365
- const res = await fetch(`${GITHUB_API_URL}/repos/${owner}/${name}`, {
366
- headers: {
367
- Accept: GITHUB_ACCEPT_HEADER,
368
- "User-Agent": USER_AGENT,
369
- ...getAuthHeaders()
370
- },
371
- signal: createSignal()
372
- });
373
- if (res.status === 404) {
374
- throw new Error(`Repository not found: ${repo}`);
375
- }
376
- handleHttpError(res);
377
- if (!res.ok) {
378
- throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);
379
- }
380
- const data = await res.json();
381
- branchCache.set(repo, data.default_branch);
382
- return data.default_branch;
383
- }
384
367
  async function fetchFile(repo, path) {
385
368
  const { owner, name } = parseRepo(repo);
386
- const branch = await getDefaultBranch(repo);
387
369
  const encodedPath = path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
388
- const url = `${GITHUB_RAW}/${owner}/${name}/${branch}/${encodedPath}`;
370
+ const url = `${GITHUB_RAW}/${owner}/${name}/HEAD/${encodedPath}`;
389
371
  const res = await fetch(url, {
390
372
  headers: { "User-Agent": USER_AGENT, ...getAuthHeaders() },
391
373
  signal: createSignal()
@@ -417,8 +399,7 @@ async function fetchManifest(repo, dir) {
417
399
  }
418
400
  async function fetchRepoTree(repo) {
419
401
  const { owner, name } = parseRepo(repo);
420
- const branch = await getDefaultBranch(repo);
421
- const url = `${GITHUB_API_URL}/repos/${owner}/${name}/git/trees/${branch}?recursive=1`;
402
+ const url = `${GITHUB_API_URL}/repos/${owner}/${name}/git/trees/HEAD?recursive=1`;
422
403
  const res = await fetch(url, {
423
404
  headers: {
424
405
  Accept: GITHUB_ACCEPT_HEADER,
@@ -809,7 +790,8 @@ async function addCommand(repoInput, options) {
809
790
  const summary = skillsInstalled > 0 ? `${installed} file(s) + ${skillsInstalled} skill(s)` : `${installed} file(s)`;
810
791
  success(`Done! ${summary} installed, ${skipped} skipped.`);
811
792
  if (installed + skillsInstalled > 0) {
812
- await recordDownload(repo, dir).catch(() => {
793
+ const projectHash = (0, import_node_crypto.createHash)("sha256").update(cwd).digest("hex");
794
+ await recordDownload(repo, dir, projectHash).catch(() => {
813
795
  });
814
796
  }
815
797
  } catch (err) {
@@ -1427,29 +1409,26 @@ function detectRepo(cwd) {
1427
1409
  var import_chalk4 = __toESM(require("chalk"), 1);
1428
1410
  async function searchCommand(query, options) {
1429
1411
  try {
1430
- if (!query && !options.target && !options.tag && !options.author) {
1431
- error(
1432
- "Please provide a search query or at least one filter (--target, --tag, --author)."
1433
- );
1434
- process.exitCode = 1;
1435
- return;
1436
- }
1437
1412
  const filters = {};
1438
1413
  if (options.target) filters.target = options.target;
1439
1414
  if (options.tag) filters.tag = options.tag;
1440
1415
  if (options.author) filters.author = options.author;
1441
- const results = await searchBoilerplates(query, filters);
1442
- const label = query ? `"${query}"` : "the given filters";
1443
- if (results.length === 0) {
1416
+ const hasParams = !!(query || filters.target || filters.tag || filters.author);
1417
+ const label = query ? `"${query}"` : hasParams ? "the given filters" : "popular boilerplates";
1418
+ const response = await searchBoilerplates(query, filters);
1419
+ if (response.data.length === 0) {
1444
1420
  info(`No boilerplates found for ${label}`);
1445
1421
  return;
1446
1422
  }
1447
1423
  console.log();
1448
- info(`Found ${results.length} result(s) for ${label}:
1424
+ info(`Found ${response.total} result(s) for ${label}:
1449
1425
  `);
1450
- for (const bp of results) {
1426
+ for (const bp of response.data) {
1451
1427
  formatResult(bp);
1452
1428
  }
1429
+ console.log(
1430
+ import_chalk4.default.dim(` Browse more results and details at https://updose.dev/`)
1431
+ );
1453
1432
  } catch (err) {
1454
1433
  error(
1455
1434
  err instanceof Error ? err.message : "An unexpected error occurred during search."
@@ -1463,10 +1442,9 @@ function formatResult(bp) {
1463
1442
  if (bp.description) {
1464
1443
  console.log(` ${bp.description}`);
1465
1444
  }
1466
- const rating = bp.avg_rating > 0 ? `${import_chalk4.default.yellow("\u2605")} ${bp.avg_rating}${bp.rating_count > 0 ? import_chalk4.default.dim(` (${bp.rating_count})`) : ""}` : import_chalk4.default.dim("\u2605 -");
1467
1445
  const downloads = `${import_chalk4.default.green("\u2193")} ${bp.downloads.toLocaleString()}`;
1468
1446
  const targets = import_chalk4.default.cyan(bp.targets.join(", "));
1469
- console.log(` ${rating} ${downloads} ${targets}`);
1447
+ console.log(` ${downloads} ${targets}`);
1470
1448
  if (bp.tags.length > 0) {
1471
1449
  console.log(` ${bp.tags.map((t) => import_chalk4.default.dim(`#${t}`)).join(" ")}`);
1472
1450
  }
@@ -1477,7 +1455,7 @@ function formatResult(bp) {
1477
1455
 
1478
1456
  // src/index.ts
1479
1457
  var program = new import_commander.Command();
1480
- program.name("updose").description("AI coding tool boilerplate marketplace").version("0.3.0");
1458
+ program.name("updose").description("AI coding tool boilerplate marketplace").version("0.4.0");
1481
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);
1482
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);
1483
1461
  program.command("init").description("Scaffold a new boilerplate repository").option("--dir <dir>", "Create boilerplate in a subdirectory").action(initCommand);
package/dist/index.js CHANGED
@@ -3,6 +3,9 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
+ // src/commands/add.ts
7
+ import { createHash } from "crypto";
8
+
6
9
  // src/constants.ts
7
10
  var USER_AGENT = "updose-cli";
8
11
  var MANIFEST_FILENAME = "updose.json";
@@ -29,14 +32,18 @@ async function searchBoilerplates(query, filters) {
29
32
  }
30
33
  return await res.json();
31
34
  }
32
- async function recordDownload(repo, dir) {
35
+ async function recordDownload(repo, dir, projectHash) {
33
36
  await fetch(`${API_BASE_URL}/download`, {
34
37
  method: "POST",
35
38
  headers: {
36
39
  "Content-Type": "application/json",
37
40
  "User-Agent": USER_AGENT
38
41
  },
39
- body: JSON.stringify({ repo, dir: dir ?? null }),
42
+ body: JSON.stringify({
43
+ repo,
44
+ dir: dir ?? null,
45
+ ...projectHash ? { project_hash: projectHash } : {}
46
+ }),
40
47
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
41
48
  });
42
49
  }
@@ -335,35 +342,10 @@ function handleHttpError(res) {
335
342
  );
336
343
  }
337
344
  }
338
- var branchCache = /* @__PURE__ */ new Map();
339
- async function getDefaultBranch(repo) {
340
- const cached = branchCache.get(repo);
341
- if (cached) return cached;
342
- const { owner, name } = parseRepo(repo);
343
- const res = await fetch(`${GITHUB_API_URL}/repos/${owner}/${name}`, {
344
- headers: {
345
- Accept: GITHUB_ACCEPT_HEADER,
346
- "User-Agent": USER_AGENT,
347
- ...getAuthHeaders()
348
- },
349
- signal: createSignal()
350
- });
351
- if (res.status === 404) {
352
- throw new Error(`Repository not found: ${repo}`);
353
- }
354
- handleHttpError(res);
355
- if (!res.ok) {
356
- throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);
357
- }
358
- const data = await res.json();
359
- branchCache.set(repo, data.default_branch);
360
- return data.default_branch;
361
- }
362
345
  async function fetchFile(repo, path) {
363
346
  const { owner, name } = parseRepo(repo);
364
- const branch = await getDefaultBranch(repo);
365
347
  const encodedPath = path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
366
- const url = `${GITHUB_RAW}/${owner}/${name}/${branch}/${encodedPath}`;
348
+ const url = `${GITHUB_RAW}/${owner}/${name}/HEAD/${encodedPath}`;
367
349
  const res = await fetch(url, {
368
350
  headers: { "User-Agent": USER_AGENT, ...getAuthHeaders() },
369
351
  signal: createSignal()
@@ -395,8 +377,7 @@ async function fetchManifest(repo, dir) {
395
377
  }
396
378
  async function fetchRepoTree(repo) {
397
379
  const { owner, name } = parseRepo(repo);
398
- const branch = await getDefaultBranch(repo);
399
- const url = `${GITHUB_API_URL}/repos/${owner}/${name}/git/trees/${branch}?recursive=1`;
380
+ const url = `${GITHUB_API_URL}/repos/${owner}/${name}/git/trees/HEAD?recursive=1`;
400
381
  const res = await fetch(url, {
401
382
  headers: {
402
383
  Accept: GITHUB_ACCEPT_HEADER,
@@ -787,7 +768,8 @@ async function addCommand(repoInput, options) {
787
768
  const summary = skillsInstalled > 0 ? `${installed} file(s) + ${skillsInstalled} skill(s)` : `${installed} file(s)`;
788
769
  success(`Done! ${summary} installed, ${skipped} skipped.`);
789
770
  if (installed + skillsInstalled > 0) {
790
- await recordDownload(repo, dir).catch(() => {
771
+ const projectHash = createHash("sha256").update(cwd).digest("hex");
772
+ await recordDownload(repo, dir, projectHash).catch(() => {
791
773
  });
792
774
  }
793
775
  } catch (err) {
@@ -1405,29 +1387,26 @@ function detectRepo(cwd) {
1405
1387
  import chalk4 from "chalk";
1406
1388
  async function searchCommand(query, options) {
1407
1389
  try {
1408
- if (!query && !options.target && !options.tag && !options.author) {
1409
- error(
1410
- "Please provide a search query or at least one filter (--target, --tag, --author)."
1411
- );
1412
- process.exitCode = 1;
1413
- return;
1414
- }
1415
1390
  const filters = {};
1416
1391
  if (options.target) filters.target = options.target;
1417
1392
  if (options.tag) filters.tag = options.tag;
1418
1393
  if (options.author) filters.author = options.author;
1419
- const results = await searchBoilerplates(query, filters);
1420
- const label = query ? `"${query}"` : "the given filters";
1421
- if (results.length === 0) {
1394
+ const hasParams = !!(query || filters.target || filters.tag || filters.author);
1395
+ const label = query ? `"${query}"` : hasParams ? "the given filters" : "popular boilerplates";
1396
+ const response = await searchBoilerplates(query, filters);
1397
+ if (response.data.length === 0) {
1422
1398
  info(`No boilerplates found for ${label}`);
1423
1399
  return;
1424
1400
  }
1425
1401
  console.log();
1426
- info(`Found ${results.length} result(s) for ${label}:
1402
+ info(`Found ${response.total} result(s) for ${label}:
1427
1403
  `);
1428
- for (const bp of results) {
1404
+ for (const bp of response.data) {
1429
1405
  formatResult(bp);
1430
1406
  }
1407
+ console.log(
1408
+ chalk4.dim(` Browse more results and details at https://updose.dev/`)
1409
+ );
1431
1410
  } catch (err) {
1432
1411
  error(
1433
1412
  err instanceof Error ? err.message : "An unexpected error occurred during search."
@@ -1441,10 +1420,9 @@ function formatResult(bp) {
1441
1420
  if (bp.description) {
1442
1421
  console.log(` ${bp.description}`);
1443
1422
  }
1444
- const rating = bp.avg_rating > 0 ? `${chalk4.yellow("\u2605")} ${bp.avg_rating}${bp.rating_count > 0 ? chalk4.dim(` (${bp.rating_count})`) : ""}` : chalk4.dim("\u2605 -");
1445
1423
  const downloads = `${chalk4.green("\u2193")} ${bp.downloads.toLocaleString()}`;
1446
1424
  const targets = chalk4.cyan(bp.targets.join(", "));
1447
- console.log(` ${rating} ${downloads} ${targets}`);
1425
+ console.log(` ${downloads} ${targets}`);
1448
1426
  if (bp.tags.length > 0) {
1449
1427
  console.log(` ${bp.tags.map((t) => chalk4.dim(`#${t}`)).join(" ")}`);
1450
1428
  }
@@ -1455,7 +1433,7 @@ function formatResult(bp) {
1455
1433
 
1456
1434
  // src/index.ts
1457
1435
  var program = new Command();
1458
- program.name("updose").description("AI coding tool boilerplate marketplace").version("0.3.0");
1436
+ program.name("updose").description("AI coding tool boilerplate marketplace").version("0.4.0");
1459
1437
  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);
1460
1438
  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);
1461
1439
  program.command("init").description("Scaffold a new boilerplate repository").option("--dir <dir>", "Create boilerplate in a subdirectory").action(initCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "updose",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "AI coding tool boilerplate marketplace",
6
6
  "main": "dist/index.cjs",