sayren 0.7.0 → 0.8.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 +168 -19
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -965,7 +965,7 @@ const cli_app_dev_store_name_required = ((inputs = {}, options = {}) => {
965
965
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
966
966
  /** @typedef {{ dir: NonNullable<unknown> }} Cli_App_Dir_ExistsInputs */
967
967
  const ko_cli_app_dir_exists = (i) => {
968
- return `${i?.dir}에 이미 sayren.app.json이 있습니다`;
968
+ return `${i?.dir}에 이미 매니페스트(manifest.json)가 있습니다`;
969
969
  };
970
970
  /**
971
971
 
@@ -1037,7 +1037,7 @@ const cli_app_handle_mismatch = ((inputs, options = {}) => {
1037
1037
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1038
1038
  /** @typedef {{ events: NonNullable<unknown> }} Cli_App_Handler_MissingInputs */
1039
1039
  const ko_cli_app_handler_missing = (i) => {
1040
- return `매니페스트(events·schedules·http.public)에 있지만 코드에 함수가 없습니다: ${i?.events} (HANDLER_MISSING)`;
1040
+ return `매니페스트(events·schedules·http.public·flow.notifications·mcp.tools)에 있지만 코드에 함수가 없습니다: ${i?.events} (HANDLER_MISSING)`;
1041
1041
  };
1042
1042
  /**
1043
1043
 
@@ -1200,6 +1200,24 @@ const cli_app_manifest_invalid = ((inputs, options = {}) => {
1200
1200
  return ko_cli_app_manifest_invalid(inputs);
1201
1201
  });
1202
1202
 
1203
+ //#endregion
1204
+ //#region ../i18n/src/paraglide/messages/cli_app_manifest_legacy.js
1205
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1206
+ /** @typedef {{}} Cli_App_Manifest_LegacyInputs */
1207
+ const ko_cli_app_manifest_legacy = () => {
1208
+ return `sayren.app.json은 옛 매니페스트 이름입니다. 파일 이름을 manifest.json으로 바꾸십시오`;
1209
+ };
1210
+ /**
1211
+
1212
+ * @param {Cli_App_Manifest_LegacyInputs} inputs
1213
+ * @param {{ locale?: "ko" }} options
1214
+ * @returns {LocalizedString}
1215
+ */
1216
+ const cli_app_manifest_legacy = ((inputs = {}, options = {}) => {
1217
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1218
+ return ko_cli_app_manifest_legacy(inputs);
1219
+ });
1220
+
1203
1221
  //#endregion
1204
1222
  //#region ../i18n/src/paraglide/messages/cli_app_manifest_missing.js
1205
1223
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
@@ -5257,6 +5275,8 @@ const ko_cli_usage = (i) => {
5257
5275
  --data <json> app trigger: 이벤트 데이터
5258
5276
  --order <orderId> app trigger: 주문 id를 데이터에 넣습니다
5259
5277
  --schedule <이름> app trigger: 이벤트 대신 예약 작업을 실행합니다
5278
+ --flow <이름> app trigger: 이벤트 대신 흐름 알림을 보냅니다(--data는 알림 값)
5279
+ --tool <이름> app trigger: 이벤트 대신 에이전트 도구를 부릅니다(--data는 입력)
5260
5280
  --lang <로케일> 메시지 언어(기본: SAYREN_LANG → 시스템 로케일 → ko)
5261
5281
 
5262
5282
  종료 코드: 0 성공 · 1 실패 · 3 승인 대기(에이전트 토큰)`;
@@ -9180,7 +9200,7 @@ async function selectStore(auth, client, io, candidates, folderStore = null) {
9180
9200
 
9181
9201
  //#endregion
9182
9202
  //#region package.json
9183
- var version = "0.7.0";
9203
+ var version = "0.8.1";
9184
9204
 
9185
9205
  //#endregion
9186
9206
  //#region src/client.ts
@@ -9222,14 +9242,16 @@ function detailsOf(error) {
9222
9242
  //#endregion
9223
9243
  //#region src/app-commands.ts
9224
9244
  /**
9225
- * `sayren app …`(이슈 #22, 매뉴얼 「앱 만들기」) — 앱 폴더(`sayren.app.json`)에서 돈다. 서버는 코드를 빌드하지 않는다(A1):
9245
+ * `sayren app …`(이슈 #22, 매뉴얼 「앱 만들기」) — 앱 폴더(`manifest.json`)에서 돈다. 서버는 코드를 빌드하지 않는다(A1):
9226
9246
  * CLI가 esbuild로 서버 번들 한 파일을 만들어 올리고 api가 `DEPENDENCIES`·`VERIFY`·`BUNDLE`·`UPLOAD`를 돈다. 앱 화면(`ui/`)은
9227
9247
  * 정적 파일 그대로 올린다.
9228
9248
  *
9229
9249
  * 인증은 둘이다. 앱 배포 키(`--key`·`SAYREN_APP_KEY`, developer.sayren.app에서 발행, `sak_…`)는 그 앱의 경로에만 쓰이고(CI 용도),
9230
9250
  * 그 밖에는 `sayren login`의 계정 토큰이다. 앱 등록(`init`)·개발 상점은 계정 토큰만 된다.
9231
9251
  */
9232
- const APP_MANIFEST_FILE = "sayren.app.json";
9252
+ const APP_MANIFEST_FILE = "manifest.json";
9253
+ /** 0.8.0까지의 매니페스트 이름 — `manifest.json`이 없을 때만 읽고 경고한다 */
9254
+ const LEGACY_APP_MANIFEST_FILE = "sayren.app.json";
9233
9255
  const APP_STATE_FILE = `${STATE_DIR}/app.json`;
9234
9256
  const APP_DEV_PORT = 4020;
9235
9257
  const SERVER_ENTRY_CANDIDATES = [
@@ -9259,10 +9281,17 @@ function writeAppState(dir, state) {
9259
9281
  mkdirSync(join(dir, STATE_DIR), { recursive: true });
9260
9282
  writeFileSync(join(dir, APP_STATE_FILE), `${JSON.stringify(state, null, 2)}\n`, { mode: 384 });
9261
9283
  }
9262
- /** 매니페스트 원문(JSON)과 검증 결과 — 문제가 있으면 CliError */
9284
+ /** 앱 폴더의 매니페스트 파일 — `manifest.json`, 없으면 옛 이름 `sayren.app.json`. 둘 다 없으면 null */
9285
+ function manifestFileOf(dir) {
9286
+ if (existsSync(join(dir, APP_MANIFEST_FILE))) return APP_MANIFEST_FILE;
9287
+ if (existsSync(join(dir, LEGACY_APP_MANIFEST_FILE))) return LEGACY_APP_MANIFEST_FILE;
9288
+ return null;
9289
+ }
9290
+ /** 매니페스트 원문(JSON)과 검증 결과 — 문제가 있으면 CliError. 옛 이름으로 읽었으면 `warnings`에 이름 변경 안내가 든다 */
9263
9291
  function loadManifest(dir) {
9264
- const path = join(dir, APP_MANIFEST_FILE);
9265
- if (!existsSync(path)) throw new CliError(cli_app_manifest_missing({ file: APP_MANIFEST_FILE }));
9292
+ const file = manifestFileOf(dir);
9293
+ if (!file) throw new CliError(cli_app_manifest_missing({ file: APP_MANIFEST_FILE }));
9294
+ const path = join(dir, file);
9266
9295
  let raw;
9267
9296
  try {
9268
9297
  raw = JSON.parse(readFileSync(path, "utf8"));
@@ -9273,9 +9302,11 @@ function loadManifest(dir) {
9273
9302
  if (!parsed.success) throw new CliError(cli_app_manifest_invalid({ problems: parsed.error.issues.map((issue) => `- ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n") }));
9274
9303
  const problems = appManifestProblems(parsed.data);
9275
9304
  if (problems.length) throw new CliError(cli_app_manifest_invalid({ problems: problems.map((p) => `- ${p.path}: ${p.message} (${p.code})`).join("\n") }));
9305
+ const warnings = file === LEGACY_APP_MANIFEST_FILE ? [cli_app_manifest_legacy()] : [];
9276
9306
  return {
9277
9307
  raw,
9278
- manifest: parsed.data
9308
+ manifest: parsed.data,
9309
+ warnings
9279
9310
  };
9280
9311
  }
9281
9312
  function appDir(flags, io) {
@@ -9283,7 +9314,7 @@ function appDir(flags, io) {
9283
9314
  }
9284
9315
  function requireState(dir) {
9285
9316
  const state = readAppState(dir);
9286
- if (!state) throw new CliError(cli_app_not_registered({ file: APP_MANIFEST_FILE }));
9317
+ if (!state) throw new CliError(cli_app_not_registered({ file: manifestFileOf(dir) ?? APP_MANIFEST_FILE }));
9287
9318
  return state;
9288
9319
  }
9289
9320
  /** 배포 키(`--key` → `SAYREN_APP_KEY`)가 있으면 그것, 없으면 계정 토큰(`resolveAuth`) */
@@ -9395,7 +9426,9 @@ async function loadBundle(dir, server) {
9395
9426
  meta: {
9396
9427
  events: [...meta.events],
9397
9428
  http: [...meta.http],
9398
- schedules: [...meta.schedules ?? []]
9429
+ schedules: [...meta.schedules ?? []],
9430
+ flows: [...meta.flows ?? []],
9431
+ tools: [...meta.tools ?? []]
9399
9432
  }
9400
9433
  };
9401
9434
  }
@@ -9436,21 +9469,40 @@ function collectListing(dir, manifest) {
9436
9469
  };
9437
9470
  }
9438
9471
  /** 로컬 검사 — 매니페스트, 번들, 이벤트 함수 존재, 비밀 값 형식, 크기. 서버 `VERIFY`·`BUNDLE`과 같은 규칙 */
9472
+ /**
9473
+ * 외부 런타임 앱(매뉴얼 「개발자 서버에서 실행」)이 올리는 서버 자리 — 코드는 개발자 서버에 있어 sayren 실행 환경에는 이것만 둔다(호출되지 않는다)
9474
+ */
9475
+ const EXTERNAL_STUB_SERVER = "export default { fetch() { return new Response(\"external runtime\", { status: 404 }); } };\n";
9476
+ const EMPTY_META = {
9477
+ events: [],
9478
+ http: [],
9479
+ schedules: [],
9480
+ flows: [],
9481
+ tools: []
9482
+ };
9439
9483
  async function verifyApp(dir) {
9440
- const { raw, manifest } = loadManifest(dir);
9441
- const server = await bundleServer(dir);
9484
+ const { raw, manifest, warnings } = loadManifest(dir);
9485
+ const external = manifest.runtime === "external";
9486
+ const server = external ? EXTERNAL_STUB_SERVER : await bundleServer(dir);
9442
9487
  if (APP_SECRET_PATTERNS.find((pattern) => pattern.test(server))) throw new CliError(cli_app_secret_in_source());
9443
9488
  const gzipBytes = gzipSync(server).byteLength;
9444
9489
  if (gzipBytes > APP_SERVER_BUNDLE_MAX_GZIP_BYTES) throw new CliError(cli_app_bundle_too_large({ size: formatMb(gzipBytes) }));
9445
- const { app, meta } = await loadBundle(dir, server);
9490
+ const { app, meta } = external ? {
9491
+ app: {
9492
+ fetch: async () => new Response("external runtime", { status: 404 }),
9493
+ __sayrenApp: EMPTY_META
9494
+ },
9495
+ meta: { ...EMPTY_META }
9496
+ } : await loadBundle(dir, server);
9446
9497
  const httpKeys = new Set(meta.http.map((key) => key.trim().replace(/\s+/g, " ")));
9447
- const missing = [
9498
+ const missing = external ? [] : [
9448
9499
  ...manifest.events.filter((event) => !meta.events.includes(event)),
9449
9500
  ...Object.keys(manifest.schedules).filter((name) => !meta.schedules.includes(name)),
9501
+ ...Object.keys(manifest.flow?.notifications ?? {}).filter((name) => !meta.flows.includes(name)),
9502
+ ...Object.keys(manifest.mcp?.tools ?? {}).filter((name) => !meta.tools.includes(name)),
9450
9503
  ...(manifest.http?.public ?? []).filter((key) => !httpKeys.has(key))
9451
9504
  ];
9452
9505
  if (missing.length) throw new CliError(cli_app_handler_missing({ events: missing.join(", ") }));
9453
- const warnings = [];
9454
9506
  const listed = manifest.events;
9455
9507
  const extra = meta.events.filter((event) => !listed.includes(event));
9456
9508
  if (extra.length) warnings.push(cli_app_handler_unlisted({ events: extra.join(", ") }));
@@ -9560,7 +9612,7 @@ export default defineApp({
9560
9612
  async function appInit(handle, flags, io) {
9561
9613
  if (!handle || !HANDLE_RE.test(handle)) throw new CliError(cli_app_handle_invalid());
9562
9614
  const dir = resolve(io.cwd, flags.dir ?? handle);
9563
- if (existsSync(join(dir, APP_MANIFEST_FILE))) throw new CliError(cli_app_dir_exists({ dir: relative(io.cwd, dir) || "." }));
9615
+ if (manifestFileOf(dir)) throw new CliError(cli_app_dir_exists({ dir: relative(io.cwd, dir) || "." }));
9564
9616
  const name = flags.name?.trim() || handle;
9565
9617
  const auth = await developerAuth(flags, dir, io, { accountOnly: true });
9566
9618
  const privateStoreId = flags.privateStore?.trim();
@@ -9825,6 +9877,16 @@ function devEnvelope(input) {
9825
9877
  role: "OWNER"
9826
9878
  } } : {},
9827
9879
  ...input.schedule ? { schedule: input.schedule } : {},
9880
+ ...input.tool ? { tool: input.tool } : {},
9881
+ ...input.flow ? { flow: {
9882
+ orderId: "ord_dev",
9883
+ orderItemIds: ["oi_dev"],
9884
+ flowId: "flow_dev",
9885
+ flowVersion: 1,
9886
+ state: "dev",
9887
+ params: {},
9888
+ ...input.flow
9889
+ } } : {},
9828
9890
  ...input.event ? { event: {
9829
9891
  id: `evt_dev_${devSeq}`,
9830
9892
  type: input.event.type,
@@ -9876,7 +9938,8 @@ function readBody(req) {
9876
9938
  async function appDev(flags, io) {
9877
9939
  const dir = appDir(flags, io);
9878
9940
  const state = requireState(dir);
9879
- const { manifest } = loadManifest(dir);
9941
+ const { manifest, warnings } = loadManifest(dir);
9942
+ for (const warning of warnings) io.err(cli_app_warning({ message: warning }));
9880
9943
  const { app } = await loadBundle(dir, await bundleServer(dir));
9881
9944
  const port = Number(flags.port ?? io.env.SAYREN_APP_PORT ?? APP_DEV_PORT);
9882
9945
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new CliError(cli_app_port_invalid({ value: String(flags.port) }));
@@ -9900,6 +9963,47 @@ async function appDev(flags, io) {
9900
9963
  try {
9901
9964
  if (req.method === "POST" && url.pathname === "/_trigger") {
9902
9965
  const body = JSON.parse(await readBody(req) || "{}");
9966
+ if (body.tool) {
9967
+ const { settings: settings$1, secrets: secrets$1 } = local();
9968
+ const result$1 = await invokeLocal(app, devEnvelope({
9969
+ kind: "tool",
9970
+ tool: {
9971
+ name: body.tool,
9972
+ input: body.data ?? {}
9973
+ },
9974
+ publicOrigin,
9975
+ token: await ensureToken(),
9976
+ apiBaseUrl,
9977
+ appId: state.appId,
9978
+ settings: settings$1,
9979
+ secrets: secrets$1
9980
+ }));
9981
+ printResult(`[tool] ${body.tool}`, result$1, io);
9982
+ if (result$1.response?.body) io.out(` → ${result$1.response.body}`);
9983
+ res.writeHead(200, { "content-type": "application/json" });
9984
+ res.end(JSON.stringify(result$1));
9985
+ return;
9986
+ }
9987
+ if (body.flow) {
9988
+ const { settings: settings$1, secrets: secrets$1 } = local();
9989
+ const result$1 = await invokeLocal(app, devEnvelope({
9990
+ kind: "flow",
9991
+ flow: {
9992
+ ...body.data ?? {},
9993
+ name: body.flow
9994
+ },
9995
+ publicOrigin,
9996
+ token: await ensureToken(),
9997
+ apiBaseUrl,
9998
+ appId: state.appId,
9999
+ settings: settings$1,
10000
+ secrets: secrets$1
10001
+ }));
10002
+ printResult(`[flow] ${body.flow}`, result$1, io);
10003
+ res.writeHead(200, { "content-type": "application/json" });
10004
+ res.end(JSON.stringify(result$1));
10005
+ return;
10006
+ }
9903
10007
  if (body.schedule) {
9904
10008
  const { settings: settings$1, secrets: secrets$1 } = local();
9905
10009
  const result$1 = await invokeLocal(app, devEnvelope({
@@ -10019,6 +10123,47 @@ async function appDev(flags, io) {
10019
10123
  }
10020
10124
  async function appTrigger(type, flags, io) {
10021
10125
  const port = Number(flags.port ?? io.env.SAYREN_APP_PORT ?? APP_DEV_PORT);
10126
+ if (flags.tool) {
10127
+ let toolInput = {};
10128
+ if (flags.data) try {
10129
+ toolInput = JSON.parse(flags.data);
10130
+ } catch {
10131
+ throw new CliError(cli_app_trigger_data_invalid());
10132
+ }
10133
+ const res$1 = await io.fetch(`http://127.0.0.1:${port}/_trigger`, {
10134
+ method: "POST",
10135
+ headers: { "content-type": "application/json" },
10136
+ body: JSON.stringify({
10137
+ tool: flags.tool,
10138
+ data: toolInput
10139
+ })
10140
+ });
10141
+ if (!res$1.ok) throw new CliError(cli_app_trigger_failed({ status: String(res$1.status) }));
10142
+ const result$1 = await res$1.json();
10143
+ printResult(`[tool] ${flags.tool}`, result$1, io);
10144
+ if (result$1.response?.body) io.out(` → ${result$1.response.body}`);
10145
+ return result$1.outcome === "OK" ? 0 : 1;
10146
+ }
10147
+ if (flags.flow) {
10148
+ let flowData = {};
10149
+ if (flags.data) try {
10150
+ flowData = JSON.parse(flags.data);
10151
+ } catch {
10152
+ throw new CliError(cli_app_trigger_data_invalid());
10153
+ }
10154
+ const res$1 = await io.fetch(`http://127.0.0.1:${port}/_trigger`, {
10155
+ method: "POST",
10156
+ headers: { "content-type": "application/json" },
10157
+ body: JSON.stringify({
10158
+ flow: flags.flow,
10159
+ data: flowData
10160
+ })
10161
+ });
10162
+ if (!res$1.ok) throw new CliError(cli_app_trigger_failed({ status: String(res$1.status) }));
10163
+ const result$1 = await res$1.json();
10164
+ printResult(`[flow] ${flags.flow}`, result$1, io);
10165
+ return result$1.outcome === "OK" ? 0 : 1;
10166
+ }
10022
10167
  if (flags.schedule) {
10023
10168
  const res$1 = await io.fetch(`http://127.0.0.1:${port}/_trigger`, {
10024
10169
  method: "POST",
@@ -11954,7 +12099,9 @@ const VALUE_FLAGS = new Set([
11954
12099
  "data",
11955
12100
  "order",
11956
12101
  "schedule",
11957
- "private-store"
12102
+ "private-store",
12103
+ "flow",
12104
+ "tool"
11958
12105
  ]);
11959
12106
  const BOOL_FLAGS = new Set([
11960
12107
  "force",
@@ -12041,6 +12188,8 @@ async function main(argv, io) {
12041
12188
  order: str(parsed.flags.order),
12042
12189
  schedule: str(parsed.flags.schedule),
12043
12190
  privateStore: str(parsed.flags["private-store"]),
12191
+ flow: str(parsed.flags.flow),
12192
+ tool: str(parsed.flags.tool),
12044
12193
  yes: parsed.flags.yes === true
12045
12194
  }, io);
12046
12195
  case "init": return init({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sayren",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "sayren CLI — 호스팅 스토어프론트(init·pull·deploy)와 서드파티 앱(app init·dev·deploy·release)",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "esbuild": "^0.28.2",
17
- "@sayren/store-sdk": "^0.30.12"
17
+ "@sayren/store-sdk": "^0.30.13"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@biomejs/biome": "^2.5.14",