sayren 0.9.0 → 0.11.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/dist/index.mjs +230 -161
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -2261,84 +2261,6 @@ const mcp_rule_payment_browser_launch_why = ((inputs = {}, options = {}) => {
|
|
|
2261
2261
|
return ko_mcp_rule_payment_browser_launch_why(inputs);
|
|
2262
2262
|
});
|
|
2263
2263
|
|
|
2264
|
-
//#endregion
|
|
2265
|
-
//#region ../mcp/src/paraglide/messages/mcp_rule_payment_popup_prepare_example.js
|
|
2266
|
-
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
2267
|
-
/** @typedef {{}} Mcp_Rule_Payment_Popup_Prepare_ExampleInputs */
|
|
2268
|
-
const ko_mcp_rule_payment_popup_prepare_example = () => {
|
|
2269
|
-
return `const paymentWindow = payments.prepareWindow(); // 클릭 시점에 먼저 연다
|
|
2270
|
-
const { start, error } = await startPayment({ data });
|
|
2271
|
-
if (!start) {
|
|
2272
|
-
paymentWindow.close();
|
|
2273
|
-
return;
|
|
2274
|
-
}
|
|
2275
|
-
await payments.open(parsed, { window: paymentWindow });`;
|
|
2276
|
-
};
|
|
2277
|
-
/**
|
|
2278
|
-
|
|
2279
|
-
* @param {Mcp_Rule_Payment_Popup_Prepare_ExampleInputs} inputs
|
|
2280
|
-
* @param {{ locale?: "ko" }} options
|
|
2281
|
-
* @returns {LocalizedString}
|
|
2282
|
-
*/
|
|
2283
|
-
const mcp_rule_payment_popup_prepare_example = ((inputs = {}, options = {}) => {
|
|
2284
|
-
experimentalStaticLocale$1 ?? options.locale ?? getLocale$1();
|
|
2285
|
-
return ko_mcp_rule_payment_popup_prepare_example(inputs);
|
|
2286
|
-
});
|
|
2287
|
-
|
|
2288
|
-
//#endregion
|
|
2289
|
-
//#region ../mcp/src/paraglide/messages/mcp_rule_payment_popup_prepare_fix.js
|
|
2290
|
-
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
2291
|
-
/** @typedef {{}} Mcp_Rule_Payment_Popup_Prepare_FixInputs */
|
|
2292
|
-
const ko_mcp_rule_payment_popup_prepare_fix = () => {
|
|
2293
|
-
return `클릭 핸들러 첫 줄에서 \`payments.prepareWindow()\`로 빈 창을 열고, 서버 함수가 끝나면 그 핸들을 \`payments.open(start, { window })\`에 넘긴다. 결제 시작이 실패하면 \`window.close()\`로 닫는다`;
|
|
2294
|
-
};
|
|
2295
|
-
/**
|
|
2296
|
-
|
|
2297
|
-
* @param {Mcp_Rule_Payment_Popup_Prepare_FixInputs} inputs
|
|
2298
|
-
* @param {{ locale?: "ko" }} options
|
|
2299
|
-
* @returns {LocalizedString}
|
|
2300
|
-
*/
|
|
2301
|
-
const mcp_rule_payment_popup_prepare_fix = ((inputs = {}, options = {}) => {
|
|
2302
|
-
experimentalStaticLocale$1 ?? options.locale ?? getLocale$1();
|
|
2303
|
-
return ko_mcp_rule_payment_popup_prepare_fix(inputs);
|
|
2304
|
-
});
|
|
2305
|
-
|
|
2306
|
-
//#endregion
|
|
2307
|
-
//#region ../mcp/src/paraglide/messages/mcp_rule_payment_popup_prepare_title.js
|
|
2308
|
-
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
2309
|
-
/** @typedef {{}} Mcp_Rule_Payment_Popup_Prepare_TitleInputs */
|
|
2310
|
-
const ko_mcp_rule_payment_popup_prepare_title = () => {
|
|
2311
|
-
return `결제 시작이 서버 함수를 거치면 클릭 시점에 \`payments.prepareWindow()\`로 창을 먼저 연다`;
|
|
2312
|
-
};
|
|
2313
|
-
/**
|
|
2314
|
-
|
|
2315
|
-
* @param {Mcp_Rule_Payment_Popup_Prepare_TitleInputs} inputs
|
|
2316
|
-
* @param {{ locale?: "ko" }} options
|
|
2317
|
-
* @returns {LocalizedString}
|
|
2318
|
-
*/
|
|
2319
|
-
const mcp_rule_payment_popup_prepare_title = ((inputs = {}, options = {}) => {
|
|
2320
|
-
experimentalStaticLocale$1 ?? options.locale ?? getLocale$1();
|
|
2321
|
-
return ko_mcp_rule_payment_popup_prepare_title(inputs);
|
|
2322
|
-
});
|
|
2323
|
-
|
|
2324
|
-
//#endregion
|
|
2325
|
-
//#region ../mcp/src/paraglide/messages/mcp_rule_payment_popup_prepare_why.js
|
|
2326
|
-
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
2327
|
-
/** @typedef {{}} Mcp_Rule_Payment_Popup_Prepare_WhyInputs */
|
|
2328
|
-
const ko_mcp_rule_payment_popup_prepare_why = () => {
|
|
2329
|
-
return `팝업은 사용자 클릭에서 동기로 열어야 차단되지 않는다. 서버 응답을 기다린 뒤 열면 브라우저가 막아 결제가 시작되지 않는다`;
|
|
2330
|
-
};
|
|
2331
|
-
/**
|
|
2332
|
-
|
|
2333
|
-
* @param {Mcp_Rule_Payment_Popup_Prepare_WhyInputs} inputs
|
|
2334
|
-
* @param {{ locale?: "ko" }} options
|
|
2335
|
-
* @returns {LocalizedString}
|
|
2336
|
-
*/
|
|
2337
|
-
const mcp_rule_payment_popup_prepare_why = ((inputs = {}, options = {}) => {
|
|
2338
|
-
experimentalStaticLocale$1 ?? options.locale ?? getLocale$1();
|
|
2339
|
-
return ko_mcp_rule_payment_popup_prepare_why(inputs);
|
|
2340
|
-
});
|
|
2341
|
-
|
|
2342
2264
|
//#endregion
|
|
2343
2265
|
//#region ../mcp/src/paraglide/messages/mcp_rule_payment_processing_status_example.js
|
|
2344
2266
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -3297,24 +3219,6 @@ const mcp_verify_payment_browser_launch_server_file = ((inputs = {}, options = {
|
|
|
3297
3219
|
return ko_mcp_verify_payment_browser_launch_server_file(inputs);
|
|
3298
3220
|
});
|
|
3299
3221
|
|
|
3300
|
-
//#endregion
|
|
3301
|
-
//#region ../mcp/src/paraglide/messages/mcp_verify_payment_popup_prepare.js
|
|
3302
|
-
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
3303
|
-
/** @typedef {{}} Mcp_Verify_Payment_Popup_PrepareInputs */
|
|
3304
|
-
const ko_mcp_verify_payment_popup_prepare = () => {
|
|
3305
|
-
return `받아 둔 결제 시작 값으로 결제창을 열지만 클릭 시점에 \`payments.prepareWindow()\`로 창을 먼저 여는 코드가 없다`;
|
|
3306
|
-
};
|
|
3307
|
-
/**
|
|
3308
|
-
|
|
3309
|
-
* @param {Mcp_Verify_Payment_Popup_PrepareInputs} inputs
|
|
3310
|
-
* @param {{ locale?: "ko" }} options
|
|
3311
|
-
* @returns {LocalizedString}
|
|
3312
|
-
*/
|
|
3313
|
-
const mcp_verify_payment_popup_prepare = ((inputs = {}, options = {}) => {
|
|
3314
|
-
experimentalStaticLocale$1 ?? options.locale ?? getLocale$1();
|
|
3315
|
-
return ko_mcp_verify_payment_popup_prepare(inputs);
|
|
3316
|
-
});
|
|
3317
|
-
|
|
3318
3222
|
//#endregion
|
|
3319
3223
|
//#region ../mcp/src/paraglide/messages/mcp_verify_payment_processing_status.js
|
|
3320
3224
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -8134,6 +8038,61 @@ const cli_retired_token = ((inputs = {}, options = {}) => {
|
|
|
8134
8038
|
return ko_cli_retired_token(inputs);
|
|
8135
8039
|
});
|
|
8136
8040
|
|
|
8041
|
+
//#endregion
|
|
8042
|
+
//#region src/paraglide/messages/cli_skills_install_failed.js
|
|
8043
|
+
/** @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}`;
|
|
8048
|
+
};
|
|
8049
|
+
/**
|
|
8050
|
+
|
|
8051
|
+
* @param {Cli_Skills_Install_FailedInputs} inputs
|
|
8052
|
+
* @param {{ locale?: "ko" }} options
|
|
8053
|
+
* @returns {LocalizedString}
|
|
8054
|
+
*/
|
|
8055
|
+
const cli_skills_install_failed = ((inputs, options = {}) => {
|
|
8056
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
8057
|
+
return ko_cli_skills_install_failed(inputs);
|
|
8058
|
+
});
|
|
8059
|
+
|
|
8060
|
+
//#endregion
|
|
8061
|
+
//#region src/paraglide/messages/cli_skills_installed.js
|
|
8062
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
8063
|
+
/** @typedef {{}} Cli_Skills_InstalledInputs */
|
|
8064
|
+
const ko_cli_skills_installed = () => {
|
|
8065
|
+
return `에이전트 스킬 설치 완료 — .claude/skills/ (배포 때 올리지 않습니다)`;
|
|
8066
|
+
};
|
|
8067
|
+
/**
|
|
8068
|
+
|
|
8069
|
+
* @param {Cli_Skills_InstalledInputs} inputs
|
|
8070
|
+
* @param {{ locale?: "ko" }} options
|
|
8071
|
+
* @returns {LocalizedString}
|
|
8072
|
+
*/
|
|
8073
|
+
const cli_skills_installed = ((inputs = {}, options = {}) => {
|
|
8074
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
8075
|
+
return ko_cli_skills_installed(inputs);
|
|
8076
|
+
});
|
|
8077
|
+
|
|
8078
|
+
//#endregion
|
|
8079
|
+
//#region src/paraglide/messages/cli_skills_installing.js
|
|
8080
|
+
/** @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}`;
|
|
8084
|
+
};
|
|
8085
|
+
/**
|
|
8086
|
+
|
|
8087
|
+
* @param {Cli_Skills_InstallingInputs} inputs
|
|
8088
|
+
* @param {{ locale?: "ko" }} options
|
|
8089
|
+
* @returns {LocalizedString}
|
|
8090
|
+
*/
|
|
8091
|
+
const cli_skills_installing = ((inputs, options = {}) => {
|
|
8092
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
8093
|
+
return ko_cli_skills_installing(inputs);
|
|
8094
|
+
});
|
|
8095
|
+
|
|
8137
8096
|
//#endregion
|
|
8138
8097
|
//#region src/paraglide/messages/cli_skipped_outside.js
|
|
8139
8098
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -9784,7 +9743,7 @@ async function selectStore(auth, client, io, candidates, folderStore = null) {
|
|
|
9784
9743
|
|
|
9785
9744
|
//#endregion
|
|
9786
9745
|
//#region package.json
|
|
9787
|
-
var version = "0.
|
|
9746
|
+
var version = "0.11.0";
|
|
9788
9747
|
|
|
9789
9748
|
//#endregion
|
|
9790
9749
|
//#region src/client.ts
|
|
@@ -10870,21 +10829,12 @@ const RULES = [
|
|
|
10870
10829
|
doc: "/guides/checkout-flow",
|
|
10871
10830
|
templateFile: "src/lib/payments.ts"
|
|
10872
10831
|
}),
|
|
10873
|
-
defineRule({
|
|
10874
|
-
id: "payment-popup-prepare",
|
|
10875
|
-
title: mcp_rule_payment_popup_prepare_title,
|
|
10876
|
-
why: mcp_rule_payment_popup_prepare_why,
|
|
10877
|
-
fix: mcp_rule_payment_popup_prepare_fix,
|
|
10878
|
-
example: mcp_rule_payment_popup_prepare_example,
|
|
10879
|
-
doc: "/guides/checkout-flow",
|
|
10880
|
-
templateFile: "src/routes/checkout.index.tsx"
|
|
10881
|
-
}),
|
|
10882
10832
|
defineRule({
|
|
10883
10833
|
id: "payment-return-route",
|
|
10884
10834
|
title: mcp_rule_payment_return_route_title,
|
|
10885
10835
|
why: mcp_rule_payment_return_route_why,
|
|
10886
10836
|
fix: mcp_rule_payment_return_route_fix,
|
|
10887
|
-
example: "useEffect(() => {\n void payments.result(
|
|
10837
|
+
example: "useEffect(() => {\n void payments.result().then((result) => {\n if (result.status === \"COMPLETED\") navigate({ to: \"/checkout/complete\", search: { orderId: result.orderId } });\n });\n}, []);",
|
|
10888
10838
|
doc: "/guides/checkout-flow",
|
|
10889
10839
|
templateFile: "src/routes/checkout.return.tsx"
|
|
10890
10840
|
}),
|
|
@@ -11128,7 +11078,6 @@ function verifySources(files, options = {}) {
|
|
|
11128
11078
|
}
|
|
11129
11079
|
if (has("startPayment(") && !has("@sayren/storefront-sdk/payments")) inProject("payment-browser-launch", mcp_verify_payment_browser_launch_missing());
|
|
11130
11080
|
const opensPayment = matches(/\bpayments\.open\(|\.open\(\s*(start|paymentStart)\b/);
|
|
11131
|
-
if (opensPayment && !has("prepareWindow(")) inFile("payment-popup-prepare", opensPayment, mcp_verify_payment_popup_prepare(), /\bpayments\.open\(|\.open\(\s*(start|paymentStart)\b/);
|
|
11132
11081
|
if (opensPayment && !/amounts/.test(opensPayment.content)) inFile("payment-amounts-final", opensPayment, mcp_verify_payment_amounts_final(), /\bpayments\.open\(/);
|
|
11133
11082
|
const fixedReturnUrl = matches(/returnUrl\s*[:=]\s*["'`]https?:\/\//);
|
|
11134
11083
|
if (fixedReturnUrl) inFile("checkout-origin", fixedReturnUrl, mcp_verify_checkout_origin(), /returnUrl\s*[:=]\s*["'`]https?:\/\//);
|
|
@@ -11476,8 +11425,18 @@ const WRITABLE_ROOT_FILES = new Set([
|
|
|
11476
11425
|
"biome.json",
|
|
11477
11426
|
".gitignore"
|
|
11478
11427
|
]);
|
|
11479
|
-
/**
|
|
11480
|
-
|
|
11428
|
+
/**
|
|
11429
|
+
* 읽기만 되는 루트 파일 — 의존성은 전용 경로로만 바꾼다(레지스트리 프록시와 잠금 파일을 함께 맞춘다).
|
|
11430
|
+
* `CLAUDE.md`·`.mcp.json`은 템플릿이 싣는 에이전트 안내·MCP 연결 설정이다(2026-10-14, blank 템플릿) — 편집 도구는 바꾸지 않는다
|
|
11431
|
+
*/
|
|
11432
|
+
const READONLY_ROOT_FILES = new Set([
|
|
11433
|
+
"package.json",
|
|
11434
|
+
"pnpm-lock.yaml",
|
|
11435
|
+
"CLAUDE.md",
|
|
11436
|
+
".mcp.json"
|
|
11437
|
+
]);
|
|
11438
|
+
/** 읽기만 되는 폴더 — 템플릿이 싣는 Claude 스킬(`.claude/skills/…`, 2026-10-14). 셀러 로컬 Claude Code와 AI 상점 도우미가 같은 기준을 읽는다 */
|
|
11439
|
+
const READONLY_DIRS = new Set([".claude"]);
|
|
11481
11440
|
/**
|
|
11482
11441
|
* 쓸 수 있는 폴더 — 코드(`src`), 정적 파일(`public`), 화면 문구 카탈로그(`messages`, 템플릿 `src/i18n`이 `messages/ko.json`을 import한다, #73).
|
|
11483
11442
|
* 템플릿이 import하는 파일이 이 밖에 있으면 첫 작업 트리(source.tgz)를 풀 때 빠져 빌드·검사가 깨진다(#78)
|
|
@@ -11490,6 +11449,7 @@ const WRITABLE_DIRS = new Set([
|
|
|
11490
11449
|
/** 작업 트리 루트에 둘 수 있는 이름(폴더·파일) — 컨테이너 스냅샷이 이 이름만 묶는다(`container-tree.ts`) */
|
|
11491
11450
|
const TRACKED_ROOT_NAMES = [
|
|
11492
11451
|
...WRITABLE_DIRS,
|
|
11452
|
+
...READONLY_DIRS,
|
|
11493
11453
|
...WRITABLE_ROOT_FILES,
|
|
11494
11454
|
...READONLY_ROOT_FILES
|
|
11495
11455
|
];
|
|
@@ -11519,9 +11479,10 @@ function accessOf(path) {
|
|
|
11519
11479
|
if (READONLY_ROOT_FILES.has(first)) return "read";
|
|
11520
11480
|
return null;
|
|
11521
11481
|
}
|
|
11522
|
-
|
|
11523
|
-
if (
|
|
11524
|
-
|
|
11482
|
+
const readonlyDir = READONLY_DIRS.has(first);
|
|
11483
|
+
if (!readonlyDir && !WRITABLE_DIRS.has(first)) return null;
|
|
11484
|
+
if (segments.slice(1).some((s) => s.startsWith("."))) return null;
|
|
11485
|
+
return readonlyDir ? "read" : "write";
|
|
11525
11486
|
}
|
|
11526
11487
|
|
|
11527
11488
|
//#endregion
|
|
@@ -11625,6 +11586,116 @@ async function gunzip(bytes, maxBytes) {
|
|
|
11625
11586
|
return out;
|
|
11626
11587
|
}
|
|
11627
11588
|
|
|
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
|
+
|
|
11628
11699
|
//#endregion
|
|
11629
11700
|
//#region src/tree.ts
|
|
11630
11701
|
/**
|
|
@@ -11658,6 +11729,11 @@ const sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
|
11658
11729
|
function collectTree(root) {
|
|
11659
11730
|
const files = [];
|
|
11660
11731
|
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
|
+
};
|
|
11661
11737
|
const walk = (dir) => {
|
|
11662
11738
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
11663
11739
|
const full = join(dir, entry.name);
|
|
@@ -11671,7 +11747,7 @@ function collectTree(root) {
|
|
|
11671
11747
|
skipped.push(rel);
|
|
11672
11748
|
continue;
|
|
11673
11749
|
}
|
|
11674
|
-
if (isNeverUpload(rel) || accessOf(rel) === null) {
|
|
11750
|
+
if (isNeverUpload(rel) || accessOf(rel) === null || isInstalledSkill(rel)) {
|
|
11675
11751
|
skipped.push(rel);
|
|
11676
11752
|
continue;
|
|
11677
11753
|
}
|
|
@@ -11871,52 +11947,21 @@ function lockImporterSpecifiers(lock) {
|
|
|
11871
11947
|
}
|
|
11872
11948
|
|
|
11873
11949
|
//#endregion
|
|
11874
|
-
//#region src/
|
|
11875
|
-
/**
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
if (!existsSync(join(dir, "node_modules"))) return { kind: "no-deps" };
|
|
11890
|
-
return { kind: "run" };
|
|
11891
|
-
}
|
|
11892
|
-
/** `npm run build`를 돌린다. 출력은 모아 두고(실패할 때 꼬리를 보여 준다) 화면에 흘리지 않는다 */
|
|
11893
|
-
function runNpmBuild(dir, env) {
|
|
11894
|
-
return new Promise((resolve$1, reject) => {
|
|
11895
|
-
let output = "";
|
|
11896
|
-
const keep = (chunk) => {
|
|
11897
|
-
output = (output + chunk.toString("utf8")).slice(-OUTPUT_KEEP_CHARS);
|
|
11898
|
-
};
|
|
11899
|
-
const child = spawn("npm", ["run", "build"], {
|
|
11900
|
-
cwd: dir,
|
|
11901
|
-
env,
|
|
11902
|
-
stdio: [
|
|
11903
|
-
"ignore",
|
|
11904
|
-
"pipe",
|
|
11905
|
-
"pipe"
|
|
11906
|
-
],
|
|
11907
|
-
shell: process.platform === "win32"
|
|
11908
|
-
});
|
|
11909
|
-
child.stdout?.on("data", keep);
|
|
11910
|
-
child.stderr?.on("data", keep);
|
|
11911
|
-
child.once("error", reject);
|
|
11912
|
-
child.once("close", (code) => resolve$1({
|
|
11913
|
-
code,
|
|
11914
|
-
output: tailOf(output)
|
|
11915
|
-
}));
|
|
11916
|
-
});
|
|
11917
|
-
}
|
|
11918
|
-
function tailOf(output, lines = OUTPUT_TAIL_LINES) {
|
|
11919
|
-
return output.trimEnd().split(/\r?\n/).slice(-lines).join("\n");
|
|
11950
|
+
//#region src/guide-skill.ts
|
|
11951
|
+
/**
|
|
11952
|
+
* 받은 폴더의 에이전트 안내 스킬(2026-10-16 사용자 결정) — 셀러의 Claude Code가 sayren 중앙 스킬을 HTTP로 읽게 한다.
|
|
11953
|
+
* 빈 템플릿이 싣는 `templates/storefront-blank/.claude/skills/sayren/SKILL.md`와 같은 내용이다(`guide-skill.test.ts`가 고정한다).
|
|
11954
|
+
*/
|
|
11955
|
+
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";
|
|
11957
|
+
/**
|
|
11958
|
+
* 받은 트리에 안내 스킬이 없으면 넣는다 — 템플릿이 싣거나 셀러가 올린 내용이 우선이다. 트리에 넣으므로 다시 받을 때 지울 파일로 잡히지 않고
|
|
11959
|
+
* `deploy`가 원격 작업 트리에도 올린다(AI 상점 도우미가 같은 안내를 읽는다). 넣었으면 true
|
|
11960
|
+
*/
|
|
11961
|
+
function withGuideSkill(files) {
|
|
11962
|
+
if (files.has(GUIDE_SKILL_PATH)) return false;
|
|
11963
|
+
files.set(GUIDE_SKILL_PATH, new TextEncoder().encode(GUIDE_SKILL));
|
|
11964
|
+
return true;
|
|
11920
11965
|
}
|
|
11921
11966
|
|
|
11922
11967
|
//#endregion
|
|
@@ -11978,7 +12023,7 @@ async function pull(flags, io) {
|
|
|
11978
12023
|
return receiveSource(client, auth, source, flags, io, authDir);
|
|
11979
12024
|
}
|
|
11980
12025
|
/**
|
|
11981
|
-
* 받은 작업
|
|
12026
|
+
* 받은 작업 트리(+ 에이전트 안내 스킬 `withGuideSkill`)를 폴더에 쓰고 받은 폴더 파일(`.sayren/state.json`·`.env`·`.mcp.json`·`.gitignore`)을 맞춘다. `pull`과 `init`(#101)이 같이 쓴다.
|
|
11982
12027
|
* 대상은 `--dir` → 받은 폴더 안이면 그 폴더 → 상점 코드다. 비어 있지 않은 폴더는 `--force`일 때 같은 사이트를 받은 폴더만 다시 받는다.
|
|
11983
12028
|
*/
|
|
11984
12029
|
async function receiveSource(client, auth, source, flags, io, migratedDir) {
|
|
@@ -12039,6 +12084,7 @@ async function receiveSource(client, auth, source, flags, io, migratedDir) {
|
|
|
12039
12084
|
const tgz = new Uint8Array(Buffer.concat(chunks));
|
|
12040
12085
|
if (sha256Hex(tgz) !== source.archive.sha256) throw new CliError(cli_pull_archive_corrupt());
|
|
12041
12086
|
const files = await unpackArchive(tgz);
|
|
12087
|
+
withGuideSkill(files);
|
|
12042
12088
|
const collisions = caseCollisions(files.keys());
|
|
12043
12089
|
if (collisions.length > 0) throw new CliError(cli_pull_case_collisions({ groups: collisions.map((group) => group.join(" · ")).join(", ") }));
|
|
12044
12090
|
const diff = diffLocal(target, files);
|
|
@@ -12106,10 +12152,33 @@ async function receiveSource(client, auth, source, flags, io, migratedDir) {
|
|
|
12106
12152
|
file: STATE_FILE,
|
|
12107
12153
|
siteId: source.siteId
|
|
12108
12154
|
}));
|
|
12155
|
+
await installSkills(target, io);
|
|
12109
12156
|
if (source.editSessionOpen) io.out(cli_pull_edit_session_open());
|
|
12110
12157
|
io.out(cli_pull_next_steps({ dir: pulledDir }));
|
|
12111
12158
|
return 0;
|
|
12112
12159
|
}
|
|
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
|
+
}
|
|
12113
12182
|
/**
|
|
12114
12183
|
* `sayren init [폴더]`(#101) — 템플릿을 골라 호스팅 사이트를 만들고(`POST /v1/storefront/site`) `pull`과 같은 폴더를 만든다.
|
|
12115
12184
|
* 사이트가 이미 있으면 만들지 않고 `pull`을 안내한다(상점당 열린 사이트는 하나다). 만들기는 상점 한도(30일 3회)를 쓰므로 확인을 묻는다.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sayren",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"imports": {
|
|
6
6
|
"#paraglide/*": "./src/paraglide/*"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"esbuild": "^0.28.2",
|
|
20
|
-
"@sayren/store-sdk": "^0.
|
|
20
|
+
"@sayren/store-sdk": "^0.33.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@biomejs/biome": "^2.5.14",
|
|
@@ -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.
|
|
31
|
+
"@sayren/mcp": "0.26.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|