@reticlehq/init 2.14.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/LICENSE +105 -0
- package/NOTICE +5 -0
- package/README.md +29 -0
- package/dist/agent-rules.d.ts +116 -0
- package/dist/agent-rules.js +279 -0
- package/dist/app-choice.d.ts +29 -0
- package/dist/app-choice.js +39 -0
- package/dist/astro-host.d.ts +21 -0
- package/dist/astro-host.js +27 -0
- package/dist/astro-patch.d.ts +33 -0
- package/dist/astro-patch.js +103 -0
- package/dist/brace-scan.d.ts +24 -0
- package/dist/brace-scan.js +46 -0
- package/dist/capabilities.d.ts +40 -0
- package/dist/capabilities.js +167 -0
- package/dist/closing-hint.d.ts +35 -0
- package/dist/closing-hint.js +122 -0
- package/dist/codex-toml.d.ts +10 -0
- package/dist/codex-toml.js +144 -0
- package/dist/connect-steps.d.ts +55 -0
- package/dist/connect-steps.js +92 -0
- package/dist/cra.d.ts +82 -0
- package/dist/cra.js +183 -0
- package/dist/csp-check.d.ts +49 -0
- package/dist/csp-check.js +168 -0
- package/dist/csp-doctor.d.ts +32 -0
- package/dist/csp-doctor.js +62 -0
- package/dist/desktop-doctor.d.ts +46 -0
- package/dist/desktop-doctor.js +201 -0
- package/dist/detect.d.ts +225 -0
- package/dist/detect.js +334 -0
- package/dist/dev-script.d.ts +51 -0
- package/dist/dev-script.js +66 -0
- package/dist/dev-server-ports.d.ts +20 -0
- package/dist/dev-server-ports.js +45 -0
- package/dist/electron-patch.d.ts +11 -0
- package/dist/electron-patch.js +70 -0
- package/dist/electron-vite-patch.d.ts +12 -0
- package/dist/electron-vite-patch.js +100 -0
- package/dist/existing-config.d.ts +31 -0
- package/dist/existing-config.js +63 -0
- package/dist/format-generated.d.ts +18 -0
- package/dist/format-generated.js +48 -0
- package/dist/framework-adapter.d.ts +60 -0
- package/dist/framework-adapter.js +138 -0
- package/dist/host.d.ts +41 -0
- package/dist/host.js +21 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/init-failure.d.ts +19 -0
- package/dist/init-failure.js +18 -0
- package/dist/init-opt-out.d.ts +17 -0
- package/dist/init-opt-out.js +30 -0
- package/dist/install-hint.d.ts +9 -0
- package/dist/install-hint.js +41 -0
- package/dist/install-retries.d.ts +22 -0
- package/dist/install-retries.js +65 -0
- package/dist/install-source-config.d.ts +18 -0
- package/dist/install-source-config.js +34 -0
- package/dist/lockfiles.d.ts +14 -0
- package/dist/lockfiles.js +49 -0
- package/dist/mcp-clients.d.ts +94 -0
- package/dist/mcp-clients.js +327 -0
- package/dist/mcp-registered.d.ts +12 -0
- package/dist/mcp-registered.js +14 -0
- package/dist/mcp.d.ts +89 -0
- package/dist/mcp.js +129 -0
- package/dist/next-patch.d.ts +49 -0
- package/dist/next-patch.js +237 -0
- package/dist/node-io.d.ts +22 -0
- package/dist/node-io.js +150 -0
- package/dist/non-js-project.d.ts +44 -0
- package/dist/non-js-project.js +137 -0
- package/dist/nuxt-patch.d.ts +18 -0
- package/dist/nuxt-patch.js +33 -0
- package/dist/patch-kind.d.ts +20 -0
- package/dist/patch-kind.js +10 -0
- package/dist/plan-electron-vite.d.ts +16 -0
- package/dist/plan-electron-vite.js +72 -0
- package/dist/plan-framework.d.ts +122 -0
- package/dist/plan-framework.js +773 -0
- package/dist/plan.d.ts +312 -0
- package/dist/plan.js +650 -0
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/preflight.d.ts +30 -0
- package/dist/preflight.js +52 -0
- package/dist/project-id.d.ts +12 -0
- package/dist/project-id.js +25 -0
- package/dist/refresh-rules.d.ts +40 -0
- package/dist/refresh-rules.js +51 -0
- package/dist/remember-project.d.ts +24 -0
- package/dist/remember-project.js +40 -0
- package/dist/run.d.ts +122 -0
- package/dist/run.js +762 -0
- package/dist/slash-command.d.ts +27 -0
- package/dist/slash-command.js +132 -0
- package/dist/snippets.d.ts +267 -0
- package/dist/snippets.js +898 -0
- package/dist/tanstack-start.d.ts +18 -0
- package/dist/tanstack-start.js +57 -0
- package/dist/version.d.ts +18 -0
- package/dist/version.js +23 -0
- package/dist/vite-config.d.ts +18 -0
- package/dist/vite-config.js +111 -0
- package/dist/vite-owning-config.d.ts +25 -0
- package/dist/vite-owning-config.js +219 -0
- package/dist/windows-quote.d.ts +38 -0
- package/dist/windows-quote.js +70 -0
- package/dist/workspace-apps.d.ts +40 -0
- package/dist/workspace-apps.js +188 -0
- package/dist/workspace-redirect.d.ts +22 -0
- package/dist/workspace-redirect.js +97 -0
- package/package.json +53 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only detection of an existing `[mcp_servers.reticle]` table in `~/.codex/config.toml`.
|
|
3
|
+
*
|
|
4
|
+
* `init` printed `[⚠] MCP server (Codex CLI) → ~/.codex/config.toml — add this by hand` on machines
|
|
5
|
+
* whose config ALREADY held a working table with the same command and args, because
|
|
6
|
+
* `mergeClientConfig` short-circuits every non-JSON format to MANUAL without ever reading the file
|
|
7
|
+
* (#681, reported independently twice). The setup instructions tell an agent it MUST resolve a `⚠`,
|
|
8
|
+
* and the install gate's own contract is zero `⚠`, so the warning sent people to hand-edit a global
|
|
9
|
+
* config for no reason.
|
|
10
|
+
*
|
|
11
|
+
* This only ANSWERS a question; it never produces TOML to write. Editing TOML without a parser is
|
|
12
|
+
* how a config gets corrupted, and a corrupted `~/.codex/config.toml` costs the user every server
|
|
13
|
+
* they had — so writing stays MANUAL, exactly as before.
|
|
14
|
+
*
|
|
15
|
+
* A deliberately narrow scanner rather than a TOML parser, because the question is narrow: which
|
|
16
|
+
* strings does one named table's `command` and `args` hold? Anything it cannot read confidently —
|
|
17
|
+
* a multi-line array, an unfamiliar quoting, a shape it has no rule for — yields no tokens and
|
|
18
|
+
* therefore no detection, which is today's behaviour. The failure mode stays "warn when we should
|
|
19
|
+
* not have", never "stay quiet when the server is genuinely unregistered".
|
|
20
|
+
*/
|
|
21
|
+
import { MCP_SERVER_NAME } from './mcp.js';
|
|
22
|
+
/** Strip a `#` comment that is not inside a string literal. */
|
|
23
|
+
function withoutComment(line) {
|
|
24
|
+
let quote;
|
|
25
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
26
|
+
const char = line[i];
|
|
27
|
+
if (undefined !== quote) {
|
|
28
|
+
if (char === quote)
|
|
29
|
+
quote = undefined;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if ('"' === char || "'" === char) {
|
|
33
|
+
quote = char;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if ('#' === char)
|
|
37
|
+
return line.slice(0, i);
|
|
38
|
+
}
|
|
39
|
+
return line;
|
|
40
|
+
}
|
|
41
|
+
/** `reticle`, `"reticle"` and `'reticle'` are the same key to TOML. */
|
|
42
|
+
function unquote(key) {
|
|
43
|
+
const trimmed = key.trim();
|
|
44
|
+
const first = trimmed[0];
|
|
45
|
+
if (('"' === first || "'" === first) && trimmed.endsWith(first) && trimmed.length >= 2) {
|
|
46
|
+
return trimmed.slice(1, -1);
|
|
47
|
+
}
|
|
48
|
+
return trimmed;
|
|
49
|
+
}
|
|
50
|
+
/** Split a dotted TOML key path, respecting quoted segments. */
|
|
51
|
+
function keyPath(raw) {
|
|
52
|
+
const parts = [];
|
|
53
|
+
let current = '';
|
|
54
|
+
let quote;
|
|
55
|
+
for (const char of raw) {
|
|
56
|
+
if (undefined !== quote) {
|
|
57
|
+
current += char;
|
|
58
|
+
if (char === quote)
|
|
59
|
+
quote = undefined;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if ('"' === char || "'" === char) {
|
|
63
|
+
quote = char;
|
|
64
|
+
current += char;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if ('.' === char) {
|
|
68
|
+
parts.push(unquote(current));
|
|
69
|
+
current = '';
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
current += char;
|
|
73
|
+
}
|
|
74
|
+
parts.push(unquote(current));
|
|
75
|
+
return parts.map((p) => p.trim()).filter((p) => 0 < p.length);
|
|
76
|
+
}
|
|
77
|
+
/** Every double- or single-quoted string in `value`, in order. */
|
|
78
|
+
function quotedStrings(value) {
|
|
79
|
+
return [...value.matchAll(/"([^"\\]*)"|'([^']*)'/g)].map((m) => m[1] ?? m[2] ?? '');
|
|
80
|
+
}
|
|
81
|
+
/** The table path our server's entry lives under, e.g. `mcp_servers.reticle`. */
|
|
82
|
+
function ourTablePath(serversKey) {
|
|
83
|
+
return [serversKey, MCP_SERVER_NAME];
|
|
84
|
+
}
|
|
85
|
+
function samePath(a, b) {
|
|
86
|
+
return a.length === b.length && a.every((segment, i) => segment === b[i]);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The `command` and `args` strings the config declares for our server, or `[]` when it declares
|
|
90
|
+
* none — including when the shape was not one this scanner reads.
|
|
91
|
+
*
|
|
92
|
+
* Handles the three forms a Codex config actually takes: the standard table header, a dotted
|
|
93
|
+
* assignment (`mcp_servers.reticle = { … }`), and a key inside an `[mcp_servers]` table.
|
|
94
|
+
*/
|
|
95
|
+
export function codexServerTokens(existing, serversKey) {
|
|
96
|
+
if (null === existing)
|
|
97
|
+
return [];
|
|
98
|
+
const want = ourTablePath(serversKey);
|
|
99
|
+
const tokens = [];
|
|
100
|
+
let table = [];
|
|
101
|
+
let inOurTable = false;
|
|
102
|
+
/** Set while a `[ ... ]` value is still open across lines; `true` when that value is ours. */
|
|
103
|
+
let openArrayIsOurs;
|
|
104
|
+
for (const rawLine of existing.split(/\r?\n/)) {
|
|
105
|
+
const line = withoutComment(rawLine).trim();
|
|
106
|
+
if (0 === line.length)
|
|
107
|
+
continue;
|
|
108
|
+
// `args = [` spread over several lines is the shape a formatter produces, and reading only the
|
|
109
|
+
// first line of it would leave a HALF-read entry that looks like somebody else's registration.
|
|
110
|
+
if (undefined !== openArrayIsOurs) {
|
|
111
|
+
if (openArrayIsOurs)
|
|
112
|
+
tokens.push(...quotedStrings(line));
|
|
113
|
+
if (line.includes(']'))
|
|
114
|
+
openArrayIsOurs = undefined;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// A table header ends whatever table we were in, whether or not it is ours.
|
|
118
|
+
const header = /^\[{1,2}([^\]]+)\]{1,2}$/.exec(line);
|
|
119
|
+
if (null !== header) {
|
|
120
|
+
table = keyPath(header[1] ?? '');
|
|
121
|
+
inOurTable = samePath(table, want);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const assignment = /^([^=]+)=(.*)$/.exec(line);
|
|
125
|
+
if (null === assignment)
|
|
126
|
+
continue;
|
|
127
|
+
const key = keyPath(assignment[1] ?? '');
|
|
128
|
+
const value = assignment[2] ?? '';
|
|
129
|
+
// Inside `[mcp_servers.reticle]`, the keys we care about are `command` and `args`.
|
|
130
|
+
const ours = (inOurTable && 1 === key.length && ('command' === key[0] || 'args' === key[0])) ||
|
|
131
|
+
// `mcp_servers.reticle = { command = "npx", args = [...] }` at the root, or
|
|
132
|
+
// `reticle = { … }` inside an `[mcp_servers]` table.
|
|
133
|
+
samePath([...table, ...key], want);
|
|
134
|
+
if (ours)
|
|
135
|
+
tokens.push(...quotedStrings(value));
|
|
136
|
+
if (value.includes('[') && !value.includes(']'))
|
|
137
|
+
openArrayIsOurs = ours;
|
|
138
|
+
}
|
|
139
|
+
return tokens;
|
|
140
|
+
}
|
|
141
|
+
/** Does this config already declare a Reticle server under our name? */
|
|
142
|
+
export function codexDeclaresOurServer(existing, serversKey) {
|
|
143
|
+
return 0 < codexServerTokens(existing, serversKey).length;
|
|
144
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step titles `init` emits, and which of them decide whether the app can dial the daemon.
|
|
3
|
+
*
|
|
4
|
+
* Lifted out of `plan.ts` when that file reached the size backstop. A cohesive unit on its own
|
|
5
|
+
* terms: one question, asked of a step title, with one consequence — a manual step in this set makes
|
|
6
|
+
* `init` exit non-zero instead of reporting a success over an app that can never connect.
|
|
7
|
+
*
|
|
8
|
+
* The membership used to be matched against free strings written out a second time here, so
|
|
9
|
+
* renaming a step in `plan-framework.ts` silently dropped it out of the set and nothing went red.
|
|
10
|
+
* `StepTitle` is now the one place a title is spelled: both files import it, so a rename is a
|
|
11
|
+
* compile error and `plan-framework.test.ts` pins that no builder has drifted back to a literal.
|
|
12
|
+
*/
|
|
13
|
+
/** Every fixed title a plan step can carry. The one spelling; nothing writes a title inline. */
|
|
14
|
+
export declare const StepTitle: {
|
|
15
|
+
readonly VITE_PLUGIN: "Vite plugin";
|
|
16
|
+
readonly CAPABILITIES: "Capabilities + store";
|
|
17
|
+
readonly CAPABILITIES_TODO: "AGENT: finish the capabilities file";
|
|
18
|
+
readonly RETICLE_DEV_COMPONENT: "ReticleDev component";
|
|
19
|
+
readonly NEXT_CONFIG: "Next config (withReticle)";
|
|
20
|
+
readonly MOUNT_RETICLE_DEV: "Mount ReticleDev";
|
|
21
|
+
readonly CONNECT_MODULE: "Reticle connect module";
|
|
22
|
+
readonly PAIRING_TOKEN: "Pairing token";
|
|
23
|
+
readonly PAIRING_TOKEN_PER_MACHINE: "Pairing token is per-machine";
|
|
24
|
+
readonly CONNECT_SNIPPET_CRA: "Connect snippet (CRA)";
|
|
25
|
+
readonly CONNECT_SNIPPET_NUXT: "Connect snippet (Nuxt)";
|
|
26
|
+
readonly NUXT_CONFIG: "Nuxt config (token + watcher)";
|
|
27
|
+
readonly NUXT_RESTART: "Restart the Nuxt dev server";
|
|
28
|
+
readonly CONNECT_SNIPPET_REACT_ROUTER: "Connect snippet (React Router)";
|
|
29
|
+
readonly CONNECT_SNIPPET_TANSTACK_START: "Connect snippet (TanStack Start)";
|
|
30
|
+
readonly ELECTRON_VITE_PLUGIN: "Vite plugin (electron-vite renderer)";
|
|
31
|
+
readonly ELECTRON_PRELOAD: "Electron preload (IPC shim)";
|
|
32
|
+
readonly ELECTRON_CAPTURE: "Electron capture (screenshots)";
|
|
33
|
+
readonly TANSTACK_START_UNVERIFIED: "TanStack Start is UNVERIFIED";
|
|
34
|
+
readonly SVELTEKIT_UNVERIFIED: "SvelteKit is UNVERIFIED";
|
|
35
|
+
readonly CLIENT_HOOK: "Reticle client hook";
|
|
36
|
+
readonly CONNECT_SNIPPET_ASTRO: "Connect snippet (Astro)";
|
|
37
|
+
readonly ASTRO_CONFIG: "Astro config (token + build target)";
|
|
38
|
+
readonly ASTRO_ENV_DTS: "Astro env types (Vite defines)";
|
|
39
|
+
readonly CSP: "Content-Security-Policy";
|
|
40
|
+
readonly CONNECT_SNIPPET: "Connect snippet";
|
|
41
|
+
};
|
|
42
|
+
export type StepTitle = (typeof StepTitle)[keyof typeof StepTitle];
|
|
43
|
+
/** Exported for the guard test: every title a step builder emits must be one of these. */
|
|
44
|
+
export declare const STEP_TITLES: readonly StepTitle[];
|
|
45
|
+
/**
|
|
46
|
+
* Titles of the steps WITHOUT which no session ever appears.
|
|
47
|
+
*
|
|
48
|
+
* A ⚠ on one of these is not a warning, it is a guaranteed failure: nothing performs the manual step,
|
|
49
|
+
* so the app will not connect and every Reticle tool will answer "no browser session connected".
|
|
50
|
+
* Reported from a field sweep, where the ⚠ count and "did it connect" were treated as independent
|
|
51
|
+
* signals and are not.
|
|
52
|
+
*/
|
|
53
|
+
export declare const CONNECT_STEP_TITLES: ReadonlySet<StepTitle>;
|
|
54
|
+
/** True when this step is what makes the app dial the daemon. */
|
|
55
|
+
export declare function isConnectStep(title: string): boolean;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step titles `init` emits, and which of them decide whether the app can dial the daemon.
|
|
3
|
+
*
|
|
4
|
+
* Lifted out of `plan.ts` when that file reached the size backstop. A cohesive unit on its own
|
|
5
|
+
* terms: one question, asked of a step title, with one consequence — a manual step in this set makes
|
|
6
|
+
* `init` exit non-zero instead of reporting a success over an app that can never connect.
|
|
7
|
+
*
|
|
8
|
+
* The membership used to be matched against free strings written out a second time here, so
|
|
9
|
+
* renaming a step in `plan-framework.ts` silently dropped it out of the set and nothing went red.
|
|
10
|
+
* `StepTitle` is now the one place a title is spelled: both files import it, so a rename is a
|
|
11
|
+
* compile error and `plan-framework.test.ts` pins that no builder has drifted back to a literal.
|
|
12
|
+
*/
|
|
13
|
+
import { CSP_STEP_TITLE } from './csp-check.js';
|
|
14
|
+
/** Every fixed title a plan step can carry. The one spelling; nothing writes a title inline. */
|
|
15
|
+
export const StepTitle = {
|
|
16
|
+
VITE_PLUGIN: 'Vite plugin',
|
|
17
|
+
CAPABILITIES: 'Capabilities + store',
|
|
18
|
+
CAPABILITIES_TODO: 'AGENT: finish the capabilities file',
|
|
19
|
+
RETICLE_DEV_COMPONENT: 'ReticleDev component',
|
|
20
|
+
NEXT_CONFIG: 'Next config (withReticle)',
|
|
21
|
+
MOUNT_RETICLE_DEV: 'Mount ReticleDev',
|
|
22
|
+
CONNECT_MODULE: 'Reticle connect module',
|
|
23
|
+
PAIRING_TOKEN: 'Pairing token',
|
|
24
|
+
PAIRING_TOKEN_PER_MACHINE: 'Pairing token is per-machine',
|
|
25
|
+
CONNECT_SNIPPET_CRA: 'Connect snippet (CRA)',
|
|
26
|
+
CONNECT_SNIPPET_NUXT: 'Connect snippet (Nuxt)',
|
|
27
|
+
NUXT_CONFIG: 'Nuxt config (token + watcher)',
|
|
28
|
+
NUXT_RESTART: 'Restart the Nuxt dev server',
|
|
29
|
+
CONNECT_SNIPPET_REACT_ROUTER: 'Connect snippet (React Router)',
|
|
30
|
+
CONNECT_SNIPPET_TANSTACK_START: 'Connect snippet (TanStack Start)',
|
|
31
|
+
ELECTRON_VITE_PLUGIN: 'Vite plugin (electron-vite renderer)',
|
|
32
|
+
ELECTRON_PRELOAD: 'Electron preload (IPC shim)',
|
|
33
|
+
ELECTRON_CAPTURE: 'Electron capture (screenshots)',
|
|
34
|
+
TANSTACK_START_UNVERIFIED: 'TanStack Start is UNVERIFIED',
|
|
35
|
+
SVELTEKIT_UNVERIFIED: 'SvelteKit is UNVERIFIED',
|
|
36
|
+
CLIENT_HOOK: 'Reticle client hook',
|
|
37
|
+
CONNECT_SNIPPET_ASTRO: 'Connect snippet (Astro)',
|
|
38
|
+
ASTRO_CONFIG: 'Astro config (token + build target)',
|
|
39
|
+
ASTRO_ENV_DTS: 'Astro env types (Vite defines)',
|
|
40
|
+
CSP: CSP_STEP_TITLE,
|
|
41
|
+
CONNECT_SNIPPET: 'Connect snippet',
|
|
42
|
+
};
|
|
43
|
+
/** Exported for the guard test: every title a step builder emits must be one of these. */
|
|
44
|
+
export const STEP_TITLES = Object.values(StepTitle);
|
|
45
|
+
/**
|
|
46
|
+
* Titles of the steps WITHOUT which no session ever appears.
|
|
47
|
+
*
|
|
48
|
+
* A ⚠ on one of these is not a warning, it is a guaranteed failure: nothing performs the manual step,
|
|
49
|
+
* so the app will not connect and every Reticle tool will answer "no browser session connected".
|
|
50
|
+
* Reported from a field sweep, where the ⚠ count and "did it connect" were treated as independent
|
|
51
|
+
* signals and are not.
|
|
52
|
+
*/
|
|
53
|
+
export const CONNECT_STEP_TITLES = new Set([
|
|
54
|
+
StepTitle.CONNECT_SNIPPET,
|
|
55
|
+
StepTitle.CONNECT_SNIPPET_CRA,
|
|
56
|
+
StepTitle.CONNECT_SNIPPET_ASTRO,
|
|
57
|
+
StepTitle.CONNECT_SNIPPET_NUXT,
|
|
58
|
+
// The config is the other half of the Nuxt connect, not a nicety: it is the only thing in a Nuxt
|
|
59
|
+
// app that can inline the pairing token, and the bridge refuses a connect without one even on
|
|
60
|
+
// localhost. A plugin written beside an unpatched config is a guaranteed non-connection.
|
|
61
|
+
StepTitle.NUXT_CONFIG,
|
|
62
|
+
StepTitle.CONNECT_SNIPPET_REACT_ROUTER,
|
|
63
|
+
StepTitle.CONNECT_SNIPPET_TANSTACK_START,
|
|
64
|
+
StepTitle.CLIENT_HOOK,
|
|
65
|
+
StepTitle.CONNECT_MODULE,
|
|
66
|
+
StepTitle.RETICLE_DEV_COMPONENT,
|
|
67
|
+
// Writing the component and MOUNTING it are two steps, and only the write was here. A root layout
|
|
68
|
+
// whose shape `init` does not recognise leaves the component on disk and never rendered: the SDK
|
|
69
|
+
// is in the project, nothing imports it, and `init` exited 0 over an app that could not connect.
|
|
70
|
+
StepTitle.MOUNT_RETICLE_DEV,
|
|
71
|
+
// NOT here, and the reason is worth keeping: `StepTitle.PAIRING_TOKEN`.
|
|
72
|
+
//
|
|
73
|
+
// It is a genuine connect step — CRA inlines only REACT_APP_*, so without the token in the env
|
|
74
|
+
// file the bridge refuses every connection and the app boots, looks correct, and never pairs. But
|
|
75
|
+
// it goes MANUAL in exactly one situation: no daemon has ever run on this machine, so there is no
|
|
76
|
+
// token to inline. That is the FIRST CRA install on a fresh machine, i.e. the first-time user —
|
|
77
|
+
// and making that exit non-zero reports a broken install to the one person least able to tell
|
|
78
|
+
// that it is not.
|
|
79
|
+
//
|
|
80
|
+
// The real fix is for `init` to mint the token rather than only read it, which is what both the
|
|
81
|
+
// daemon and the Vite plugin already do. `runInit` is synchronous and the existing helpers are
|
|
82
|
+
// not, and `gate:install` scaffolds no CRA app, so that change would ship with no coverage of the
|
|
83
|
+
// path it changes. It is worth doing, and worth doing with a scaffold behind it.
|
|
84
|
+
StepTitle.VITE_PLUGIN,
|
|
85
|
+
// electron-vite's connect IS the renderer plugin. Without it the SDK never injects, and the
|
|
86
|
+
// preload/capture steps are not enough to produce a session.
|
|
87
|
+
StepTitle.ELECTRON_VITE_PLUGIN,
|
|
88
|
+
]);
|
|
89
|
+
/** True when this step is what makes the app dial the daemon. */
|
|
90
|
+
export function isConnectStep(title) {
|
|
91
|
+
return CONNECT_STEP_TITLES.has(title);
|
|
92
|
+
}
|
package/dist/cra.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiring Reticle into a Create React App project.
|
|
3
|
+
*
|
|
4
|
+
* CRA had no automated path: init reported `⚠ Connect snippet → index.html`, a target that cannot
|
|
5
|
+
* work. CRA's `public/index.html` is a static template the bundler never processes for modules, so a
|
|
6
|
+
* bare import in it resolves to nothing. `src/index.tsx` is what gets bundled, and that is where the
|
|
7
|
+
* connect has to arrive.
|
|
8
|
+
*
|
|
9
|
+
* The token is the second half of the problem. Every other stack has somewhere to inline it —
|
|
10
|
+
* Vite defines `__RETICLE_TOKEN__`, the Astro and HTML snippets read the file in Node. `src/index.tsx`
|
|
11
|
+
* is browser code inside a bundler that inlines exactly one thing: environment variables prefixed
|
|
12
|
+
* `REACT_APP_`. So it travels through `.env.development.local`, which is CRA's own documented
|
|
13
|
+
* mechanism and is gitignored by CRA's own template.
|
|
14
|
+
*/
|
|
15
|
+
/** The line added to `src/index.tsx` / `src/index.js`. Side-effect import: the module guards itself on NODE_ENV. */
|
|
16
|
+
export declare const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
|
|
17
|
+
/**
|
|
18
|
+
* Where the connect module lands — `.ts` only when the app already speaks TypeScript.
|
|
19
|
+
*
|
|
20
|
+
* A JavaScript CRA project has no tsconfig and cannot resolve `.ts` (#675). Emitting `.ts` there
|
|
21
|
+
* makes the first compile after init fail while the plan still reports `[✓]`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function craDevModulePath(typescript: boolean): string;
|
|
24
|
+
/** TypeScript default path — prefer `craDevModulePath` when the project's language is known. */
|
|
25
|
+
export declare const CRA_DEV_MODULE_PATH: string;
|
|
26
|
+
export declare const CRA_ENV_PATH = ".env.development.local";
|
|
27
|
+
export declare const TOKEN_VAR = "REACT_APP_RETICLE_TOKEN";
|
|
28
|
+
/**
|
|
29
|
+
* The bridge URL, carried through the same channel as the token.
|
|
30
|
+
*
|
|
31
|
+
* CRA is the one supported stack with no build-time hook of ours: no plugin, no config wrapper, no
|
|
32
|
+
* `define`. Vite and Astro re-resolve the daemon every dev-server start and Next does it through
|
|
33
|
+
* `withReticle`; CRA has nowhere to run that code. So the URL goes where the token already goes, and
|
|
34
|
+
* is refreshed by the same `init` step that refreshes the token.
|
|
35
|
+
*
|
|
36
|
+
* That makes `reticle init` the heal rather than the dev server, which is weaker and is stated
|
|
37
|
+
* plainly in the step's own detail. It is still strictly better than a URL frozen into a source file
|
|
38
|
+
* the owner has to hand-edit, and `.env.development.local` is already the per-machine file.
|
|
39
|
+
*/
|
|
40
|
+
export declare const URL_VAR = "REACT_APP_RETICLE_URL";
|
|
41
|
+
/**
|
|
42
|
+
* What the app itself says when the token is not there.
|
|
43
|
+
*
|
|
44
|
+
* The token is a per-machine secret and CRA's own template gitignores the file it lives in, so it
|
|
45
|
+
* CANNOT travel with the repo — that part is correct and must stay. What was wrong is that it failed
|
|
46
|
+
* silently: a teammate clones, runs `npm start`, and the app connects with an empty token, so the
|
|
47
|
+
* only signal is the bridge's generic `authentication failed` in a console nobody had open. Naming
|
|
48
|
+
* the variable, the gitignored file and the one command that fixes it turns a dead app into a
|
|
49
|
+
* one-line repair. Every other stack reads the token in Node at dev-server start (the Vite plugin,
|
|
50
|
+
* withReticle); CRA has no such hook without ejecting, so this message is the fix.
|
|
51
|
+
*/
|
|
52
|
+
export declare const CRA_TOKEN_MISSING_NOTE: string;
|
|
53
|
+
/**
|
|
54
|
+
* Said as its own NOTICE line, beside the write — not inside it.
|
|
55
|
+
*
|
|
56
|
+
* The caveat has always been in the write step's `detail`, and the write step renders `[✓]`. But
|
|
57
|
+
* `SKILL.md` tells whoever reads the report: *"If every line is `✓`, `·` or `–`, skip to Step 4 and
|
|
58
|
+
* validate."* So the one fact that makes this install conditional sat in the one place the reading
|
|
59
|
+
* protocol says to ignore — which is exactly how it was reported to us as "4 OK marks and no
|
|
60
|
+
* warning". The words were on screen; the reader was following instructions.
|
|
61
|
+
*
|
|
62
|
+
* It cannot simply be promoted: `run.ts` only writes steps whose status is APPLY, so demoting the
|
|
63
|
+
* token step to NOTICE would stop it writing the token and break the install outright.
|
|
64
|
+
*/
|
|
65
|
+
export declare const CRA_TOKEN_PER_MACHINE_NOTICE: string;
|
|
66
|
+
/** Add the import after the last existing one, or null when it is already present. */
|
|
67
|
+
export declare function craImportPatch(source: string): string | null;
|
|
68
|
+
/** Set the token variable, or null when nothing needs to change. */
|
|
69
|
+
export declare function craEnvPatch(existing: string | null, token: string, url?: string): string | null;
|
|
70
|
+
/** Options for the CRA connect module. */
|
|
71
|
+
interface CraDevModuleOptions {
|
|
72
|
+
/**
|
|
73
|
+
* When false, emit a `.js`-shaped body (no `export {}`). Default true for callers that predate
|
|
74
|
+
* the language branch.
|
|
75
|
+
*/
|
|
76
|
+
typescript?: boolean;
|
|
77
|
+
/** `data-testid` values scanned from the app's source, declared so the app is verifiable. */
|
|
78
|
+
testids?: readonly string[];
|
|
79
|
+
}
|
|
80
|
+
/** The dev-only connect module imported from the app entry. */
|
|
81
|
+
export declare function craDevModuleFile(port: number | undefined, projectId?: string, options?: CraDevModuleOptions): string;
|
|
82
|
+
export {};
|
package/dist/cra.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiring Reticle into a Create React App project.
|
|
3
|
+
*
|
|
4
|
+
* CRA had no automated path: init reported `⚠ Connect snippet → index.html`, a target that cannot
|
|
5
|
+
* work. CRA's `public/index.html` is a static template the bundler never processes for modules, so a
|
|
6
|
+
* bare import in it resolves to nothing. `src/index.tsx` is what gets bundled, and that is where the
|
|
7
|
+
* connect has to arrive.
|
|
8
|
+
*
|
|
9
|
+
* The token is the second half of the problem. Every other stack has somewhere to inline it —
|
|
10
|
+
* Vite defines `__RETICLE_TOKEN__`, the Astro and HTML snippets read the file in Node. `src/index.tsx`
|
|
11
|
+
* is browser code inside a bundler that inlines exactly one thing: environment variables prefixed
|
|
12
|
+
* `REACT_APP_`. So it travels through `.env.development.local`, which is CRA's own documented
|
|
13
|
+
* mechanism and is gitignored by CRA's own template.
|
|
14
|
+
*/
|
|
15
|
+
import { bridgeWsUrl } from '@reticlehq/core';
|
|
16
|
+
import { CLI } from './agent-rules.js';
|
|
17
|
+
import { registerCapabilitiesCall } from './snippets.js';
|
|
18
|
+
/** The line added to `src/index.tsx` / `src/index.js`. Side-effect import: the module guards itself on NODE_ENV. */
|
|
19
|
+
export const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
|
|
20
|
+
/**
|
|
21
|
+
* Where the connect module lands — `.ts` only when the app already speaks TypeScript.
|
|
22
|
+
*
|
|
23
|
+
* A JavaScript CRA project has no tsconfig and cannot resolve `.ts` (#675). Emitting `.ts` there
|
|
24
|
+
* makes the first compile after init fail while the plan still reports `[✓]`.
|
|
25
|
+
*/
|
|
26
|
+
export function craDevModulePath(typescript) {
|
|
27
|
+
return typescript ? 'src/reticle-dev.ts' : 'src/reticle-dev.js';
|
|
28
|
+
}
|
|
29
|
+
/** TypeScript default path — prefer `craDevModulePath` when the project's language is known. */
|
|
30
|
+
export const CRA_DEV_MODULE_PATH = craDevModulePath(true);
|
|
31
|
+
export const CRA_ENV_PATH = '.env.development.local';
|
|
32
|
+
export const TOKEN_VAR = 'REACT_APP_RETICLE_TOKEN';
|
|
33
|
+
/**
|
|
34
|
+
* The bridge URL, carried through the same channel as the token.
|
|
35
|
+
*
|
|
36
|
+
* CRA is the one supported stack with no build-time hook of ours: no plugin, no config wrapper, no
|
|
37
|
+
* `define`. Vite and Astro re-resolve the daemon every dev-server start and Next does it through
|
|
38
|
+
* `withReticle`; CRA has nowhere to run that code. So the URL goes where the token already goes, and
|
|
39
|
+
* is refreshed by the same `init` step that refreshes the token.
|
|
40
|
+
*
|
|
41
|
+
* That makes `reticle init` the heal rather than the dev server, which is weaker and is stated
|
|
42
|
+
* plainly in the step's own detail. It is still strictly better than a URL frozen into a source file
|
|
43
|
+
* the owner has to hand-edit, and `.env.development.local` is already the per-machine file.
|
|
44
|
+
*/
|
|
45
|
+
export const URL_VAR = 'REACT_APP_RETICLE_URL';
|
|
46
|
+
/**
|
|
47
|
+
* What the app itself says when the token is not there.
|
|
48
|
+
*
|
|
49
|
+
* The token is a per-machine secret and CRA's own template gitignores the file it lives in, so it
|
|
50
|
+
* CANNOT travel with the repo — that part is correct and must stay. What was wrong is that it failed
|
|
51
|
+
* silently: a teammate clones, runs `npm start`, and the app connects with an empty token, so the
|
|
52
|
+
* only signal is the bridge's generic `authentication failed` in a console nobody had open. Naming
|
|
53
|
+
* the variable, the gitignored file and the one command that fixes it turns a dead app into a
|
|
54
|
+
* one-line repair. Every other stack reads the token in Node at dev-server start (the Vite plugin,
|
|
55
|
+
* withReticle); CRA has no such hook without ejecting, so this message is the fix.
|
|
56
|
+
*/
|
|
57
|
+
export const CRA_TOKEN_MISSING_NOTE = `${TOKEN_VAR} is not set, so Reticle cannot pair with the daemon. ` +
|
|
58
|
+
`The pairing token is per-machine and ${CRA_ENV_PATH} is gitignored by CRA's template, ` +
|
|
59
|
+
`so it does not survive a clone. Run \`${CLI} init\` in this project to write it for this machine.`;
|
|
60
|
+
/**
|
|
61
|
+
* Said as its own NOTICE line, beside the write — not inside it.
|
|
62
|
+
*
|
|
63
|
+
* The caveat has always been in the write step's `detail`, and the write step renders `[✓]`. But
|
|
64
|
+
* `SKILL.md` tells whoever reads the report: *"If every line is `✓`, `·` or `–`, skip to Step 4 and
|
|
65
|
+
* validate."* So the one fact that makes this install conditional sat in the one place the reading
|
|
66
|
+
* protocol says to ignore — which is exactly how it was reported to us as "4 OK marks and no
|
|
67
|
+
* warning". The words were on screen; the reader was following instructions.
|
|
68
|
+
*
|
|
69
|
+
* It cannot simply be promoted: `run.ts` only writes steps whose status is APPLY, so demoting the
|
|
70
|
+
* token step to NOTICE would stop it writing the token and break the install outright.
|
|
71
|
+
*/
|
|
72
|
+
export const CRA_TOKEN_PER_MACHINE_NOTICE = `${CRA_ENV_PATH} is in CRA's own .gitignore, so the token just written does not travel. ` +
|
|
73
|
+
'It works on THIS machine and nowhere else: a teammate cloning the repo, CI, or a container ' +
|
|
74
|
+
'gets an app that boots, never pairs, and reports it only in the browser console. ' +
|
|
75
|
+
`Each developer runs \`${CLI} init\` once locally.`;
|
|
76
|
+
/** Add the import after the last existing one, or null when it is already present. */
|
|
77
|
+
export function craImportPatch(source) {
|
|
78
|
+
if (source.includes(CRA_DEV_MODULE_IMPORT))
|
|
79
|
+
return null;
|
|
80
|
+
const lines = source.split('\n');
|
|
81
|
+
// After the LAST import: React's own imports must still run first, and a side-effect import placed
|
|
82
|
+
// above them would connect before the app exists.
|
|
83
|
+
let lastImport = -1;
|
|
84
|
+
for (const [index, line] of lines.entries()) {
|
|
85
|
+
if (/^\s*import\s/.test(line))
|
|
86
|
+
lastImport = index;
|
|
87
|
+
}
|
|
88
|
+
lines.splice(lastImport + 1, 0, CRA_DEV_MODULE_IMPORT);
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
/** Set the token variable, or null when nothing needs to change. */
|
|
92
|
+
export function craEnvPatch(existing, token, url) {
|
|
93
|
+
if ('' === token)
|
|
94
|
+
return null;
|
|
95
|
+
const pairs = [
|
|
96
|
+
[TOKEN_VAR, token],
|
|
97
|
+
...(url !== undefined && url.length > 0 ? [[URL_VAR, url]] : []),
|
|
98
|
+
];
|
|
99
|
+
let next = null === existing || '' === existing.trim() ? '' : existing;
|
|
100
|
+
for (const [name, value] of pairs) {
|
|
101
|
+
const line = `${name}=${value}`;
|
|
102
|
+
if (next.includes(line))
|
|
103
|
+
continue;
|
|
104
|
+
// Replace rather than append: two assignments of one variable is a silent coin flip on which
|
|
105
|
+
// wins, and for the URL that coin decides which daemon the app talks to.
|
|
106
|
+
if (new RegExp(`^${name}=`, 'm').test(next)) {
|
|
107
|
+
next = next.replace(new RegExp(`^${name}=.*$`, 'm'), line);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
next = `${'' === next || next.endsWith('\n') ? next : `${next}\n`}${line}\n`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return next === (existing ?? '') ? null : next;
|
|
114
|
+
}
|
|
115
|
+
/** The dev-only connect module imported from the app entry. */
|
|
116
|
+
export function craDevModuleFile(port, projectId, options = {}) {
|
|
117
|
+
const typescript = false !== options.typescript;
|
|
118
|
+
const fields = [];
|
|
119
|
+
// `bridgeWsUrl`, not a hand-written string. This was the only client URL in the product spelling
|
|
120
|
+
// the host as `127.0.0.1` while every other generator said `localhost` — same endpoint, but a
|
|
121
|
+
// difference with no reason behind it is the kind that becomes a real one after somebody edits half
|
|
122
|
+
// of it.
|
|
123
|
+
if (port !== undefined)
|
|
124
|
+
fields.push(`url: '${bridgeWsUrl(port)}'`);
|
|
125
|
+
if (projectId !== undefined && projectId.length > 0)
|
|
126
|
+
fields.push(`projectId: '${projectId}'`);
|
|
127
|
+
// Multiline on purpose (#684): a single-line connect + console.error fails CRA boilerplate
|
|
128
|
+
// Prettier (printWidth 80) and the project's own lint blocks a clean install.
|
|
129
|
+
const connectFields = [
|
|
130
|
+
...fields.map((f) => ` ${f},`),
|
|
131
|
+
' ...(url.length > 0 ? { url } : {}),',
|
|
132
|
+
' ...(token.length > 0 ? { token } : {}),',
|
|
133
|
+
].join('\n');
|
|
134
|
+
// Split so every emitted line stays under a CRA boilerplate printWidth of 80 (#684). A single
|
|
135
|
+
// JSON.stringify of the whole note is ~290 characters and fails prettier-as-eslint on install.
|
|
136
|
+
// Chunks are joined with `+` in the emitted file; together they equal `[reticle] ${CRA_TOKEN_MISSING_NOTE}`.
|
|
137
|
+
const missingChunks = [
|
|
138
|
+
`[reticle] ${TOKEN_VAR} is not set, so Reticle `,
|
|
139
|
+
'cannot pair with the daemon. The pairing token is ',
|
|
140
|
+
`per-machine and ${CRA_ENV_PATH} is gitignored by `,
|
|
141
|
+
"CRA's template, so it does not survive a clone. Run ",
|
|
142
|
+
`\`${CLI} init\` in this project to write it for this `,
|
|
143
|
+
'machine.',
|
|
144
|
+
].map((c) => JSON.stringify(c));
|
|
145
|
+
// Eight spaces match the indent inside `console.error(` below.
|
|
146
|
+
const missingExpr = missingChunks.join(' +\n ');
|
|
147
|
+
// `export {}` is a TypeScript empty-module marker. A `.js` file with it is fine under Babel, but
|
|
148
|
+
// the workaround reporters used was to drop it when renaming to `.js` — keep the JS emit clean.
|
|
149
|
+
const trailer = typescript ? '\nexport {};\n' : '\n';
|
|
150
|
+
return `// Dev-only: connect Reticle. Imported for its side effect from src/index.tsx.
|
|
151
|
+
//
|
|
152
|
+
// CRA's public/index.html is a static template the bundler never processes for
|
|
153
|
+
// modules, so the connect cannot live there. The pairing token arrives through
|
|
154
|
+
// REACT_APP_RETICLE_TOKEN because REACT_APP_* is the only thing CRA inlines
|
|
155
|
+
// into browser code.
|
|
156
|
+
if (process.env.NODE_ENV === 'development') {
|
|
157
|
+
void import('@reticlehq/react').then((sdk) => {
|
|
158
|
+
// On its own line: CRA boilerplate prettier caps lines at 80.
|
|
159
|
+
const { reticle, install, registerCapabilities } = sdk;
|
|
160
|
+
install();
|
|
161
|
+
const token = process.env.${TOKEN_VAR} ?? '';
|
|
162
|
+
// Written by \`reticle init\` from the daemon that was live when it ran, and
|
|
163
|
+
// refreshed by re-running it. CRA gives us no hook to resolve this at
|
|
164
|
+
// dev-server start, so if the daemon moves, re-run \`reticle init\` rather
|
|
165
|
+
// than editing the url below.
|
|
166
|
+
const url = process.env.${URL_VAR} ?? '';
|
|
167
|
+
// Loud on purpose: without this the only symptom is the bridge's generic
|
|
168
|
+
// auth failure.
|
|
169
|
+
if (token.length === 0) {
|
|
170
|
+
console.error(
|
|
171
|
+
${missingExpr},
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
// Still attempt it — a bridge running without a token pairs fine.
|
|
175
|
+
reticle.connect({
|
|
176
|
+
${connectFields}
|
|
177
|
+
});
|
|
178
|
+
// What the agent can drive without guessing.
|
|
179
|
+
${registerCapabilitiesCall(options.testids ?? [], ' ')}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
${trailer}`;
|
|
183
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does this app's Content-Security-Policy let the browser reach the Reticle bridge?
|
|
3
|
+
*
|
|
4
|
+
* Two independent field reports, both Next: `init` reported every step successful, the SDK mounted,
|
|
5
|
+
* the dial URL was correct, and the app never connected because a strict `connect-src` excluded
|
|
6
|
+
* `ws://localhost:<port>`. The browser blocks the WebSocket and says so in its own console, which
|
|
7
|
+
* nothing on the Reticle side reads — so `status` and `doctor` reported perfect health at an app
|
|
8
|
+
* that could not connect at all, forever. A setup step that exits 0 while leaving the user broken is
|
|
9
|
+
* the worst failure mode this repo has.
|
|
10
|
+
*
|
|
11
|
+
* A TEXT SCAN, deliberately. `headers()` is a function; `init` cannot execute a user's Next config
|
|
12
|
+
* without importing their whole app, and the thing worth finding is the policy string a developer
|
|
13
|
+
* typed — in `next.config.*`, in middleware, or in a `<meta http-equiv>` tag. The scan finds the
|
|
14
|
+
* string wherever it is written.
|
|
15
|
+
*
|
|
16
|
+
* NARROW on purpose: it fires only when a `connect-src` exists AND does not admit the bridge. No
|
|
17
|
+
* CSP, or one that already covers us, produces nothing. A warning that fires on a working setup
|
|
18
|
+
* costs precisely what a green check on a broken one costs, and this file exists because of the
|
|
19
|
+
* second kind.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The problem with this app's `connect-src`, or undefined if there is not one.
|
|
23
|
+
*
|
|
24
|
+
* `https:` counts as a wildcard here only in the sense that a policy written that loosely will not
|
|
25
|
+
* be the thing blocking a dev WebSocket; the check stays quiet rather than arguing about it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function cspConnectSrcProblem(text: string, port: number): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* The problem with this app's `script-src`, or undefined if there is not one.
|
|
30
|
+
*
|
|
31
|
+
* Reports the remedy that works under `'self'` — serve the connect code as an external module file —
|
|
32
|
+
* rather than telling anyone to weaken their policy with `'unsafe-inline'`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function cspInlineScriptProblem(text: string, port: number): string | undefined;
|
|
35
|
+
/** Where the external connect module goes, for an app served from a static directory. */
|
|
36
|
+
export declare const EXTERNAL_CONNECT_PATH = "public/reticle-connect.js";
|
|
37
|
+
/** The remedy that works under `script-src 'self'`, as text to copy. */
|
|
38
|
+
export declare function externalScriptRemedy(): string;
|
|
39
|
+
/** The exact addition to paste. Text to copy, not advice to interpret. */
|
|
40
|
+
export declare function devCspAddition(port: number): string;
|
|
41
|
+
/** The step title, named here so `doctor` and the plan cannot drift apart on what this check is called. */
|
|
42
|
+
export declare const CSP_STEP_TITLE = "Content-Security-Policy";
|
|
43
|
+
/**
|
|
44
|
+
* The first blocking policy among the sources `init` already has in hand, or undefined.
|
|
45
|
+
*
|
|
46
|
+
* At most one finding: a second copy of the same policy in a second file is the same problem, and
|
|
47
|
+
* two identical notices read as two problems.
|
|
48
|
+
*/
|
|
49
|
+
export declare function cspPlanProblem(sources: readonly (string | null | undefined)[], port: number): string | undefined;
|