@openbuilder/cli 0.50.27 → 0.50.29
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/chunks/{main-tui-CDEiflqI.js → main-tui-Cklcr3FX.js} +2 -2
- package/dist/chunks/{main-tui-CDEiflqI.js.map → main-tui-Cklcr3FX.js.map} +1 -1
- package/dist/chunks/{run-Di8CGWtT.js → run-wycadErJ.js} +2 -1
- package/dist/chunks/{run-Di8CGWtT.js.map → run-wycadErJ.js.map} +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/index.js +751 -13
- package/dist/index.js.map +1 -1
- package/dist/instrument.js +10 -10
- package/dist/instrument.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,9 +10,10 @@ import 'zod/v4';
|
|
|
10
10
|
import 'zod/v3';
|
|
11
11
|
import { parse } from 'jsonc-parser';
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
-
import { existsSync } from 'fs';
|
|
13
|
+
import { existsSync, mkdirSync as mkdirSync$1 } from 'fs';
|
|
14
14
|
import { existsSync as existsSync$1, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
15
|
import { readFile } from 'fs/promises';
|
|
16
|
+
import * as path from 'path';
|
|
16
17
|
import { join } from 'path';
|
|
17
18
|
import WebSocket$1, { WebSocketServer, WebSocket } from 'ws';
|
|
18
19
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
@@ -30,6 +31,7 @@ import { EventEmitter } from 'node:events';
|
|
|
30
31
|
import { createServer, createConnection } from 'node:net';
|
|
31
32
|
import { readFile as readFile$1, rm, writeFile, readdir } from 'node:fs/promises';
|
|
32
33
|
import { simpleGit } from 'simple-git';
|
|
34
|
+
import * as os from 'os';
|
|
33
35
|
import { tunnelManager } from './chunks/manager-CvGX9qqe.js';
|
|
34
36
|
import 'chalk';
|
|
35
37
|
import 'node:util';
|
|
@@ -2139,7 +2141,7 @@ var init_design$1 = __esm$1({
|
|
|
2139
2141
|
|
|
2140
2142
|
// src/config/tags.ts
|
|
2141
2143
|
function findTagDefinition$1(key) {
|
|
2142
|
-
for (const def of TAG_DEFINITIONS$
|
|
2144
|
+
for (const def of TAG_DEFINITIONS$2) {
|
|
2143
2145
|
if (def.key === key) return def;
|
|
2144
2146
|
if (def.children) {
|
|
2145
2147
|
const child = def.children.find((c) => c.key === key);
|
|
@@ -2148,10 +2150,10 @@ function findTagDefinition$1(key) {
|
|
|
2148
2150
|
}
|
|
2149
2151
|
return void 0;
|
|
2150
2152
|
}
|
|
2151
|
-
var TAG_DEFINITIONS$
|
|
2153
|
+
var TAG_DEFINITIONS$2;
|
|
2152
2154
|
var init_tags$1 = __esm$1({
|
|
2153
2155
|
"src/config/tags.ts"() {
|
|
2154
|
-
TAG_DEFINITIONS$
|
|
2156
|
+
TAG_DEFINITIONS$2 = [
|
|
2155
2157
|
// Model Selection (explicit provider + model mapping)
|
|
2156
2158
|
{
|
|
2157
2159
|
key: "model",
|
|
@@ -3914,6 +3916,7 @@ Create as many tasks as needed for the request (3-15+ tasks based on complexity)
|
|
|
3914
3916
|
|
|
3915
3917
|
// src/shared/runner/messages.ts
|
|
3916
3918
|
var COMMAND_TYPES = [
|
|
3919
|
+
"analyze-project",
|
|
3917
3920
|
"start-build",
|
|
3918
3921
|
"cancel-build",
|
|
3919
3922
|
"start-dev-server",
|
|
@@ -6382,9 +6385,62 @@ async function runMigrations(migrationsFolder = "./drizzle") {
|
|
|
6382
6385
|
await pool.end();
|
|
6383
6386
|
}
|
|
6384
6387
|
}
|
|
6388
|
+
var ProjectMetadataSchema = z.object({
|
|
6389
|
+
slug: z.string().describe("URL-friendly project identifier (lowercase, hyphens)"),
|
|
6390
|
+
friendlyName: z.string().describe("Human-readable project name"),
|
|
6391
|
+
description: z.string().describe("Brief description of what the project does"),
|
|
6392
|
+
icon: z.enum([
|
|
6393
|
+
"Folder",
|
|
6394
|
+
"Code",
|
|
6395
|
+
"Layout",
|
|
6396
|
+
"Database",
|
|
6397
|
+
"Zap",
|
|
6398
|
+
"Globe",
|
|
6399
|
+
"Lock",
|
|
6400
|
+
"Users",
|
|
6401
|
+
"ShoppingCart",
|
|
6402
|
+
"Calendar",
|
|
6403
|
+
"MessageSquare",
|
|
6404
|
+
"FileText",
|
|
6405
|
+
"Image",
|
|
6406
|
+
"Music",
|
|
6407
|
+
"Video",
|
|
6408
|
+
"CheckCircle",
|
|
6409
|
+
"Star"
|
|
6410
|
+
]).describe("Icon name from available Lucide icons")
|
|
6411
|
+
});
|
|
6412
|
+
var TemplateAnalysisSchema = z.object({
|
|
6413
|
+
templateId: z.string().describe("ID of the selected template"),
|
|
6414
|
+
reasoning: z.string().describe("Brief explanation of why this template was chosen"),
|
|
6415
|
+
confidence: z.number().min(0).max(1).describe("Confidence score from 0.0 to 1.0")
|
|
6416
|
+
});
|
|
6417
|
+
var ProjectNamingSchema = z.object({
|
|
6418
|
+
slug: z.string().describe("URL-friendly project identifier (lowercase, hyphens, 2-4 words)"),
|
|
6419
|
+
friendlyName: z.string().describe("Human-readable project name (Title Case, 2-5 words)")
|
|
6420
|
+
});
|
|
6421
|
+
var AVAILABLE_ICONS = [
|
|
6422
|
+
"Folder",
|
|
6423
|
+
"Code",
|
|
6424
|
+
"Layout",
|
|
6425
|
+
"Database",
|
|
6426
|
+
"Zap",
|
|
6427
|
+
"Globe",
|
|
6428
|
+
"Lock",
|
|
6429
|
+
"Users",
|
|
6430
|
+
"ShoppingCart",
|
|
6431
|
+
"Calendar",
|
|
6432
|
+
"MessageSquare",
|
|
6433
|
+
"FileText",
|
|
6434
|
+
"Image",
|
|
6435
|
+
"Music",
|
|
6436
|
+
"Video",
|
|
6437
|
+
"CheckCircle",
|
|
6438
|
+
"Star"
|
|
6439
|
+
];
|
|
6385
6440
|
|
|
6386
6441
|
var AgentCore = /*#__PURE__*/Object.freeze({
|
|
6387
6442
|
__proto__: null,
|
|
6443
|
+
AVAILABLE_ICONS: AVAILABLE_ICONS,
|
|
6388
6444
|
CLAUDE_SYSTEM_PROMPT: CLAUDE_SYSTEM_PROMPT,
|
|
6389
6445
|
CODEX_SYSTEM_PROMPT: CODEX_SYSTEM_PROMPT,
|
|
6390
6446
|
DEFAULT_AGENT_ID: DEFAULT_AGENT_ID,
|
|
@@ -6394,6 +6450,9 @@ var AgentCore = /*#__PURE__*/Object.freeze({
|
|
|
6394
6450
|
LEGACY_MODEL_MAP: LEGACY_MODEL_MAP,
|
|
6395
6451
|
MODEL_METADATA: MODEL_METADATA,
|
|
6396
6452
|
NEONDB_CHAT_MESSAGES: NEONDB_CHAT_MESSAGES,
|
|
6453
|
+
ProjectMetadataSchema: ProjectMetadataSchema,
|
|
6454
|
+
ProjectNamingSchema: ProjectNamingSchema,
|
|
6455
|
+
TemplateAnalysisSchema: TemplateAnalysisSchema,
|
|
6397
6456
|
buildLogger: buildLogger$2,
|
|
6398
6457
|
buildWebSocketServer: buildWebSocketServer,
|
|
6399
6458
|
db: db,
|
|
@@ -7600,7 +7659,7 @@ var init_design = __esm({
|
|
|
7600
7659
|
|
|
7601
7660
|
// src/config/tags.ts
|
|
7602
7661
|
function findTagDefinition(key) {
|
|
7603
|
-
for (const def of TAG_DEFINITIONS) {
|
|
7662
|
+
for (const def of TAG_DEFINITIONS$1) {
|
|
7604
7663
|
if (def.key === key) return def;
|
|
7605
7664
|
if (def.children) {
|
|
7606
7665
|
const child = def.children.find((c) => c.key === key);
|
|
@@ -7609,10 +7668,10 @@ function findTagDefinition(key) {
|
|
|
7609
7668
|
}
|
|
7610
7669
|
return void 0;
|
|
7611
7670
|
}
|
|
7612
|
-
var TAG_DEFINITIONS;
|
|
7671
|
+
var TAG_DEFINITIONS$1;
|
|
7613
7672
|
var init_tags = __esm({
|
|
7614
7673
|
"src/config/tags.ts"() {
|
|
7615
|
-
TAG_DEFINITIONS = [
|
|
7674
|
+
TAG_DEFINITIONS$1 = [
|
|
7616
7675
|
// Model Selection (explicit provider + model mapping)
|
|
7617
7676
|
{
|
|
7618
7677
|
key: "model",
|
|
@@ -11543,6 +11602,644 @@ async function orchestrateBuild(context) {
|
|
|
11543
11602
|
};
|
|
11544
11603
|
}
|
|
11545
11604
|
|
|
11605
|
+
// src/config/tags.ts
|
|
11606
|
+
var TAG_DEFINITIONS = [
|
|
11607
|
+
// Model Selection (explicit provider + model mapping)
|
|
11608
|
+
{
|
|
11609
|
+
key: "model",
|
|
11610
|
+
label: "Model",
|
|
11611
|
+
description: "AI agent and model to use for generation",
|
|
11612
|
+
category: "model",
|
|
11613
|
+
inputType: "select",
|
|
11614
|
+
options: [
|
|
11615
|
+
{
|
|
11616
|
+
value: "claude-sonnet-4-5",
|
|
11617
|
+
label: "Claude Sonnet 4.5",
|
|
11618
|
+
description: "Anthropic Claude - Balanced performance and speed",
|
|
11619
|
+
logo: "/claude.png",
|
|
11620
|
+
provider: "claude-code",
|
|
11621
|
+
model: "claude-sonnet-4-5"
|
|
11622
|
+
},
|
|
11623
|
+
{
|
|
11624
|
+
value: "claude-opus-4-5",
|
|
11625
|
+
label: "Claude Opus 4.5",
|
|
11626
|
+
description: "Anthropic Claude - Most capable for complex tasks",
|
|
11627
|
+
logo: "/claude.png",
|
|
11628
|
+
provider: "claude-code",
|
|
11629
|
+
model: "claude-opus-4-5"
|
|
11630
|
+
},
|
|
11631
|
+
{
|
|
11632
|
+
value: "claude-haiku-4-5",
|
|
11633
|
+
label: "Claude Haiku 4.5",
|
|
11634
|
+
description: "Anthropic Claude - Fastest, good for iterations",
|
|
11635
|
+
logo: "/claude.png",
|
|
11636
|
+
provider: "claude-code",
|
|
11637
|
+
model: "claude-haiku-4-5"
|
|
11638
|
+
},
|
|
11639
|
+
{
|
|
11640
|
+
value: "gpt-5.2-codex",
|
|
11641
|
+
label: "GPT-5.2 Codex",
|
|
11642
|
+
description: "OpenAI Codex - Advanced code generation",
|
|
11643
|
+
logo: "/openai.png",
|
|
11644
|
+
provider: "openai-codex",
|
|
11645
|
+
model: "openai/gpt-5.2-codex"
|
|
11646
|
+
}
|
|
11647
|
+
]
|
|
11648
|
+
},
|
|
11649
|
+
// Framework Selection
|
|
11650
|
+
{
|
|
11651
|
+
key: "framework",
|
|
11652
|
+
label: "Framework",
|
|
11653
|
+
description: "Frontend framework to use",
|
|
11654
|
+
category: "framework",
|
|
11655
|
+
inputType: "select",
|
|
11656
|
+
options: [
|
|
11657
|
+
{
|
|
11658
|
+
value: "next",
|
|
11659
|
+
label: "Next.js",
|
|
11660
|
+
description: "Full-stack React with SSR, App Router, and file-based routing",
|
|
11661
|
+
logo: "/logos/nextjs.svg",
|
|
11662
|
+
repository: "github:codyde/template-nextjs",
|
|
11663
|
+
branch: "main"
|
|
11664
|
+
},
|
|
11665
|
+
{
|
|
11666
|
+
value: "tanstack",
|
|
11667
|
+
label: "TanStack Start",
|
|
11668
|
+
description: "Minimal TanStack Start foundation with React 19, Router, Query, and Tailwind",
|
|
11669
|
+
logo: "/logos/tanstack.png",
|
|
11670
|
+
repository: "github:codyde/template-tanstackstart",
|
|
11671
|
+
branch: "main"
|
|
11672
|
+
},
|
|
11673
|
+
{
|
|
11674
|
+
value: "vite",
|
|
11675
|
+
label: "React + Vite",
|
|
11676
|
+
description: "Fast React SPA with Vite - perfect for client-side apps",
|
|
11677
|
+
logo: "/logos/react.svg",
|
|
11678
|
+
repository: "github:codyde/template-reactvite",
|
|
11679
|
+
branch: "main"
|
|
11680
|
+
},
|
|
11681
|
+
{
|
|
11682
|
+
value: "astro",
|
|
11683
|
+
label: "Astro",
|
|
11684
|
+
description: "Content-focused static sites with islands architecture",
|
|
11685
|
+
logo: "/astro.png",
|
|
11686
|
+
repository: "github:codyde/template-astro",
|
|
11687
|
+
branch: "main"
|
|
11688
|
+
}
|
|
11689
|
+
],
|
|
11690
|
+
promptTemplate: "CRITICAL: You MUST use the {label} template. Clone it using: npx degit {repository}#{branch} {{projectName}}"
|
|
11691
|
+
},
|
|
11692
|
+
// Runner Selection (options populated dynamically)
|
|
11693
|
+
{
|
|
11694
|
+
key: "runner",
|
|
11695
|
+
label: "Runner",
|
|
11696
|
+
description: "Build runner to execute the build",
|
|
11697
|
+
category: "runner",
|
|
11698
|
+
inputType: "select",
|
|
11699
|
+
options: []
|
|
11700
|
+
// Populated dynamically from connected runners
|
|
11701
|
+
},
|
|
11702
|
+
// Design Configuration (nested)
|
|
11703
|
+
{
|
|
11704
|
+
key: "design",
|
|
11705
|
+
label: "Design",
|
|
11706
|
+
description: "Visual design preferences",
|
|
11707
|
+
category: "design",
|
|
11708
|
+
inputType: "nested",
|
|
11709
|
+
children: [
|
|
11710
|
+
// Brand Themes
|
|
11711
|
+
{
|
|
11712
|
+
key: "brand",
|
|
11713
|
+
label: "Brand",
|
|
11714
|
+
description: "Pre-configured brand color themes",
|
|
11715
|
+
category: "design",
|
|
11716
|
+
inputType: "select",
|
|
11717
|
+
maxSelections: 1,
|
|
11718
|
+
options: [
|
|
11719
|
+
{
|
|
11720
|
+
value: "sentry",
|
|
11721
|
+
label: "Sentry",
|
|
11722
|
+
description: "Error monitoring and performance - vibrant purple and pink",
|
|
11723
|
+
logo: "/logos/sentry.svg",
|
|
11724
|
+
values: {
|
|
11725
|
+
primaryColor: "#9D58BF",
|
|
11726
|
+
secondaryColor: "#FF708C",
|
|
11727
|
+
accentColor: "#FF9838",
|
|
11728
|
+
neutralLight: "#F0ECF3",
|
|
11729
|
+
neutralDark: "#2B2233"
|
|
11730
|
+
}
|
|
11731
|
+
},
|
|
11732
|
+
{
|
|
11733
|
+
value: "stripe",
|
|
11734
|
+
label: "Stripe",
|
|
11735
|
+
description: "Modern payments aesthetic",
|
|
11736
|
+
logo: "/logos/stripe.svg",
|
|
11737
|
+
values: {
|
|
11738
|
+
primaryColor: "#635bff",
|
|
11739
|
+
secondaryColor: "#0a2540",
|
|
11740
|
+
accentColor: "#00d4ff",
|
|
11741
|
+
neutralLight: "#f6f9fc",
|
|
11742
|
+
neutralDark: "#0a2540"
|
|
11743
|
+
}
|
|
11744
|
+
},
|
|
11745
|
+
{
|
|
11746
|
+
value: "vercel",
|
|
11747
|
+
label: "Vercel",
|
|
11748
|
+
description: "Clean developer tools",
|
|
11749
|
+
logo: "/logos/vercel.svg",
|
|
11750
|
+
values: {
|
|
11751
|
+
primaryColor: "#000000",
|
|
11752
|
+
secondaryColor: "#ffffff",
|
|
11753
|
+
accentColor: "#0070f3",
|
|
11754
|
+
neutralLight: "#fafafa",
|
|
11755
|
+
neutralDark: "#000000"
|
|
11756
|
+
}
|
|
11757
|
+
},
|
|
11758
|
+
{
|
|
11759
|
+
value: "linear",
|
|
11760
|
+
label: "Linear",
|
|
11761
|
+
description: "Sleek project management",
|
|
11762
|
+
logo: "/logos/linear.svg",
|
|
11763
|
+
values: {
|
|
11764
|
+
primaryColor: "#5e6ad2",
|
|
11765
|
+
secondaryColor: "#26b5ce",
|
|
11766
|
+
accentColor: "#f2994a",
|
|
11767
|
+
neutralLight: "#f7f8f8",
|
|
11768
|
+
neutralDark: "#1a1a1a"
|
|
11769
|
+
}
|
|
11770
|
+
},
|
|
11771
|
+
{
|
|
11772
|
+
value: "notion",
|
|
11773
|
+
label: "Notion",
|
|
11774
|
+
description: "Warm productivity",
|
|
11775
|
+
logo: "/logos/notion.svg",
|
|
11776
|
+
values: {
|
|
11777
|
+
primaryColor: "#2383e2",
|
|
11778
|
+
secondaryColor: "#e69138",
|
|
11779
|
+
accentColor: "#d44c47",
|
|
11780
|
+
neutralLight: "#f7f6f3",
|
|
11781
|
+
neutralDark: "#37352f"
|
|
11782
|
+
}
|
|
11783
|
+
},
|
|
11784
|
+
{
|
|
11785
|
+
value: "github",
|
|
11786
|
+
label: "GitHub",
|
|
11787
|
+
description: "Developer-first",
|
|
11788
|
+
logo: "/logos/github.svg",
|
|
11789
|
+
values: {
|
|
11790
|
+
primaryColor: "#238636",
|
|
11791
|
+
secondaryColor: "#1f6feb",
|
|
11792
|
+
accentColor: "#f85149",
|
|
11793
|
+
neutralLight: "#f6f8fa",
|
|
11794
|
+
neutralDark: "#24292f"
|
|
11795
|
+
}
|
|
11796
|
+
},
|
|
11797
|
+
{
|
|
11798
|
+
value: "airbnb",
|
|
11799
|
+
label: "Airbnb",
|
|
11800
|
+
description: "Friendly travel",
|
|
11801
|
+
logo: "/logos/airbnb.svg",
|
|
11802
|
+
values: {
|
|
11803
|
+
primaryColor: "#ff385c",
|
|
11804
|
+
secondaryColor: "#00a699",
|
|
11805
|
+
accentColor: "#fc642d",
|
|
11806
|
+
neutralLight: "#f7f7f7",
|
|
11807
|
+
neutralDark: "#222222"
|
|
11808
|
+
}
|
|
11809
|
+
},
|
|
11810
|
+
{
|
|
11811
|
+
value: "spotify",
|
|
11812
|
+
label: "Spotify",
|
|
11813
|
+
description: "Bold music streaming",
|
|
11814
|
+
logo: "/logos/spotify.svg",
|
|
11815
|
+
values: {
|
|
11816
|
+
primaryColor: "#1db954",
|
|
11817
|
+
secondaryColor: "#191414",
|
|
11818
|
+
accentColor: "#1ed760",
|
|
11819
|
+
neutralLight: "#f6f6f6",
|
|
11820
|
+
neutralDark: "#000000"
|
|
11821
|
+
}
|
|
11822
|
+
}
|
|
11823
|
+
],
|
|
11824
|
+
promptTemplate: "Use the {value} brand aesthetic with the following color palette: Primary: {primaryColor}, Secondary: {secondaryColor}, Accent: {accentColor}, Neutral Light: {neutralLight}, Neutral Dark: {neutralDark}. Match the design style and feel of {value}."
|
|
11825
|
+
}
|
|
11826
|
+
]
|
|
11827
|
+
}
|
|
11828
|
+
];
|
|
11829
|
+
|
|
11830
|
+
/**
|
|
11831
|
+
* Project Analyzer Module
|
|
11832
|
+
*
|
|
11833
|
+
* Handles AI-based project analysis before builds:
|
|
11834
|
+
* - Template selection (or uses framework tag fast-path)
|
|
11835
|
+
* - Project name/slug generation
|
|
11836
|
+
* - Project metadata generation (icon, description)
|
|
11837
|
+
*
|
|
11838
|
+
* This consolidates AI calls that were previously split between frontend and runner,
|
|
11839
|
+
* ensuring the runner is the single source of truth for all project decisions.
|
|
11840
|
+
*/
|
|
11841
|
+
// Map model IDs to Claude Agent SDK model names
|
|
11842
|
+
const MODEL_MAP = {
|
|
11843
|
+
'claude-haiku-4-5': 'claude-sonnet-4-5', // Haiku 4.5 not yet available, use Sonnet
|
|
11844
|
+
'claude-sonnet-4-5': 'claude-sonnet-4-5',
|
|
11845
|
+
'claude-opus-4-5': 'claude-opus-4-5',
|
|
11846
|
+
};
|
|
11847
|
+
function resolveModelName(modelId) {
|
|
11848
|
+
return MODEL_MAP[modelId] || 'claude-sonnet-4-5';
|
|
11849
|
+
}
|
|
11850
|
+
/**
|
|
11851
|
+
* Get a clean env object with only string values
|
|
11852
|
+
*/
|
|
11853
|
+
function getCleanEnv() {
|
|
11854
|
+
const env = {};
|
|
11855
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
11856
|
+
if (value !== undefined) {
|
|
11857
|
+
env[key] = value;
|
|
11858
|
+
}
|
|
11859
|
+
}
|
|
11860
|
+
if (!env.PATH) {
|
|
11861
|
+
env.PATH = '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin';
|
|
11862
|
+
}
|
|
11863
|
+
return env;
|
|
11864
|
+
}
|
|
11865
|
+
/**
|
|
11866
|
+
* Ensure a directory exists
|
|
11867
|
+
*/
|
|
11868
|
+
function ensureDir(dir) {
|
|
11869
|
+
if (!existsSync(dir)) {
|
|
11870
|
+
mkdirSync$1(dir, { recursive: true });
|
|
11871
|
+
}
|
|
11872
|
+
}
|
|
11873
|
+
/**
|
|
11874
|
+
* Generate structured output using Claude Agent SDK
|
|
11875
|
+
*/
|
|
11876
|
+
async function generateStructuredOutput(options) {
|
|
11877
|
+
const modelName = resolveModelName(options.model);
|
|
11878
|
+
const jsonSchema = z.toJSONSchema(options.schema);
|
|
11879
|
+
const jsonInstructions = `You must respond with ONLY valid JSON that matches this schema. Do not include any text before or after the JSON object. Do not wrap in markdown code blocks.
|
|
11880
|
+
|
|
11881
|
+
JSON Schema:
|
|
11882
|
+
${JSON.stringify(jsonSchema, null, 2)}
|
|
11883
|
+
|
|
11884
|
+
CRITICAL: Your response must START with { and END with }. Output only the JSON object.`;
|
|
11885
|
+
const fullPrompt = options.system
|
|
11886
|
+
? `${options.system}\n\n${jsonInstructions}\n\nUser request: ${options.prompt}`
|
|
11887
|
+
: `${jsonInstructions}\n\nUser request: ${options.prompt}`;
|
|
11888
|
+
const tempDir = path.join(os.tmpdir(), 'runner-ai');
|
|
11889
|
+
ensureDir(tempDir);
|
|
11890
|
+
const sdkOptions = {
|
|
11891
|
+
model: modelName,
|
|
11892
|
+
maxTurns: 1,
|
|
11893
|
+
tools: [],
|
|
11894
|
+
cwd: tempDir,
|
|
11895
|
+
permissionMode: 'bypassPermissions',
|
|
11896
|
+
allowDangerouslySkipPermissions: true,
|
|
11897
|
+
env: getCleanEnv(),
|
|
11898
|
+
};
|
|
11899
|
+
let responseText = '';
|
|
11900
|
+
try {
|
|
11901
|
+
for await (const message of query({ prompt: fullPrompt, options: sdkOptions })) {
|
|
11902
|
+
if (message.type === 'assistant') {
|
|
11903
|
+
for (const block of message.message.content) {
|
|
11904
|
+
if (block.type === 'text') {
|
|
11905
|
+
responseText += block.text;
|
|
11906
|
+
}
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
}
|
|
11910
|
+
}
|
|
11911
|
+
catch (error) {
|
|
11912
|
+
console.error('[project-analyzer] SDK query failed:', error);
|
|
11913
|
+
captureException(error);
|
|
11914
|
+
throw error;
|
|
11915
|
+
}
|
|
11916
|
+
if (!responseText) {
|
|
11917
|
+
throw new Error('No text response from Claude Agent SDK');
|
|
11918
|
+
}
|
|
11919
|
+
// Clean up any markdown code blocks if present
|
|
11920
|
+
let jsonText = responseText.trim();
|
|
11921
|
+
const codeBlockMatch = jsonText.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
11922
|
+
if (codeBlockMatch) {
|
|
11923
|
+
jsonText = codeBlockMatch[1].trim();
|
|
11924
|
+
}
|
|
11925
|
+
const jsonMatch = jsonText.match(/\{[\s\S]*\}/);
|
|
11926
|
+
if (jsonMatch) {
|
|
11927
|
+
jsonText = jsonMatch[0];
|
|
11928
|
+
}
|
|
11929
|
+
const parsed = JSON.parse(jsonText);
|
|
11930
|
+
const validated = options.schema.parse(parsed);
|
|
11931
|
+
return { object: validated };
|
|
11932
|
+
}
|
|
11933
|
+
/**
|
|
11934
|
+
* Generate project name/slug from user prompt
|
|
11935
|
+
*/
|
|
11936
|
+
async function generateProjectName(prompt, model) {
|
|
11937
|
+
const namePrompt = `Extract the core project concept from this request and create appropriate names:
|
|
11938
|
+
|
|
11939
|
+
User's project request: "${prompt}"
|
|
11940
|
+
|
|
11941
|
+
IMPORTANT: Extract only the PROJECT TYPE/CONCEPT. Ignore all conversational phrases:
|
|
11942
|
+
- Ignore: "I want", "I need", "I would like", "please", "can you", "build me", "create me", "make me"
|
|
11943
|
+
- Ignore: "a", "an", "the", "using", "with", "for me"
|
|
11944
|
+
- Focus ONLY on WHAT is being built, not how the user asked for it
|
|
11945
|
+
|
|
11946
|
+
Generate:
|
|
11947
|
+
1. A URL-friendly slug (lowercase, hyphens, 2-4 words, max 30 chars)
|
|
11948
|
+
2. A human-readable friendly name (Title Case, 2-5 words)
|
|
11949
|
+
|
|
11950
|
+
Examples:
|
|
11951
|
+
- "I want to build a todo app" → slug: "todo-app", friendlyName: "Todo App"
|
|
11952
|
+
- "I want a workflow automation tool" → slug: "workflow-automation", friendlyName: "Workflow Automation"
|
|
11953
|
+
- "Can you create an error monitoring dashboard for me" → slug: "error-monitoring", friendlyName: "Error Monitoring Dashboard"
|
|
11954
|
+
- "I need a chat app with real-time messaging please" → slug: "realtime-chat", friendlyName: "Realtime Chat"
|
|
11955
|
+
|
|
11956
|
+
Requirements:
|
|
11957
|
+
- Slug: lowercase, hyphens only, no special characters, max 30 chars
|
|
11958
|
+
- Friendly name: Title Case, readable, professional, 2-5 words
|
|
11959
|
+
- NEVER include words like "want", "need", "please", "build", "create", "make" in the output
|
|
11960
|
+
- Focus on the core product/application concept`;
|
|
11961
|
+
try {
|
|
11962
|
+
console.log('[project-analyzer] Generating project name...');
|
|
11963
|
+
const result = await generateStructuredOutput({
|
|
11964
|
+
model,
|
|
11965
|
+
schema: ProjectNamingSchema,
|
|
11966
|
+
prompt: namePrompt,
|
|
11967
|
+
});
|
|
11968
|
+
const { slug, friendlyName } = result.object;
|
|
11969
|
+
// Validate slug format
|
|
11970
|
+
if (slug.length < 2 || slug.length > 100 || !/^[a-z0-9-]+$/.test(slug)) {
|
|
11971
|
+
throw new Error('Generated slug is invalid format');
|
|
11972
|
+
}
|
|
11973
|
+
console.log(`[project-analyzer] Generated name: ${friendlyName} (${slug})`);
|
|
11974
|
+
return { slug, friendlyName };
|
|
11975
|
+
}
|
|
11976
|
+
catch (error) {
|
|
11977
|
+
console.error('[project-analyzer] Name generation failed, using fallback:', error);
|
|
11978
|
+
// Fallback: extract words from prompt
|
|
11979
|
+
const words = prompt
|
|
11980
|
+
.toLowerCase()
|
|
11981
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
11982
|
+
.split(/\s+/)
|
|
11983
|
+
.filter(w => w.length > 2)
|
|
11984
|
+
.filter(w => !['the', 'and', 'for', 'with', 'build', 'create', 'make', 'want', 'need', 'please', 'can', 'you', 'help', 'using'].includes(w))
|
|
11985
|
+
.slice(0, 4);
|
|
11986
|
+
const slug = words.length > 0 ? words.join('-') : 'new-project';
|
|
11987
|
+
const friendlyName = words.length > 0
|
|
11988
|
+
? words.map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
|
|
11989
|
+
: 'New Project';
|
|
11990
|
+
return { slug, friendlyName };
|
|
11991
|
+
}
|
|
11992
|
+
}
|
|
11993
|
+
/**
|
|
11994
|
+
* Generate project metadata (icon, description)
|
|
11995
|
+
*/
|
|
11996
|
+
async function generateProjectMetadata(prompt, model) {
|
|
11997
|
+
const metadataPrompt = `Based on this project request, generate appropriate metadata:
|
|
11998
|
+
|
|
11999
|
+
User's request: "${prompt}"
|
|
12000
|
+
|
|
12001
|
+
Available icons: Folder, Code, Layout, Database, Zap, Globe, Lock, Users, ShoppingCart, Calendar, MessageSquare, FileText, Image, Music, Video, CheckCircle, Star
|
|
12002
|
+
|
|
12003
|
+
Generate:
|
|
12004
|
+
- icon: Most appropriate icon from the list above
|
|
12005
|
+
- description: 1-2 sentences describing what the project does
|
|
12006
|
+
|
|
12007
|
+
IMPORTANT: Focus on the actual functionality being requested.`;
|
|
12008
|
+
try {
|
|
12009
|
+
console.log('[project-analyzer] Generating project metadata...');
|
|
12010
|
+
const result = await generateStructuredOutput({
|
|
12011
|
+
model,
|
|
12012
|
+
schema: ProjectMetadataSchema.pick({ icon: true, description: true }).extend({
|
|
12013
|
+
slug: z.string().optional(),
|
|
12014
|
+
friendlyName: z.string().optional(),
|
|
12015
|
+
}),
|
|
12016
|
+
prompt: metadataPrompt,
|
|
12017
|
+
});
|
|
12018
|
+
console.log(`[project-analyzer] Generated icon: ${result.object.icon}`);
|
|
12019
|
+
return {
|
|
12020
|
+
icon: result.object.icon,
|
|
12021
|
+
description: result.object.description
|
|
12022
|
+
};
|
|
12023
|
+
}
|
|
12024
|
+
catch (error) {
|
|
12025
|
+
console.error('[project-analyzer] Metadata generation failed, using defaults:', error);
|
|
12026
|
+
return {
|
|
12027
|
+
icon: 'Code',
|
|
12028
|
+
description: prompt.substring(0, 150),
|
|
12029
|
+
};
|
|
12030
|
+
}
|
|
12031
|
+
}
|
|
12032
|
+
/**
|
|
12033
|
+
* Select template based on prompt (AI analysis)
|
|
12034
|
+
*/
|
|
12035
|
+
async function selectTemplateWithAI(prompt, templates, model) {
|
|
12036
|
+
const templateContext = templates.map(t => `
|
|
12037
|
+
## ${t.name} (ID: ${t.id})
|
|
12038
|
+
${t.description}
|
|
12039
|
+
Keywords: ${t.selection.keywords.join(', ')}
|
|
12040
|
+
Use cases: ${t.selection.useCases.join('; ')}
|
|
12041
|
+
Tech: ${t.tech.framework} ${t.tech.version}, ${t.tech.language}
|
|
12042
|
+
`).join('\n---\n');
|
|
12043
|
+
const selectionPrompt = `Select the best template for this project:
|
|
12044
|
+
|
|
12045
|
+
User's request: "${prompt}"
|
|
12046
|
+
|
|
12047
|
+
Available templates:
|
|
12048
|
+
${templateContext}
|
|
12049
|
+
|
|
12050
|
+
Selection guidelines:
|
|
12051
|
+
- react-vite: Simple SPAs, prototypes, basic UIs
|
|
12052
|
+
- nextjs-fullstack: Full-stack apps needing auth, database, API routes, SSR
|
|
12053
|
+
- astro-static: Blogs, documentation, landing pages
|
|
12054
|
+
|
|
12055
|
+
Return the template ID, your reasoning, and confidence score.
|
|
12056
|
+
VALID templateId values: ${templates.map(t => t.id).join(', ')}`;
|
|
12057
|
+
try {
|
|
12058
|
+
console.log('[project-analyzer] Analyzing prompt for template selection...');
|
|
12059
|
+
const result = await generateStructuredOutput({
|
|
12060
|
+
model,
|
|
12061
|
+
schema: TemplateAnalysisSchema,
|
|
12062
|
+
prompt: selectionPrompt,
|
|
12063
|
+
});
|
|
12064
|
+
const template = templates.find(t => t.id === result.object.templateId);
|
|
12065
|
+
if (!template) {
|
|
12066
|
+
throw new Error(`Template ${result.object.templateId} not found`);
|
|
12067
|
+
}
|
|
12068
|
+
console.log(`[project-analyzer] Selected template: ${template.name} (confidence: ${result.object.confidence})`);
|
|
12069
|
+
console.log(`[project-analyzer] Reasoning: ${result.object.reasoning}`);
|
|
12070
|
+
return {
|
|
12071
|
+
template,
|
|
12072
|
+
reasoning: result.object.reasoning,
|
|
12073
|
+
confidence: result.object.confidence,
|
|
12074
|
+
};
|
|
12075
|
+
}
|
|
12076
|
+
catch (error) {
|
|
12077
|
+
console.error('[project-analyzer] Template selection failed, using fallback:', error);
|
|
12078
|
+
// Fallback to keyword-based selection
|
|
12079
|
+
const promptLower = prompt.toLowerCase();
|
|
12080
|
+
let template = templates.find(t => t.id === 'react-vite'); // Default
|
|
12081
|
+
if (promptLower.includes('next') || promptLower.includes('full-stack') || promptLower.includes('database') || promptLower.includes('auth')) {
|
|
12082
|
+
template = templates.find(t => t.id === 'nextjs-fullstack') || template;
|
|
12083
|
+
}
|
|
12084
|
+
else if (promptLower.includes('blog') || promptLower.includes('landing') || promptLower.includes('static') || promptLower.includes('docs')) {
|
|
12085
|
+
template = templates.find(t => t.id === 'astro-static') || template;
|
|
12086
|
+
}
|
|
12087
|
+
return {
|
|
12088
|
+
template: template,
|
|
12089
|
+
reasoning: 'Fallback keyword-based selection',
|
|
12090
|
+
confidence: 0.5,
|
|
12091
|
+
};
|
|
12092
|
+
}
|
|
12093
|
+
}
|
|
12094
|
+
/**
|
|
12095
|
+
* Get template from framework tag (fast path - no AI)
|
|
12096
|
+
*/
|
|
12097
|
+
function getTemplateFromTag(templates, tags) {
|
|
12098
|
+
const frameworkTag = tags.find(t => t.key === 'framework');
|
|
12099
|
+
if (!frameworkTag)
|
|
12100
|
+
return null;
|
|
12101
|
+
// Find template matching the framework tag
|
|
12102
|
+
const matchingTemplate = templates.find(t => t.tech.framework.toLowerCase() === frameworkTag.value.toLowerCase());
|
|
12103
|
+
if (matchingTemplate) {
|
|
12104
|
+
console.log(`[project-analyzer] Using template from framework tag: ${matchingTemplate.name}`);
|
|
12105
|
+
return matchingTemplate;
|
|
12106
|
+
}
|
|
12107
|
+
// Try to get template info from TAG_DEFINITIONS
|
|
12108
|
+
const frameworkDef = TAG_DEFINITIONS.find(d => d.key === 'framework');
|
|
12109
|
+
const frameworkOption = frameworkDef?.options?.find(o => o.value === frameworkTag.value);
|
|
12110
|
+
if (frameworkOption?.repository) {
|
|
12111
|
+
console.log(`[project-analyzer] Framework tag found but no matching template, using tag metadata`);
|
|
12112
|
+
// Return null - caller should use tag metadata directly
|
|
12113
|
+
return null;
|
|
12114
|
+
}
|
|
12115
|
+
return null;
|
|
12116
|
+
}
|
|
12117
|
+
/**
|
|
12118
|
+
* Main analysis function - orchestrates all AI calls
|
|
12119
|
+
*
|
|
12120
|
+
* Sends events via callback as analysis progresses:
|
|
12121
|
+
* - analysis-started
|
|
12122
|
+
* - project-metadata (with results)
|
|
12123
|
+
* - analysis-complete
|
|
12124
|
+
*/
|
|
12125
|
+
async function analyzeProject(options, sendEvent, commandId) {
|
|
12126
|
+
const { prompt, agent, claudeModel, tags } = options;
|
|
12127
|
+
const model = claudeModel || 'claude-sonnet-4-5';
|
|
12128
|
+
console.log('[project-analyzer] Starting project analysis...');
|
|
12129
|
+
console.log(`[project-analyzer] Agent: ${agent}, Model: ${model}`);
|
|
12130
|
+
// Emit analysis started
|
|
12131
|
+
sendEvent({
|
|
12132
|
+
type: 'analysis-started',
|
|
12133
|
+
commandId,
|
|
12134
|
+
timestamp: new Date().toISOString(),
|
|
12135
|
+
});
|
|
12136
|
+
// Load templates
|
|
12137
|
+
const templates = await getAllTemplates();
|
|
12138
|
+
console.log(`[project-analyzer] Loaded ${templates.length} templates`);
|
|
12139
|
+
// Step 1: Template Selection
|
|
12140
|
+
let selectedTemplate = null;
|
|
12141
|
+
// Fast path: Check if framework tag is present
|
|
12142
|
+
if (tags && tags.length > 0) {
|
|
12143
|
+
const tagTemplate = getTemplateFromTag(templates, tags);
|
|
12144
|
+
if (tagTemplate) {
|
|
12145
|
+
selectedTemplate = tagTemplate;
|
|
12146
|
+
console.log('[project-analyzer] FAST PATH: Using template from framework tag');
|
|
12147
|
+
}
|
|
12148
|
+
else {
|
|
12149
|
+
// Check if we have tag metadata without matching template
|
|
12150
|
+
const frameworkTag = tags.find(t => t.key === 'framework');
|
|
12151
|
+
if (frameworkTag) {
|
|
12152
|
+
const frameworkDef = TAG_DEFINITIONS.find(d => d.key === 'framework');
|
|
12153
|
+
const frameworkOption = frameworkDef?.options?.find(o => o.value === frameworkTag.value);
|
|
12154
|
+
if (frameworkOption?.repository) {
|
|
12155
|
+
// Build synthetic template from tag
|
|
12156
|
+
selectedTemplate = {
|
|
12157
|
+
id: `${frameworkTag.value}-default`,
|
|
12158
|
+
name: frameworkOption.label,
|
|
12159
|
+
description: `Template for ${frameworkOption.label}`,
|
|
12160
|
+
repository: frameworkOption.repository,
|
|
12161
|
+
branch: frameworkOption.branch || 'main',
|
|
12162
|
+
selection: { keywords: [], useCases: [], examples: [] },
|
|
12163
|
+
tech: {
|
|
12164
|
+
framework: frameworkTag.value,
|
|
12165
|
+
version: 'latest',
|
|
12166
|
+
language: 'TypeScript',
|
|
12167
|
+
styling: 'Tailwind CSS',
|
|
12168
|
+
packageManager: 'pnpm',
|
|
12169
|
+
nodeVersion: '20',
|
|
12170
|
+
},
|
|
12171
|
+
setup: {
|
|
12172
|
+
defaultPort: 3000,
|
|
12173
|
+
installCommand: 'pnpm install',
|
|
12174
|
+
devCommand: 'pnpm dev',
|
|
12175
|
+
buildCommand: 'pnpm build',
|
|
12176
|
+
},
|
|
12177
|
+
ai: { systemPromptAddition: '', includedFeatures: [] },
|
|
12178
|
+
};
|
|
12179
|
+
console.log('[project-analyzer] FAST PATH: Built template from tag metadata');
|
|
12180
|
+
}
|
|
12181
|
+
}
|
|
12182
|
+
}
|
|
12183
|
+
}
|
|
12184
|
+
// Slow path: AI template selection (if not already selected via tag)
|
|
12185
|
+
if (!selectedTemplate) {
|
|
12186
|
+
console.log('[project-analyzer] Running AI template selection...');
|
|
12187
|
+
const selection = await selectTemplateWithAI(prompt, templates, model);
|
|
12188
|
+
selectedTemplate = selection.template;
|
|
12189
|
+
}
|
|
12190
|
+
// At this point selectedTemplate is guaranteed to be assigned
|
|
12191
|
+
const finalTemplate = selectedTemplate;
|
|
12192
|
+
// Step 2: Generate project name (parallel-capable but keeping sequential for simplicity)
|
|
12193
|
+
const { slug, friendlyName } = await generateProjectName(prompt, model);
|
|
12194
|
+
// Step 3: Generate metadata (icon, description)
|
|
12195
|
+
const { icon, description } = await generateProjectMetadata(prompt, model);
|
|
12196
|
+
// Build result
|
|
12197
|
+
const result = {
|
|
12198
|
+
slug,
|
|
12199
|
+
friendlyName,
|
|
12200
|
+
description,
|
|
12201
|
+
icon,
|
|
12202
|
+
template: {
|
|
12203
|
+
id: finalTemplate.id,
|
|
12204
|
+
name: finalTemplate.name,
|
|
12205
|
+
framework: finalTemplate.tech.framework,
|
|
12206
|
+
port: finalTemplate.setup.defaultPort,
|
|
12207
|
+
runCommand: finalTemplate.setup.devCommand,
|
|
12208
|
+
repository: finalTemplate.repository,
|
|
12209
|
+
branch: finalTemplate.branch,
|
|
12210
|
+
},
|
|
12211
|
+
};
|
|
12212
|
+
// Emit project metadata
|
|
12213
|
+
sendEvent({
|
|
12214
|
+
type: 'project-metadata',
|
|
12215
|
+
commandId,
|
|
12216
|
+
timestamp: new Date().toISOString(),
|
|
12217
|
+
payload: {
|
|
12218
|
+
path: '', // Not yet created
|
|
12219
|
+
projectType: result.template.framework,
|
|
12220
|
+
runCommand: result.template.runCommand,
|
|
12221
|
+
port: result.template.port,
|
|
12222
|
+
detectedFramework: result.template.framework,
|
|
12223
|
+
slug: result.slug,
|
|
12224
|
+
friendlyName: result.friendlyName,
|
|
12225
|
+
description: result.description,
|
|
12226
|
+
icon: result.icon,
|
|
12227
|
+
template: result.template,
|
|
12228
|
+
},
|
|
12229
|
+
});
|
|
12230
|
+
// Emit analysis complete
|
|
12231
|
+
sendEvent({
|
|
12232
|
+
type: 'analysis-complete',
|
|
12233
|
+
commandId,
|
|
12234
|
+
timestamp: new Date().toISOString(),
|
|
12235
|
+
});
|
|
12236
|
+
console.log('[project-analyzer] Analysis complete!');
|
|
12237
|
+
console.log(`[project-analyzer] Project: ${friendlyName} (${slug})`);
|
|
12238
|
+
console.log(`[project-analyzer] Template: ${finalTemplate.name}`);
|
|
12239
|
+
console.log(`[project-analyzer] Icon: ${icon}`);
|
|
12240
|
+
return result;
|
|
12241
|
+
}
|
|
12242
|
+
|
|
11546
12243
|
// Silent mode for TUI
|
|
11547
12244
|
let isSilentMode$1 = false;
|
|
11548
12245
|
function setSilentMode(silent) {
|
|
@@ -12694,8 +13391,45 @@ async function startRunner(options = {}) {
|
|
|
12694
13391
|
};
|
|
12695
13392
|
}
|
|
12696
13393
|
async function handleCommand(command) {
|
|
13394
|
+
// Handle analyze-project specially - no projectId yet
|
|
13395
|
+
if (command.type === 'analyze-project') {
|
|
13396
|
+
debugLog(`Received command: analyze-project`);
|
|
13397
|
+
debugLog(`Command ID: ${command.id}`);
|
|
13398
|
+
debugLog(`Timestamp: ${command.timestamp}`);
|
|
13399
|
+
// Send ack without projectId
|
|
13400
|
+
sendEvent({
|
|
13401
|
+
type: "ack",
|
|
13402
|
+
commandId: command.id,
|
|
13403
|
+
timestamp: new Date().toISOString(),
|
|
13404
|
+
message: `Command analyze-project accepted`,
|
|
13405
|
+
});
|
|
13406
|
+
// Handle the analyze-project command
|
|
13407
|
+
try {
|
|
13408
|
+
log('🔍 Starting project analysis...');
|
|
13409
|
+
await analyzeProject({
|
|
13410
|
+
prompt: command.payload.prompt,
|
|
13411
|
+
agent: command.payload.agent,
|
|
13412
|
+
claudeModel: command.payload.claudeModel,
|
|
13413
|
+
tags: command.payload.tags,
|
|
13414
|
+
}, sendEvent, command.id);
|
|
13415
|
+
log('✅ Project analysis complete');
|
|
13416
|
+
}
|
|
13417
|
+
catch (error) {
|
|
13418
|
+
log('❌ Project analysis failed:', error);
|
|
13419
|
+
sendEvent({
|
|
13420
|
+
type: 'error',
|
|
13421
|
+
commandId: command.id,
|
|
13422
|
+
timestamp: new Date().toISOString(),
|
|
13423
|
+
error: error instanceof Error ? error.message : 'Analysis failed',
|
|
13424
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
13425
|
+
});
|
|
13426
|
+
}
|
|
13427
|
+
return;
|
|
13428
|
+
}
|
|
13429
|
+
// All other commands have projectId
|
|
13430
|
+
const projectId = command.projectId;
|
|
12697
13431
|
// Log command receipt - only verbose details in debug mode
|
|
12698
|
-
debugLog(`Received command: ${command.type} for project: ${
|
|
13432
|
+
debugLog(`Received command: ${command.type} for project: ${projectId}`);
|
|
12699
13433
|
debugLog(`Command ID: ${command.id}`);
|
|
12700
13434
|
debugLog(`Timestamp: ${command.timestamp}`);
|
|
12701
13435
|
// Log command-specific details (verbose only)
|
|
@@ -12715,7 +13449,7 @@ async function startRunner(options = {}) {
|
|
|
12715
13449
|
}
|
|
12716
13450
|
sendEvent({
|
|
12717
13451
|
type: "ack",
|
|
12718
|
-
...buildEventBase(
|
|
13452
|
+
...buildEventBase(projectId, command.id),
|
|
12719
13453
|
message: `Command ${command.type} accepted`,
|
|
12720
13454
|
});
|
|
12721
13455
|
switch (command.type) {
|
|
@@ -14208,6 +14942,10 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
|
|
|
14208
14942
|
const command = JSON.parse(String(data));
|
|
14209
14943
|
// BUG FIX: Update lastCommandReceived timestamp
|
|
14210
14944
|
lastCommandReceived = Date.now();
|
|
14945
|
+
// Get projectId - analyze-project commands don't have one yet
|
|
14946
|
+
const projectIdForTelemetry = command.type === 'analyze-project'
|
|
14947
|
+
? 'pending-analysis'
|
|
14948
|
+
: command.projectId;
|
|
14211
14949
|
// Continue trace from frontend - each build now starts its trace in the frontend
|
|
14212
14950
|
// This creates a span within the continued trace for the runner's work
|
|
14213
14951
|
if (command._sentry?.trace) {
|
|
@@ -14223,13 +14961,13 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
|
|
|
14223
14961
|
attributes: {
|
|
14224
14962
|
'command.type': command.type,
|
|
14225
14963
|
'command.id': command.id,
|
|
14226
|
-
'project.id':
|
|
14964
|
+
'project.id': projectIdForTelemetry,
|
|
14227
14965
|
'trace.continued': true,
|
|
14228
14966
|
},
|
|
14229
14967
|
}, async (span) => {
|
|
14230
14968
|
try {
|
|
14231
14969
|
setTag("command_type", command.type);
|
|
14232
|
-
setTag("project_id",
|
|
14970
|
+
setTag("project_id", projectIdForTelemetry);
|
|
14233
14971
|
setTag("command_id", command.id);
|
|
14234
14972
|
// Capture build metrics for start-build commands
|
|
14235
14973
|
if (command.type === 'start-build' && command.payload) {
|
|
@@ -14267,13 +15005,13 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
|
|
|
14267
15005
|
attributes: {
|
|
14268
15006
|
'command.type': command.type,
|
|
14269
15007
|
'command.id': command.id,
|
|
14270
|
-
'project.id':
|
|
15008
|
+
'project.id': projectIdForTelemetry,
|
|
14271
15009
|
'trace.continued': false,
|
|
14272
15010
|
},
|
|
14273
15011
|
}, async (span) => {
|
|
14274
15012
|
try {
|
|
14275
15013
|
setTag("command_type", command.type);
|
|
14276
|
-
setTag("project_id",
|
|
15014
|
+
setTag("project_id", projectIdForTelemetry);
|
|
14277
15015
|
setTag("command_id", command.id);
|
|
14278
15016
|
// Capture build metrics for start-build commands
|
|
14279
15017
|
if (command.type === 'start-build' && command.payload) {
|