@fiodos/cli 0.1.25 → 0.1.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiodos/cli",
3
- "version": "0.1.25",
3
+ "version": "0.1.28",
4
4
  "description": "Fiodos CLI — analyzes your app's source code and generates the in-app voice-agent manifest, then wires the orb. Powers `npx @fiodos/cli analyze`.",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/src/aiAnalyze.js CHANGED
@@ -141,7 +141,9 @@ function buildUserPrompt(appMeta, files, omitted, staticRoutes, surface = null,
141
141
  : '';
142
142
  const routeBlock = staticRoutes.length
143
143
  ? `Statically-scanned routes (existence is verified — use these hrefs):\n${routeHint}\n`
144
- : `No static route list (web app): infer routes/views from the router and navigation calls in the code below.\n`;
144
+ : opts.platform === 'shopify'
145
+ ? 'Shopify storefront: derive routes from the PUBLIC STOREFRONT SNAPSHOT files under _fyodos/storefront/ (universal pages + one route per real collection/page handle) and from the theme templates below.\n'
146
+ : `No static route list (web app): infer routes/views from the router and navigation calls in the code below.\n`;
145
147
 
146
148
  // Detected interactive link elements on the product surface — concrete hrefs
147
149
  // the model should turn into LINK actions (verified later against the file).
@@ -171,7 +173,7 @@ function buildUserPrompt(appMeta, files, omitted, staticRoutes, surface = null,
171
173
  routeBlock +
172
174
  linkBlock +
173
175
  omittedNote +
174
- (includeSchema && LOCAL_PROMPTS ? `\nManifest schema:\n${LOCAL_PROMPTS.manifestSchemaDoc()}\n\n` : '\n') +
176
+ (includeSchema && LOCAL_PROMPTS ? `\nManifest schema:\n${LOCAL_PROMPTS.manifestSchemaDoc(opts.platform === 'shopify')}\n\n` : '\n') +
175
177
  `FULL SOURCE CODE (${files.length} files):\n\n${codeBlob}`
176
178
  );
177
179
  }
@@ -383,7 +385,7 @@ async function analyzeWithAI({
383
385
  if (useProxy) {
384
386
  // Server prompt mode: the backend builds the system prompt (with the
385
387
  // manifest schema embedded), so the user payload carries only the inputs.
386
- user = buildUserPrompt(appMeta, files, omitted, staticRoutes, surface, spec, { includeSchema: false });
388
+ user = buildUserPrompt(appMeta, files, omitted, staticRoutes, surface, spec, { includeSchema: false, platform });
387
389
  ({ parsed, usage, serverModel, analysisToken } = await analyzeViaProxy({
388
390
  apiKey, apiUrl, model: resolved, user, task: 'analysis', platform, hasUserSpec: Boolean(spec),
389
391
  }));
@@ -391,7 +393,7 @@ async function analyzeWithAI({
391
393
  const sp = await resolveOwnKeySystemPrompt({
392
394
  task: 'analysis', platform, hasUserSpec: Boolean(spec), apiKey, apiUrl,
393
395
  });
394
- user = buildUserPrompt(appMeta, files, omitted, staticRoutes, surface, spec, { includeSchema: sp.schemaInUser });
396
+ user = buildUserPrompt(appMeta, files, omitted, staticRoutes, surface, spec, { includeSchema: sp.schemaInUser, platform });
395
397
  ({ parsed, usage } = isAnthropicModel(resolved)
396
398
  ? await completeAnthropic({ model: resolved, system: sp.system, user })
397
399
  : await completeOpenAI({ model: resolved, system: sp.system, user }));
@@ -237,8 +237,31 @@ function summarizeSession(result) {
237
237
  }
238
238
  }
239
239
 
240
+ function summarizeScreen(result) {
241
+ if (!result) return { status: 'skipped', applied: false };
242
+ const base = { status: result.status };
243
+ switch (result.status) {
244
+ case 'applied':
245
+ case 'applied-untested':
246
+ return {
247
+ ...base,
248
+ applied: true,
249
+ what: 'Connected the app\'s ACTIVE screen state to the orb (state-driven screens, no URL change).',
250
+ revert: 'Delete screen.generated.* and remove the FYODOS:SCREEN blocks + mount prop.',
251
+ };
252
+ case 'review':
253
+ return { ...base, applied: false, note: `Screen wiring left for review: ${result.reason || ''}` };
254
+ case 'reverted':
255
+ return { ...base, applied: false, note: `Screen wiring reverted: ${result.reason || ''}` };
256
+ case 'url-screens':
257
+ return { ...base, applied: false, note: 'Screens are URL-driven — no screen wiring needed.' };
258
+ default:
259
+ return { ...base, applied: false };
260
+ }
261
+ }
262
+
240
263
  function collectFileEntries(appRoot, ctx) {
241
- const { orbResult, handlerResult, registryResult, sessionResult, envResult } = ctx;
264
+ const { orbResult, handlerResult, registryResult, sessionResult, screenResult, envResult } = ctx;
242
265
  const created = [];
243
266
  const modified = [];
244
267
  const docs = [];
@@ -344,6 +367,36 @@ function collectFileEntries(appRoot, ctx) {
344
367
  }
345
368
  }
346
369
 
370
+ // Screen-truth wiring (state-driven screens — automatic)
371
+ if (
372
+ screenResult &&
373
+ (screenResult.status === 'applied' || screenResult.status === 'applied-untested')
374
+ ) {
375
+ if (screenResult.screenFile) {
376
+ created.push({
377
+ path: normRel(appRoot, screenResult.screenFile),
378
+ phase: 'screen',
379
+ what: 'Generated screen module (`screen.generated.*`): reports the ACTIVE screen to the orb.',
380
+ revert: 'Safe to delete; re-run the installer to regenerate.',
381
+ });
382
+ }
383
+ if (screenResult.editedFile) {
384
+ modified.push({
385
+ path: screenResult.editedFile,
386
+ phase: 'screen',
387
+ what: 'Registered the live active-screen state in the bridge (reversible FYODOS:SCREEN block).',
388
+ revert: 'Remove the FYODOS:SCREEN blocks.',
389
+ });
390
+ }
391
+ if (screenResult.docPath) {
392
+ docs.push({
393
+ path: normRel(appRoot, screenResult.docPath),
394
+ phase: 'screen',
395
+ what: 'Screen-truth wiring summary: why it was needed, honesty contract.',
396
+ });
397
+ }
398
+ }
399
+
347
400
  // Env (never record values)
348
401
  if (envResult?.status === 'written' && envResult.writePlan?.targetRel) {
349
402
  modified.push({
@@ -433,6 +486,7 @@ function buildRunRecord(appRoot, ctx) {
433
486
  handlers: summarizeHandlers(ctx.handlerResult, appRoot),
434
487
  registries: summarizeRegistries(ctx.registryResult),
435
488
  session: summarizeSession(ctx.sessionResult),
489
+ screen: summarizeScreen(ctx.screenResult),
436
490
  env: summarizeEnv(ctx.envResult),
437
491
  },
438
492
  files: collectFileEntries(appRoot, ctx),
package/src/collect.js CHANGED
@@ -47,6 +47,12 @@ const PROFILES = {
47
47
  exts: ['.kt'],
48
48
  skipDirs: ['.git', '.gradle', 'build', '.idea'],
49
49
  },
50
+ // Shopify theme repo (GitHub-synced Liquid theme). The "code" is the theme's
51
+ // Liquid templates + JSON templates/settings; heavy static assets are noise.
52
+ shopify: {
53
+ exts: ['.liquid', '.json'],
54
+ skipDirs: ['.git', 'node_modules', 'assets', 'dist', 'build'],
55
+ },
50
56
  };
51
57
 
52
58
  const SKIP_FILES = /\.(d\.ts|test\.|spec\.)|babel\.config|metro\.config|tailwind\.config|postcss\.config|jest\.config|eslint/;
@@ -64,8 +70,16 @@ const CHARS_PER_TOKEN = 3.0;
64
70
 
65
71
  /** Lower tier = higher priority when the budget forces choices. Generalized so
66
72
  * it ranks screens/state/services first across web, RN AND native naming. */
67
- function tierFor(rel) {
73
+ function tierFor(rel, platform) {
68
74
  const p = rel.replace(/\\/g, '/');
75
+ if (platform === 'shopify') {
76
+ // Shopify theme layout: templates define the store's pages; layout + locales
77
+ // carry structure and language; sections/snippets are secondary UI.
78
+ if (/^(layout|templates)\//.test(p)) return 0;
79
+ if (/^locales\//.test(p)) return 1;
80
+ if (/^sections\//.test(p)) return 2;
81
+ return 3; // snippets/, config/ (settings_data.json can be huge), rest
82
+ }
69
83
  if (/^(src\/)?app\//.test(p)) return 0; // expo-router / Next screens
70
84
  if (/(^|\/)(screens?|pages?|views?|routes?|activities|fragments)\//i.test(p)) return 0;
71
85
  if (/(screen|page|view|activity|fragment)\.(dart|kt|swift)$/i.test(p)) return 0;
@@ -95,7 +109,7 @@ function collectFiles(appRoot, { maxInputTokens = 150_000, platform = 'mobile' }
95
109
  if (SKIP_FILES.test(rel)) continue;
96
110
  if (isNative && SKIP_NATIVE_FILES.test(rel)) continue;
97
111
  const content = fs.readFileSync(full, 'utf8');
98
- files.push({ rel, content, tier: tierFor(rel), chars: content.length });
112
+ files.push({ rel, content, tier: tierFor(rel, platform), chars: content.length });
99
113
  }
100
114
  };
101
115
  walk(appRoot);