@kokimoki/kit 1.6.7 → 1.8.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 ADDED
@@ -0,0 +1,76 @@
1
+ # @kokimoki/kit
2
+
3
+ Vite plugin and development tools for building Kokimoki apps.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @kokimoki/kit
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ Add the plugin to your `vite.config.ts`:
14
+
15
+ ```typescript
16
+ import { defineConfig } from "vite";
17
+ import { kokimokiKitPlugin } from "@kokimoki/kit";
18
+ import { z } from "@kokimoki/kit";
19
+
20
+ export default defineConfig({
21
+ plugins: [
22
+ kokimokiKitPlugin({
23
+ conceptId: "your-concept-id",
24
+ deployCodes: [
25
+ {
26
+ name: "default",
27
+ description: "Default configuration",
28
+ clientContext: {},
29
+ },
30
+ ],
31
+ schema: z.object({
32
+ title: z.string(),
33
+ maxPlayers: z.number(),
34
+ }),
35
+ stores: [
36
+ { pattern: "game", schema: z.object({ status: z.string() }) },
37
+ {
38
+ pattern: "player-*",
39
+ schema: z.object({ name: z.string() }),
40
+ local: true,
41
+ },
42
+ ],
43
+ i18nPath: "./src/i18n",
44
+ i18nPrimaryLng: "en",
45
+ devView: [
46
+ [{ label: "host", clientContext: { mode: "host" } }],
47
+ [
48
+ { label: "player1", clientContext: { mode: "player" } },
49
+ { label: "player2", clientContext: { mode: "player" } },
50
+ ],
51
+ ],
52
+ }),
53
+ ],
54
+ });
55
+ ```
56
+
57
+ ## Features
58
+
59
+ - **Vite Plugin** - Integrates Kokimoki into your Vite build
60
+ - **Dev Frame** - Multi-window development view for testing different player modes
61
+ - **Schema Validation** - Zod-based validation for stores and project config
62
+ - **i18n Sync** - Automatic synchronization of translations during development
63
+ - **Style Preprocessing** - Theme color processing for CSS variables
64
+
65
+ ## Documentation
66
+
67
+ See the [docs](./docs/) folder for detailed instructions:
68
+
69
+ | File | Description |
70
+ | ------------------------------------------------------------------- | ------------------------------ |
71
+ | [kokimoki-kit.instructions.md](./docs/kokimoki-kit.instructions.md) | Plugin configuration and usage |
72
+
73
+ ## Related Packages
74
+
75
+ - **@kokimoki/app** - Core SDK for runtime (stores, transactions, AI, etc.)
76
+ - **@kokimoki/cli** - CLI for project creation and deployment
package/dist/api.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Kokimoki API utilities
3
+ * Shared between @kokimoki/cli and @kokimoki/kit
4
+ */
5
+ import { Organization } from "./credentials";
6
+ export declare const DEFAULT_ENDPOINT = "https://api.kokimoki.com";
7
+ export interface KokimokiApiOptions {
8
+ endpoint: string;
9
+ apiKey: string;
10
+ }
11
+ export interface CreateConceptRequest {
12
+ packageName: string;
13
+ name: string;
14
+ }
15
+ export interface CreateConceptResponse {
16
+ id: string;
17
+ }
18
+ export interface CreateBuildRequest {
19
+ projectId: string;
20
+ schema: string;
21
+ deployCodes?: {
22
+ name: string;
23
+ description: string;
24
+ clientContext: any;
25
+ }[];
26
+ defaultStyle?: string;
27
+ defaultConfig?: string;
28
+ version: string;
29
+ changelog?: string;
30
+ i18n?: {
31
+ path: string;
32
+ primaryLng: string;
33
+ namespaces: string[];
34
+ languages: string[];
35
+ };
36
+ }
37
+ export interface CreateBuildResponse {
38
+ id: string;
39
+ publicPath: string;
40
+ }
41
+ /**
42
+ * Create a new concept via the Kokimoki API
43
+ */
44
+ export declare function createConcept(options: KokimokiApiOptions, request: CreateConceptRequest): Promise<CreateConceptResponse>;
45
+ /**
46
+ * Get organization details via the Kokimoki API
47
+ * Used for authentication validation
48
+ */
49
+ export declare function getOrganization(endpoint: string, apiKey: string): Promise<Organization>;
50
+ /**
51
+ * Get the organization that owns a concept (by concept ID)
52
+ */
53
+ export declare function getConceptOrganization(endpoint: string, conceptId: string): Promise<Organization>;
54
+ /**
55
+ * Create a new build via the Kokimoki API
56
+ */
57
+ export declare function createBuild(options: KokimokiApiOptions, request: CreateBuildRequest, cliVersion?: string): Promise<CreateBuildResponse>;
58
+ /**
59
+ * Get the deploy URL for a build
60
+ */
61
+ export declare function getDeployUrl(endpoint: string, apiKey: string, buildId: string): Promise<{
62
+ url: string;
63
+ }>;
package/dist/api.js ADDED
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Kokimoki API utilities
4
+ * Shared between @kokimoki/cli and @kokimoki/kit
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getDeployUrl = exports.createBuild = exports.getConceptOrganization = exports.getOrganization = exports.createConcept = exports.DEFAULT_ENDPOINT = void 0;
8
+ exports.DEFAULT_ENDPOINT = "https://api.kokimoki.com";
9
+ /**
10
+ * Make a request to the Kokimoki API
11
+ */
12
+ async function kokimokiApiRequest(path, options, init) {
13
+ const url = `${options.endpoint}${path}`;
14
+ const res = await fetch(url, {
15
+ ...init,
16
+ headers: {
17
+ Authorization: options.apiKey,
18
+ "Content-Type": "application/json",
19
+ ...init?.headers,
20
+ },
21
+ });
22
+ if (!res.ok) {
23
+ const errorText = await res.text();
24
+ throw new Error(`Kokimoki API request failed: ${res.status} ${res.statusText} - ${errorText}`);
25
+ }
26
+ return await res.json();
27
+ }
28
+ /**
29
+ * Create a new concept via the Kokimoki API
30
+ */
31
+ async function createConcept(options, request) {
32
+ return kokimokiApiRequest("/concepts", options, {
33
+ method: "POST",
34
+ body: JSON.stringify(request),
35
+ });
36
+ }
37
+ exports.createConcept = createConcept;
38
+ /**
39
+ * Get organization details via the Kokimoki API
40
+ * Used for authentication validation
41
+ */
42
+ async function getOrganization(endpoint, apiKey) {
43
+ return kokimokiApiRequest("/auth", { endpoint, apiKey }, { method: "GET" });
44
+ }
45
+ exports.getOrganization = getOrganization;
46
+ /**
47
+ * Get the organization that owns a concept (by concept ID)
48
+ */
49
+ async function getConceptOrganization(endpoint, conceptId) {
50
+ const url = `${endpoint}/concepts/${conceptId}/organization`;
51
+ const res = await fetch(url, {
52
+ method: "GET",
53
+ headers: {
54
+ "Content-Type": "application/json",
55
+ },
56
+ });
57
+ if (!res.ok) {
58
+ const errorData = await res.json().catch(() => ({}));
59
+ throw new Error(errorData.message || `Failed to get concept organization: ${res.status}`);
60
+ }
61
+ return await res.json();
62
+ }
63
+ exports.getConceptOrganization = getConceptOrganization;
64
+ /**
65
+ * Create a new build via the Kokimoki API
66
+ */
67
+ async function createBuild(options, request, cliVersion) {
68
+ return kokimokiApiRequest("/builds", options, {
69
+ method: "POST",
70
+ headers: {
71
+ "X-KM-CLI-Version": cliVersion || "",
72
+ },
73
+ body: JSON.stringify(request),
74
+ });
75
+ }
76
+ exports.createBuild = createBuild;
77
+ /**
78
+ * Get the deploy URL for a build
79
+ */
80
+ async function getDeployUrl(endpoint, apiKey, buildId) {
81
+ const url = `${endpoint}/deploy-urls/builds/${buildId}`;
82
+ const res = await fetch(url, {
83
+ method: "GET",
84
+ headers: {
85
+ "Content-Type": "application/json",
86
+ Authorization: apiKey,
87
+ },
88
+ });
89
+ if (!res.ok) {
90
+ const errorData = await res.json().catch(() => ({}));
91
+ throw new Error(errorData.message || `Failed to get deploy URL: ${res.status}`);
92
+ }
93
+ return await res.json();
94
+ }
95
+ exports.getDeployUrl = getDeployUrl;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Credentials management for Kokimoki CLI and Kit
3
+ * Shared between @kokimoki/cli and @kokimoki/kit
4
+ */
5
+ export interface Credentials {
6
+ apiKeys: {
7
+ [organizationId: string]: string;
8
+ };
9
+ }
10
+ export interface Organization {
11
+ id: string;
12
+ name: string;
13
+ }
14
+ /**
15
+ * Get the path to the global credentials file (~/.kokimoki)
16
+ */
17
+ export declare function getCredentialsPath(): string;
18
+ /**
19
+ * Read credentials from the ~/.kokimoki file
20
+ */
21
+ export declare function readCredentials(): Promise<Credentials | null>;
22
+ /**
23
+ * Write credentials to the ~/.kokimoki file
24
+ */
25
+ export declare function writeCredentials(credentials: Credentials): Promise<void>;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * Credentials management for Kokimoki CLI and Kit
4
+ * Shared between @kokimoki/cli and @kokimoki/kit
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.writeCredentials = exports.readCredentials = exports.getCredentialsPath = void 0;
11
+ const promises_1 = __importDefault(require("fs/promises"));
12
+ const os_1 = require("os");
13
+ const path_1 = __importDefault(require("path"));
14
+ /**
15
+ * Get the path to the global credentials file (~/.kokimoki)
16
+ */
17
+ function getCredentialsPath() {
18
+ return path_1.default.join((0, os_1.homedir)(), ".kokimoki");
19
+ }
20
+ exports.getCredentialsPath = getCredentialsPath;
21
+ /**
22
+ * Read credentials from the ~/.kokimoki file
23
+ */
24
+ async function readCredentials() {
25
+ try {
26
+ const credentialsPath = getCredentialsPath();
27
+ const credentialsJson = await promises_1.default.readFile(credentialsPath, "utf-8");
28
+ return JSON.parse(credentialsJson);
29
+ }
30
+ catch (e) {
31
+ if (e?.code === "ENOENT") {
32
+ return null;
33
+ }
34
+ throw e;
35
+ }
36
+ }
37
+ exports.readCredentials = readCredentials;
38
+ /**
39
+ * Write credentials to the ~/.kokimoki file
40
+ */
41
+ async function writeCredentials(credentials) {
42
+ const credentialsPath = getCredentialsPath();
43
+ await promises_1.default.writeFile(credentialsPath, JSON.stringify(credentials, null, 2));
44
+ }
45
+ exports.writeCredentials = writeCredentials;
@@ -0,0 +1,57 @@
1
+ import { Credentials, Organization } from "./credentials";
2
+ export type { Credentials, Organization };
3
+ export interface DevAppConfig {
4
+ conceptId: string;
5
+ endpoint?: string;
6
+ }
7
+ export interface DevAppError {
8
+ code: "CONCEPT_NOT_FOUND" | "NO_CREDENTIALS" | "DEV_APP_INIT_FAILED";
9
+ message: string;
10
+ }
11
+ export interface DevAppResult {
12
+ appId: string;
13
+ buildUrl?: string;
14
+ organization?: Organization;
15
+ error?: DevAppError;
16
+ }
17
+ /**
18
+ * Delete the app ID file from .kokimoki/app-id
19
+ */
20
+ export declare function deleteAppId(): Promise<void>;
21
+ /**
22
+ * Set i18n metadata for a dev app
23
+ */
24
+ export declare function setDevAppI18n(endpoint: string, apiKey: string, appId: string, i18nMeta: {
25
+ path: string;
26
+ primaryLng: string;
27
+ namespaces: string[];
28
+ }): Promise<void>;
29
+ /**
30
+ * Set translations for a specific language and namespace in a dev app
31
+ */
32
+ export declare function setDevAppTranslation(endpoint: string, apiKey: string, appId: string, lng: string, namespace: string, translations: Record<string, unknown>): Promise<void>;
33
+ /**
34
+ * Compute a hash of the stores configuration
35
+ */
36
+ export declare function computeStoresHash(stores: unknown): string;
37
+ /**
38
+ * Read the stored stores hash from .kokimoki/stores-hash
39
+ */
40
+ export declare function readStoresHash(): Promise<string | null>;
41
+ /**
42
+ * Write the stores hash to .kokimoki/stores-hash
43
+ */
44
+ export declare function writeStoresHash(hash: string): Promise<void>;
45
+ /**
46
+ * Get or create a dev app for local development.
47
+ *
48
+ * This function will:
49
+ * 1. Verify the concept ID and get the organization it belongs to
50
+ * 2. Read credentials from ~/.kokimoki
51
+ * 3. If an app ID exists locally, verify it with the API
52
+ * 4. If no app ID exists or verification fails, initialize a new dev app
53
+ * 5. Store the app ID in .kokimoki/app-id
54
+ *
55
+ * If credentials are not available, falls back to generating a local-only ObjectId.
56
+ */
57
+ export declare function getOrCreateDevApp(config: DevAppConfig): Promise<DevAppResult>;
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getOrCreateDevApp = exports.writeStoresHash = exports.readStoresHash = exports.computeStoresHash = exports.setDevAppTranslation = exports.setDevAppI18n = exports.deleteAppId = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const bson_objectid_1 = __importDefault(require("bson-objectid"));
9
+ const promises_1 = __importDefault(require("fs/promises"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const api_1 = require("./api");
12
+ const credentials_1 = require("./credentials");
13
+ const KOKIMOKI_DIR = ".kokimoki";
14
+ const APP_ID_FILE = "app-id";
15
+ const STORES_HASH_FILE = "stores-hash";
16
+ /**
17
+ * Initialize a new dev app via the Kokimoki API
18
+ */
19
+ async function initializeDevApp(endpoint, apiKey, conceptId) {
20
+ const url = `${endpoint}/dev-apps`;
21
+ const res = await fetch(url, {
22
+ method: "POST",
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ Authorization: apiKey,
26
+ },
27
+ body: JSON.stringify({ conceptId }),
28
+ });
29
+ if (!res.ok) {
30
+ const errorData = await res.json().catch(() => ({}));
31
+ throw new Error(errorData.message || `Failed to initialize dev app: ${res.status}`);
32
+ }
33
+ const data = await res.json();
34
+ return { appId: data.appId, buildUrl: data.buildUrl };
35
+ }
36
+ /**
37
+ * Verify an existing dev app ID via the Kokimoki API
38
+ * Returns buildUrl if valid, null if invalid
39
+ */
40
+ async function verifyDevApp(endpoint, apiKey, appId, conceptId) {
41
+ const url = `${endpoint}/dev-apps/${appId}/verify`;
42
+ const res = await fetch(url, {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ Authorization: apiKey,
47
+ },
48
+ body: JSON.stringify({ conceptId }),
49
+ });
50
+ if (!res.ok) {
51
+ // If verification fails, the app ID may be invalid or belong to a different concept
52
+ return null;
53
+ }
54
+ const data = await res.json();
55
+ return data.valid === true ? data.buildUrl : null;
56
+ }
57
+ /**
58
+ * Ensure the .kokimoki directory exists
59
+ */
60
+ async function ensureKokimokiDir() {
61
+ try {
62
+ await promises_1.default.mkdir(KOKIMOKI_DIR);
63
+ }
64
+ catch (e) {
65
+ if (e?.code !== "EEXIST") {
66
+ throw e;
67
+ }
68
+ }
69
+ }
70
+ /**
71
+ * Read the local app ID from .kokimoki/app-id
72
+ */
73
+ async function readLocalAppId() {
74
+ try {
75
+ const appId = await promises_1.default.readFile(path_1.default.join(KOKIMOKI_DIR, APP_ID_FILE), "utf8");
76
+ return appId.trim() || null;
77
+ }
78
+ catch (e) {
79
+ if (e?.code === "ENOENT") {
80
+ return null;
81
+ }
82
+ throw e;
83
+ }
84
+ }
85
+ /**
86
+ * Write the app ID to .kokimoki/app-id
87
+ */
88
+ async function writeLocalAppId(appId) {
89
+ await ensureKokimokiDir();
90
+ await promises_1.default.writeFile(path_1.default.join(KOKIMOKI_DIR, APP_ID_FILE), appId);
91
+ }
92
+ /**
93
+ * Delete the app ID file from .kokimoki/app-id
94
+ */
95
+ async function deleteAppId() {
96
+ try {
97
+ await promises_1.default.unlink(path_1.default.join(KOKIMOKI_DIR, APP_ID_FILE));
98
+ }
99
+ catch (e) {
100
+ if (e?.code !== "ENOENT") {
101
+ throw e;
102
+ }
103
+ }
104
+ }
105
+ exports.deleteAppId = deleteAppId;
106
+ /**
107
+ * Set i18n metadata for a dev app
108
+ */
109
+ async function setDevAppI18n(endpoint, apiKey, appId, i18nMeta) {
110
+ const url = `${endpoint}/dev-apps/${appId}/i18n`;
111
+ const res = await fetch(url, {
112
+ method: "PUT",
113
+ headers: {
114
+ "Content-Type": "application/json",
115
+ Authorization: apiKey,
116
+ },
117
+ body: JSON.stringify(i18nMeta),
118
+ });
119
+ if (!res.ok) {
120
+ const errorText = await res.text().catch(() => "");
121
+ throw new Error(`Failed to set dev app i18n: ${res.status} ${errorText}`);
122
+ }
123
+ }
124
+ exports.setDevAppI18n = setDevAppI18n;
125
+ /**
126
+ * Set translations for a specific language and namespace in a dev app
127
+ */
128
+ async function setDevAppTranslation(endpoint, apiKey, appId, lng, namespace, translations) {
129
+ const url = `${endpoint}/dev-apps/${appId}/translations/${lng}/${namespace}`;
130
+ const res = await fetch(url, {
131
+ method: "PUT",
132
+ headers: {
133
+ "Content-Type": "application/json",
134
+ Authorization: apiKey,
135
+ },
136
+ body: JSON.stringify(translations),
137
+ });
138
+ if (!res.ok) {
139
+ const errorText = await res.text().catch(() => "");
140
+ throw new Error(`Failed to set dev app translation: ${res.status} ${errorText}`);
141
+ }
142
+ }
143
+ exports.setDevAppTranslation = setDevAppTranslation;
144
+ /**
145
+ * Compute a hash of the stores configuration
146
+ */
147
+ function computeStoresHash(stores) {
148
+ const content = JSON.stringify(stores ?? []);
149
+ return crypto_1.default.createHash("sha256").update(content).digest("hex").slice(0, 16);
150
+ }
151
+ exports.computeStoresHash = computeStoresHash;
152
+ /**
153
+ * Read the stored stores hash from .kokimoki/stores-hash
154
+ */
155
+ async function readStoresHash() {
156
+ try {
157
+ const hash = await promises_1.default.readFile(path_1.default.join(KOKIMOKI_DIR, STORES_HASH_FILE), "utf8");
158
+ return hash.trim() || null;
159
+ }
160
+ catch (e) {
161
+ if (e?.code === "ENOENT") {
162
+ return null;
163
+ }
164
+ throw e;
165
+ }
166
+ }
167
+ exports.readStoresHash = readStoresHash;
168
+ /**
169
+ * Write the stores hash to .kokimoki/stores-hash
170
+ */
171
+ async function writeStoresHash(hash) {
172
+ await ensureKokimokiDir();
173
+ await promises_1.default.writeFile(path_1.default.join(KOKIMOKI_DIR, STORES_HASH_FILE), hash);
174
+ }
175
+ exports.writeStoresHash = writeStoresHash;
176
+ /**
177
+ * Get or create a dev app for local development.
178
+ *
179
+ * This function will:
180
+ * 1. Verify the concept ID and get the organization it belongs to
181
+ * 2. Read credentials from ~/.kokimoki
182
+ * 3. If an app ID exists locally, verify it with the API
183
+ * 4. If no app ID exists or verification fails, initialize a new dev app
184
+ * 5. Store the app ID in .kokimoki/app-id
185
+ *
186
+ * If credentials are not available, falls back to generating a local-only ObjectId.
187
+ */
188
+ async function getOrCreateDevApp(config) {
189
+ const endpoint = config.endpoint ?? api_1.DEFAULT_ENDPOINT;
190
+ await ensureKokimokiDir();
191
+ // Try to read existing app ID
192
+ let appId = await readLocalAppId();
193
+ // Try to verify concept and get organization
194
+ let organization;
195
+ try {
196
+ organization = await (0, api_1.getConceptOrganization)(endpoint, config.conceptId);
197
+ }
198
+ catch (e) {
199
+ const errorMessage = e instanceof Error ? e.message : "unknown error";
200
+ console.warn(`[kokimoki-kit] Could not verify concept: ${errorMessage}`);
201
+ // Fall back to local-only mode but report error
202
+ if (!appId) {
203
+ appId = new bson_objectid_1.default().toHexString();
204
+ await writeLocalAppId(appId);
205
+ }
206
+ return {
207
+ appId,
208
+ error: {
209
+ code: "CONCEPT_NOT_FOUND",
210
+ message: `Could not verify concept "${config.conceptId}": ${errorMessage}. Make sure the conceptId in your vite.config is correct.`,
211
+ },
212
+ };
213
+ }
214
+ // Try to read credentials
215
+ const credentials = await (0, credentials_1.readCredentials)();
216
+ const apiKey = credentials?.apiKeys?.[organization.id];
217
+ if (!apiKey) {
218
+ const errorMessage = `No credentials found for organization ${organization.name} (${organization.id}). Run "kokimoki login" to authenticate.`;
219
+ console.warn(`[kokimoki-kit] ${errorMessage}`);
220
+ // Fall back to local-only mode but report error
221
+ if (!appId) {
222
+ appId = new bson_objectid_1.default().toHexString();
223
+ await writeLocalAppId(appId);
224
+ }
225
+ return {
226
+ appId,
227
+ organization,
228
+ error: {
229
+ code: "NO_CREDENTIALS",
230
+ message: errorMessage,
231
+ },
232
+ };
233
+ }
234
+ // If we have an existing app ID, verify it
235
+ if (appId) {
236
+ try {
237
+ const buildUrl = await verifyDevApp(endpoint, apiKey, appId, config.conceptId);
238
+ if (buildUrl) {
239
+ return { appId, buildUrl, organization };
240
+ }
241
+ console.warn(`[kokimoki-kit] Existing dev app ID is invalid or belongs to a different concept. Creating new one.`);
242
+ }
243
+ catch (e) {
244
+ console.warn(`[kokimoki-kit] Could not verify dev app: ${e instanceof Error ? e.message : "unknown error"}`);
245
+ }
246
+ }
247
+ // Initialize a new dev app
248
+ try {
249
+ const { appId: newAppId, buildUrl } = await initializeDevApp(endpoint, apiKey, config.conceptId);
250
+ appId = newAppId;
251
+ await writeLocalAppId(appId);
252
+ return { appId, buildUrl, organization };
253
+ }
254
+ catch (e) {
255
+ const errorMessage = e instanceof Error ? e.message : "unknown error";
256
+ console.warn(`[kokimoki-kit] Could not initialize dev app: ${errorMessage}`);
257
+ // Fall back to local-only mode but report error
258
+ if (!appId) {
259
+ appId = new bson_objectid_1.default().toHexString();
260
+ await writeLocalAppId(appId);
261
+ }
262
+ return {
263
+ appId,
264
+ organization,
265
+ error: {
266
+ code: "DEV_APP_INIT_FAILED",
267
+ message: `Could not initialize dev app: ${errorMessage}`,
268
+ },
269
+ };
270
+ }
271
+ }
272
+ exports.getOrCreateDevApp = getOrCreateDevApp;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Dev frame module - provides a multi-window development view
3
+ * for testing host, presenter, and player modes simultaneously
4
+ */
5
+ export { renderDevFrame, type DevFrameCell, type DevFrameConfig, } from "./render-dev-frame";
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * Dev frame module - provides a multi-window development view
4
+ * for testing host, presenter, and player modes simultaneously
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.renderDevFrame = void 0;
8
+ var render_dev_frame_1 = require("./render-dev-frame");
9
+ Object.defineProperty(exports, "renderDevFrame", { enumerable: true, get: function () { return render_dev_frame_1.renderDevFrame; } });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Renders the dev frame HTML page - a multi-window view for development
3
+ */
4
+ export interface DevFrameCell {
5
+ /** Label shown in the frame header */
6
+ label: string;
7
+ /** Client context passed to the app */
8
+ clientContext: unknown;
9
+ }
10
+ export interface DevFrameConfig {
11
+ /** Title shown in browser tab */
12
+ title?: string;
13
+ /**
14
+ * App meta for SEO and social sharing. Title will be appended with " - Dev View".
15
+ */
16
+ appMeta?: {
17
+ lang?: string;
18
+ title?: string;
19
+ description?: string;
20
+ ogTitle?: string;
21
+ ogDescription?: string;
22
+ ogImage?: string;
23
+ favicon?: string;
24
+ };
25
+ /**
26
+ * Grid layout for the dev frame. Each inner array is a row,
27
+ * and each item in the row is a cell/frame.
28
+ *
29
+ * Example:
30
+ * ```
31
+ * [
32
+ * [{ label: 'host', clientContext: {...} }, { label: 'presenter', clientContext: {...} }],
33
+ * [{ label: 'player1', clientContext: {...} }, { label: 'player2', clientContext: {...} }]
34
+ * ]
35
+ * ```
36
+ */
37
+ rows: readonly (readonly DevFrameCell[])[];
38
+ }
39
+ /**
40
+ * Render the complete dev frame HTML page
41
+ */
42
+ export declare function renderDevFrame(config: DevFrameConfig): string;