@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 +1 -1
- package/src/aiAnalyze.js +6 -4
- package/src/changeRegistry.js +55 -1
- package/src/collect.js +16 -2
- package/src/index.js +404 -8
- package/src/postWireTest.js +27 -6
- package/src/shopifyTheme.js +339 -0
- package/src/verify.js +124 -1
- package/src/verifyWire.js +10 -0
- package/src/wireHandlers.js +216 -28
- package/src/wireScreen.js +562 -0
- package/src/wireSession.js +147 -11
- package/src/wireWeb.js +115 -2
- package/src/wireWebMount.js +98 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiodos/cli",
|
|
3
|
-
"version": "0.1.
|
|
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
|
-
:
|
|
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 }));
|
package/src/changeRegistry.js
CHANGED
|
@@ -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);
|