@sayren/store-sdk 0.30.16 → 0.30.18

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/server.d.mts CHANGED
@@ -23,7 +23,7 @@ export declare class MemberDelegationError extends Error {
23
23
  * `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
24
24
  */
25
25
  export declare function delegateMember(options: {
26
- /** api 주소(예: `https://api.sayren.app`) */
26
+ /** api 주소(예: `https://api.sayren.io`) */
27
27
  baseUrl: string;
28
28
  accessToken: string;
29
29
  storeId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayren/store-sdk",
3
- "version": "0.30.16",
3
+ "version": "0.30.18",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "exports": {
package/src/client.ts CHANGED
@@ -284,6 +284,7 @@ import {
284
284
  savedOptionSetRequestSchema,
285
285
  savedOptionSetSchema,
286
286
  } from "./schemas/saved-option-set";
287
+ import { starterStatusSchema } from "./schemas/starter";
287
288
  import {
288
289
  calendarDaySchema,
289
290
  calendarQuerySchema,
@@ -1860,6 +1861,8 @@ export function createStoreClient(options: StoreClientOptions) {
1860
1861
  storeHttp.request("POST", "/storefront/site/preview-link", storefrontPreviewLinkSchema, {
1861
1862
  body: createStorefrontPreviewLinkRequestSchema.parse(body),
1862
1863
  }),
1864
+ /** `GET /v1/storefront/starter` — 쇼핑몰 만들기 진행 상태(서버 상태로 계산한 단계·셀러 항목, 공개 문서 제외) */
1865
+ getStarterStatus: () => storeHttp.request("GET", "/storefront/starter", starterStatusSchema),
1863
1866
  /** `GET /v1/storefront/site/domains` — 주소 목록(기본 주소 + 내 도메인)과 DNS 안내 */
1864
1867
  listDomains: () =>
1865
1868
  storeHttp.request("GET", "/storefront/site/domains", storefrontSiteDomainsSchema),
@@ -28,7 +28,7 @@ export class MemberDelegationError extends Error {
28
28
  * `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
29
29
  */
30
30
  export async function delegateMember(options: {
31
- /** api 주소(예: `https://api.sayren.app`) */
31
+ /** api 주소(예: `https://api.sayren.io`) */
32
32
  baseUrl: string;
33
33
  accessToken: string;
34
34
  storeId: string;
package/src/oauth.ts CHANGED
@@ -30,7 +30,7 @@ export async function createPkcePair(): Promise<{ verifier: string; challenge: s
30
30
  }
31
31
 
32
32
  export interface AuthorizeUrlOptions {
33
- /** authorize 엔드포인트 전체 주소(예: `https://accounts.sayren.app/api/auth/oauth2/authorize`) */
33
+ /** authorize 엔드포인트 전체 주소(예: `https://accounts.sayren.io/api/auth/oauth2/authorize`) */
34
34
  authorizeUrl: string;
35
35
  clientId: string;
36
36
  redirectUri: string;
@@ -59,6 +59,12 @@ export function buildAuthorizeUrl(options: AuthorizeUrlOptions): string {
59
59
  return url.toString();
60
60
  }
61
61
 
62
+ /**
63
+ * 토큰 요청 상한 — 응답 없는 요청이 끝나지 않으면 refresh가 탭 간 잠금(`sayren.store.refresh`)을 쥔 채 멈추고, 그 잠금을 기다리는 모든 API 호출이
64
+ * 같이 멈춘다(2026-10-12 상점 만들기 버튼이 진행 표시로 굳었다). 넘으면 실패로 끝낸다
65
+ */
66
+ export const TOKEN_REQUEST_TIMEOUT_MS = 15_000;
67
+
62
68
  async function tokenRequest(
63
69
  endpoint: string,
64
70
  form: Record<string, string>,
@@ -69,6 +75,10 @@ async function tokenRequest(
69
75
  method: "POST",
70
76
  headers: { "content-type": "application/x-www-form-urlencoded" },
71
77
  body: new URLSearchParams(form).toString(),
78
+ // AbortSignal.timeout이 없는 런타임은 제한 없이 보낸다
79
+ ...(typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function"
80
+ ? { signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS) }
81
+ : {}),
72
82
  });
73
83
  const json: unknown = await response.json().catch(() => null);
74
84
  if (!response.ok) {
package/src/schemas/ai.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { storefrontSlugSchema } from "./storefront-hosting";
2
3
 
3
4
  /**
4
5
  * 상점 플랫폼 AI 채팅(이슈 #124) — 대화·메시지·실행과 실행 이벤트 스트림의 계약.
@@ -72,13 +73,85 @@ export const aiRunSchema = z.object({
72
73
  });
73
74
  export type AiRun = z.infer<typeof aiRunSchema>;
74
75
 
76
+ /**
77
+ * AI가 지금 하는 일의 종류 — 도구 이름·operationId·파일 경로로 서버가 정한다(상점 만들기 진행 화면이 아이콘·문구를 고른다). 정하지 못한 도구는 없다
78
+ */
79
+ export const aiActivitySchema = z.enum([
80
+ "status",
81
+ "brief",
82
+ "template",
83
+ "site",
84
+ "info",
85
+ "shipping",
86
+ "upload",
87
+ "design",
88
+ "category",
89
+ "product",
90
+ "files",
91
+ "code",
92
+ "check",
93
+ "verify",
94
+ "version",
95
+ "preview",
96
+ ]);
97
+ export type AiActivity = z.infer<typeof aiActivitySchema>;
98
+
99
+ /**
100
+ * 도구가 바꾼 파일 하나(2026-10-13, AI 상점 도우미 작업 공간의 수정 중인 코드) — 서버가 도구 입력으로 계산한다.
101
+ * `edit`는 바꾼 부분의 줄 비교, `write`는 새로 쓴 내용(이전 내용을 모르므로 모두 추가로 센다). `preview`는 바뀐 줄 앞부분 12줄까지다
102
+ */
103
+ export const aiToolChangeSchema = z.object({
104
+ path: z.string().max(300),
105
+ mode: z.enum(["edit", "write"]),
106
+ added: z.number().int().min(0),
107
+ removed: z.number().int().min(0),
108
+ preview: z
109
+ .array(z.object({ kind: z.enum(["added", "removed"]), text: z.string().max(300) }))
110
+ .max(12),
111
+ });
112
+ export type AiToolChange = z.infer<typeof aiToolChangeSchema>;
113
+
114
+ /**
115
+ * 에이전트 작업 단계(2026-10-13, AI 상점 도우미 작업 공간) — 에이전트가 패널 도구로 알린다.
116
+ * 상점·상품·배포 준비 → 쇼핑몰 구조 설계 → 개발 환경 준비(라이브러리·SDK) → 코드 수정 루프 → 완료
117
+ */
118
+ export const AI_AGENT_PHASES = ["STORE", "STRUCTURE", "ENVIRONMENT", "BUILD", "DONE"] as const;
119
+ export const aiAgentPhaseSchema = z.enum(AI_AGENT_PHASES);
120
+ export type AiAgentPhase = z.infer<typeof aiAgentPhaseSchema>;
121
+
122
+ /**
123
+ * AI 상점 도우미의 작업 메모 폴더(2026-10-14) — 회차마다 읽는 숨은 규칙(`rules.md`)과 디자인 계획(`design.md`). 셀러 화면(타임라인·코드 탭·변경 기록)에
124
+ * 보이지 않는다. 편집 Sandbox가 점(.)으로 시작하는 경로를 받지 않아 작업 트리의 `src` 아래에 둔다(앱이 import하지 않는 문서라 빌드에 들어가지 않는다)
125
+ */
126
+ export const AI_AGENT_NOTES_DIR = "src/site/agent";
127
+
128
+ /**
129
+ * 에이전트의 할 일 목록(2026-10-14, Claude Code TodoWrite와 같은 루프) — 에이전트가 패널 도구 `update_todos`로 목록 전체를 바꿔 쓴다.
130
+ * 작업 공간 왼쪽 패널이 마지막 목록을 체크리스트로 그린다. 진행중은 한 번에 하나다
131
+ */
132
+ export const AI_TODO_STATUSES = ["pending", "in_progress", "completed"] as const;
133
+ export const aiTodoSchema = z.object({
134
+ content: z.string().min(1).max(200),
135
+ status: z.enum(AI_TODO_STATUSES),
136
+ });
137
+ export type AiTodo = z.infer<typeof aiTodoSchema>;
138
+ export const AI_TODOS_MAX = 30;
139
+
140
+ export function isAiAgentNotesPath(path: string): boolean {
141
+ return path === AI_AGENT_NOTES_DIR || path.startsWith(`${AI_AGENT_NOTES_DIR}/`);
142
+ }
143
+
75
144
  /**
76
145
  * 화면에 그리는 메시지 조각. 모델 원문(tool_use 블록 등)은 api가 보관하고 화면에는 이 형태만 준다.
77
146
  * - `text`: 글
78
147
  * - `tool`: 도구 호출 한 번(이름은 사람이 읽는 라벨, 결과 요약·링크)
79
148
  * - `choices`: 선택지 버튼(가장 최근 것만 누를 수 있다)
149
+ * - `business_form`: 사업자 정보 입력 폼(가장 최근 것만 제출할 수 있다, 건너뛸 수 있다)
80
150
  * - `attachment`: 사용자가 올린 첨부
81
151
  * - `approval`: 승인 대기로 남은 쓰기
152
+ * - `reasoning`: 모델의 생각 요약(2026-10-13, AI 상점 도우미 작업 공간이 도구 호출과 따로 표시한다)
153
+ * - `phase`: 에이전트가 알린 작업 단계(작업 공간의 단계 표시)
154
+ * - `todos`: 에이전트의 할 일 목록 전체(작업 공간의 체크리스트)
82
155
  */
83
156
  export const aiMessagePartSchema = z.discriminatedUnion("type", [
84
157
  z.object({ type: z.literal("text"), text: z.string() }),
@@ -89,6 +162,12 @@ export const aiMessagePartSchema = z.discriminatedUnion("type", [
89
162
  label: z.string(),
90
163
  status: z.enum(["ok", "error", "unknown"]),
91
164
  summary: z.string().nullable(),
165
+ /** 하는 일의 종류(`aiActivitySchema`, 정하지 못했거나 옛 메시지면 없다) */
166
+ activity: aiActivitySchema.optional(),
167
+ /** 도구 대상 — 파일 경로나 검색어(파일 도구만) */
168
+ target: z.string().max(300).optional(),
169
+ /** 바꾼 파일(파일 쓰기 도구만) */
170
+ changes: z.array(aiToolChangeSchema).max(10).optional(),
92
171
  }),
93
172
  z.object({
94
173
  type: z.literal("choices"),
@@ -98,11 +177,16 @@ export const aiMessagePartSchema = z.discriminatedUnion("type", [
98
177
  .min(1)
99
178
  .max(6),
100
179
  }),
180
+ z.object({ type: z.literal("business_form"), question: z.string() }),
101
181
  z.object({ type: z.literal("attachment"), attachment: aiAttachmentSchema }),
102
182
  /** 공개 뒤 승인이 필요한 쓰기를 승인 요청으로 남겼다(셀러가 승인함에서 결정한다) */
103
183
  z.object({ type: z.literal("approval"), approvalId: z.string(), title: z.string() }),
104
184
  /** 사용자가 미리보기에서 고른 요소(#65) */
105
185
  z.object({ type: z.literal("selection"), selection: aiPreviewSelectionSchema }),
186
+ z.object({ type: z.literal("reasoning"), text: z.string() }),
187
+ z.object({ type: z.literal("phase"), phase: aiAgentPhaseSchema }),
188
+ /** 에이전트의 할 일 목록 — 그 시점의 목록 전체다(가장 최근 것이 지금 목록) */
189
+ z.object({ type: z.literal("todos"), todos: z.array(aiTodoSchema).max(AI_TODOS_MAX) }),
106
190
  ]);
107
191
  export type AiMessagePart = z.infer<typeof aiMessagePartSchema>;
108
192
 
@@ -132,9 +216,38 @@ export const aiConversationSchema = aiConversationSummarySchema.extend({
132
216
  });
133
217
  export type AiConversation = z.infer<typeof aiConversationSchema>;
134
218
 
219
+ /** 쇼핑몰 형태(#124, 2026-10-13) — 템플릿은 basic 하나이고 형태는 에이전트에게 주는 화면 구성 지시다 */
220
+ export const STORE_SHOP_TYPES = [
221
+ "BASIC",
222
+ "INTERACTIVE",
223
+ "STORYTELLING",
224
+ "MOBILE",
225
+ "CATALOG",
226
+ "LOOKBOOK",
227
+ ] as const;
228
+ export const storeShopTypeSchema = z.enum(STORE_SHOP_TYPES);
229
+ export type StoreShopType = z.infer<typeof storeShopTypeSchema>;
230
+
231
+ /**
232
+ * AI 상점 만들기 질문 화면의 답(#124, 2026-10-13) — 화면이 고정 질문으로 모은다. 초안과 함께 첫 메시지가 된다.
233
+ * 배송비는 묻지 않는다(설정하지 않으면 무료배송). `slug`는 바라는 주소이고 쓸 수 있는지는 에이전트가 사이트를 만들 때 확인한다
234
+ */
235
+ export const storeSetupBriefSchema = z.object({
236
+ products: z.string().trim().min(1).max(500).describe("판매 상품"),
237
+ storeName: z.string().trim().min(1).max(50).describe("쇼핑몰 이름"),
238
+ audience: z.string().trim().min(1).max(300).describe("주요 고객"),
239
+ highlights: z.string().trim().max(500).describe("강조할 특징 (비우면 AI가 정한다)"),
240
+ shopType: storeShopTypeSchema.describe("쇼핑몰 형태"),
241
+ heroProduct: z.string().trim().max(500).describe("첫 화면에 크게 걸 상품 (비우면 AI가 정한다)"),
242
+ slug: storefrontSlugSchema.optional().describe("바라는 스토어프론트 주소"),
243
+ });
244
+ export type StoreSetupBrief = z.infer<typeof storeSetupBriefSchema>;
245
+
135
246
  export const createAiConversationRequestSchema = z.object({
136
247
  /** 웹사이트에서 남긴 초안(#124 M2) — 연결한 사용자만 쓴다. 주면 첫 메시지로 실행을 시작한다 */
137
248
  draftId: z.string().max(64).optional(),
249
+ /** 질문 화면의 답 — `draftId`와 함께만 쓴다. 초안 앞에 붙어 첫 메시지가 된다 */
250
+ brief: storeSetupBriefSchema.optional(),
138
251
  });
139
252
  export type CreateAiConversationRequest = z.infer<typeof createAiConversationRequestSchema>;
140
253
 
@@ -279,6 +392,16 @@ export type AiRunRevertConflictDetails = z.infer<typeof aiRunRevertConflictDetai
279
392
  export const aiStreamEventSchema = z.discriminatedUnion("type", [
280
393
  /** 글 조각(약 100ms 묶음). 같은 `messageId`의 `text`에 이어 붙인다 */
281
394
  z.object({ type: z.literal("delta"), messageId: z.string(), text: z.string() }),
395
+ /** 생각 요약 조각 — 같은 `messageId`의 생각에 이어 붙인다(글과 따로 표시한다) */
396
+ z.object({ type: z.literal("reasoning_delta"), messageId: z.string(), text: z.string() }),
397
+ /** 에이전트가 작업 단계를 알렸다 */
398
+ z.object({ type: z.literal("phase"), messageId: z.string(), phase: aiAgentPhaseSchema }),
399
+ /** 에이전트가 할 일 목록을 바꿔 썼다(목록 전체) */
400
+ z.object({
401
+ type: z.literal("todos"),
402
+ messageId: z.string(),
403
+ todos: z.array(aiTodoSchema).max(AI_TODOS_MAX),
404
+ }),
282
405
  z.object({
283
406
  type: z.literal("tool_start"),
284
407
  messageId: z.string(),
@@ -287,6 +410,10 @@ export const aiStreamEventSchema = z.discriminatedUnion("type", [
287
410
  label: z.string(),
288
411
  /** 쓰기 도구가 바꾸는 화면(읽기 도구는 없다) */
289
412
  focus: aiFocusSchema.optional(),
413
+ /** 하는 일의 종류(정하지 못한 도구는 없다) */
414
+ activity: aiActivitySchema.optional(),
415
+ target: z.string().max(300).optional(),
416
+ changes: z.array(aiToolChangeSchema).max(10).optional(),
290
417
  }),
291
418
  z.object({
292
419
  type: z.literal("tool_result"),
@@ -301,6 +428,8 @@ export const aiStreamEventSchema = z.discriminatedUnion("type", [
301
428
  question: z.string(),
302
429
  options: z.array(z.object({ label: z.string(), value: z.string() })),
303
430
  }),
431
+ /** 사업자 정보 입력 폼을 보였다 — 답은 다음 메시지로 온다 */
432
+ z.object({ type: z.literal("business_form"), messageId: z.string(), question: z.string() }),
304
433
  /** 공개 뒤 승인이 필요한 쓰기를 승인 요청으로 남겼다 */
305
434
  z.object({
306
435
  type: z.literal("approval"),
@@ -170,6 +170,7 @@ export function scopesCoverEvent(scopes: readonly string[], event: AppEventType)
170
170
  const HOST_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
171
171
  const TLD = /^(?:[a-z]{2,63}|xn--[a-z0-9-]{1,59})$/;
172
172
  export const APP_NETWORK_DENIED_SUFFIXES: readonly string[] = [
173
+ "sayren.io",
173
174
  "sayren.app",
174
175
  "sayren.co",
175
176
  "avarlabs.com",
@@ -544,7 +544,7 @@ export const appInvocationSchema = z.object({
544
544
  });
545
545
  export type AppInvocation = z.infer<typeof appInvocationSchema>;
546
546
 
547
- /** 앱 배포 키(developer.sayren.app 「키 발행」) — 값은 만들 때 한 번만 보인다 */
547
+ /** 앱 배포 키(developer.sayren.io 「키 발행」) — 값은 만들 때 한 번만 보인다 */
548
548
  export const appDeployKeySchema = z.object({
549
549
  keyId: z.string(),
550
550
  name: z.string(),
@@ -590,6 +590,7 @@ export type StorefrontPreviewLink = z.infer<typeof storefrontPreviewLinkSchema>;
590
590
  */
591
591
  export const STOREFRONT_PLATFORM_DOMAINS: readonly string[] = [
592
592
  "sayren.co",
593
+ "sayren.io",
593
594
  "sayren.app",
594
595
  "sayren.shop",
595
596
  "avarlabs.com",
package/src/session.ts CHANGED
@@ -39,7 +39,7 @@ const REVOKE_TIMEOUT_MS = 5_000;
39
39
  const REFRESH_LOCK = STORE_SESSION_KEYS.refresh;
40
40
 
41
41
  /**
42
- * 인가 서버(Sayren 계정, accounts) 주소 — `https://accounts.sayren.app/api/auth/oauth2/{authorize,token,revoke}`.
42
+ * 인가 서버(Sayren 계정, accounts) 주소 — `https://accounts.sayren.io/api/auth/oauth2/{authorize,token,revoke}`.
43
43
  * `resource`를 주면 authorize·code 교환·refresh에 RFC 8707 `resource`를 싣는다(JWT access token의 `aud`).
44
44
  */
45
45
  export interface StoreAuthServer {