@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,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Astro file owns the document, and therefore where the SDK script belongs.
|
|
3
|
+
*
|
|
4
|
+
* The previous rule was "exactly one `.astro` file in `src/layouts/`". Measured on two real Astro
|
|
5
|
+
* projects, it fired on neither: one has no `src/layouts/` at all and renders from
|
|
6
|
+
* `src/pages/index.astro`; the other has three files there, two of which are partials. Both got a
|
|
7
|
+
* printed recipe instead of a working install.
|
|
8
|
+
*
|
|
9
|
+
* A file COUNT cannot tell a layout from a partial, and a directory NAME cannot tell you where the
|
|
10
|
+
* document shell is. `</body>` can — the file that closes the body is the one every page renders
|
|
11
|
+
* through, and the only place a script reaches the whole app.
|
|
12
|
+
*
|
|
13
|
+
* Ambiguity is still refused rather than guessed. Two document shells means two entry points, and
|
|
14
|
+
* silently instrumenting one of them would leave half the app unverified while reporting success.
|
|
15
|
+
*/
|
|
16
|
+
interface AstroCandidate {
|
|
17
|
+
path: string;
|
|
18
|
+
source: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function pickAstroHost(candidates: readonly AstroCandidate[]): AstroCandidate | undefined;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Astro file owns the document, and therefore where the SDK script belongs.
|
|
3
|
+
*
|
|
4
|
+
* The previous rule was "exactly one `.astro` file in `src/layouts/`". Measured on two real Astro
|
|
5
|
+
* projects, it fired on neither: one has no `src/layouts/` at all and renders from
|
|
6
|
+
* `src/pages/index.astro`; the other has three files there, two of which are partials. Both got a
|
|
7
|
+
* printed recipe instead of a working install.
|
|
8
|
+
*
|
|
9
|
+
* A file COUNT cannot tell a layout from a partial, and a directory NAME cannot tell you where the
|
|
10
|
+
* document shell is. `</body>` can — the file that closes the body is the one every page renders
|
|
11
|
+
* through, and the only place a script reaches the whole app.
|
|
12
|
+
*
|
|
13
|
+
* Ambiguity is still refused rather than guessed. Two document shells means two entry points, and
|
|
14
|
+
* silently instrumenting one of them would leave half the app unverified while reporting success.
|
|
15
|
+
*/
|
|
16
|
+
/** Tolerant of whitespace and casing: `</body>`, `</BODY >`, `</body\n>`. */
|
|
17
|
+
const CLOSES_BODY = /<\/\s*body\s*>/i;
|
|
18
|
+
/** Layout files win over pages: a page shell is one route, a layout shell is what routes inherit. */
|
|
19
|
+
const LAYOUT_DIR = 'layouts/';
|
|
20
|
+
export function pickAstroHost(candidates) {
|
|
21
|
+
const owners = candidates.filter((c) => CLOSES_BODY.test(c.source));
|
|
22
|
+
if (0 === owners.length)
|
|
23
|
+
return undefined;
|
|
24
|
+
const layouts = owners.filter((c) => c.path.includes(LAYOUT_DIR));
|
|
25
|
+
const preferred = layouts.length > 0 ? layouts : owners;
|
|
26
|
+
return 1 === preferred.length ? preferred[0] : undefined;
|
|
27
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, conservative patchers for an Astro app: the config gets the token/root `define` and the
|
|
3
|
+
* raised build target, one layout gets the dev-only connect `<script>`.
|
|
4
|
+
*
|
|
5
|
+
* Astro was the last gated framework where `init` printed a correct recipe and applied none of it —
|
|
6
|
+
* the only ⚠ left on a supported stack, and the user's first session was two hand-copied snippets
|
|
7
|
+
* away. Astro is Vite-based but renders its own HTML, so the Vite plugin's injection never fires and
|
|
8
|
+
* there is no entry module to wire: the connect has to live in a page or layout `<script>`, and the
|
|
9
|
+
* token has to be inlined by the config because nothing else is in the page's path to inject it.
|
|
10
|
+
*
|
|
11
|
+
* Both patchers bail to `manual` (the printed recipe) on any shape they do not fully recognise.
|
|
12
|
+
* Half-editing a build config is worse than a documented manual step.
|
|
13
|
+
*/
|
|
14
|
+
import { type SourcePatch } from './patch-kind.js';
|
|
15
|
+
import { UiLibrary } from './detect.js';
|
|
16
|
+
export declare function patchAstroConfig(source: string): SourcePatch;
|
|
17
|
+
export declare function patchAstroLayout(source: string, port: number | undefined, projectId: string | undefined, uiLibrary?: UiLibrary, testids?: readonly string[]): SourcePatch;
|
|
18
|
+
/** Where Astro keeps ambient types — `create-astro` ships this file. */
|
|
19
|
+
export declare const ASTRO_ENV_DTS_PATH = "src/env.d.ts";
|
|
20
|
+
/**
|
|
21
|
+
* The declarations `astro check` needs for the Vite `define` names (#677).
|
|
22
|
+
*
|
|
23
|
+
* Matching SvelteKit's `hooks.client.ts` typing: `string | undefined`, because the define can be
|
|
24
|
+
* absent when the pairing token file is missing.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ASTRO_ENV_DTS_DECLARES: string;
|
|
27
|
+
/**
|
|
28
|
+
* Append the Vite-define ambient declarations to `src/env.d.ts`, or create the file.
|
|
29
|
+
*
|
|
30
|
+
* Without this, `create-astro`'s default `"astro check && astro build"` fails with four
|
|
31
|
+
* `ts(2304) Cannot find name '__RETICLE_TOKEN__'` errors after a clean init (#677).
|
|
32
|
+
*/
|
|
33
|
+
export declare function patchAstroEnvDts(existing: string | null): SourcePatch;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, conservative patchers for an Astro app: the config gets the token/root `define` and the
|
|
3
|
+
* raised build target, one layout gets the dev-only connect `<script>`.
|
|
4
|
+
*
|
|
5
|
+
* Astro was the last gated framework where `init` printed a correct recipe and applied none of it —
|
|
6
|
+
* the only ⚠ left on a supported stack, and the user's first session was two hand-copied snippets
|
|
7
|
+
* away. Astro is Vite-based but renders its own HTML, so the Vite plugin's injection never fires and
|
|
8
|
+
* there is no entry module to wire: the connect has to live in a page or layout `<script>`, and the
|
|
9
|
+
* token has to be inlined by the config because nothing else is in the page's path to inject it.
|
|
10
|
+
*
|
|
11
|
+
* Both patchers bail to `manual` (the printed recipe) on any shape they do not fully recognise.
|
|
12
|
+
* Half-editing a build config is worse than a documented manual step.
|
|
13
|
+
*/
|
|
14
|
+
import { RETICLE_DEFAULT_PORT, bridgeWsUrl } from '@reticlehq/core';
|
|
15
|
+
import { PatchKind } from './patch-kind.js';
|
|
16
|
+
import { UiLibrary } from './detect.js';
|
|
17
|
+
import { registerCapabilitiesCall, sdkImport } from './snippets.js';
|
|
18
|
+
import { patchViteOwningConfig } from './vite-owning-config.js';
|
|
19
|
+
/** Present in a patched layout and in the printed recipe's script. */
|
|
20
|
+
const LAYOUT_MARKER = 'reticle.connect';
|
|
21
|
+
const BODY_CLOSE = '</body>';
|
|
22
|
+
/**
|
|
23
|
+
* Astro's half of the Vite-owning config patch: the call to extend, and the SDK to pre-bundle.
|
|
24
|
+
*
|
|
25
|
+
* `@reticlehq/react` and not the detected UI library: Astro hosts islands from any framework, but
|
|
26
|
+
* `frameworkPackages` gives every Astro app the React kit (see FRAMEWORK_ADAPTERS), so that is the
|
|
27
|
+
* package the connect script imports and the one Vite must pre-bundle.
|
|
28
|
+
*/
|
|
29
|
+
const ASTRO_VITE_CONFIG = {
|
|
30
|
+
defineCall: /defineConfig\s*\(\s*\{/,
|
|
31
|
+
sdkSpecifier: sdkImport(UiLibrary.REACT).specifier,
|
|
32
|
+
};
|
|
33
|
+
export function patchAstroConfig(source) {
|
|
34
|
+
return patchViteOwningConfig(source, ASTRO_VITE_CONFIG);
|
|
35
|
+
}
|
|
36
|
+
/** The dev-only connect that goes inside the layout's `<body>`. */
|
|
37
|
+
function astroConnectScript(port, projectId, uiLibrary, testids) {
|
|
38
|
+
// Must match what `frameworkPackages` installed. Astro hosts islands from any framework, so an
|
|
39
|
+
// Astro app whose islands are Vue or Svelte gets the sensor, and a script importing the React
|
|
40
|
+
// adapter would load a package that is not in node_modules. See sdkImport.
|
|
41
|
+
const sdk = sdkImport(uiLibrary);
|
|
42
|
+
const url = port !== undefined && port !== RETICLE_DEFAULT_PORT
|
|
43
|
+
? `\n url: '${bridgeWsUrl(port)}',`
|
|
44
|
+
: '';
|
|
45
|
+
const id = projectId !== undefined && projectId.length > 0 ? `\n projectId: '${projectId}',` : '';
|
|
46
|
+
return ` <script>
|
|
47
|
+
if (import.meta.env.DEV) {
|
|
48
|
+
const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
|
|
49
|
+
const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
|
|
50
|
+
const { reticle${sdk.usesInstall ? ', install' : ''}, registerCapabilities } = await import('${sdk.specifier}');
|
|
51
|
+
${sdk.usesInstall ? 'install();' : '// The sensor has no install(); that is the React adapter.'}
|
|
52
|
+
reticle.connect({${id}${url}
|
|
53
|
+
...(token.length > 0 ? { token } : {}),
|
|
54
|
+
...(root.length > 0 ? { root } : {}),
|
|
55
|
+
});
|
|
56
|
+
${registerCapabilitiesCall(testids, ' ')}
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
export function patchAstroLayout(source, port, projectId, uiLibrary = UiLibrary.REACT, testids = []) {
|
|
62
|
+
if (source.includes(LAYOUT_MARKER))
|
|
63
|
+
return { kind: PatchKind.ALREADY };
|
|
64
|
+
const at = source.lastIndexOf(BODY_CLOSE);
|
|
65
|
+
if (at < 0) {
|
|
66
|
+
return {
|
|
67
|
+
kind: PatchKind.MANUAL,
|
|
68
|
+
reason: "couldn't find a `</body>` to place the connect script before",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
kind: PatchKind.APPLY,
|
|
73
|
+
code: `${source.slice(0, at)}${astroConnectScript(port, projectId, uiLibrary, testids)}${source.slice(at)}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Where Astro keeps ambient types — `create-astro` ships this file. */
|
|
77
|
+
export const ASTRO_ENV_DTS_PATH = 'src/env.d.ts';
|
|
78
|
+
/**
|
|
79
|
+
* The declarations `astro check` needs for the Vite `define` names (#677).
|
|
80
|
+
*
|
|
81
|
+
* Matching SvelteKit's `hooks.client.ts` typing: `string | undefined`, because the define can be
|
|
82
|
+
* absent when the pairing token file is missing.
|
|
83
|
+
*/
|
|
84
|
+
export const ASTRO_ENV_DTS_DECLARES = 'declare const __RETICLE_TOKEN__: string | undefined;\n' +
|
|
85
|
+
'declare const __RETICLE_ROOT__: string | undefined;\n';
|
|
86
|
+
/** Present once either declare is in the file — both are written together. */
|
|
87
|
+
const ENV_DTS_MARKER = '__RETICLE_TOKEN__';
|
|
88
|
+
/**
|
|
89
|
+
* Append the Vite-define ambient declarations to `src/env.d.ts`, or create the file.
|
|
90
|
+
*
|
|
91
|
+
* Without this, `create-astro`'s default `"astro check && astro build"` fails with four
|
|
92
|
+
* `ts(2304) Cannot find name '__RETICLE_TOKEN__'` errors after a clean init (#677).
|
|
93
|
+
*/
|
|
94
|
+
export function patchAstroEnvDts(existing) {
|
|
95
|
+
if (null !== existing && existing.includes(ENV_DTS_MARKER)) {
|
|
96
|
+
return { kind: PatchKind.ALREADY };
|
|
97
|
+
}
|
|
98
|
+
if (null === existing || '' === existing.trim()) {
|
|
99
|
+
return { kind: PatchKind.APPLY, code: ASTRO_ENV_DTS_DECLARES };
|
|
100
|
+
}
|
|
101
|
+
const base = existing.endsWith('\n') ? existing : `${existing}\n`;
|
|
102
|
+
return { kind: PatchKind.APPLY, code: `${base}${ASTRO_ENV_DTS_DECLARES}` };
|
|
103
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brace / paren matching for conservative source patchers.
|
|
3
|
+
*
|
|
4
|
+
* A count, not a parser. A `{` or `}` inside a string literal would fool it. That only ever
|
|
5
|
+
* shortens or lengthens the window we search, so the worst case is a MANUAL bail or a missed
|
|
6
|
+
* merge — never a corrupted file written as APPLY.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The text from just inside an opening brace to its match.
|
|
10
|
+
*
|
|
11
|
+
* `at` is the index just past `{`. Shared by the Astro and electron-vite patchers, which both
|
|
12
|
+
* need "the object literal after this key" and must agree on what that means.
|
|
13
|
+
*/
|
|
14
|
+
export declare function blockAfter(source: string, at: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* Index of the matching `}` for an object whose inner start is `at` (just past `{`).
|
|
17
|
+
* `undefined` when the braces never balance.
|
|
18
|
+
*/
|
|
19
|
+
export declare function matchingBraceEnd(source: string, at: number): number | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Index of the matching `)` for a call whose opening `(` is at `openAt`.
|
|
22
|
+
* `undefined` when the parens never balance.
|
|
23
|
+
*/
|
|
24
|
+
export declare function matchingParenEnd(source: string, openAt: number): number | undefined;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brace / paren matching for conservative source patchers.
|
|
3
|
+
*
|
|
4
|
+
* A count, not a parser. A `{` or `}` inside a string literal would fool it. That only ever
|
|
5
|
+
* shortens or lengthens the window we search, so the worst case is a MANUAL bail or a missed
|
|
6
|
+
* merge — never a corrupted file written as APPLY.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The text from just inside an opening brace to its match.
|
|
10
|
+
*
|
|
11
|
+
* `at` is the index just past `{`. Shared by the Astro and electron-vite patchers, which both
|
|
12
|
+
* need "the object literal after this key" and must agree on what that means.
|
|
13
|
+
*/
|
|
14
|
+
export function blockAfter(source, at) {
|
|
15
|
+
const end = matchingCloser(source, at, '{', '}');
|
|
16
|
+
return end === undefined ? source.slice(at) : source.slice(at, end);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Index of the matching `}` for an object whose inner start is `at` (just past `{`).
|
|
20
|
+
* `undefined` when the braces never balance.
|
|
21
|
+
*/
|
|
22
|
+
export function matchingBraceEnd(source, at) {
|
|
23
|
+
return matchingCloser(source, at, '{', '}');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Index of the matching `)` for a call whose opening `(` is at `openAt`.
|
|
27
|
+
* `undefined` when the parens never balance.
|
|
28
|
+
*/
|
|
29
|
+
export function matchingParenEnd(source, openAt) {
|
|
30
|
+
if (source[openAt] !== '(')
|
|
31
|
+
return undefined;
|
|
32
|
+
return matchingCloser(source, openAt + 1, '(', ')');
|
|
33
|
+
}
|
|
34
|
+
function matchingCloser(source, innerStart, open, close) {
|
|
35
|
+
let depth = 1;
|
|
36
|
+
for (let i = innerStart; i < source.length; i++) {
|
|
37
|
+
if (source[i] === open)
|
|
38
|
+
depth += 1;
|
|
39
|
+
else if (source[i] === close) {
|
|
40
|
+
depth -= 1;
|
|
41
|
+
if (0 === depth)
|
|
42
|
+
return i;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the app can be driven by: its `data-testid` values, and which state library holds its truth.
|
|
3
|
+
*
|
|
4
|
+
* `init` wired none of this, so every app came up with `hasCapabilities: false`, empty capabilities,
|
|
5
|
+
* and a `reticle_state` holding nothing but `__reticle_renders` — the state-truth read that SKILL.md
|
|
6
|
+
* calls the highest-value line was unavailable on every app out of the box. Measured across six.
|
|
7
|
+
*
|
|
8
|
+
* Testids are scanned rather than asked for. Stores are NAMED but not wired: detecting that an app
|
|
9
|
+
* depends on zustand is easy, knowing which module exports the store instance is not, and generating
|
|
10
|
+
* a wrong import would break the dev module that everything else now hangs off.
|
|
11
|
+
*/
|
|
12
|
+
/** Cap the generated list. A capabilities block is a hint for an agent, not an inventory. */
|
|
13
|
+
export declare const MAX_TESTIDS = 60;
|
|
14
|
+
/** Every distinct `data-testid` literal in the given sources, in first-seen order. */
|
|
15
|
+
export declare function scanTestids(sources: readonly string[]): string[];
|
|
16
|
+
/**
|
|
17
|
+
* The store libraries this app depends on AND the running app cannot reveal on its own, as
|
|
18
|
+
* ready-to-uncomment registration lines.
|
|
19
|
+
*/
|
|
20
|
+
export declare function storeHints(deps: ReadonlySet<string>): string[];
|
|
21
|
+
/** A store instance we found in the app's own source, with everything needed to import and register it. */
|
|
22
|
+
export interface FoundStore {
|
|
23
|
+
/** The name it registers under — what `reticle_state` keys its output by. */
|
|
24
|
+
key: string;
|
|
25
|
+
/** The exported identifier. */
|
|
26
|
+
ident: string;
|
|
27
|
+
/** Module specifier relative to `src/reticle-dev.ts`. */
|
|
28
|
+
importPath: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Store instances declared in the app's own source, ready to import and register.
|
|
32
|
+
*
|
|
33
|
+
* Only exported declarations of a factory the app actually depends on: anything less certain would
|
|
34
|
+
* generate an import that breaks the dev module everything else hangs off, and a broken module is
|
|
35
|
+
* worse than the commented hint it replaces.
|
|
36
|
+
*/
|
|
37
|
+
export declare function scanStores(files: readonly {
|
|
38
|
+
path: string;
|
|
39
|
+
source: string;
|
|
40
|
+
}[], deps: ReadonlySet<string>, fromDir?: string): FoundStore[];
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the app can be driven by: its `data-testid` values, and which state library holds its truth.
|
|
3
|
+
*
|
|
4
|
+
* `init` wired none of this, so every app came up with `hasCapabilities: false`, empty capabilities,
|
|
5
|
+
* and a `reticle_state` holding nothing but `__reticle_renders` — the state-truth read that SKILL.md
|
|
6
|
+
* calls the highest-value line was unavailable on every app out of the box. Measured across six.
|
|
7
|
+
*
|
|
8
|
+
* Testids are scanned rather than asked for. Stores are NAMED but not wired: detecting that an app
|
|
9
|
+
* depends on zustand is easy, knowing which module exports the store instance is not, and generating
|
|
10
|
+
* a wrong import would break the dev module that everything else now hangs off.
|
|
11
|
+
*/
|
|
12
|
+
import { posix } from 'node:path';
|
|
13
|
+
/** `data-testid="foo"` / `data-testid='foo'` / `data-testid={"foo"}` — the forms people actually write. */
|
|
14
|
+
const TESTID_ATTR = /data-testid\s*=\s*[{]?\s*["'`]([^"'`]+)["'`]/g;
|
|
15
|
+
/** Cap the generated list. A capabilities block is a hint for an agent, not an inventory. */
|
|
16
|
+
export const MAX_TESTIDS = 60;
|
|
17
|
+
/**
|
|
18
|
+
* An unresolved template interpolation, anywhere in a captured value.
|
|
19
|
+
*
|
|
20
|
+
* The attribute regex excludes quote characters and nothing else, so `data-testid={`chip-${x}`}`
|
|
21
|
+
* captures `chip-${x}` whole. Reported from a monorepo audit, where the generated capabilities file
|
|
22
|
+
* listed `'${testId}'` and `` 'chip-${group.label}' `` verbatim.
|
|
23
|
+
*
|
|
24
|
+
* Nothing in the running app ever carries those as an attribute value, so each one is an advertised
|
|
25
|
+
* handle that cannot be driven. The capabilities block exists to tell an agent what it CAN act on,
|
|
26
|
+
* and a list that is partly fiction is worse than a shorter true one — an agent that tries three
|
|
27
|
+
* imaginary testids learns to distrust the whole block.
|
|
28
|
+
*
|
|
29
|
+
* A bare `$` is fine and stays: it is legal in an attribute value and appears in real ids
|
|
30
|
+
* (`total$usd`). Only `${` is evidence of an interpolation that was never resolved.
|
|
31
|
+
*/
|
|
32
|
+
const UNRESOLVED_INTERPOLATION = '${';
|
|
33
|
+
/** Every distinct `data-testid` literal in the given sources, in first-seen order. */
|
|
34
|
+
export function scanTestids(sources) {
|
|
35
|
+
const found = new Set();
|
|
36
|
+
for (const src of sources) {
|
|
37
|
+
for (const m of src.matchAll(TESTID_ATTR)) {
|
|
38
|
+
const id = m[1];
|
|
39
|
+
if (id !== undefined && id.length > 0 && !id.includes(UNRESOLVED_INTERPOLATION)) {
|
|
40
|
+
found.add(id);
|
|
41
|
+
}
|
|
42
|
+
if (found.size >= MAX_TESTIDS)
|
|
43
|
+
return [...found];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [...found];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* State libraries the browser SDK has an adapter for, in the order we would recommend registering
|
|
50
|
+
* them. TanStack Query is first on purpose: a stale cache served as fresh fires NO network request,
|
|
51
|
+
* so the network log shows silence and the DOM shows a plausible number — the cache is the only
|
|
52
|
+
* witness, which makes it the highest-value store in the list and the least obvious one to add.
|
|
53
|
+
*/
|
|
54
|
+
const STORE_LIBRARIES = [
|
|
55
|
+
['@tanstack/react-query', "registerStore('queries', tanstackQueryStore(queryClient))"],
|
|
56
|
+
[
|
|
57
|
+
'zustand',
|
|
58
|
+
"registerStore('app', useStore) // pass the store itself, not () => store.getState()",
|
|
59
|
+
],
|
|
60
|
+
['@reduxjs/toolkit', "registerStore('app', store)"],
|
|
61
|
+
['redux', "registerStore('app', store)"],
|
|
62
|
+
['jotai', "registerStore('app', jotaiStore(getDefaultStore(), { cart, user }))"],
|
|
63
|
+
['valtio', "registerStore('app', valtioStore(state))"],
|
|
64
|
+
['mobx', "registerStore('app', mobxStore(state))"],
|
|
65
|
+
['xstate', "registerStore('machine', xstateStore(actor))"],
|
|
66
|
+
['pinia', "registerStore('cart', piniaStore(useCartStore()))"],
|
|
67
|
+
['svelte', "registerStore('cart', svelteStore(cartStore))"],
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* Libraries that hand their store to React through a context provider, and therefore need no hint.
|
|
71
|
+
*
|
|
72
|
+
* `<Provider store>` and `<QueryClientProvider client>` put the store instance in the fiber tree as
|
|
73
|
+
* a prop, so the React adapter finds and registers it on the first commit — see `auto-stores.ts` in
|
|
74
|
+
* `@reticlehq/react`. Offering a commented `registerStore` line for these was asking someone to do
|
|
75
|
+
* by hand, and be nagged about, work that is already done by the time they read the report.
|
|
76
|
+
*
|
|
77
|
+
* Everything absent from this set is module-scope (Zustand, Valtio, a MobX singleton) or needs an
|
|
78
|
+
* argument only the source supplies (Jotai atoms, an XState actor). Nothing in the running app
|
|
79
|
+
* points at those, so they still need the app to hand them over, and the hint still earns its place.
|
|
80
|
+
*/
|
|
81
|
+
const AUTO_DISCOVERED_DEPS = new Set([
|
|
82
|
+
'@tanstack/react-query',
|
|
83
|
+
'@reduxjs/toolkit',
|
|
84
|
+
'redux',
|
|
85
|
+
]);
|
|
86
|
+
/**
|
|
87
|
+
* The store libraries this app depends on AND the running app cannot reveal on its own, as
|
|
88
|
+
* ready-to-uncomment registration lines.
|
|
89
|
+
*/
|
|
90
|
+
export function storeHints(deps) {
|
|
91
|
+
return STORE_LIBRARIES.filter(([dep]) => deps.has(dep) && !AUTO_DISCOVERED_DEPS.has(dep)).map(([, hint]) => hint);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Store factories whose EXPORTED result is the store instance itself — the form `registerStore`
|
|
95
|
+
* takes directly. Each is gated on the app actually depending on that library, so a local helper
|
|
96
|
+
* called `create` in an app with no store library is never mistaken for one.
|
|
97
|
+
*
|
|
98
|
+
* Deliberately short. The adapter-wrapped libraries (Jotai, XState, Valtio, MobX, TanStack Query)
|
|
99
|
+
* need an argument we cannot infer — which atoms, which actor, which queryClient — so they keep the
|
|
100
|
+
* commented hint. Guessing there produces a module that throws on import, which is strictly worse
|
|
101
|
+
* than a comment.
|
|
102
|
+
*/
|
|
103
|
+
const STORE_FACTORIES = [
|
|
104
|
+
['zustand', 'create'],
|
|
105
|
+
['@reduxjs/toolkit', 'configureStore'],
|
|
106
|
+
['redux', 'createStore'],
|
|
107
|
+
];
|
|
108
|
+
/** `export const useCart = create<S>()(...)` / `export const store = configureStore({...})`. */
|
|
109
|
+
function storeDeclaration(factory) {
|
|
110
|
+
return new RegExp(`export\\s+const\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*${factory}\\s*[<(]`, 'g');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* `src/cart-store.ts` → `cart`; `src/store.ts` → `app`. The key is what the agent reads state under,
|
|
114
|
+
* so it has to be stable and distinct — two stores sharing one key would silently overwrite.
|
|
115
|
+
*/
|
|
116
|
+
function storeKey(path, taken) {
|
|
117
|
+
const file = (path.split('/').pop() ?? '').replace(/\.[^.]+$/, '');
|
|
118
|
+
const base = file.replace(/[-_.]?stores?$/i, '').replace(/[^a-z0-9]+/gi, '-') || 'app';
|
|
119
|
+
const key = base.toLowerCase();
|
|
120
|
+
if (!taken.has(key))
|
|
121
|
+
return key;
|
|
122
|
+
for (let n = 2;; n += 1) {
|
|
123
|
+
const candidate = `${key}-${String(n)}`;
|
|
124
|
+
if (!taken.has(candidate))
|
|
125
|
+
return candidate;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Module specifier for `store.ts` as seen FROM the generated dev module.
|
|
130
|
+
*
|
|
131
|
+
* The dev module is not always a sibling: Vite puts it at `src/reticle-dev.ts`, Next at
|
|
132
|
+
* `app/reticle-dev.tsx` or `src/app/reticle-dev.tsx`. A specifier computed for one is dead in the
|
|
133
|
+
* other, and a dead import in this file breaks the module every other capability hangs off — so the
|
|
134
|
+
* relative path is computed, not assumed.
|
|
135
|
+
*/
|
|
136
|
+
function importPathFor(storePath, fromDir) {
|
|
137
|
+
const noExt = storePath.replace(/\.[^./]+$/, '');
|
|
138
|
+
const rel = posix.relative(fromDir, noExt);
|
|
139
|
+
return rel.startsWith('.') ? rel : `./${rel}`;
|
|
140
|
+
}
|
|
141
|
+
/** Where the generated dev module lives, for Vite. Next passes its own. */
|
|
142
|
+
const DEFAULT_DEV_MODULE_DIR = 'src';
|
|
143
|
+
/**
|
|
144
|
+
* Store instances declared in the app's own source, ready to import and register.
|
|
145
|
+
*
|
|
146
|
+
* Only exported declarations of a factory the app actually depends on: anything less certain would
|
|
147
|
+
* generate an import that breaks the dev module everything else hangs off, and a broken module is
|
|
148
|
+
* worse than the commented hint it replaces.
|
|
149
|
+
*/
|
|
150
|
+
export function scanStores(files, deps, fromDir = DEFAULT_DEV_MODULE_DIR) {
|
|
151
|
+
const factories = STORE_FACTORIES.filter(([dep]) => deps.has(dep)).map(([, f]) => f);
|
|
152
|
+
const found = [];
|
|
153
|
+
const keys = new Set();
|
|
154
|
+
for (const { path, source } of files) {
|
|
155
|
+
for (const factory of factories) {
|
|
156
|
+
for (const m of source.matchAll(storeDeclaration(factory))) {
|
|
157
|
+
const ident = m[1];
|
|
158
|
+
if (ident === undefined)
|
|
159
|
+
continue;
|
|
160
|
+
const key = storeKey(path, keys);
|
|
161
|
+
keys.add(key);
|
|
162
|
+
found.push({ key, ident, importPath: importPathFor(path, fromDir) });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return found;
|
|
167
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last thing `init` prints, and the only part of it a user is asked to act on.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
|
|
5
|
+
* this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
|
|
6
|
+
* what to say afterwards. The two failure modes are different too — a wrong write breaks an
|
|
7
|
+
* install, a wrong sentence loses a user quietly.
|
|
8
|
+
*/
|
|
9
|
+
import { StepStatus } from './plan.js';
|
|
10
|
+
/**
|
|
11
|
+
* The standing ask, printed at the end of every `init`.
|
|
12
|
+
*
|
|
13
|
+
* Addressed to the agent as much as the human: whichever of them just ran this command is the one
|
|
14
|
+
* holding the experience, and neither has a Reticle tool surface to file through at this point.
|
|
15
|
+
*
|
|
16
|
+
* Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
|
|
17
|
+
* the workspace redirect is kept from asking twice.
|
|
18
|
+
*/
|
|
19
|
+
export declare const FEEDBACK_HINT: string;
|
|
20
|
+
/**
|
|
21
|
+
* The last two lines a user reads, and the order matters.
|
|
22
|
+
*
|
|
23
|
+
* This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
|
|
24
|
+
* asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
|
|
25
|
+
* an agent client reads its tool list when it STARTS and never re-reads it, so the session that
|
|
26
|
+
* just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
|
|
27
|
+
* instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
|
|
28
|
+
* failed.
|
|
29
|
+
*
|
|
30
|
+
* So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
|
|
31
|
+
* run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
|
|
32
|
+
*/
|
|
33
|
+
export declare function restartHint(mcpStatus: StepStatus | undefined,
|
|
34
|
+
/** The project's own dev command, when package.json named one. */
|
|
35
|
+
devCommand?: string): string;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last thing `init` prints, and the only part of it a user is asked to act on.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
|
|
5
|
+
* this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
|
|
6
|
+
* what to say afterwards. The two failure modes are different too — a wrong write breaks an
|
|
7
|
+
* install, a wrong sentence loses a user quietly.
|
|
8
|
+
*/
|
|
9
|
+
import { StepStatus } from './plan.js';
|
|
10
|
+
import { wasMcpRegistered } from './mcp-registered.js';
|
|
11
|
+
import { CLI } from './agent-rules.js';
|
|
12
|
+
/**
|
|
13
|
+
* The standing ask, printed at the end of every `init`.
|
|
14
|
+
*
|
|
15
|
+
* Addressed to the agent as much as the human: whichever of them just ran this command is the one
|
|
16
|
+
* holding the experience, and neither has a Reticle tool surface to file through at this point.
|
|
17
|
+
*
|
|
18
|
+
* Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
|
|
19
|
+
* the workspace redirect is kept from asking twice.
|
|
20
|
+
*/
|
|
21
|
+
export const FEEDBACK_HINT = 'Anything wrong, missing, or awkward — in this setup or in Reticle itself? Tell us; it is the ' +
|
|
22
|
+
'one thing that decides what gets fixed:\n' +
|
|
23
|
+
` ${CLI} feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened" (agents)\n` +
|
|
24
|
+
` ${CLI} feedback "what worked, what didn't" (humans)`;
|
|
25
|
+
/**
|
|
26
|
+
* What to say about the dev server.
|
|
27
|
+
*
|
|
28
|
+
* Names the project's own script when `package.json` has a recognisable one, and otherwise says the
|
|
29
|
+
* THING rather than guessing the command.
|
|
30
|
+
*
|
|
31
|
+
* The guesses that used to live here — `vite`, `next dev`, `astro dev` — were wrong often enough to
|
|
32
|
+
* be worth deleting. Measured on a real app: rowy has no `dev` script at all, so "Restart `vite`"
|
|
33
|
+
* sent the reader to work out what was meant instead of starting a server. Nobody needs the guess:
|
|
34
|
+
* an agent reading this can open package.json, and a human already knows how they start their own
|
|
35
|
+
* app. A wrong command costs more than a missing one.
|
|
36
|
+
*/
|
|
37
|
+
function devServerRestart(devCommand) {
|
|
38
|
+
return devCommand === undefined
|
|
39
|
+
? 'Restart your dev server'
|
|
40
|
+
: `Restart your dev server (\`${devCommand}\`)`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The last two lines a user reads, and the order matters.
|
|
44
|
+
*
|
|
45
|
+
* This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
|
|
46
|
+
* asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
|
|
47
|
+
* an agent client reads its tool list when it STARTS and never re-reads it, so the session that
|
|
48
|
+
* just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
|
|
49
|
+
* instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
|
|
50
|
+
* failed.
|
|
51
|
+
*
|
|
52
|
+
* So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
|
|
53
|
+
* run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
|
|
54
|
+
*/
|
|
55
|
+
export function restartHint(mcpStatus,
|
|
56
|
+
/** The project's own dev command, when package.json named one. */
|
|
57
|
+
devCommand) {
|
|
58
|
+
const dev = `${devServerRestart(devCommand)}.`;
|
|
59
|
+
// NAME THE COMMAND THAT PROVES IT, not one that merely asks.
|
|
60
|
+
//
|
|
61
|
+
// `init` writes files and stops; the install is not finished until an app carrying the SDK has
|
|
62
|
+
// actually dialled the daemon, and nothing here confirmed that. The field shape is unambiguous:
|
|
63
|
+
// people complete the agent half, never complete the app half, and keep a daemon running for
|
|
64
|
+
// weeks with nothing to drive — so this is the last instruction most of them read.
|
|
65
|
+
//
|
|
66
|
+
// "Ask your agent: List Reticle sessions" asks a question whose failure is a dead end. `reticle
|
|
67
|
+
// status` ANSWERS it: as of 2.7.0 it reports the session, or says why there is none — no app
|
|
68
|
+
// running, an app running that never dialled us, a tab that closed — with the fix for each.
|
|
69
|
+
// The one thing that works BEFORE the restart, so it goes before it.
|
|
70
|
+
//
|
|
71
|
+
// What comes after the restart, named as a JOB rather than as a capability.
|
|
72
|
+
//
|
|
73
|
+
// This used to point at a `reticle demo` command that opened its own browser and drove one
|
|
74
|
+
// control. It was cut: a demo command is a side show. The thing a user needs to see is THEIR app,
|
|
75
|
+
// instrumented in THEIR codebase, driven by THEIR agent through the tools they just installed —
|
|
76
|
+
// because that is the workflow they are deciding whether to adopt, and a bespoke demo path proves
|
|
77
|
+
// nothing about it.
|
|
78
|
+
//
|
|
79
|
+
// So the close hands the agent the sequence instead. It is deliberately one drive of one real
|
|
80
|
+
// flow ending in a verdict, because an install that ends in "the tools are available" is an
|
|
81
|
+
// install that ends in nothing.
|
|
82
|
+
const demo = 'Then drive one real flow of your app end to end and report the verdict — that is the install ' +
|
|
83
|
+
"proving itself, and it is the agent's job, not a separate command.";
|
|
84
|
+
const prove = 'Then run `npx @reticlehq/server status` — it confirms the app connected, or says exactly why ' +
|
|
85
|
+
'it has not.';
|
|
86
|
+
// Nothing registered this run (`--no-mcp`, or a client that needs a manual edit): a restart picks
|
|
87
|
+
// up nothing, so advice about one would be advice about something we did not do.
|
|
88
|
+
if (!wasMcpRegistered(mcpStatus))
|
|
89
|
+
return `${dev}\n${demo}\n${prove}`;
|
|
90
|
+
// ALREADY: the tools are reachable RIGHT NOW, and this is the branch that used to lie.
|
|
91
|
+
//
|
|
92
|
+
// `wasMcpRegistered` is true for both APPLY and ALREADY, which is correct for the funnel field it
|
|
93
|
+
// was written for and wrong here, because the two cases have opposite answers to "must anybody
|
|
94
|
+
// restart". Every second project on a machine, and every Claude Code plugin install, took the
|
|
95
|
+
// APPLY text: "restart your agent — the tools only appear after that". An agent reads this AFTER
|
|
96
|
+
// whatever its skill file said, and it describes the output in front of it, so it wins. The agent
|
|
97
|
+
// stops with the project wired and nothing driven, which is the exact failure the install path
|
|
98
|
+
// exists to prevent, caused by the install path itself.
|
|
99
|
+
if (mcpStatus === StepStatus.ALREADY) {
|
|
100
|
+
return (`${dev}\n` +
|
|
101
|
+
`${demo}\n` +
|
|
102
|
+
'The Reticle MCP server was already registered on this machine, so there is no restart to do ' +
|
|
103
|
+
'and the tools are available now.\n' +
|
|
104
|
+
`${prove}\n` +
|
|
105
|
+
'Once it shows a session, drive a flow — that is the install finished. Carry straight on; ' +
|
|
106
|
+
'nothing here is waiting on a human.');
|
|
107
|
+
}
|
|
108
|
+
return (`${dev}\n` +
|
|
109
|
+
`${demo}\n` +
|
|
110
|
+
'Then restart your agent so it picks up the new MCP server. On Claude Code and Codex, do NOT ' +
|
|
111
|
+
'ask a human to do it: `npx @reticlehq/server init --relaunch` prints the exact resume command ' +
|
|
112
|
+
'for THIS conversation — run that command and the restart continues the work on the other ' +
|
|
113
|
+
'side. Everywhere else it is a human step — reload the window in Cursor, or hit Start in ' +
|
|
114
|
+
'`.vscode/mcp.json` in VS Code — so name the one that applies and ask for that alone.\n' +
|
|
115
|
+
'The tools only appear after that: your agent read its server list before Reticle existed, ' +
|
|
116
|
+
'and no slash command re-reads it (`/mcp` manages servers already loaded, so it cannot pick ' +
|
|
117
|
+
'up a new one). This is once per machine — Reticle is registered globally, so every later ' +
|
|
118
|
+
'project starts with the tools already there.\n' +
|
|
119
|
+
`${prove}\n` +
|
|
120
|
+
'Once it shows a session, drive one flow and report the verdict — that is the install ' +
|
|
121
|
+
'finished. Do it now; nothing here is waiting on a human.');
|
|
122
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `command` and `args` strings the config declares for our server, or `[]` when it declares
|
|
3
|
+
* none — including when the shape was not one this scanner reads.
|
|
4
|
+
*
|
|
5
|
+
* Handles the three forms a Codex config actually takes: the standard table header, a dotted
|
|
6
|
+
* assignment (`mcp_servers.reticle = { … }`), and a key inside an `[mcp_servers]` table.
|
|
7
|
+
*/
|
|
8
|
+
export declare function codexServerTokens(existing: string | null, serversKey: string): string[];
|
|
9
|
+
/** Does this config already declare a Reticle server under our name? */
|
|
10
|
+
export declare function codexDeclaresOurServer(existing: string | null, serversKey: string): boolean;
|