@reticlehq/server 2.3.0 → 2.4.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/dist/bridge.js +43 -3
- package/dist/bridge.test-harness.d.ts +5 -0
- package/dist/bridge.test-harness.js +12 -0
- package/dist/cli-parse.d.ts +14 -2
- package/dist/cli-parse.js +40 -12
- package/dist/cli-port.d.ts +3 -0
- package/dist/cli-port.js +21 -0
- package/dist/cli.js +9 -2
- package/dist/flows/decision.d.ts +0 -6
- package/dist/flows/decision.js +47 -6
- package/dist/flows/flow-tools.js +14 -6
- package/dist/init/capabilities.d.ts +17 -0
- package/dist/init/capabilities.js +51 -0
- package/dist/init/detect.d.ts +25 -0
- package/dist/init/detect.js +65 -3
- package/dist/init/next-patch.d.ts +41 -0
- package/dist/init/next-patch.js +106 -0
- package/dist/init/node-io.js +25 -0
- package/dist/init/patch-kind.d.ts +20 -0
- package/dist/init/patch-kind.js +10 -0
- package/dist/init/plan-framework.d.ts +35 -0
- package/dist/init/plan-framework.js +217 -0
- package/dist/init/plan.d.ts +51 -3
- package/dist/init/plan.js +125 -125
- package/dist/init/run.d.ts +17 -0
- package/dist/init/run.js +207 -16
- package/dist/init/slash-command.d.ts +18 -0
- package/dist/init/slash-command.js +52 -0
- package/dist/init/snippets.d.ts +44 -3
- package/dist/init/snippets.js +152 -6
- package/dist/init/vite-config.d.ts +4 -10
- package/dist/init/vite-config.js +15 -7
- package/dist/input/driven-redaction.d.ts +15 -0
- package/dist/input/driven-redaction.js +76 -0
- package/dist/input/network-detail.d.ts +9 -3
- package/dist/input/network-detail.js +35 -15
- package/dist/mcp-proxy.d.ts +35 -3
- package/dist/mcp-proxy.js +174 -27
- package/dist/session/session-manager.js +6 -0
- package/dist/session/session.d.ts +6 -0
- package/dist/session/session.js +7 -0
- package/dist/tools/read-tools.js +4 -0
- package/package.json +2 -2
package/dist/init/plan.d.ts
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
* runner performs the `write` side-effects; this module decides *what* should happen.
|
|
5
5
|
*/
|
|
6
6
|
import { Framework, type Detection } from './detect.js';
|
|
7
|
+
/**
|
|
8
|
+
* Pin the SDK to the CLI's own version.
|
|
9
|
+
*
|
|
10
|
+
* `pnpm add -D @reticlehq/react` installed **2.2.1** in one project while npm and yarn took 2.3.0 in
|
|
11
|
+
* the next — a stale registry metadata cache, invisible to the user and to us. A version-skewed SDK
|
|
12
|
+
* talking to a newer daemon is the `-32000` failure path: the app connects, the protocol disagrees,
|
|
13
|
+
* and nothing on either side names a version. Asking for the CLI's exact version makes the cache
|
|
14
|
+
* irrelevant, and a skewed pair impossible to install by accident.
|
|
15
|
+
*/
|
|
16
|
+
export declare function pinnedPackages(packages: readonly string[], version: string | undefined): readonly string[];
|
|
7
17
|
/** The dev-dependencies `reticle init` installs for a given framework — kit first, build plugin next. */
|
|
8
18
|
export declare function frameworkPackages(framework: Framework): readonly string[];
|
|
9
19
|
/** Exported so the init telemetry can tell an MCP-registration failure from a dependency install. */
|
|
@@ -15,9 +25,16 @@ export declare const StepStatus: {
|
|
|
15
25
|
readonly MANUAL: "manual";
|
|
16
26
|
readonly ALREADY: "already";
|
|
17
27
|
readonly SKIP: "skip";
|
|
28
|
+
/**
|
|
29
|
+
* Something the user should KNOW, not something they must DO. The UNVERIFIED lines are the case:
|
|
30
|
+
* a Preact or SvelteKit app is wired and working, it just isn't covered by a gate. Reporting those
|
|
31
|
+
* as `manual` made "steps left to do" a number that could never reach zero, and made a release gate
|
|
32
|
+
* read two regressions that were not regressions.
|
|
33
|
+
*/
|
|
34
|
+
readonly NOTICE: "notice";
|
|
18
35
|
};
|
|
19
36
|
export type StepStatus = (typeof StepStatus)[keyof typeof StepStatus];
|
|
20
|
-
interface Step {
|
|
37
|
+
export interface Step {
|
|
21
38
|
title: string;
|
|
22
39
|
target: string;
|
|
23
40
|
status: StepStatus;
|
|
@@ -33,6 +50,13 @@ interface Step {
|
|
|
33
50
|
args: string[];
|
|
34
51
|
fallback: string;
|
|
35
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* This step wires the app to a package the install step provides. If that install fails, applying
|
|
55
|
+
* it anyway leaves the app importing a module that is not there — `next.config.ts` importing
|
|
56
|
+
* `@reticlehq/next` took a dev server down exactly this way. Installing Reticle must never be the
|
|
57
|
+
* reason an app stops booting.
|
|
58
|
+
*/
|
|
59
|
+
dependsOnInstall?: boolean;
|
|
36
60
|
}
|
|
37
61
|
export interface Plan {
|
|
38
62
|
framework: Framework;
|
|
@@ -46,6 +70,8 @@ export interface PlanInput {
|
|
|
46
70
|
mcpExists: boolean;
|
|
47
71
|
/** Whether Cursor is installed for this user (its global config dir exists). */
|
|
48
72
|
cursorPresent: boolean;
|
|
73
|
+
/** Whether THIS project has a .cursor/ directory — the signal that Cursor works on this repo. */
|
|
74
|
+
cursorProjectPresent?: boolean | undefined;
|
|
49
75
|
/** Current ~/.cursor/mcp.json content, or null if absent. */
|
|
50
76
|
cursorConfig: string | null;
|
|
51
77
|
/** Absolute path of ~/.cursor/mcp.json (the write target). */
|
|
@@ -57,8 +83,25 @@ export interface PlanInput {
|
|
|
57
83
|
} | null;
|
|
58
84
|
/** Discovered Next config filename (e.g. 'next.config.mjs'), or null. */
|
|
59
85
|
nextConfigFile: string | null;
|
|
60
|
-
/**
|
|
86
|
+
/** Source of that Next config, so the export can be wrapped in withReticle. */
|
|
87
|
+
nextConfigSource?: string | null | undefined;
|
|
88
|
+
/** Discovered Next root layout: its path + source, or null (App Router only). */
|
|
89
|
+
nextLayout?: {
|
|
90
|
+
path: string;
|
|
91
|
+
source: string;
|
|
92
|
+
} | null | undefined;
|
|
93
|
+
/** Where the dev-only connect component goes — never inside `pages/`, which routes on presence. */
|
|
94
|
+
nextReticleDevPath?: string | undefined;
|
|
95
|
+
/** What the mount file should import — a sibling for App Router, `../components/…` for Pages. */
|
|
96
|
+
nextReticleDevImport?: string | undefined;
|
|
97
|
+
/** Whether the ReticleDev component file already exists. */
|
|
61
98
|
nextReticleDevExists: boolean;
|
|
99
|
+
/** `data-testid` values scanned from the app's source, for the generated capabilities block. */
|
|
100
|
+
testids?: readonly string[] | undefined;
|
|
101
|
+
/** Ready-to-uncomment `registerStore` lines for the state libraries the app actually depends on. */
|
|
102
|
+
storeHints?: readonly string[] | undefined;
|
|
103
|
+
/** Whether src/reticle-dev.ts already exists — it is the one generated file users are meant to edit. */
|
|
104
|
+
viteDevModuleExists?: boolean | undefined;
|
|
62
105
|
/** Whether src/hooks.client.ts already exists (SvelteKit idempotency). */
|
|
63
106
|
svelteKitHooksExists?: boolean;
|
|
64
107
|
/** Whether .reticle.json already exists in the project root (idempotency). */
|
|
@@ -69,13 +112,18 @@ export interface PlanInput {
|
|
|
69
112
|
agentsMdContent?: string | null | undefined;
|
|
70
113
|
/** Whether .cursor/rules/reticle.mdc already exists (agent-rule idempotency). */
|
|
71
114
|
cursorRuleExists?: boolean | undefined;
|
|
115
|
+
/** Whether .claude/commands/reticle.md already exists (slash-command idempotency). */
|
|
116
|
+
claudeCommandExists?: boolean | undefined;
|
|
117
|
+
/** Whether .cursor/commands/reticle.md already exists. */
|
|
118
|
+
cursorCommandExists?: boolean | undefined;
|
|
72
119
|
options: {
|
|
73
120
|
port: number | undefined;
|
|
74
121
|
mcp: boolean;
|
|
75
122
|
install: boolean;
|
|
76
123
|
/** Stable project identity derived at init (package.json name + root). Baked into snippets/.reticle.json. */
|
|
77
124
|
projectId?: string;
|
|
125
|
+
/** The CLI's own version, pinned onto the SDK install so a stale registry cache cannot skew it. */
|
|
126
|
+
sdkVersion?: string;
|
|
78
127
|
};
|
|
79
128
|
}
|
|
80
129
|
export declare function buildPlan(input: PlanInput): Plan;
|
|
81
|
-
export {};
|
package/dist/init/plan.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* contents, produce an ordered list of steps — each marked apply / manual / already / skip. The
|
|
4
4
|
* runner performs the `write` side-effects; this module decides *what* should happen.
|
|
5
5
|
*/
|
|
6
|
-
import { Framework, installCommand, installCommandParts } from './detect.js';
|
|
6
|
+
import { Framework, PackageManager, UiLibrary, installCommand, installCommandParts, } from './detect.js';
|
|
7
7
|
import { claudeAddCommand, mcpManual } from './mcp.js';
|
|
8
8
|
import { mergeCursorConfig, CursorMergeStatus, cursorServerEntry } from './cursor.js';
|
|
9
|
+
import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH, SLASH_COMMAND_BODY, } from './slash-command.js';
|
|
9
10
|
import { mergeMarkedInstruction, cursorRuleFile, AgentRuleStatus, CLAUDE_MD_PATH, AGENTS_MD_PATH, CURSOR_RULE_PATH, } from './agent-rules.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { viteSteps, nextSteps, svelteKitSteps, VITE_PLUGIN_DETAIL } from './plan-framework.js';
|
|
12
|
+
import { htmlManual, reticleConfigContent, unverifiedUiLibraryNote, astroManual, } from './snippets.js';
|
|
12
13
|
// An app dev installs exactly the audience-scoped browser-side dependencies — never the retired
|
|
13
14
|
// `@reticlehq/core` umbrella (which dragged the Node MCP server + ws into every app). The kit is the
|
|
14
15
|
// framework adapter (it re-exports the browser sensor), paired with that framework's dev-only build
|
|
@@ -16,6 +17,20 @@ import { viteManual, htmlManual, NEXT_LAYOUT_MANUAL, nextReticleDevFile, NEXT_RE
|
|
|
16
17
|
const RETICLE_REACT_KIT = '@reticlehq/react';
|
|
17
18
|
const RETICLE_VITE_PLUGIN = '@reticlehq/vite-plugin';
|
|
18
19
|
const RETICLE_NEXT_PLUGIN = '@reticlehq/next';
|
|
20
|
+
/**
|
|
21
|
+
* Pin the SDK to the CLI's own version.
|
|
22
|
+
*
|
|
23
|
+
* `pnpm add -D @reticlehq/react` installed **2.2.1** in one project while npm and yarn took 2.3.0 in
|
|
24
|
+
* the next — a stale registry metadata cache, invisible to the user and to us. A version-skewed SDK
|
|
25
|
+
* talking to a newer daemon is the `-32000` failure path: the app connects, the protocol disagrees,
|
|
26
|
+
* and nothing on either side names a version. Asking for the CLI's exact version makes the cache
|
|
27
|
+
* irrelevant, and a skewed pair impossible to install by accident.
|
|
28
|
+
*/
|
|
29
|
+
export function pinnedPackages(packages, version) {
|
|
30
|
+
if (version === undefined || version.length === 0)
|
|
31
|
+
return packages;
|
|
32
|
+
return packages.map((p) => `${p}@${version}`);
|
|
33
|
+
}
|
|
19
34
|
/** The dev-dependencies `reticle init` installs for a given framework — kit first, build plugin next. */
|
|
20
35
|
export function frameworkPackages(framework) {
|
|
21
36
|
switch (framework) {
|
|
@@ -25,6 +40,10 @@ export function frameworkPackages(framework) {
|
|
|
25
40
|
case Framework.SVELTEKIT:
|
|
26
41
|
// SvelteKit builds on Vite; until a dedicated Svelte kit exists it uses the Vite build plugin.
|
|
27
42
|
return [RETICLE_REACT_KIT, RETICLE_VITE_PLUGIN];
|
|
43
|
+
case Framework.ASTRO:
|
|
44
|
+
// Astro owns its own Vite instance and renders its own HTML, so there is no config for the
|
|
45
|
+
// plugin to attach to — the kit alone, connected from a page <script> (see astroManual).
|
|
46
|
+
return [RETICLE_REACT_KIT];
|
|
28
47
|
case Framework.HTML:
|
|
29
48
|
// No bundler plugin to install — just the kit; connect is wired by hand (see htmlManual).
|
|
30
49
|
return [RETICLE_REACT_KIT];
|
|
@@ -40,6 +59,13 @@ export const StepStatus = {
|
|
|
40
59
|
MANUAL: 'manual',
|
|
41
60
|
ALREADY: 'already',
|
|
42
61
|
SKIP: 'skip',
|
|
62
|
+
/**
|
|
63
|
+
* Something the user should KNOW, not something they must DO. The UNVERIFIED lines are the case:
|
|
64
|
+
* a Preact or SvelteKit app is wired and working, it just isn't covered by a gate. Reporting those
|
|
65
|
+
* as `manual` made "steps left to do" a number that could never reach zero, and made a release gate
|
|
66
|
+
* read two regressions that were not regressions.
|
|
67
|
+
*/
|
|
68
|
+
NOTICE: 'notice',
|
|
43
69
|
};
|
|
44
70
|
const CLAUDE_MCP_TITLE = 'MCP server (Claude, global)';
|
|
45
71
|
const CURSOR_MCP_TITLE = 'MCP server (Cursor, global)';
|
|
@@ -116,6 +142,37 @@ function mcpSteps(input) {
|
|
|
116
142
|
},
|
|
117
143
|
];
|
|
118
144
|
}
|
|
145
|
+
const SLASH_COMMAND_TITLE = 'The /reticle command';
|
|
146
|
+
/**
|
|
147
|
+
* `/reticle` — the entry point SKILL.md promises in three places and nothing ever created, so it
|
|
148
|
+
* silently did nothing in every tool. One file per agent that supports custom commands.
|
|
149
|
+
*/
|
|
150
|
+
function slashCommandSteps(input) {
|
|
151
|
+
const targets = [
|
|
152
|
+
{ path: CLAUDE_COMMAND_PATH, when: input.claudeCli, exists: input.claudeCommandExists === true },
|
|
153
|
+
{
|
|
154
|
+
path: CURSOR_COMMAND_PATH,
|
|
155
|
+
when: input.cursorProjectPresent === true || (input.cursorPresent && !input.claudeCli),
|
|
156
|
+
exists: input.cursorCommandExists === true,
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
return targets
|
|
160
|
+
.filter((t) => t.when)
|
|
161
|
+
.map((t) => t.exists
|
|
162
|
+
? {
|
|
163
|
+
title: SLASH_COMMAND_TITLE,
|
|
164
|
+
target: t.path,
|
|
165
|
+
status: StepStatus.ALREADY,
|
|
166
|
+
detail: 'command already exists',
|
|
167
|
+
}
|
|
168
|
+
: {
|
|
169
|
+
title: SLASH_COMMAND_TITLE,
|
|
170
|
+
target: t.path,
|
|
171
|
+
status: StepStatus.APPLY,
|
|
172
|
+
detail: 'type /reticle to verify one flow in the browser',
|
|
173
|
+
write: { path: t.path, content: SLASH_COMMAND_BODY },
|
|
174
|
+
});
|
|
175
|
+
}
|
|
119
176
|
const AGENT_RULE_TITLE = 'Agent verification rule';
|
|
120
177
|
const AGENT_RULE_DETAIL = 'teach the agent to verify features with Reticle after building them';
|
|
121
178
|
function claudeRuleStep(input) {
|
|
@@ -138,9 +195,17 @@ function claudeRuleStep(input) {
|
|
|
138
195
|
write: { path: CLAUDE_MD_PATH, content: r.content },
|
|
139
196
|
};
|
|
140
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* The Cursor rule is a PROJECT file, so it is written only when Cursor plausibly works on THIS
|
|
200
|
+
* project — the repo has a `.cursor/` dir, or Cursor is the only agent found. `~/.cursor` merely
|
|
201
|
+
* existing on the machine meant every Claude Code user got an unexplained `.cursor/rules/reticle.mdc`
|
|
202
|
+
* committed into their repo. (Global MCP registration is different: it is global, and stays.)
|
|
203
|
+
*/
|
|
141
204
|
function cursorRuleStep(input) {
|
|
142
205
|
if (!input.cursorPresent)
|
|
143
206
|
return null;
|
|
207
|
+
if (input.cursorProjectPresent !== true && input.claudeCli)
|
|
208
|
+
return null;
|
|
144
209
|
if (input.cursorRuleExists === true) {
|
|
145
210
|
return {
|
|
146
211
|
title: AGENT_RULE_TITLE,
|
|
@@ -186,9 +251,28 @@ function agentRuleSteps(input) {
|
|
|
186
251
|
},
|
|
187
252
|
];
|
|
188
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* What to say when the install command fails.
|
|
256
|
+
*
|
|
257
|
+
* pnpm's `minimumReleaseAge` refuses any release younger than the configured window — a deliberate
|
|
258
|
+
* supply-chain policy, not a bug — with `ERR_PNPM_NO_MATURE_MATCHING_VERSION`. Unpinned it silently
|
|
259
|
+
* resolves to an OLDER version instead, which is how an app ends up running a 2.2.1 SDK against a
|
|
260
|
+
* 2.3.0 daemon: the connection succeeds, the protocol disagrees, and the failure surfaces as -32000
|
|
261
|
+
* with nothing naming a version. Pinning turns that into this loud failure, which is the better
|
|
262
|
+
* trade — but only if the message says what to do about it.
|
|
263
|
+
*/
|
|
264
|
+
function installFailureHint(pm) {
|
|
265
|
+
if (pm !== PackageManager.PNPM)
|
|
266
|
+
return 'If the version was refused, install the SDK yourself.';
|
|
267
|
+
return ('If pnpm reported ERR_PNPM_NO_MATURE_MATCHING_VERSION, its minimumReleaseAge setting is holding ' +
|
|
268
|
+
'this release back. Either wait out the window, or allow these packages explicitly:\n' +
|
|
269
|
+
' pnpm config set minimumReleaseAgeExclude "@reticlehq/*"\n' +
|
|
270
|
+
'Do NOT drop the version pin — unpinned, pnpm installs an older SDK against a newer daemon, and ' +
|
|
271
|
+
'that mismatch surfaces as a -32000 with nothing naming a version.');
|
|
272
|
+
}
|
|
189
273
|
function installStep(input) {
|
|
190
274
|
const pm = input.detection.packageManager;
|
|
191
|
-
const packages = frameworkPackages(input.detection.framework);
|
|
275
|
+
const packages = pinnedPackages(frameworkPackages(input.detection.framework), input.options.sdkVersion);
|
|
192
276
|
const command = installCommand(pm, packages);
|
|
193
277
|
if (!input.options.install) {
|
|
194
278
|
return {
|
|
@@ -204,129 +288,12 @@ function installStep(input) {
|
|
|
204
288
|
target: 'package.json',
|
|
205
289
|
status: StepStatus.APPLY,
|
|
206
290
|
detail: command,
|
|
207
|
-
exec: {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const cfg = input.viteConfig;
|
|
212
|
-
const port = input.options.port;
|
|
213
|
-
if (cfg === null) {
|
|
214
|
-
return [
|
|
215
|
-
{
|
|
216
|
-
title: 'Vite plugin',
|
|
217
|
-
target: 'vite.config',
|
|
218
|
-
status: StepStatus.MANUAL,
|
|
219
|
-
detail: viteManual(port),
|
|
220
|
-
},
|
|
221
|
-
];
|
|
222
|
-
}
|
|
223
|
-
const patch = patchViteConfig(cfg.source, port);
|
|
224
|
-
if (patch.kind === VitePatchKind.ALREADY) {
|
|
225
|
-
return [
|
|
226
|
-
{
|
|
227
|
-
title: 'Vite plugin',
|
|
228
|
-
target: cfg.path,
|
|
229
|
-
status: StepStatus.ALREADY,
|
|
230
|
-
detail: 'reticle() already in plugins',
|
|
231
|
-
},
|
|
232
|
-
];
|
|
233
|
-
}
|
|
234
|
-
if (patch.kind === VitePatchKind.MANUAL) {
|
|
235
|
-
return [
|
|
236
|
-
{
|
|
237
|
-
title: 'Vite plugin',
|
|
238
|
-
target: cfg.path,
|
|
239
|
-
status: StepStatus.MANUAL,
|
|
240
|
-
detail: `${patch.reason}\n\n${viteManual(port)}`,
|
|
241
|
-
},
|
|
242
|
-
];
|
|
243
|
-
}
|
|
244
|
-
return [
|
|
245
|
-
{
|
|
246
|
-
title: 'Vite plugin',
|
|
247
|
-
target: cfg.path,
|
|
248
|
-
status: StepStatus.APPLY,
|
|
249
|
-
detail: 'add reticle() to plugins (also injects connect())',
|
|
250
|
-
write: { path: cfg.path, content: patch.code },
|
|
251
|
-
},
|
|
252
|
-
];
|
|
253
|
-
}
|
|
254
|
-
function nextSteps(input) {
|
|
255
|
-
const configFile = input.nextConfigFile ?? 'next.config.mjs';
|
|
256
|
-
const devFile = input.nextReticleDevExists
|
|
257
|
-
? {
|
|
258
|
-
title: 'ReticleDev component',
|
|
259
|
-
target: NEXT_RETICLE_DEV_PATH,
|
|
260
|
-
status: StepStatus.ALREADY,
|
|
261
|
-
detail: 'file exists',
|
|
262
|
-
}
|
|
263
|
-
: {
|
|
264
|
-
title: 'ReticleDev component',
|
|
265
|
-
target: NEXT_RETICLE_DEV_PATH,
|
|
266
|
-
status: StepStatus.APPLY,
|
|
267
|
-
detail: 'create dev-only connect component',
|
|
268
|
-
write: {
|
|
269
|
-
path: NEXT_RETICLE_DEV_PATH,
|
|
270
|
-
content: nextReticleDevFile(input.options.port, input.options.projectId),
|
|
271
|
-
},
|
|
272
|
-
};
|
|
273
|
-
return [
|
|
274
|
-
devFile,
|
|
275
|
-
{
|
|
276
|
-
title: 'Next config (withReticle)',
|
|
277
|
-
target: configFile,
|
|
278
|
-
status: StepStatus.MANUAL,
|
|
279
|
-
detail: nextConfigManual(configFile),
|
|
291
|
+
exec: {
|
|
292
|
+
command: parts.command,
|
|
293
|
+
args: parts.args,
|
|
294
|
+
fallback: `${command}\n\n${installFailureHint(pm)}`,
|
|
280
295
|
},
|
|
281
|
-
{
|
|
282
|
-
title: 'Mount ReticleDev',
|
|
283
|
-
target: 'app/layout.tsx',
|
|
284
|
-
status: StepStatus.MANUAL,
|
|
285
|
-
detail: NEXT_LAYOUT_MANUAL,
|
|
286
|
-
},
|
|
287
|
-
];
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* SvelteKit is WIRED but not SUPPORTED, and the plan says so out loud.
|
|
291
|
-
*
|
|
292
|
-
* There is no SvelteKit app in `apps/` and no CI gate for one, so nothing proves this hook still
|
|
293
|
-
* registers a session — every other framework init offers (React, Next, Remix, Astro) has both. The
|
|
294
|
-
* wiring is real and may well work; what is missing is anything that would tell us when it stops.
|
|
295
|
-
* Silently emitting it reads as a support claim, which is the thing this project exists to not do.
|
|
296
|
-
*/
|
|
297
|
-
function svelteKitSteps(input) {
|
|
298
|
-
const unverified = {
|
|
299
|
-
title: 'SvelteKit is UNVERIFIED',
|
|
300
|
-
target: SVELTEKIT_HOOKS_PATH,
|
|
301
|
-
status: StepStatus.MANUAL,
|
|
302
|
-
detail: UNVERIFIED_FRAMEWORK_NOTE,
|
|
303
296
|
};
|
|
304
|
-
// SvelteKit can't use the Vite-plugin injection (it renders via app.html) — wire a client hook
|
|
305
|
-
// that SvelteKit runs on startup, which is the path that can register a session at all.
|
|
306
|
-
if (input.svelteKitHooksExists === true) {
|
|
307
|
-
return [
|
|
308
|
-
unverified,
|
|
309
|
-
{
|
|
310
|
-
title: 'Reticle client hook',
|
|
311
|
-
target: SVELTEKIT_HOOKS_PATH,
|
|
312
|
-
status: StepStatus.ALREADY,
|
|
313
|
-
detail: 'file exists',
|
|
314
|
-
},
|
|
315
|
-
];
|
|
316
|
-
}
|
|
317
|
-
return [
|
|
318
|
-
unverified,
|
|
319
|
-
{
|
|
320
|
-
title: 'Reticle client hook',
|
|
321
|
-
target: SVELTEKIT_HOOKS_PATH,
|
|
322
|
-
status: StepStatus.APPLY,
|
|
323
|
-
detail: 'create dev-only client connect (SvelteKit renders via app.html)',
|
|
324
|
-
write: {
|
|
325
|
-
path: SVELTEKIT_HOOKS_PATH,
|
|
326
|
-
content: svelteKitHooksFile(input.options.port, input.options.projectId),
|
|
327
|
-
},
|
|
328
|
-
},
|
|
329
|
-
];
|
|
330
297
|
}
|
|
331
298
|
function reticleConfigStep(input) {
|
|
332
299
|
if (input.reticleConfigExists === true) {
|
|
@@ -346,10 +313,31 @@ function reticleConfigStep(input) {
|
|
|
346
313
|
write: { path: RETICLE_CONFIG_FILE, content },
|
|
347
314
|
};
|
|
348
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* A step that says out loud when the app isn't React. SvelteKit already carries its own unverified
|
|
318
|
+
* note, so it isn't doubled up here.
|
|
319
|
+
*/
|
|
320
|
+
function uiLibraryStep(input) {
|
|
321
|
+
const lib = input.detection.uiLibrary;
|
|
322
|
+
if (lib === UiLibrary.REACT || input.detection.framework === Framework.SVELTEKIT)
|
|
323
|
+
return [];
|
|
324
|
+
if (lib === UiLibrary.UNKNOWN)
|
|
325
|
+
return [];
|
|
326
|
+
return [
|
|
327
|
+
{
|
|
328
|
+
title: `${lib} is UNVERIFIED`,
|
|
329
|
+
target: 'package.json',
|
|
330
|
+
status: StepStatus.NOTICE,
|
|
331
|
+
detail: unverifiedUiLibraryNote(lib),
|
|
332
|
+
},
|
|
333
|
+
];
|
|
334
|
+
}
|
|
349
335
|
export function buildPlan(input) {
|
|
350
336
|
const steps = [
|
|
351
337
|
...mcpSteps(input),
|
|
352
338
|
...agentRuleSteps(input),
|
|
339
|
+
...slashCommandSteps(input),
|
|
340
|
+
...uiLibraryStep(input),
|
|
353
341
|
installStep(input),
|
|
354
342
|
reticleConfigStep(input),
|
|
355
343
|
];
|
|
@@ -359,8 +347,20 @@ export function buildPlan(input) {
|
|
|
359
347
|
else if (input.detection.framework === Framework.NEXT) {
|
|
360
348
|
steps.push(...nextSteps(input));
|
|
361
349
|
}
|
|
350
|
+
else if (input.detection.framework === Framework.ASTRO) {
|
|
351
|
+
steps.push({
|
|
352
|
+
title: 'Connect snippet (Astro)',
|
|
353
|
+
target: 'astro.config + layout',
|
|
354
|
+
status: StepStatus.MANUAL,
|
|
355
|
+
detail: astroManual(input.options.port, input.options.projectId),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
362
358
|
else if (input.detection.framework === Framework.SVELTEKIT) {
|
|
363
359
|
steps.push(...svelteKitSteps(input));
|
|
360
|
+
// The Vite plugin as well as the client hook. `init` already INSTALLS @reticlehq/vite-plugin for
|
|
361
|
+
// SvelteKit and then never wired it into the config, so it sat in package.json doing nothing —
|
|
362
|
+
// which is why a SvelteKit app connected fine and every verdict came back with no file:line.
|
|
363
|
+
steps.push(...viteSteps(input, VITE_PLUGIN_DETAIL.SVELTEKIT));
|
|
364
364
|
}
|
|
365
365
|
else {
|
|
366
366
|
steps.push({
|
package/dist/init/run.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface InitOptions {
|
|
|
15
15
|
mcp: boolean;
|
|
16
16
|
dryRun: boolean;
|
|
17
17
|
install: boolean;
|
|
18
|
+
/** Set on the recursive call after a workspace redirect, so the search happens at most once. */
|
|
19
|
+
redirected?: boolean;
|
|
18
20
|
}
|
|
19
21
|
export interface InitIo {
|
|
20
22
|
/** Returns file content or null if it does not exist. Path is project-relative or absolute. */
|
|
@@ -26,6 +28,12 @@ export interface InitIo {
|
|
|
26
28
|
homeDir(): string;
|
|
27
29
|
/** Basenames present in the project root. */
|
|
28
30
|
rootFiles(): readonly string[];
|
|
31
|
+
/** Subdirectory names inside a project-relative directory; empty when it isn't one. */
|
|
32
|
+
listDirs(relPath: string): readonly string[];
|
|
33
|
+
/** File (non-directory) basenames inside a project-relative directory, including dotfiles. */
|
|
34
|
+
listFiles(relPath: string): readonly string[];
|
|
35
|
+
/** The same IO re-rooted at a project-relative subdirectory (used for the workspace redirect). */
|
|
36
|
+
scoped(relPath: string): InitIo;
|
|
29
37
|
/** Runs a subprocess to completion (inherits stdio); returns true on exit code 0. */
|
|
30
38
|
exec(command: string, args: readonly string[]): boolean;
|
|
31
39
|
/** Runs a subprocess quietly (no stdio) for a yes/no check; returns true on exit code 0. */
|
|
@@ -37,5 +45,14 @@ interface InitResult {
|
|
|
37
45
|
applied: number;
|
|
38
46
|
manual: number;
|
|
39
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* App directories under a workspace root.
|
|
50
|
+
*
|
|
51
|
+
* Running `reticle init` at the repo root is what people actually do, and in a monorepo the app is a
|
|
52
|
+
* directory down — so init detected "no framework", printed a wall of manual HTML instructions, and
|
|
53
|
+
* would have installed the SDK into the ROOT package.json. It already walks UP for the lockfile, so
|
|
54
|
+
* it knows it is in a workspace; this is the matching walk DOWN.
|
|
55
|
+
*/
|
|
56
|
+
export declare function findWorkspaceApps(io: Pick<InitIo, 'exists' | 'readFile' | 'listDirs'>): string[];
|
|
40
57
|
export declare function runInit(options: InitOptions, io: InitIo): InitResult;
|
|
41
58
|
export {};
|