@reticlehq/init 3.6.0 → 3.7.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/detect/dev-script.d.ts +17 -0
- package/dist/detect/dev-script.js +128 -0
- package/dist/detect/workspace-apps.d.ts +11 -0
- package/dist/detect/workspace-apps.js +63 -0
- package/dist/detect/workspace-redirect.js +49 -5
- package/dist/diagnose/init-failure.d.ts +2 -0
- package/dist/diagnose/init-failure.js +2 -0
- package/dist/gather-plan-input.d.ts +11 -0
- package/dist/gather-plan-input.js +416 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -2
- package/dist/node-io.d.ts +1 -1
- package/dist/node-io.js +11 -2
- package/dist/patch/next-patch.d.ts +28 -0
- package/dist/patch/next-patch.js +66 -0
- package/dist/patch/nuxt-snippets.js +1 -1
- package/dist/patch/snippets.d.ts +1 -1
- package/dist/patch/snippets.js +2 -2
- package/dist/patch/static-page.d.ts +1 -0
- package/dist/patch/static-page.js +3 -3
- package/dist/plan/connect-steps.d.ts +1 -0
- package/dist/plan/connect-steps.js +5 -0
- package/dist/plan/framework-adapter.js +2 -2
- package/dist/plan/plan-angular.js +1 -1
- package/dist/plan/plan-framework.d.ts +1 -1
- package/dist/plan/plan-framework.js +47 -13
- package/dist/plan/plan-types.d.ts +23 -2
- package/dist/plan/plan-vite.js +15 -0
- package/dist/plan/plan.js +68 -10
- package/dist/plan/stop-hook-step.d.ts +19 -0
- package/dist/plan/stop-hook-step.js +74 -1
- package/dist/project/agent-rules.d.ts +1 -1
- package/dist/project/agent-rules.js +16 -11
- package/dist/register/codex-toml.d.ts +12 -0
- package/dist/register/codex-toml.js +44 -0
- package/dist/register/mcp.d.ts +15 -2
- package/dist/register/mcp.js +16 -0
- package/dist/run-types.d.ts +6 -0
- package/dist/run.d.ts +1 -1
- package/dist/run.js +11 -389
- package/package.json +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { InitIo } from '../run-types.js';
|
|
1
2
|
/**
|
|
2
3
|
* Which command starts this project, and should we start it ourselves?
|
|
3
4
|
*
|
|
@@ -72,4 +73,20 @@ export declare function desktopLaunch(pkg: unknown): DesktopLaunch | undefined;
|
|
|
72
73
|
export declare function devCommandFrom(pkg: unknown, packageManagerCommand: string): string | undefined;
|
|
73
74
|
/** The dev script's own command line (`react-router dev`, `vite`), or undefined when there is none. */
|
|
74
75
|
export declare function devScriptBody(pkg: unknown): string | undefined;
|
|
76
|
+
interface ViteConfigDiscovery {
|
|
77
|
+
config: {
|
|
78
|
+
path: string;
|
|
79
|
+
source: string;
|
|
80
|
+
} | null;
|
|
81
|
+
/** Paths that may own Vite but cannot safely be selected automatically. */
|
|
82
|
+
candidates: readonly string[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Find the config that a plain-Vite dev script actually loads.
|
|
86
|
+
*
|
|
87
|
+
* An explicit `--config` is authoritative. A direct `vite` command without one keeps Vite's own
|
|
88
|
+
* root lookup. A custom server with no root config but nested ones is ambiguous, so it returns
|
|
89
|
+
* evidence for a precise MANUAL step instead of creating a root config the server may never read.
|
|
90
|
+
*/
|
|
91
|
+
export declare function discoverPlainViteConfig(io: InitIo, pkg: unknown, rootFiles: ReadonlySet<string>, configNames: readonly string[]): ViteConfigDiscovery;
|
|
75
92
|
export {};
|
|
@@ -137,3 +137,131 @@ export function devScriptBody(pkg) {
|
|
|
137
137
|
const body = name === undefined ? undefined : table[name];
|
|
138
138
|
return 'string' === typeof body ? body : undefined;
|
|
139
139
|
}
|
|
140
|
+
const SHELL_TOKEN = /"(?:\\.|[^"\\])*"|'[^']*'|&&|\|\||[;|]|[^\s;&|]+/g;
|
|
141
|
+
const SHELL_SEPARATORS = new Set(['&&', '||', ';', '|']);
|
|
142
|
+
const ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
143
|
+
const MAX_CONFIG_DEPTH = 4;
|
|
144
|
+
const MAX_CONFIG_CANDIDATES = 20;
|
|
145
|
+
const SKIP_CONFIG_DIRS = new Set([
|
|
146
|
+
'node_modules',
|
|
147
|
+
'dist',
|
|
148
|
+
'out',
|
|
149
|
+
'coverage',
|
|
150
|
+
'target',
|
|
151
|
+
'.git',
|
|
152
|
+
'.next',
|
|
153
|
+
'.nuxt',
|
|
154
|
+
'.svelte-kit',
|
|
155
|
+
]);
|
|
156
|
+
function unquote(token) {
|
|
157
|
+
if (token.startsWith("'") && token.endsWith("'"))
|
|
158
|
+
return token.slice(1, -1);
|
|
159
|
+
if (token.startsWith('"') && token.endsWith('"')) {
|
|
160
|
+
return token.slice(1, -1).replace(/\\(["\\])/g, '$1');
|
|
161
|
+
}
|
|
162
|
+
return token;
|
|
163
|
+
}
|
|
164
|
+
function safeRelativePath(value) {
|
|
165
|
+
const normal = unquote(value).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
166
|
+
if ('' === normal ||
|
|
167
|
+
normal.startsWith('/') ||
|
|
168
|
+
normal.startsWith('~') ||
|
|
169
|
+
/^[A-Za-z]:\//.test(normal) ||
|
|
170
|
+
normal.split('/').includes('..') ||
|
|
171
|
+
/[\0$`]/.test(normal)) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return normal;
|
|
175
|
+
}
|
|
176
|
+
function isViteCommand(token) {
|
|
177
|
+
const normal = token.replace(/\\/g, '/');
|
|
178
|
+
return 'vite' === normal || 'vite.cmd' === normal || normal.endsWith('/node_modules/.bin/vite');
|
|
179
|
+
}
|
|
180
|
+
/** The direct Vite invocation in the selected dev script, if that script has one. */
|
|
181
|
+
function viteInvocation(script) {
|
|
182
|
+
const tokens = script.match(SHELL_TOKEN) ?? [];
|
|
183
|
+
let start = 0;
|
|
184
|
+
while (start < tokens.length) {
|
|
185
|
+
let end = start;
|
|
186
|
+
while (end < tokens.length && !SHELL_SEPARATORS.has(tokens[end] ?? ''))
|
|
187
|
+
end += 1;
|
|
188
|
+
const segment = tokens.slice(start, end).map(unquote);
|
|
189
|
+
let command = 0;
|
|
190
|
+
while (command < segment.length && ENV_ASSIGNMENT.test(segment[command] ?? ''))
|
|
191
|
+
command += 1;
|
|
192
|
+
if (isViteCommand(segment[command] ?? '')) {
|
|
193
|
+
const args = segment.slice(command + 1);
|
|
194
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
195
|
+
const arg = args[i] ?? '';
|
|
196
|
+
const inline = /^(?:--config|-c)=(.*)$/.exec(arg);
|
|
197
|
+
const raw = inline?.[1] ?? ('--config' === arg || '-c' === arg ? args[i + 1] : undefined);
|
|
198
|
+
if (raw !== undefined) {
|
|
199
|
+
return { configArgument: raw, configPath: safeRelativePath(raw) };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { configArgument: null, configPath: null };
|
|
203
|
+
}
|
|
204
|
+
start = end + 1;
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function nestedViteConfigs(io, configNames) {
|
|
209
|
+
const found = [];
|
|
210
|
+
const walk = (dir, depth) => {
|
|
211
|
+
if (found.length >= MAX_CONFIG_CANDIDATES || depth > MAX_CONFIG_DEPTH)
|
|
212
|
+
return;
|
|
213
|
+
for (const file of io.listFiles(dir)) {
|
|
214
|
+
if (configNames.includes(file))
|
|
215
|
+
found.push(`${dir}/${file}`);
|
|
216
|
+
if (found.length >= MAX_CONFIG_CANDIDATES)
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
for (const child of io.listDirs(dir)) {
|
|
220
|
+
if (found.length >= MAX_CONFIG_CANDIDATES)
|
|
221
|
+
return;
|
|
222
|
+
if (child.startsWith('.') || SKIP_CONFIG_DIRS.has(child))
|
|
223
|
+
continue;
|
|
224
|
+
walk(`${dir}/${child}`, depth + 1);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
for (const dir of io.listDirs('.')) {
|
|
228
|
+
if (found.length >= MAX_CONFIG_CANDIDATES)
|
|
229
|
+
break;
|
|
230
|
+
if (dir.startsWith('.') || SKIP_CONFIG_DIRS.has(dir))
|
|
231
|
+
continue;
|
|
232
|
+
walk(dir, 1);
|
|
233
|
+
}
|
|
234
|
+
return [...new Set(found)];
|
|
235
|
+
}
|
|
236
|
+
function readable(io, path) {
|
|
237
|
+
if (null === path)
|
|
238
|
+
return null;
|
|
239
|
+
const source = io.readFile(path);
|
|
240
|
+
return null === source ? null : { path, source };
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Find the config that a plain-Vite dev script actually loads.
|
|
244
|
+
*
|
|
245
|
+
* An explicit `--config` is authoritative. A direct `vite` command without one keeps Vite's own
|
|
246
|
+
* root lookup. A custom server with no root config but nested ones is ambiguous, so it returns
|
|
247
|
+
* evidence for a precise MANUAL step instead of creating a root config the server may never read.
|
|
248
|
+
*/
|
|
249
|
+
export function discoverPlainViteConfig(io, pkg, rootFiles, configNames) {
|
|
250
|
+
const rootPath = configNames.find((path) => rootFiles.has(path)) ?? null;
|
|
251
|
+
const script = devScriptBody(pkg);
|
|
252
|
+
const invocation = script === undefined ? undefined : viteInvocation(script);
|
|
253
|
+
if (invocation !== undefined && invocation.configArgument !== null) {
|
|
254
|
+
const config = readable(io, invocation.configPath);
|
|
255
|
+
return {
|
|
256
|
+
config,
|
|
257
|
+
candidates: null === config ? [invocation.configArgument] : [],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (invocation !== undefined)
|
|
261
|
+
return { config: readable(io, rootPath), candidates: [] };
|
|
262
|
+
const nested = script === undefined ? [] : nestedViteConfigs(io, configNames);
|
|
263
|
+
if (null === rootPath && nested.length > 0) {
|
|
264
|
+
return { config: null, candidates: nested };
|
|
265
|
+
}
|
|
266
|
+
return { config: readable(io, rootPath), candidates: [] };
|
|
267
|
+
}
|
|
@@ -40,6 +40,15 @@ export declare function isWebApp(dir: string, io: Pick<InitIo, 'exists' | 'readF
|
|
|
40
40
|
* it knows it is in a workspace; this is the matching walk DOWN.
|
|
41
41
|
*/
|
|
42
42
|
export declare function findWorkspaceApps(io: Pick<InitIo, 'exists' | 'readFile' | 'listDirs'>): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Apps anywhere below a folder that is not one, for `init` run above its app with no workspace to
|
|
45
|
+
* say where the app is: a backend + frontend repo, a home folder, a docs site in `site/`.
|
|
46
|
+
*
|
|
47
|
+
* Breadth first and bounded (depth and folders read), never into dependencies, build output or
|
|
48
|
+
* hidden folders, and never below an app it found. Apps with a page win; a bare `index.html` with
|
|
49
|
+
* no package.json counts only when there is none, as the static page it is.
|
|
50
|
+
*/
|
|
51
|
+
export declare function findAppsBelow(io: Pick<InitIo, 'exists' | 'readFile' | 'listDirs'>): string[];
|
|
43
52
|
/**
|
|
44
53
|
* Every package directory the workspace declares or holds, app-shaped or not.
|
|
45
54
|
*
|
|
@@ -61,4 +70,6 @@ export declare function workspacePackageDirs(io: Pick<InitIo, 'readFile' | 'list
|
|
|
61
70
|
* config into a stranger's root.
|
|
62
71
|
*/
|
|
63
72
|
export declare function enclosingWorkspaceRoot(cwd: string, io: Pick<InitIo, 'readFile'>): string | undefined;
|
|
73
|
+
/** The apps of the workspace at `root`, read through an io standing somewhere else in it. */
|
|
74
|
+
export declare function appsAt(root: string, io: Pick<InitIo, 'exists' | 'readFile' | 'listDirs'>): string[];
|
|
64
75
|
export {};
|
|
@@ -237,6 +237,61 @@ export function findWorkspaceApps(io) {
|
|
|
237
237
|
// app is exactly that — so it is demoted, never dropped.
|
|
238
238
|
return [...new Set(found.length > 0 ? found : served)];
|
|
239
239
|
}
|
|
240
|
+
/** Folders the search for an app never enters: dependencies, output, and a home folder's own. */
|
|
241
|
+
const NEVER_SEARCHED = new Set([
|
|
242
|
+
...NEVER_A_PACKAGE,
|
|
243
|
+
'vendor',
|
|
244
|
+
'venv',
|
|
245
|
+
'__pycache__',
|
|
246
|
+
'Library',
|
|
247
|
+
'Applications',
|
|
248
|
+
'AppData',
|
|
249
|
+
'Pictures',
|
|
250
|
+
'Music',
|
|
251
|
+
'Movies',
|
|
252
|
+
]);
|
|
253
|
+
/** How deep the search goes: `client/web/app` is three, and nothing real is deeper than four. */
|
|
254
|
+
const SEARCH_DEPTH = 4;
|
|
255
|
+
/** Folders read before the search gives up, so a home folder costs a fraction of a second. */
|
|
256
|
+
const SEARCH_BUDGET = 600;
|
|
257
|
+
/**
|
|
258
|
+
* Apps anywhere below a folder that is not one, for `init` run above its app with no workspace to
|
|
259
|
+
* say where the app is: a backend + frontend repo, a home folder, a docs site in `site/`.
|
|
260
|
+
*
|
|
261
|
+
* Breadth first and bounded (depth and folders read), never into dependencies, build output or
|
|
262
|
+
* hidden folders, and never below an app it found. Apps with a page win; a bare `index.html` with
|
|
263
|
+
* no package.json counts only when there is none, as the static page it is.
|
|
264
|
+
*/
|
|
265
|
+
export function findAppsBelow(io) {
|
|
266
|
+
const apps = [];
|
|
267
|
+
const pages = [];
|
|
268
|
+
let level = io.listDirs('.').filter(searchable);
|
|
269
|
+
let read = 0;
|
|
270
|
+
for (let depth = 1; depth <= SEARCH_DEPTH && level.length > 0 && read < SEARCH_BUDGET; depth++) {
|
|
271
|
+
const next = [];
|
|
272
|
+
for (const dir of level) {
|
|
273
|
+
if (++read > SEARCH_BUDGET)
|
|
274
|
+
break;
|
|
275
|
+
if (AppSignal.UI === appSignal(dir, io)) {
|
|
276
|
+
apps.push(dir);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (!io.exists(`${dir}/${PACKAGE_JSON}`) && io.exists(`${dir}/index.html`)) {
|
|
280
|
+
pages.push(dir);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
next.push(...io
|
|
284
|
+
.listDirs(dir)
|
|
285
|
+
.filter(searchable)
|
|
286
|
+
.map((name) => `${dir}/${name}`));
|
|
287
|
+
}
|
|
288
|
+
level = next;
|
|
289
|
+
}
|
|
290
|
+
return apps.length > 0 ? apps : pages;
|
|
291
|
+
}
|
|
292
|
+
function searchable(name) {
|
|
293
|
+
return !name.startsWith('.') && !NEVER_SEARCHED.has(name);
|
|
294
|
+
}
|
|
240
295
|
/**
|
|
241
296
|
* Every package directory the workspace declares or holds, app-shaped or not.
|
|
242
297
|
*
|
|
@@ -290,3 +345,11 @@ export function enclosingWorkspaceRoot(cwd, io) {
|
|
|
290
345
|
}
|
|
291
346
|
return undefined;
|
|
292
347
|
}
|
|
348
|
+
/** The apps of the workspace at `root`, read through an io standing somewhere else in it. */
|
|
349
|
+
export function appsAt(root, io) {
|
|
350
|
+
return findWorkspaceApps({
|
|
351
|
+
exists: (p) => io.exists(join(root, p)),
|
|
352
|
+
readFile: (p) => io.readFile(join(root, p)),
|
|
353
|
+
listDirs: (p) => io.listDirs(join(root, p)),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { detect, Framework } from './detect.js';
|
|
13
13
|
import { detectNonJsEcosystem, noPackageJsonMessage } from './non-js-project.js';
|
|
14
|
-
import { findWorkspaceApps, PACKAGE_JSON, workspacePackageDirs } from './workspace-apps.js';
|
|
14
|
+
import { findAppsBelow, findWorkspaceApps, PACKAGE_JSON, workspacePackageDirs, } from './workspace-apps.js';
|
|
15
15
|
import { projectIdOf, RETICLE_CONFIG_FILE } from './existing-config.js';
|
|
16
16
|
import { chooseWorkspaceApp, withoutTrailingSlashes } from './declared/app-choice.js';
|
|
17
17
|
const ROOT_CONFIG_LEAD = `This root's ${RETICLE_CONFIG_FILE} names`;
|
|
@@ -30,7 +30,28 @@ function appNamedByRootConfig(io) {
|
|
|
30
30
|
return 1 === named.length ? named[0] : undefined;
|
|
31
31
|
}
|
|
32
32
|
const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle init` for the one you want:';
|
|
33
|
+
const BELOW_HEADER = 'No app in this folder. Several below it; re-run `reticle init` for the one you want:';
|
|
34
|
+
const HOME_HEADER = 'This is your home folder, so init wires nothing on its own here. Apps found below it; run one:';
|
|
35
|
+
const STATIC_PAGE = 'index.html';
|
|
36
|
+
/** The folder `init` was run in is the person's home: a place to find apps, never to wire one. */
|
|
37
|
+
function inHome(io) {
|
|
38
|
+
const trim = (p) => withoutTrailingSlashes(p.replace(/\\/g, '/'));
|
|
39
|
+
return trim(io.cwd()) === trim(io.homeDir());
|
|
40
|
+
}
|
|
41
|
+
/** Print the candidates as commands to paste, and stop: the choice is the person's. */
|
|
42
|
+
function listApps(io, header, apps) {
|
|
43
|
+
io.print(header);
|
|
44
|
+
for (const a of apps) {
|
|
45
|
+
const displayed = displayedWorkspaceAppCommand(a, process.platform);
|
|
46
|
+
if (displayed.label !== undefined)
|
|
47
|
+
io.print(` ${displayed.label}`);
|
|
48
|
+
io.print(` ${displayed.command}`);
|
|
49
|
+
}
|
|
50
|
+
return { ok: false, applied: 0, manual: apps.length, listedAppsBelow: true };
|
|
51
|
+
}
|
|
33
52
|
const WORKSPACE_APP_COMMAND = 'reticle init --app';
|
|
53
|
+
const ALREADY_WIRED = 'Already wired here:';
|
|
54
|
+
const WIRE_ANOTHER = 'To wire another app:';
|
|
34
55
|
const POWERSHELL_ONLY_LABEL = 'PowerShell only:';
|
|
35
56
|
const PLAIN_SHELL_ARG = /^[A-Za-z0-9_./-]+$/;
|
|
36
57
|
function posixShellArg(arg) {
|
|
@@ -88,7 +109,8 @@ export function redirectToWorkspaceApp(options, io, pkg, runInit) {
|
|
|
88
109
|
const named = options.app === undefined || '' === options.app ? undefined : options.app;
|
|
89
110
|
if (named !== undefined) {
|
|
90
111
|
const wanted = withoutTrailingSlashes(named);
|
|
91
|
-
|
|
112
|
+
// A plain page is an app too: `init` wires its index.html once it is standing in that folder.
|
|
113
|
+
if (io.exists(`${wanted}/${PACKAGE_JSON}`) || io.exists(`${wanted}/${STATIC_PAGE}`))
|
|
92
114
|
return enterApp(options, io, wanted, 'Wiring', runInit);
|
|
93
115
|
const inWanted = (file) => io.exists(`${wanted}/${file}`);
|
|
94
116
|
io.print(detectNonJsEcosystem(inWanted) === undefined
|
|
@@ -113,18 +135,39 @@ export function redirectToWorkspaceApp(options, io, pkg, runInit) {
|
|
|
113
135
|
io.print(chosen.message);
|
|
114
136
|
return { ok: false, applied: 0, manual: 1 };
|
|
115
137
|
}
|
|
138
|
+
// A home folder holds every project the person has; picking one for them is a guess.
|
|
139
|
+
if (chosen.app === undefined && inHome(io)) {
|
|
140
|
+
const found = 0 < apps.length ? apps : findAppsBelow(io);
|
|
141
|
+
return 0 < found.length ? listApps(io, HOME_HEADER, found) : null;
|
|
142
|
+
}
|
|
143
|
+
// No app one or two levels down and no manifest here: look deeper before giving up, so a repo
|
|
144
|
+
// whose web app sits at `client/web/app`, or a bare page in `site/`, is wired rather than refused.
|
|
145
|
+
if (0 === apps.length && !io.exists(PACKAGE_JSON) && chosen.app === undefined) {
|
|
146
|
+
const below = findAppsBelow(io);
|
|
147
|
+
if (1 === below.length && below[0] !== undefined)
|
|
148
|
+
return enterApp(options, io, below[0], 'No app in this directory — wiring', runInit);
|
|
149
|
+
if (1 < below.length)
|
|
150
|
+
return listApps(io, BELOW_HEADER, below);
|
|
151
|
+
}
|
|
116
152
|
const target = chosen.app ?? (1 === apps.length ? apps[0] : undefined);
|
|
117
153
|
if (target === undefined) {
|
|
118
154
|
if (0 === apps.length)
|
|
119
155
|
return null; // not a workspace — fall through to the normal HTML plan
|
|
120
|
-
|
|
121
|
-
|
|
156
|
+
// Apps already wired answer a re-run here: nothing to do at the root, which is pinned to none.
|
|
157
|
+
const wired = apps.filter((a) => io.exists(`${a}/${RETICLE_CONFIG_FILE}`));
|
|
158
|
+
io.print(0 === wired.length ? AMBIGUOUS_HEADER : `${ALREADY_WIRED} ${wired.join(', ')}.`);
|
|
159
|
+
const rest = apps.filter((a) => !wired.includes(a));
|
|
160
|
+
if (0 < wired.length && 0 < rest.length)
|
|
161
|
+
io.print(WIRE_ANOTHER);
|
|
162
|
+
for (const a of 0 === wired.length ? apps : rest) {
|
|
122
163
|
const displayed = displayedWorkspaceAppCommand(a, process.platform);
|
|
123
164
|
if (displayed.label !== undefined)
|
|
124
165
|
io.print(` ${displayed.label}`);
|
|
125
166
|
io.print(` ${displayed.command}`);
|
|
126
167
|
}
|
|
127
|
-
return
|
|
168
|
+
return 0 === wired.length
|
|
169
|
+
? { ok: false, applied: 0, manual: apps.length }
|
|
170
|
+
: { ok: true, applied: 0, manual: 0 };
|
|
128
171
|
}
|
|
129
172
|
return enterApp(options, io, target, 'No app in this directory — wiring', runInit);
|
|
130
173
|
}
|
|
@@ -146,5 +189,6 @@ function enterApp(options, io, target, lead, runInit) {
|
|
|
146
189
|
// Where the human is STANDING, kept across the redirect: their agent session runs here, so
|
|
147
190
|
// this is the only place a `/reticle` command file can be found by it.
|
|
148
191
|
agentRoot: options.agentRoot ?? options.cwd,
|
|
192
|
+
severalApps: true === options.severalApps || 1 < findWorkspaceApps(io).length,
|
|
149
193
|
}, io.scoped(target));
|
|
150
194
|
}
|
|
@@ -20,6 +20,8 @@ export declare const InitFailure: {
|
|
|
20
20
|
readonly APP_CONNECTION: "app_connection";
|
|
21
21
|
readonly BRIDGE_OCCUPIED: "bridge_occupied";
|
|
22
22
|
readonly RUNTIME_ERROR: "runtime_error";
|
|
23
|
+
/** Ctrl-C (or a SIGTERM) during the runtime phase, before it could say how setup went. */
|
|
24
|
+
readonly INTERRUPTED: "interrupted";
|
|
23
25
|
readonly OTHER: "other";
|
|
24
26
|
};
|
|
25
27
|
export type InitFailure = (typeof InitFailure)[keyof typeof InitFailure];
|
|
@@ -20,5 +20,7 @@ export const InitFailure = {
|
|
|
20
20
|
APP_CONNECTION: 'app_connection',
|
|
21
21
|
BRIDGE_OCCUPIED: 'bridge_occupied',
|
|
22
22
|
RUNTIME_ERROR: 'runtime_error',
|
|
23
|
+
/** Ctrl-C (or a SIGTERM) during the runtime phase, before it could say how setup went. */
|
|
24
|
+
INTERRUPTED: 'interrupted',
|
|
23
25
|
OTHER: 'other',
|
|
24
26
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { InitOptions, InitIo } from './run-types.js';
|
|
2
|
+
import { type PlanInput } from './plan/plan.js';
|
|
3
|
+
/**
|
|
4
|
+
* The directory the human's agent runs in, when it is not the app's directory.
|
|
5
|
+
*
|
|
6
|
+
* `undefined` for a single-package repo, which keeps every agent-file path project-relative exactly
|
|
7
|
+
* as it was — the two roots are the same there, which is why writing them beside the app went
|
|
8
|
+
* unnoticed until a repo with its app at `src/admin` reported it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function agentRootOf(options: InitOptions): string | undefined;
|
|
11
|
+
export declare function gatherPlanInput(options: InitOptions, io: InitIo, pkg: unknown): PlanInput;
|