sayren 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +102 -183
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -6502,24 +6502,6 @@ const cli_init_dir_not_empty = ((inputs, options = {}) => {
6502
6502
  return ko_cli_init_dir_not_empty(inputs);
6503
6503
  });
6504
6504
 
6505
- //#endregion
6506
- //#region src/paraglide/messages/cli_init_hosting_disabled.js
6507
- /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
6508
- /** @typedef {{}} Cli_Init_Hosting_DisabledInputs */
6509
- const ko_cli_init_hosting_disabled = () => {
6510
- return `지금은 새 호스팅 사이트를 만들 수 없습니다(호스팅 기능이 꺼져 있습니다)`;
6511
- };
6512
- /**
6513
-
6514
- * @param {Cli_Init_Hosting_DisabledInputs} inputs
6515
- * @param {{ locale?: "ko" }} options
6516
- * @returns {LocalizedString}
6517
- */
6518
- const cli_init_hosting_disabled = ((inputs = {}, options = {}) => {
6519
- experimentalStaticLocale ?? options.locale ?? getLocale();
6520
- return ko_cli_init_hosting_disabled(inputs);
6521
- });
6522
-
6523
6505
  //#endregion
6524
6506
  //#region src/paraglide/messages/cli_init_inside_pulled.js
6525
6507
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
@@ -8039,58 +8021,57 @@ const cli_retired_token = ((inputs = {}, options = {}) => {
8039
8021
  });
8040
8022
 
8041
8023
  //#endregion
8042
- //#region src/paraglide/messages/cli_skills_install_failed.js
8024
+ //#region src/paraglide/messages/cli_skill_failed.js
8043
8025
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
8044
- /** @typedef {{ command: NonNullable<unknown>, detail: NonNullable<unknown> }} Cli_Skills_Install_FailedInputs */
8045
- const ko_cli_skills_install_failed = (i) => {
8046
- return `에이전트 스킬을 설치하지 못했습니다. 받은 폴더에서 직접 실행하십시오: ${i?.command}
8047
- ${i?.detail}`;
8026
+ /** @typedef {{ status: NonNullable<unknown> }} Cli_Skill_FailedInputs */
8027
+ const ko_cli_skill_failed = (i) => {
8028
+ return `스킬을 읽지 못했습니다(HTTP ${i?.status})`;
8048
8029
  };
8049
8030
  /**
8050
8031
 
8051
- * @param {Cli_Skills_Install_FailedInputs} inputs
8032
+ * @param {Cli_Skill_FailedInputs} inputs
8052
8033
  * @param {{ locale?: "ko" }} options
8053
8034
  * @returns {LocalizedString}
8054
8035
  */
8055
- const cli_skills_install_failed = ((inputs, options = {}) => {
8036
+ const cli_skill_failed = ((inputs, options = {}) => {
8056
8037
  experimentalStaticLocale ?? options.locale ?? getLocale();
8057
- return ko_cli_skills_install_failed(inputs);
8038
+ return ko_cli_skill_failed(inputs);
8058
8039
  });
8059
8040
 
8060
8041
  //#endregion
8061
- //#region src/paraglide/messages/cli_skills_installed.js
8042
+ //#region src/paraglide/messages/cli_skill_not_found.js
8062
8043
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
8063
- /** @typedef {{}} Cli_Skills_InstalledInputs */
8064
- const ko_cli_skills_installed = () => {
8065
- return `에이전트 스킬 설치 완료 — .claude/skills/ (배포 때 올리지 않습니다)`;
8044
+ /** @typedef {{ name: NonNullable<unknown> }} Cli_Skill_Not_FoundInputs */
8045
+ const ko_cli_skill_not_found = (i) => {
8046
+ return `스킬 ${i?.name}이(가) 없습니다. npx sayren skill list로 목록을 확인하십시오`;
8066
8047
  };
8067
8048
  /**
8068
8049
 
8069
- * @param {Cli_Skills_InstalledInputs} inputs
8050
+ * @param {Cli_Skill_Not_FoundInputs} inputs
8070
8051
  * @param {{ locale?: "ko" }} options
8071
8052
  * @returns {LocalizedString}
8072
8053
  */
8073
- const cli_skills_installed = ((inputs = {}, options = {}) => {
8054
+ const cli_skill_not_found = ((inputs, options = {}) => {
8074
8055
  experimentalStaticLocale ?? options.locale ?? getLocale();
8075
- return ko_cli_skills_installed(inputs);
8056
+ return ko_cli_skill_not_found(inputs);
8076
8057
  });
8077
8058
 
8078
8059
  //#endregion
8079
- //#region src/paraglide/messages/cli_skills_installing.js
8060
+ //#region src/paraglide/messages/cli_skill_usage.js
8080
8061
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
8081
- /** @typedef {{ source: NonNullable<unknown> }} Cli_Skills_InstallingInputs */
8082
- const ko_cli_skills_installing = (i) => {
8083
- return `에이전트 스킬 설치 중 — ${i?.source}`;
8062
+ /** @typedef {{}} Cli_Skill_UsageInputs */
8063
+ const ko_cli_skill_usage = () => {
8064
+ return `스킬을 지정하십시오: npx sayren skill list 또는 npx sayren skill <이름>`;
8084
8065
  };
8085
8066
  /**
8086
8067
 
8087
- * @param {Cli_Skills_InstallingInputs} inputs
8068
+ * @param {Cli_Skill_UsageInputs} inputs
8088
8069
  * @param {{ locale?: "ko" }} options
8089
8070
  * @returns {LocalizedString}
8090
8071
  */
8091
- const cli_skills_installing = ((inputs, options = {}) => {
8072
+ const cli_skill_usage = ((inputs = {}, options = {}) => {
8092
8073
  experimentalStaticLocale ?? options.locale ?? getLocale();
8093
- return ko_cli_skills_installing(inputs);
8074
+ return ko_cli_skill_usage(inputs);
8094
8075
  });
8095
8076
 
8096
8077
  //#endregion
@@ -8801,6 +8782,7 @@ const ko_cli_usage = (i) => {
8801
8782
  npx sayren deploy [--no-publish] [--skip-build] [--message <메모>] [--force [--yes]]
8802
8783
  npx sayren status
8803
8784
  npx sayren verify
8785
+ npx sayren skill list | <이름> # sayren 스킬 목록·본문(에이전트용, 로그인 없음)
8804
8786
  npx sayren app <명령> # 서드파티 앱(init·dev·trigger·verify·deploy·release·status·logs·secret·dev-store)
8805
8787
 
8806
8788
  명령:
@@ -8812,6 +8794,7 @@ const ko_cli_usage = (i) => {
8812
8794
  deploy 로컬 검사 → 로컬 빌드 → 작업 트리 올리기 → 빌드 → 발행. 원격이 pull 이후 바뀌었으면 덮어쓰지 않습니다
8813
8795
  status 사이트·발행 버전·최근 빌드·편집 환경
8814
8796
  verify 로컬 규칙·경로·크기 검사(서버 빌드와 같은 규칙)
8797
+ skill sayren 스킬 목록(list)과 본문(<이름>)을 출력합니다. 스킬은 중앙에서 고치므로 늘 최신입니다
8815
8798
  app 서드파티 앱 — init <handle>로 만들고 dev로 로컬 실행, deploy로 버전을 올리고 release로 적용합니다. 자세히: https://docs.sayren.io/guides/apps-getting-started
8816
8799
 
8817
8800
  토큰은 Sayren 계정 토큰만 받고 --token → SAYREN_TOKEN 환경 변수 → 로그인(credentials.json) 순서로 읽습니다.
@@ -9743,7 +9726,7 @@ async function selectStore(auth, client, io, candidates, folderStore = null) {
9743
9726
 
9744
9727
  //#endregion
9745
9728
  //#region package.json
9746
- var version = "0.11.0";
9729
+ var version = "0.12.1";
9747
9730
 
9748
9731
  //#endregion
9749
9732
  //#region src/client.ts
@@ -11586,116 +11569,6 @@ async function gunzip(bytes, maxBytes) {
11586
11569
  return out;
11587
11570
  }
11588
11571
 
11589
- //#endregion
11590
- //#region src/local-build.ts
11591
- /** 실패할 때 보여 주는 빌드 출력 줄 수 */
11592
- const OUTPUT_TAIL_LINES = 40;
11593
- /** 모아 두는 출력 상한(문자) — 긴 빌드 로그를 다 쥐고 있지 않는다 */
11594
- const OUTPUT_KEEP_CHARS$1 = 64 * 1024;
11595
- /** 빌드를 돌릴 수 있는 폴더인가 — `build` 스크립트와 설치된 의존성(`node_modules`)이 있어야 한다 */
11596
- function planLocalBuild(dir) {
11597
- let scripts;
11598
- try {
11599
- scripts = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).scripts;
11600
- } catch {
11601
- return { kind: "no-script" };
11602
- }
11603
- const build = scripts && typeof scripts === "object" ? scripts.build : void 0;
11604
- if (typeof build !== "string" || !build.trim()) return { kind: "no-script" };
11605
- if (!existsSync(join(dir, "node_modules"))) return { kind: "no-deps" };
11606
- return { kind: "run" };
11607
- }
11608
- /** `npm run build`를 돌린다. 출력은 모아 두고(실패할 때 꼬리를 보여 준다) 화면에 흘리지 않는다 */
11609
- function runNpmBuild(dir, env) {
11610
- return new Promise((resolve$1, reject) => {
11611
- let output = "";
11612
- const keep = (chunk) => {
11613
- output = (output + chunk.toString("utf8")).slice(-OUTPUT_KEEP_CHARS$1);
11614
- };
11615
- const child = spawn("npm", ["run", "build"], {
11616
- cwd: dir,
11617
- env,
11618
- stdio: [
11619
- "ignore",
11620
- "pipe",
11621
- "pipe"
11622
- ],
11623
- shell: process.platform === "win32"
11624
- });
11625
- child.stdout?.on("data", keep);
11626
- child.stderr?.on("data", keep);
11627
- child.once("error", reject);
11628
- child.once("close", (code) => resolve$1({
11629
- code,
11630
- output: tailOf(output)
11631
- }));
11632
- });
11633
- }
11634
- function tailOf(output, lines = OUTPUT_TAIL_LINES) {
11635
- return output.trimEnd().split(/\r?\n/).slice(-lines).join("\n");
11636
- }
11637
-
11638
- //#endregion
11639
- //#region src/skills-install.ts
11640
- /**
11641
- * sayren 중앙 스킬 설치(2026-10-16 사용자 결정) — `pull`·`init`이 받은 폴더에서 `npx skills add https://sayren.io`를 돌려
11642
- * `.claude/skills/{name}/`에 스킬을 넣는다. 설치는 실패해도 받기를 실패시키지 않는다(명령을 알려 준다).
11643
- * 설치된 스킬은 로컬 설치물이라 올리지 않는다(`installedSkillNames` → `collectTree`).
11644
- */
11645
- const SKILLS_SOURCE = "https://sayren.io";
11646
- /** 확인한 skills CLI 버전 — 디스커버리 0.2.0 색인을 읽는다 */
11647
- const SKILLS_CLI = "skills@1.7.2";
11648
- const SKILLS_ARGS = [
11649
- "-y",
11650
- SKILLS_CLI,
11651
- "add",
11652
- SKILLS_SOURCE,
11653
- "--agent",
11654
- "claude-code",
11655
- "-y"
11656
- ];
11657
- const SKILLS_COMMAND = `npx ${SKILLS_CLI} add ${SKILLS_SOURCE} --agent claude-code`;
11658
- /** skills CLI가 설치한 스킬을 적는 파일 */
11659
- const SKILLS_LOCK_FILE = "skills-lock.json";
11660
- const OUTPUT_KEEP_CHARS = 64 * 1024;
11661
- /** `npx skills add`를 돌린다. 출력은 모아 두고(실패할 때 꼬리를 보여 준다) 화면에 흘리지 않는다 */
11662
- function runSkillsInstall(dir, env) {
11663
- return new Promise((resolve$1, reject) => {
11664
- let output = "";
11665
- const keep = (chunk) => {
11666
- output = (output + chunk.toString("utf8")).slice(-OUTPUT_KEEP_CHARS);
11667
- };
11668
- const child = spawn("npx", SKILLS_ARGS, {
11669
- cwd: dir,
11670
- env,
11671
- stdio: [
11672
- "ignore",
11673
- "pipe",
11674
- "pipe"
11675
- ],
11676
- shell: process.platform === "win32"
11677
- });
11678
- child.stdout?.on("data", keep);
11679
- child.stderr?.on("data", keep);
11680
- child.once("error", reject);
11681
- child.once("close", (code) => resolve$1({
11682
- code,
11683
- output: tailOf(output, OUTPUT_TAIL_LINES)
11684
- }));
11685
- });
11686
- }
11687
- /** skills CLI로 설치한 스킬 이름(`skills-lock.json`) — 없거나 읽지 못하면 빈 집합 */
11688
- function installedSkillNames(root) {
11689
- const path = join(root, SKILLS_LOCK_FILE);
11690
- if (!existsSync(path)) return /* @__PURE__ */ new Set();
11691
- try {
11692
- const skills = JSON.parse(readFileSync(path, "utf8")).skills;
11693
- return new Set(skills && typeof skills === "object" ? Object.keys(skills) : []);
11694
- } catch {
11695
- return /* @__PURE__ */ new Set();
11696
- }
11697
- }
11698
-
11699
11572
  //#endregion
11700
11573
  //#region src/tree.ts
11701
11574
  /**
@@ -11729,11 +11602,6 @@ const sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
11729
11602
  function collectTree(root) {
11730
11603
  const files = [];
11731
11604
  const skipped = [];
11732
- const installedSkills = installedSkillNames(root);
11733
- const isInstalledSkill = (rel) => {
11734
- const [first, second, name] = rel.split("/");
11735
- return first === ".claude" && second === "skills" && name !== void 0 && installedSkills.has(name);
11736
- };
11737
11605
  const walk = (dir) => {
11738
11606
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
11739
11607
  const full = join(dir, entry.name);
@@ -11747,7 +11615,7 @@ function collectTree(root) {
11747
11615
  skipped.push(rel);
11748
11616
  continue;
11749
11617
  }
11750
- if (isNeverUpload(rel) || accessOf(rel) === null || isInstalledSkill(rel)) {
11618
+ if (isNeverUpload(rel) || accessOf(rel) === null) {
11751
11619
  skipped.push(rel);
11752
11620
  continue;
11753
11621
  }
@@ -11949,11 +11817,11 @@ function lockImporterSpecifiers(lock) {
11949
11817
  //#endregion
11950
11818
  //#region src/guide-skill.ts
11951
11819
  /**
11952
- * 받은 폴더의 에이전트 안내 스킬(2026-10-16 사용자 결정) — 셀러의 Claude Code가 sayren 중앙 스킬을 HTTP로 읽게 한다.
11820
+ * 받은 폴더의 에이전트 안내 스킬(2026-10-16 사용자 결정) — 셀러의 Claude Code가 기능·화면을 고치기 전에 sayren 중앙 스킬을 `npx sayren skill`로 읽게 한다(스킬은 이 하나만 설치한다).
11953
11821
  * 빈 템플릿이 싣는 `templates/storefront-blank/.claude/skills/sayren/SKILL.md`와 같은 내용이다(`guide-skill.test.ts`가 고정한다).
11954
11822
  */
11955
11823
  const GUIDE_SKILL_PATH = ".claude/skills/sayren/SKILL.md";
11956
- const GUIDE_SKILL = "---\nname: sayren\ndescription: Use for any work on this sayren storefront — pages, product display, cart, checkout, payments, buyer login, account pages, analytics, design and code quality. Loads the current sayren skills over HTTP.\n---\n\n# sayren skills (central)\n\nThe sayren skills are maintained centrally and change without updating this project. Read them over HTTP at the start of a task instead of relying on memory.\n\n1. List the skills: `GET https://api.sayren.io/v1/public/agent-skills`\n The response is `{ \"data\": [{ \"name\", \"description\", \"version\", \"updatedAt\" }] }`.\n2. Read the skills that match the task: `GET https://api.sayren.io/v1/public/agent-skills/{name}`\n The response is `{ \"data\": { \"name\", \"description\", \"body\", \"version\", \"updatedAt\" } }`; `body` is the guide (markdown).\n\nStart points:\n- Any code change: `code-quality`, `sayren-storefront-rules`, `sdk-client`, `template-stack`\n- Product pages: `sayren-product-display`, `sdk-catalog`\n- Cart, checkout, payment: `sdk-cart`, `sdk-checkout`, `sdk-payments`\n- Buyer login and account pages: `sdk-buyer-auth`, `sdk-account`\n- Visual design: `design-quality`\n\nFollow the project's `CLAUDE.md` first; the skills add the sayren-specific details.\n";
11824
+ const GUIDE_SKILL = "---\nname: sayren\ndescription: Load before adding, implementing or changing any feature or page of this sayren storefront. Lists the current sayren skills and loads the ones the task needs.\n---\n\n# sayren skills\n\nThe sayren skills are maintained centrally and change without updating this project.\nDo not rely on memory or earlier copies. Load them at the start of each task.\n\n1. List the skills (name and when to use each):\n `npx -y sayren@latest skill list`\n2. Read every skill that matches the task, in full, and follow it:\n `npx -y sayren@latest skill <name>`\n\nIf the sayren MCP server is connected, the tools `list_skills` and `get_skill` return the same content.\n\nFollow the project's `CLAUDE.md` first. The skills add the sayren-specific details.\n";
11957
11825
  /**
11958
11826
  * 받은 트리에 안내 스킬이 없으면 넣는다 — 템플릿이 싣거나 셀러가 올린 내용이 우선이다. 트리에 넣으므로 다시 받을 때 지울 파일로 잡히지 않고
11959
11827
  * `deploy`가 원격 작업 트리에도 올린다(AI 상점 도우미가 같은 안내를 읽는다). 넣었으면 true
@@ -11964,6 +11832,55 @@ function withGuideSkill(files) {
11964
11832
  return true;
11965
11833
  }
11966
11834
 
11835
+ //#endregion
11836
+ //#region src/local-build.ts
11837
+ /** 실패할 때 보여 주는 빌드 출력 줄 수 */
11838
+ const OUTPUT_TAIL_LINES = 40;
11839
+ /** 모아 두는 출력 상한(문자) — 긴 빌드 로그를 다 쥐고 있지 않는다 */
11840
+ const OUTPUT_KEEP_CHARS = 64 * 1024;
11841
+ /** 빌드를 돌릴 수 있는 폴더인가 — `build` 스크립트와 설치된 의존성(`node_modules`)이 있어야 한다 */
11842
+ function planLocalBuild(dir) {
11843
+ let scripts;
11844
+ try {
11845
+ scripts = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).scripts;
11846
+ } catch {
11847
+ return { kind: "no-script" };
11848
+ }
11849
+ const build = scripts && typeof scripts === "object" ? scripts.build : void 0;
11850
+ if (typeof build !== "string" || !build.trim()) return { kind: "no-script" };
11851
+ if (!existsSync(join(dir, "node_modules"))) return { kind: "no-deps" };
11852
+ return { kind: "run" };
11853
+ }
11854
+ /** `npm run build`를 돌린다. 출력은 모아 두고(실패할 때 꼬리를 보여 준다) 화면에 흘리지 않는다 */
11855
+ function runNpmBuild(dir, env) {
11856
+ return new Promise((resolve$1, reject) => {
11857
+ let output = "";
11858
+ const keep = (chunk) => {
11859
+ output = (output + chunk.toString("utf8")).slice(-OUTPUT_KEEP_CHARS);
11860
+ };
11861
+ const child = spawn("npm", ["run", "build"], {
11862
+ cwd: dir,
11863
+ env,
11864
+ stdio: [
11865
+ "ignore",
11866
+ "pipe",
11867
+ "pipe"
11868
+ ],
11869
+ shell: process.platform === "win32"
11870
+ });
11871
+ child.stdout?.on("data", keep);
11872
+ child.stderr?.on("data", keep);
11873
+ child.once("error", reject);
11874
+ child.once("close", (code) => resolve$1({
11875
+ code,
11876
+ output: tailOf(output)
11877
+ }));
11878
+ });
11879
+ }
11880
+ function tailOf(output, lines = OUTPUT_TAIL_LINES) {
11881
+ return output.trimEnd().split(/\r?\n/).slice(-lines).join("\n");
11882
+ }
11883
+
11967
11884
  //#endregion
11968
11885
  //#region src/commands.ts
11969
11886
  /** 종료 코드 — 0 성공 · 1 실패 · 3 발행이 승인 대기(에이전트 토큰) */
@@ -12152,33 +12069,10 @@ async function receiveSource(client, auth, source, flags, io, migratedDir) {
12152
12069
  file: STATE_FILE,
12153
12070
  siteId: source.siteId
12154
12071
  }));
12155
- await installSkills(target, io);
12156
12072
  if (source.editSessionOpen) io.out(cli_pull_edit_session_open());
12157
12073
  io.out(cli_pull_next_steps({ dir: pulledDir }));
12158
12074
  return 0;
12159
12075
  }
12160
- /** sayren 중앙 스킬을 받은 폴더에 설치한다 — 실패해도 받기는 성공이다(직접 돌릴 명령을 알린다) */
12161
- async function installSkills(dir, io) {
12162
- io.out(cli_skills_installing({ source: SKILLS_SOURCE }));
12163
- let result;
12164
- try {
12165
- result = await (io.installSkills ?? ((at) => runSkillsInstall(at, io.env)))(dir);
12166
- } catch (error) {
12167
- io.err(cli_skills_install_failed({
12168
- command: SKILLS_COMMAND,
12169
- detail: error instanceof Error ? error.message : String(error)
12170
- }));
12171
- return;
12172
- }
12173
- if (result.code === 0) {
12174
- io.out(cli_skills_installed());
12175
- return;
12176
- }
12177
- io.err(cli_skills_install_failed({
12178
- command: SKILLS_COMMAND,
12179
- detail: result.output || `exit ${result.code}`
12180
- }));
12181
- }
12182
12076
  /**
12183
12077
  * `sayren init [폴더]`(#101) — 템플릿을 골라 호스팅 사이트를 만들고(`POST /v1/storefront/site`) `pull`과 같은 폴더를 만든다.
12184
12078
  * 사이트가 이미 있으면 만들지 않고 `pull`을 안내한다(상점당 열린 사이트는 하나다). 만들기는 상점 한도(30일 3회)를 쓰므로 확인을 묻는다.
@@ -12197,7 +12091,6 @@ async function init(flags, io) {
12197
12091
  io.out(cli_init_site_url({ url: hosting.site.url }));
12198
12092
  throw new CliError(cli_init_site_exists({ storeId }));
12199
12093
  }
12200
- if (!hosting.enabled) throw new CliError(cli_init_hosting_disabled());
12201
12094
  if (!hosting.editingEnabled) throw new CliError(cli_pull_editing_disabled());
12202
12095
  const template = await pickTemplate(await client.storefront.listTemplates(), flags.template, io);
12203
12096
  io.out(cli_init_summary({
@@ -12730,6 +12623,31 @@ function relativeOf(io, path) {
12730
12623
  return relative(io.cwd, path) || ".";
12731
12624
  }
12732
12625
 
12626
+ //#endregion
12627
+ //#region src/skill-commands.ts
12628
+ /**
12629
+ * `sayren skill list`·`sayren skill <name>`(2026-10-16 사용자 결정) — 안내 스킬 `sayren`이 에이전트에게 부르게 하는 중앙 스킬 읽기.
12630
+ * 공개 스킬 API(`/v1/public/agent-skills`, 인증 없음)를 읽어 원문을 출력한다. 셸·OS와 무관하게 같은 명령이고 요약되지 않는다
12631
+ */
12632
+ async function skill(args, flags, io) {
12633
+ const [target] = args;
12634
+ if (!target) throw new CliError(cli_skill_usage());
12635
+ const { apiOrigin } = resolveApiOrigin(flags, io.cwd, null, io.env);
12636
+ const path = target === "list" ? "" : `/${encodeURIComponent(target)}`;
12637
+ const res = await io.fetch(`${apiOrigin}/v1/public/agent-skills${path}`, { headers: { "User-Agent": CLI_USER_AGENT } });
12638
+ if (res.status === 404) throw new CliError(cli_skill_not_found({ name: target }));
12639
+ if (!res.ok) throw new CliError(cli_skill_failed({ status: res.status }));
12640
+ const { data } = await res.json();
12641
+ if (target === "list") {
12642
+ const skills = data;
12643
+ const width = Math.max(...skills.map((s) => s.name.length));
12644
+ for (const s of skills) io.out(`${s.name.padEnd(width)} ${s.description}`);
12645
+ return 0;
12646
+ }
12647
+ io.out(data.body);
12648
+ return 0;
12649
+ }
12650
+
12733
12651
  //#endregion
12734
12652
  //#region src/index.ts
12735
12653
  /** 도움말 — 쓰는 자리에서 만든다(로케일이 정해진 뒤) */
@@ -12869,6 +12787,7 @@ async function main(argv, io) {
12869
12787
  case "login": return login(loginFlags, io, () => ask(cli_prompt_access_token()));
12870
12788
  case "logout": return logout(loginFlags, io);
12871
12789
  case "whoami": return whoami(common, io);
12790
+ case "skill": return skill(parsed.positional, { apiOrigin: common.apiOrigin }, io);
12872
12791
  default: throw new CliError(cli_unknown_command({
12873
12792
  command: parsed.command,
12874
12793
  usage: usage()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sayren",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "type": "module",
5
5
  "imports": {
6
6
  "#paraglide/*": "./src/paraglide/*"
@@ -28,7 +28,7 @@
28
28
  "tsx": "^4.20.3",
29
29
  "typescript": "^6.0.3",
30
30
  "vitest": "^5.0.1",
31
- "@sayren/mcp": "0.26.0"
31
+ "@sayren/mcp": "0.26.1"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"