gencow 0.1.156 → 0.1.158

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.
@@ -13,6 +13,7 @@ export function renderDeployHelp() {
13
13
  log(` ${BOLD}Options:${RESET}`);
14
14
  log(` ${DIM}--static [dir]${RESET} Deploy backend first, then static files`);
15
15
  log(` ${DIM}--prod${RESET} Deploy to production (Pro+ only)`);
16
+ log(` ${DIM}--region <slug>${RESET} First production app placement region`);
16
17
  log(` ${DIM}--rollback${RESET} Rollback to previous deployment`);
17
18
  log(` ${DIM}--force, -f${RESET} Skip optional dependency scan`);
18
19
  log(` ${DIM}--app, -a${RESET} Target specific app\n`);
@@ -21,7 +22,7 @@ export function renderDeployHelp() {
21
22
  log(` gencow deploy status`);
22
23
  log(` gencow deploy logs -n 100`);
23
24
  log(` gencow deploy --static dist/`);
24
- log(` gencow deploy --prod`);
25
+ log(` gencow deploy --prod --region kr`);
25
26
  log(` gencow deploy --rollback\n`);
26
27
  }
27
28
 
@@ -15,6 +15,7 @@ export function parseDeployArgs(deployArgs) {
15
15
  envTarget: "dev",
16
16
  forceDeploy: false,
17
17
  isRollback: false,
18
+ region: null,
18
19
  staticDeploy: false,
19
20
  staticDirArg: null,
20
21
  unknownArg: null,
@@ -26,7 +27,21 @@ export function parseDeployArgs(deployArgs) {
26
27
  else if (arg === "--force" || arg === "-f") parsed.forceDeploy = true;
27
28
  else if (arg === "--rollback") parsed.isRollback = true;
28
29
  else if (arg === "--app" || arg === "-a") parsed.appId = deployArgs[++index];
29
- else if (arg === "--static") {
30
+ else if (arg === "--region") {
31
+ const value = deployArgs[index + 1];
32
+ if (!value || value.startsWith("-")) {
33
+ parsed.unknownArg = arg;
34
+ break;
35
+ }
36
+ parsed.region = value;
37
+ index += 1;
38
+ } else if (arg.startsWith("--region=")) {
39
+ parsed.region = arg.slice("--region=".length) || null;
40
+ if (!parsed.region) {
41
+ parsed.unknownArg = arg;
42
+ break;
43
+ }
44
+ } else if (arg === "--static") {
30
45
  parsed.staticDeploy = true;
31
46
  const maybeDir = deployArgs[index + 1];
32
47
  if (maybeDir && !maybeDir.startsWith("-")) {
@@ -87,6 +102,7 @@ function showUnknownDeployArgument(arg, { errorImpl = error, infoImpl = info, lo
87
102
  infoImpl("Usage: gencow deploy [options]");
88
103
  infoImpl(" gencow deploy Dev backend deploy");
89
104
  infoImpl(" gencow deploy --prod Production backend deploy (Pro+)");
105
+ infoImpl(" gencow deploy --prod --region kr First production app placement");
90
106
  infoImpl(" gencow deploy --rollback Rollback to previous version");
91
107
  infoImpl(" gencow deploy logs View server logs");
92
108
  infoImpl(" gencow deploy status Check app status");
@@ -134,6 +150,7 @@ async function ensureProductionDeployTarget({
134
150
  writeFileSyncImpl = writeFileSync,
135
151
  exitImpl = (code) => process.exit(code),
136
152
  prodAppId = null,
153
+ region = null,
137
154
  }) {
138
155
  if (!appId || prodAppId) {
139
156
  return prodAppId ?? appId;
@@ -154,6 +171,7 @@ async function ensureProductionDeployTarget({
154
171
  const createProdRes = await platformFetchImpl(creds, `/platform/apps/${appId}/create-prod`, {
155
172
  method: "POST",
156
173
  headers: { "Content-Type": "application/json" },
174
+ body: JSON.stringify(region ? { region } : {}),
157
175
  });
158
176
 
159
177
  if (!createProdRes.ok) {
@@ -187,9 +205,17 @@ async function ensureProductionDeployTarget({
187
205
 
188
206
  if (createProdData.alreadyExists) {
189
207
  infoImpl(`Prod app verified: ${nextProdAppId}`);
208
+ if (region && createProdData.region && createProdData.region !== region) {
209
+ infoImpl(
210
+ `Region unchanged: existing prod app is in ${createProdData.region}; --region only applies to first prod creation.`,
211
+ );
212
+ }
190
213
  } else {
191
214
  successImpl(`Prod app created: ${nextProdAppId}`);
192
215
  infoImpl(`URL: ${createProdData.url}`);
216
+ if (createProdData.region) {
217
+ infoImpl(`Region: ${createProdData.region} (${createProdData.regionSource || "selected"})`);
218
+ }
193
219
  await new Promise((resolveDelay) => setTimeoutImpl(resolveDelay, 3000));
194
220
  }
195
221
 
@@ -391,10 +417,14 @@ export function createDeployCommand({
391
417
  writeFileSyncImpl,
392
418
  exitImpl,
393
419
  prodAppId,
420
+ region: parsed.region,
394
421
  });
395
422
  if (!prodTarget) return;
396
423
  appId = prodTarget;
397
424
  infoImpl(`Deploy target: ${CYAN}${appId}${RESET} (production)`);
425
+ if (prodAppId && parsed.region) {
426
+ infoImpl("Region option only applies when the production app is first created; no app move was requested.");
427
+ }
398
428
  }
399
429
 
400
430
  if (parsed.staticDeploy) {
@@ -298,7 +298,7 @@ export function buildAiPrompt(apiObj, namespaces) {
298
298
  md += `- Existing generated components can use ai.chat(), ai.vision.extractText(), ai.image.generate(), and ai.generateObject() through the Gencow facade.\n`;
299
299
  md += `- Do not import @ai-sdk/openai/createOpenAI directly, call provider APIs directly, or create wrapper files such as openai-direct.ts.\n`;
300
300
  md += `- Do not log image base64, prompts, provider tokens, or raw private document snippets.\n`;
301
- md += `- Recommended models: gpt-5.4-mini for most apps, gpt-5.5 for highest quality, gpt-5.4-nano for high-volume extraction.\n`;
301
+ md += `- Recommended chat models: gpt-5.4-mini for most apps, gpt-5.5 for Pro/Scale highest quality, gpt-5.4-nano for high-volume extraction.\n`;
302
302
  md += `- Use ai.generateObject() with a Zod schema for structured output. Do not use ai.chat() + JSON.parse().\n`;
303
303
  md += `- Install AI with gencow add AI, RAG with gencow add RAG, Memory with gencow add Memory, and durable agents with gencow add Agent.\n`;
304
304
  md += `\`\`\`\n\n`;
@@ -307,7 +307,7 @@ export function buildAiPrompt(apiObj, namespaces) {
307
307
 
308
308
  export function buildAiUsageSection() {
309
309
  let md = `---\n\n## AI Usage\n\n`;
310
- md += `Gencow supports GPT-5.5, GPT-5.4, GPT-5 mini/nano, GPT-4.1, GPT-4o, and GPT Image models through the platform \`model_pricing\` catalog.\n`;
310
+ md += `Gencow supports a focused active chat catalog: gpt-5.5 on Pro/Scale, gpt-5.4-mini by default, gpt-5.4-nano for high volume, and temporary legacy gpt-4o-mini compatibility. Image and embedding models are also controlled by the platform \`model_pricing\` catalog.\n`;
311
311
  md += `Local development uses \`OPENAI_API_KEY\`; cloud deployments automatically use the Gencow AI proxy and service credits.\n\n`;
312
312
  md += `\`\`\`typescript\n`;
313
313
  md += `import { generateImage, generateText } from "ai";\n`;
@@ -341,10 +341,10 @@ export function buildAiUsageSection() {
341
341
  md += `\`\`\`\n\n`;
342
342
  md += `| Purpose | Recommended model |\n`;
343
343
  md += `|---|---|\n`;
344
- md += `| Highest quality / complex reasoning | \`gpt-5.5\` |\n`;
344
+ md += `| Highest quality / complex reasoning | \`gpt-5.5\` (Pro/Scale only) |\n`;
345
345
  md += `| Most production chat, coding, and agent tasks | \`gpt-5.4-mini\` |\n`;
346
346
  md += `| High-volume classification and extraction | \`gpt-5.4-nano\` |\n`;
347
- md += `| Compatibility low-cost path | \`gpt-4o-mini\` |\n`;
347
+ md += `| Temporary legacy compatibility path | \`gpt-4o-mini\` |\n`;
348
348
  md += `| Image-to-text / OCR-style extraction | \`gpt-5.4-mini\` or \`gpt-4o-mini\` |\n`;
349
349
  md += `| Image generation | \`gpt-image-2\` or \`gpt-image-1-mini\` |\n\n`;
350
350
  md += `Do not wire \`@ai-sdk/openai\` or provider keys directly. Use \`createGencowAI()\` or the \`ai.*\` facade from \`./ai\`.\n\n`;
@@ -1,8 +1,9 @@
1
- import { existsSync, readFileSync } from "fs";
1
+ import { existsSync, readFileSync, writeFileSync } from "fs";
2
2
  import { resolve } from "path";
3
3
 
4
4
  import { readProjectDisplayName } from "./cli-project-runtime.mjs";
5
- import { BOLD, CYAN, DIM, RESET, error, info, log } from "./output.mjs";
5
+ import { BOLD, CYAN, DIM, RESET, error, info, log, success } from "./output.mjs";
6
+ import { platformFetch } from "./platform-client.mjs";
6
7
  import { detectStaticDeployBackend } from "./static-deploy-command.mjs";
7
8
 
8
9
  export function parseStaticArgs(staticArgs) {
@@ -10,6 +11,7 @@ export function parseStaticArgs(staticArgs) {
10
11
  appId: null,
11
12
  forceDeploy: false,
12
13
  isProd: false,
14
+ region: null,
13
15
  staticDir: null,
14
16
  };
15
17
 
@@ -17,6 +19,8 @@ export function parseStaticArgs(staticArgs) {
17
19
  const arg = staticArgs[index];
18
20
  if (arg === "--force" || arg === "-f") parsed.forceDeploy = true;
19
21
  else if (arg === "--prod") parsed.isProd = true;
22
+ else if (arg === "--region") parsed.region = staticArgs[++index] || null;
23
+ else if (arg.startsWith("--region=")) parsed.region = arg.slice("--region=".length) || null;
20
24
  else if (arg === "--app" || arg === "-a") parsed.appId = staticArgs[++index];
21
25
  else if (!arg.startsWith("-")) parsed.staticDir = arg;
22
26
  }
@@ -62,6 +66,57 @@ export function readStaticProjectContext(options = {}) {
62
66
  };
63
67
  }
64
68
 
69
+ async function ensureStaticProductionTarget({
70
+ appId,
71
+ creds,
72
+ gencowJsonPath,
73
+ platformFetchImpl = platformFetch,
74
+ readFileSyncImpl = readFileSync,
75
+ region = null,
76
+ setTimeoutImpl = setTimeout,
77
+ successImpl = success,
78
+ errorImpl = error,
79
+ infoImpl = info,
80
+ writeFileSyncImpl = writeFileSync,
81
+ }) {
82
+ if (!appId) {
83
+ errorImpl("App not found. Run from the project root with gencow.json.");
84
+ return null;
85
+ }
86
+
87
+ infoImpl("Creating prod app...");
88
+ const createProdRes = await platformFetchImpl(creds, `/platform/apps/${appId}/create-prod`, {
89
+ method: "POST",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify(region ? { region } : {}),
92
+ });
93
+
94
+ if (!createProdRes.ok) {
95
+ const errData = await createProdRes.json().catch(() => ({}));
96
+ errorImpl(`Prod app creation failed: ${errData.error || createProdRes.statusText}`);
97
+ return null;
98
+ }
99
+
100
+ const createProdData = await createProdRes.json();
101
+ const nextProdAppId = createProdData.prodApp;
102
+ const gencowJson = existsSync(gencowJsonPath) ? JSON.parse(readFileSyncImpl(gencowJsonPath, "utf8")) : {};
103
+ gencowJson.prodApp = nextProdAppId;
104
+ writeFileSyncImpl(gencowJsonPath, JSON.stringify(gencowJson, null, 2));
105
+
106
+ if (createProdData.alreadyExists) {
107
+ infoImpl(`Prod app verified: ${nextProdAppId}`);
108
+ } else {
109
+ successImpl(`Prod app created: ${nextProdAppId}`);
110
+ if (createProdData.url) infoImpl(`URL: ${createProdData.url}`);
111
+ if (createProdData.region) {
112
+ infoImpl(`Region: ${createProdData.region} (${createProdData.regionSource || "selected"})`);
113
+ }
114
+ await new Promise((resolveDelay) => setTimeoutImpl(resolveDelay, 3000));
115
+ }
116
+
117
+ return nextProdAppId;
118
+ }
119
+
65
120
  export function createStaticCommand(options = {}) {
66
121
  const {
67
122
  BOLDValue = BOLD,
@@ -72,9 +127,14 @@ export function createStaticCommand(options = {}) {
72
127
  errorImpl = error,
73
128
  infoImpl = info,
74
129
  logImpl = log,
130
+ platformFetchImpl = platformFetch,
131
+ readFileSyncImpl = readFileSync,
75
132
  readStaticProjectContextImpl = readStaticProjectContext,
76
133
  requireCredsImpl,
77
134
  runStaticDeployRuntimeImpl,
135
+ setTimeoutImpl = setTimeout,
136
+ successImpl = success,
137
+ writeFileSyncImpl = writeFileSync,
78
138
  } = options;
79
139
 
80
140
  return async function runStaticCommand(...staticArgs) {
@@ -87,6 +147,7 @@ export function createStaticCommand(options = {}) {
87
147
  );
88
148
  logImpl(` ${BOLDValue}Options:${RESETValue}`);
89
149
  logImpl(` ${DIMValue}--prod${RESETValue} Deploy to production app`);
150
+ logImpl(` ${DIMValue}--region <slug>${RESETValue} First production app placement region`);
90
151
  logImpl(` ${DIMValue}--force, -f${RESETValue} Skip optional dependency scan`);
91
152
  logImpl(` ${DIMValue}--app, -a${RESETValue} Target specific app\n`);
92
153
  logImpl(` ${BOLDValue}Examples:${RESETValue}`);
@@ -104,13 +165,26 @@ export function createStaticCommand(options = {}) {
104
165
  cwd: cwdImpl(),
105
166
  explicitAppId: parsed.appId,
106
167
  });
107
- const deployTarget = parsed.isProd ? prodAppId || appId : appId;
168
+ let deployTarget = parsed.isProd ? prodAppId || appId : appId;
108
169
  const envTarget = parsed.isProd ? "prod" : "dev";
109
170
 
110
171
  if (parsed.isProd && !prodAppId) {
111
- errorImpl("No production app found. Run 'gencow deploy' first to create one.");
112
- infoImpl(`${DIMValue}For dev static deploy, omit --prod: gencow static [dir]${RESETValue}\n`);
113
- return;
172
+ deployTarget = await ensureStaticProductionTarget({
173
+ appId,
174
+ creds,
175
+ gencowJsonPath,
176
+ platformFetchImpl,
177
+ readFileSyncImpl,
178
+ region: parsed.region,
179
+ setTimeoutImpl,
180
+ successImpl,
181
+ errorImpl,
182
+ infoImpl,
183
+ writeFileSyncImpl,
184
+ });
185
+ if (!deployTarget) return;
186
+ } else if (parsed.isProd && parsed.region) {
187
+ infoImpl("Region option only applies when the production app is first created; no app move was requested.");
114
188
  }
115
189
 
116
190
  return runStaticDeployRuntimeImpl({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.156",
3
+ "version": "0.1.158",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {