ptbk 0.119.0-21 → 0.119.0-26
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/README.md +16 -9
- package/dist/browser/browser.d.ts +50 -36
- package/dist/browser/browser.js +162 -118
- package/dist/browser/browser.js.map +1 -1
- package/dist/browser/fleet.d.ts +59 -0
- package/dist/browser/fleet.js +148 -0
- package/dist/browser/fleet.js.map +1 -0
- package/dist/browser/recording.js +2 -2
- package/dist/browser/recording.js.map +1 -1
- package/dist/chats/conversation.d.ts +1 -1
- package/dist/chats/conversation.js +8 -6
- package/dist/chats/conversation.js.map +1 -1
- package/dist/chats/local.js +5 -5
- package/dist/chats/local.js.map +1 -1
- package/dist/cli/commands/add.js +1 -1
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/boilerplates.js +1 -1
- package/dist/cli/commands/boilerplates.js.map +1 -1
- package/dist/cli/commands/find.js +1 -1
- package/dist/cli/commands/find.js.map +1 -1
- package/dist/cli/commands/fix.js +9 -4
- package/dist/cli/commands/fix.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.js +279 -118
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/migrate.js +1 -1
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/ping.js +20 -12
- package/dist/cli/commands/ping.js.map +1 -1
- package/dist/cli/commands/plan.js +20 -12
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/recover.js +1 -1
- package/dist/cli/commands/recover.js.map +1 -1
- package/dist/cli/commands/restart.js +6 -5
- package/dist/cli/commands/restart.js.map +1 -1
- package/dist/cli/commands/run.js +26 -15
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/start.js +11 -7
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/tool.d.ts +19 -0
- package/dist/cli/commands/tool.js +320 -0
- package/dist/cli/commands/tool.js.map +1 -0
- package/dist/cli/commands/verify.js +1 -1
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/common.d.ts +7 -1
- package/dist/cli/common.js +37 -20
- package/dist/cli/common.js.map +1 -1
- package/dist/cli/compatibility.d.ts +8 -0
- package/dist/cli/compatibility.js +32 -0
- package/dist/cli/compatibility.js.map +1 -0
- package/dist/cli/parser.js +7 -3
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/registry.js +182 -7
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/service-view.js +1 -1
- package/dist/cli/service-view.js.map +1 -1
- package/dist/cli/sources.js +2 -0
- package/dist/cli/sources.js.map +1 -1
- package/dist/cli/spec.js +8 -6
- package/dist/cli/spec.js.map +1 -1
- package/dist/common/naming.d.ts +53 -0
- package/dist/common/naming.js +90 -0
- package/dist/common/naming.js.map +1 -0
- package/dist/engine/agenda.d.ts +70 -9
- package/dist/engine/agenda.js +294 -61
- package/dist/engine/agenda.js.map +1 -1
- package/dist/engine/executor.d.ts +5 -0
- package/dist/engine/executor.js +24 -11
- package/dist/engine/executor.js.map +1 -1
- package/dist/engine/harnesses.d.ts +82 -18
- package/dist/engine/harnesses.js +191 -39
- package/dist/engine/harnesses.js.map +1 -1
- package/dist/engine/preview.js +2 -2
- package/dist/engine/preview.js.map +1 -1
- package/dist/engine/routing.d.ts +10 -0
- package/dist/engine/routing.js +67 -28
- package/dist/engine/routing.js.map +1 -1
- package/dist/engine/snapshot.d.ts +107 -7
- package/dist/engine/state.d.ts +4 -2
- package/dist/engine/state.js.map +1 -1
- package/dist/engine/sync.d.ts +96 -0
- package/dist/engine/sync.js +199 -0
- package/dist/engine/sync.js.map +1 -0
- package/dist/engine/team.d.ts +1 -0
- package/dist/engine/team.js +12 -8
- package/dist/engine/team.js.map +1 -1
- package/dist/expert/_main.md +96 -67
- package/dist/expert/agents/_index.md +10 -7
- package/dist/expert/agents/core-agents.md +4 -2
- package/dist/expert/agents/fixer.md +15 -12
- package/dist/expert/cli/acceptance.md +96 -78
- package/dist/expert/cli/api.md +26 -14
- package/dist/expert/cli/autonomous-operation.md +17 -19
- package/dist/expert/cli/browser.md +24 -24
- package/dist/expert/cli/checks.md +18 -10
- package/dist/expert/cli/cli.md +48 -41
- package/dist/expert/cli/configuration-migrations.md +43 -0
- package/dist/expert/cli/configuration.md +24 -14
- package/dist/expert/cli/controls.md +23 -11
- package/dist/expert/cli/dashboard-details.md +28 -10
- package/dist/expert/cli/dashboard-layout.md +15 -15
- package/dist/expert/cli/dashboard.md +8 -10
- package/dist/expert/cli/environment.md +33 -0
- package/dist/expert/cli/execution.md +10 -8
- package/dist/expert/cli/git-persistence.md +27 -21
- package/dist/expert/cli/git-preflight.md +8 -4
- package/dist/expert/cli/git-synchronization.md +32 -6
- package/dist/expert/cli/harness-plugins.md +12 -12
- package/dist/expert/cli/harnesses.md +22 -16
- package/dist/expert/cli/initialization-ui.md +14 -14
- package/dist/expert/cli/initialization.md +31 -21
- package/dist/expert/cli/installation.md +6 -0
- package/dist/expert/cli/isolation.md +5 -3
- package/dist/expert/cli/releasing.md +17 -3
- package/dist/expert/cli/retries.md +10 -8
- package/dist/expert/cli/run.md +7 -5
- package/dist/expert/cli/start.md +21 -15
- package/dist/expert/cli/status-view.md +15 -17
- package/dist/expert/cli/synchronization-setup.md +24 -0
- package/dist/expert/cli/terminal.md +14 -16
- package/dist/expert/cli/tool-management.md +60 -0
- package/dist/expert/cli/tool-runtime.md +35 -0
- package/dist/expert/cli/tools-migration.md +50 -0
- package/dist/expert/cli/tools.md +73 -0
- package/dist/expert/cli/trace-configuration.md +9 -11
- package/dist/expert/cli/updates.md +7 -7
- package/dist/expert/cli/windows-installer.md +67 -0
- package/dist/expert/cli/workspace.md +2 -0
- package/dist/expert/coding-standards/_index.md +11 -0
- package/dist/expert/coding-standards/naming.md +30 -0
- package/dist/expert/coding-standards/shared-logic.md +27 -0
- package/dist/expert/concepts/automatic-repair.md +33 -0
- package/dist/expert/concepts/engine-and-project.md +7 -5
- package/dist/expert/dictionary.md +18 -18
- package/dist/harness/plugins/claude-code/index.js +4 -2
- package/dist/harness/plugins/claude-code/index.js.map +1 -1
- package/dist/harness/plugins/cline/index.js +1 -0
- package/dist/harness/plugins/cline/index.js.map +1 -1
- package/dist/harness/plugins/gemini/index.js +2 -0
- package/dist/harness/plugins/gemini/index.js.map +1 -1
- package/dist/harness/plugins/github-copilot/index.js +1 -0
- package/dist/harness/plugins/github-copilot/index.js.map +1 -1
- package/dist/harness/plugins/openai-codex/index.js +13 -9
- package/dist/harness/plugins/openai-codex/index.js.map +1 -1
- package/dist/harness/plugins/opencode/index.js +1 -0
- package/dist/harness/plugins/opencode/index.js.map +1 -1
- package/dist/harness/plugins/qwen-code/index.js +1 -0
- package/dist/harness/plugins/qwen-code/index.js.map +1 -1
- package/dist/harness/types.d.ts +16 -5
- package/dist/project/config.d.ts +135 -48
- package/dist/project/config.js +331 -119
- package/dist/project/config.js.map +1 -1
- package/dist/project/configuration.d.ts +43 -0
- package/dist/project/configuration.js +165 -0
- package/dist/project/configuration.js.map +1 -0
- package/dist/project/defaults.d.ts +4 -2
- package/dist/project/defaults.js +10 -5
- package/dist/project/defaults.js.map +1 -1
- package/dist/project/git.d.ts +28 -0
- package/dist/project/git.js +81 -0
- package/dist/project/git.js.map +1 -1
- package/dist/project/migrations.d.ts +96 -0
- package/dist/project/migrations.js +301 -0
- package/dist/project/migrations.js.map +1 -0
- package/dist/project/publication.d.ts +57 -0
- package/dist/project/publication.js +117 -0
- package/dist/project/publication.js.map +1 -0
- package/dist/project/readiness.d.ts +14 -5
- package/dist/project/readiness.js +23 -22
- package/dist/project/readiness.js.map +1 -1
- package/dist/project/setup.js +2 -0
- package/dist/project/setup.js.map +1 -1
- package/dist/service/actions.d.ts +12 -4
- package/dist/service/actions.js +32 -2
- package/dist/service/actions.js.map +1 -1
- package/dist/service/control.d.ts +1 -1
- package/dist/service/control.js +1 -1
- package/dist/service/host.d.ts +5 -2
- package/dist/service/host.js +26 -16
- package/dist/service/host.js.map +1 -1
- package/dist/service/http.js +7 -2
- package/dist/service/http.js.map +1 -1
- package/dist/service/launcher.d.ts +2 -1
- package/dist/service/launcher.js +1 -0
- package/dist/service/launcher.js.map +1 -1
- package/dist/service/records.d.ts +3 -0
- package/dist/service/records.js +1 -1
- package/dist/service/records.js.map +1 -1
- package/dist/service/spa/app.css +39 -0
- package/dist/service/spa/app.js +242 -39
- package/dist/service/worker.js +1 -0
- package/dist/service/worker.js.map +1 -1
- package/dist/tools/environment.d.ts +122 -0
- package/dist/tools/environment.js +199 -0
- package/dist/tools/environment.js.map +1 -0
- package/dist/tools/model.d.ts +180 -0
- package/dist/tools/model.js +111 -0
- package/dist/tools/model.js.map +1 -0
- package/dist/tools/normalize.d.ts +51 -0
- package/dist/tools/normalize.js +321 -0
- package/dist/tools/normalize.js.map +1 -0
- package/dist/tools/operations.d.ts +198 -0
- package/dist/tools/operations.js +378 -0
- package/dist/tools/operations.js.map +1 -0
- package/dist/tools/readiness.d.ts +72 -0
- package/dist/tools/readiness.js +160 -0
- package/dist/tools/readiness.js.map +1 -0
- package/dist/ui/dashboard.d.ts +3 -0
- package/dist/ui/dashboard.js +173 -2
- package/dist/ui/dashboard.js.map +1 -1
- package/dist/ui/details.d.ts +35 -1
- package/dist/ui/details.js +103 -2
- package/dist/ui/details.js.map +1 -1
- package/dist/ui/secret.d.ts +8 -0
- package/dist/ui/secret.js +41 -0
- package/dist/ui/secret.js.map +1 -0
- package/dist/ui/status-view.d.ts +1 -0
- package/dist/ui/status-view.js +33 -8
- package/dist/ui/status-view.js.map +1 -1
- package/dist/ui/tool-forms.d.ts +24 -0
- package/dist/ui/tool-forms.js +133 -0
- package/dist/ui/tool-forms.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
2
|
+
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { EXIT, PtbkError, errorMessage, usageError } from '../../common/errors.js';
|
|
4
|
-
import { ensureDirectory, isDirectory, pathExists, toProjectRelative } from '../../common/fs.js';
|
|
4
|
+
import { ensureDirectory, isDirectory, pathExists, toProjectRelative, writeFileAtomic } from '../../common/fs.js';
|
|
5
|
+
import { slugify } from '../../common/ids.js';
|
|
5
6
|
import { ENGINE_VERSION } from '../../common/version.js';
|
|
6
|
-
import {
|
|
7
|
+
import { userChromeProfile } from '../../browser/browser.js';
|
|
7
8
|
import { BookIndex } from '../../project/books.js';
|
|
8
9
|
import { generatedName } from '../../tasks/identity.js';
|
|
9
|
-
import {
|
|
10
|
-
import { enabledHarnesses, getPath, loadSettings, readProjectConfigFile,
|
|
10
|
+
import { validateCheckCommand } from '../../project/checks.js';
|
|
11
|
+
import { CHECK_PLACEHOLDER, SETTING_SPEC_BY_KEY, canonicalizeConfig, configText, enabledHarnesses, getPath, interpretConfig, isCheckPlaceholder, loadSettings, profileFromSpelling, readProjectConfigFile, serializeSettingValue, setPath, setting } from '../../project/config.js';
|
|
12
|
+
import { maintainProjectConfiguration } from '../../project/configuration.js';
|
|
13
|
+
import { MIGRATED_BROWSER_NAME, planConfigurationMigration } from '../../project/migrations.js';
|
|
14
|
+
import { requireEngineUpdate } from '../compatibility.js';
|
|
15
|
+
import { existingOrigin, githubLogin, publishProject } from '../../project/publication.js';
|
|
16
|
+
import { instanceEnvironment } from '../../engine/harnesses.js';
|
|
17
|
+
import { redactUrl } from '../../engine/sync.js';
|
|
18
|
+
import { applyToolOperationsToConfig } from '../../tools/operations.js';
|
|
19
|
+
import { inspectBrowserTool } from '../../tools/readiness.js';
|
|
20
|
+
import { describeProfileSource, harnessVariantOf } from '../../tools/model.js';
|
|
11
21
|
import { INITIAL_ASSETS, INITIAL_SPECIFICATION, bootstrapTask, sampleReadme } from '../../project/defaults.js';
|
|
12
22
|
import { clearSetupDraft, readSetupDraft, saveSetupDraft } from '../../project/draft.js';
|
|
13
23
|
import { commitPaths, inspectGit } from '../../project/git.js';
|
|
@@ -64,7 +74,7 @@ const STEP_TITLES = {
|
|
|
64
74
|
harnesses: 'Harnesses',
|
|
65
75
|
browser: 'Project browser',
|
|
66
76
|
traces: 'Traces',
|
|
67
|
-
|
|
77
|
+
sync: 'Automatic synchronization',
|
|
68
78
|
finish: 'Set up and start',
|
|
69
79
|
};
|
|
70
80
|
/**
|
|
@@ -130,6 +140,18 @@ async function nearestExisting(path) {
|
|
|
130
140
|
function blankIntent(segments) {
|
|
131
141
|
return segments.every((segment) => segment.type === 'text' && !segment.text.trim());
|
|
132
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* The destination's saved configuration as this engine interprets it (a pending migration applied in memory only).
|
|
145
|
+
*/
|
|
146
|
+
async function savedConfiguration(root) {
|
|
147
|
+
return interpretConfig(await readProjectConfigFile(projectAt(root))).interpreted;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Whether a configuration already has a browser tool (the browser question is then not asked again).
|
|
151
|
+
*/
|
|
152
|
+
function hasBrowserTool(raw) {
|
|
153
|
+
return Array.isArray(raw.tools) && raw.tools.some((tool) => tool !== null && typeof tool === 'object' && String(tool.type).toUpperCase() === 'BROWSER');
|
|
154
|
+
}
|
|
133
155
|
/**
|
|
134
156
|
* Whether a destination already records its purpose (initial specification or an earlier initialization).
|
|
135
157
|
*/
|
|
@@ -160,8 +182,8 @@ export async function initializeProject(context, options = {}) {
|
|
|
160
182
|
const notes = [];
|
|
161
183
|
const missingDecisions = [];
|
|
162
184
|
const title = options.fromStart ? 'ptbk start · setup' : 'ptbk init';
|
|
163
|
-
const steps = new StepTracker(['intent', 'update', 'destination', 'installation', 'harnesses', 'browser', 'traces', '
|
|
164
|
-
const stepOrder = ['intent', 'update', 'destination', 'installation', 'harnesses', 'browser', 'traces', '
|
|
185
|
+
const steps = new StepTracker(['intent', 'update', 'destination', 'installation', 'harnesses', 'browser', 'traces', 'sync', 'finish']);
|
|
186
|
+
const stepOrder = ['intent', 'update', 'destination', 'installation', 'harnesses', 'browser', 'traces', 'sync', 'finish'];
|
|
165
187
|
const frame = (id, note, activity = 'working') => ({
|
|
166
188
|
title,
|
|
167
189
|
step: steps.label(id),
|
|
@@ -330,6 +352,9 @@ export async function initializeProject(context, options = {}) {
|
|
|
330
352
|
break;
|
|
331
353
|
}
|
|
332
354
|
answers.set('destination', `Destination: ${state.root}${state.nested ? ` (${state.nested.strategy} child)` : ''}`);
|
|
355
|
+
const compatibility = planConfigurationMigration((await readProjectConfigFile(projectAt(state.root))).raw);
|
|
356
|
+
if (compatibility.kind === 'older-engine')
|
|
357
|
+
await requireEngineUpdate(context, compatibility.saved);
|
|
333
358
|
await predictPath();
|
|
334
359
|
// A changed destination may already record its purpose.
|
|
335
360
|
if (!state.purposeRecorded && (await recordedPurpose(state.root))) {
|
|
@@ -423,10 +448,10 @@ export async function initializeProject(context, options = {}) {
|
|
|
423
448
|
},
|
|
424
449
|
// 6. Browser (independent of harness logins).
|
|
425
450
|
browser: async () => {
|
|
426
|
-
const
|
|
451
|
+
const saved = await savedConfiguration(state.root);
|
|
427
452
|
state.browserProfile = option(context, 'browser-profile') ?? state.browserProfile;
|
|
428
453
|
state.browserMode = option(context, 'browser-mode') ?? state.browserMode;
|
|
429
|
-
if (
|
|
454
|
+
if (hasBrowserTool(saved) || !interactive || option(context, 'browser-profile')) {
|
|
430
455
|
steps.set('browser', false);
|
|
431
456
|
return;
|
|
432
457
|
}
|
|
@@ -464,19 +489,20 @@ export async function initializeProject(context, options = {}) {
|
|
|
464
489
|
],
|
|
465
490
|
defaultValue: state.browserMode ?? 'headless',
|
|
466
491
|
}));
|
|
467
|
-
|
|
492
|
+
const profile = profileFromSpelling(state.browserProfile ?? 'global');
|
|
493
|
+
answers.set('browser', `Browser: ${describeProfileSource(profile.source, profile.path)}, ${state.browserMode}`);
|
|
468
494
|
},
|
|
469
495
|
// 7. Trace recording and Git inclusion (three explicit project choices).
|
|
470
496
|
traces: async () => {
|
|
471
|
-
const raw =
|
|
497
|
+
const raw = await savedConfiguration(state.root);
|
|
472
498
|
const saved = (key) => (typeof getPath(raw, key) === 'boolean' ? getPath(raw, key) : undefined);
|
|
473
499
|
const flagged = { commit: flag(context, 'commit-traces'), ignoreVideos: flag(context, 'ignore-trace-videos'), recordVideo: flag(context, 'record-video') };
|
|
474
500
|
const current = {
|
|
475
|
-
commit: saved('traces.
|
|
476
|
-
ignoreVideos: saved('traces.
|
|
477
|
-
recordVideo: saved('traces.
|
|
501
|
+
commit: saved('traces.isCommitted') ?? flagged.commit ?? state.traces?.commit ?? false,
|
|
502
|
+
ignoreVideos: saved('traces.isVideoIgnored') ?? flagged.ignoreVideos ?? state.traces?.ignoreVideos ?? true,
|
|
503
|
+
recordVideo: saved('traces.isVideoRecordingEnabled') ?? flagged.recordVideo ?? state.traces?.recordVideo ?? true,
|
|
478
504
|
};
|
|
479
|
-
const complete = saved('traces.
|
|
505
|
+
const complete = saved('traces.isCommitted') !== undefined && saved('traces.isVideoIgnored') !== undefined && saved('traces.isVideoRecordingEnabled') !== undefined;
|
|
480
506
|
const decided = Object.values(flagged).every((value) => value !== undefined);
|
|
481
507
|
if (complete || decided || !interactive) {
|
|
482
508
|
state.traces = current;
|
|
@@ -502,48 +528,118 @@ export async function initializeProject(context, options = {}) {
|
|
|
502
528
|
}));
|
|
503
529
|
answers.set('traces', `Traces: ${state.traces.commit ? 'committed' : 'local only'}, videos ${state.traces.recordVideo ? `recorded${state.traces.commit && !state.traces.ignoreVideos ? ' and committed' : ' locally'}` : 'off'}`);
|
|
504
530
|
},
|
|
505
|
-
// 8.
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
if (
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
else if (!existing.ok && fresh)
|
|
517
|
-
state.saveCheck = state.installation === 'global' ? 'ptbk lint' : 'npm run check';
|
|
518
|
-
steps.set('validation', false);
|
|
531
|
+
// 8. Automatic synchronization: one opt-in decision, default off; "no" asks nothing further.
|
|
532
|
+
sync: async () => {
|
|
533
|
+
const saved = await savedConfiguration(state.root);
|
|
534
|
+
const explicit = flag(context, 'auto-sync') ?? (flag(context, 'auto-pull') || flag(context, 'auto-push') ? true : undefined);
|
|
535
|
+
const savedChoice = typeof saved.isAutoSync === 'boolean' ? saved.isAutoSync : undefined;
|
|
536
|
+
const repositoryGit = await inspectGit(await nearestExisting(state.root));
|
|
537
|
+
const ownRepository = repositoryGit.state === 'repository' && canonicalPath(repositoryGit.root) === canonicalPath(state.root);
|
|
538
|
+
const origin = ownRepository ? await existingOrigin(repositoryGit.root) : undefined;
|
|
539
|
+
if (explicit === undefined && savedChoice !== undefined) {
|
|
540
|
+
state.sync = { enabled: savedChoice, reused: true };
|
|
541
|
+
steps.set('sync', false);
|
|
519
542
|
return;
|
|
520
543
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
question: check !== undefined ? `The configured check is not usable: ${existing.ok ? '' : existing.reason} Choose the project check:` : 'Which command validates this project? (it must pass before each task is accepted)',
|
|
527
|
-
choices: [
|
|
528
|
-
...candidates.map((candidate) => ({ value: candidate.command, label: candidate.command, description: candidate.description })),
|
|
529
|
-
{ value: '__custom', label: 'Enter a custom command' },
|
|
530
|
-
{ value: '__defer', label: 'Defer (setup stays incomplete)' },
|
|
531
|
-
],
|
|
532
|
-
defaultValue: candidates[0]?.command ?? '__custom',
|
|
533
|
-
}));
|
|
534
|
-
if (choice === '__defer')
|
|
544
|
+
let enabled = explicit;
|
|
545
|
+
if (enabled === undefined) {
|
|
546
|
+
if (!interactive) {
|
|
547
|
+
state.sync = { enabled: false, reused: false };
|
|
548
|
+
steps.set('sync', false);
|
|
535
549
|
return;
|
|
536
|
-
const command = choice === '__custom' ? answered(await inputLine(terminal, { frame: frame('validation'), question: 'Check command (runs in the project directory):' })) : choice;
|
|
537
|
-
const validation = await validateCheckCommand(state.root, command, context.env);
|
|
538
|
-
if (!validation.ok) {
|
|
539
|
-
say(style.warning(`${marks.warn} ${validation.reason}`));
|
|
540
|
-
continue;
|
|
541
550
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
551
|
+
shown.add('sync');
|
|
552
|
+
enabled = answered(await confirm(terminal, {
|
|
553
|
+
frame: frame('sync', 'optional: keep a Git remote up to date'),
|
|
554
|
+
question: 'Synchronize this project automatically with a Git remote? (pull every minute and before work, then push completed work)',
|
|
555
|
+
defaultYes: false,
|
|
556
|
+
yesLabel: 'Yes, set up automatic synchronization',
|
|
557
|
+
noLabel: 'No, keep the project local (default)',
|
|
558
|
+
}));
|
|
559
|
+
}
|
|
560
|
+
if (!enabled) {
|
|
561
|
+
state.sync = { enabled: false, reused: false };
|
|
562
|
+
answers.set('sync', 'Synchronization: off');
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (!commit)
|
|
566
|
+
throw usageError('Automatic synchronization publishes commits, so it conflicts with --no-commit.', 'init');
|
|
567
|
+
const provider = option(context, 'sync-provider');
|
|
568
|
+
const repositoryFlag = option(context, 'github-repository');
|
|
569
|
+
const originUrl = option(context, 'origin-url');
|
|
570
|
+
const visibility = option(context, 'github-visibility') ?? 'private';
|
|
571
|
+
const github = (value) => {
|
|
572
|
+
const [owner, name] = value.includes('/') ? value.split('/', 2) : [undefined, value];
|
|
573
|
+
return { kind: 'github', owner: owner || undefined, name: name, visibility };
|
|
574
|
+
};
|
|
575
|
+
let choice;
|
|
576
|
+
if (originUrl)
|
|
577
|
+
choice = { kind: 'origin', url: originUrl };
|
|
578
|
+
else if (repositoryFlag || provider === 'github')
|
|
579
|
+
choice = repositoryFlag ? github(repositoryFlag) : undefined;
|
|
580
|
+
else if (provider === 'existing' || provider === 'origin' || (savedChoice === true && origin))
|
|
581
|
+
choice = origin ? { kind: 'existing' } : undefined;
|
|
582
|
+
if (!choice && interactive) {
|
|
583
|
+
shown.add('sync');
|
|
584
|
+
if (origin) {
|
|
585
|
+
// An existing remote is reused explicitly, never silently replaced or duplicated on GitHub.
|
|
586
|
+
const reuse = answered(await confirm(terminal, { frame: frame('sync'), question: `This repository already has origin ${redactUrl(origin)}. Synchronize with it?`, defaultYes: true, yesLabel: 'Yes, use the existing origin', noLabel: 'No, keep synchronization off' }));
|
|
587
|
+
if (!reuse) {
|
|
588
|
+
state.sync = { enabled: false, reused: false };
|
|
589
|
+
answers.set('sync', 'Synchronization: off');
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
choice = { kind: 'existing' };
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
const kind = answered(await selectOne(terminal, {
|
|
596
|
+
frame: frame('sync', 'where should the project be published?'),
|
|
597
|
+
question: 'Where should the project be synchronized?',
|
|
598
|
+
choices: [
|
|
599
|
+
{ value: 'github', label: 'GitHub', description: 'create a new repository (private by default) with your GitHub CLI login and publish this project' },
|
|
600
|
+
{ value: 'origin', label: 'Another Git origin', description: 'enter a remote URL (any Git host or server)' },
|
|
601
|
+
],
|
|
602
|
+
defaultValue: 'github',
|
|
603
|
+
}));
|
|
604
|
+
if (kind === 'origin') {
|
|
605
|
+
const url = answered(await inputLine(terminal, { frame: frame('sync'), question: 'Remote URL of the Git origin:', validate: (value) => (!value.trim() ? 'Enter a Git remote URL.' : undefined) })).trim();
|
|
606
|
+
choice = { kind: 'origin', url };
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
const login = await githubLogin(context.env);
|
|
610
|
+
if (login.problem)
|
|
611
|
+
say(style.warning(`${marks.warn} ${login.problem} Publication is attempted after the local setup and can be retried later.`));
|
|
612
|
+
const name = slugify(basename(state.root)) || 'project';
|
|
613
|
+
const repository = answered(await inputLine(terminal, {
|
|
614
|
+
frame: frame('sync'),
|
|
615
|
+
question: 'New GitHub repository (owner/name):',
|
|
616
|
+
initial: `${login.login ?? ''}${login.login ? '/' : ''}${name}`,
|
|
617
|
+
validate: (value) => (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(value.trim()) || (/^[A-Za-z0-9_.-]+$/.test(value.trim()) && login.login) ? undefined : 'Enter owner/name, e.g. my-company/newsletter.'),
|
|
618
|
+
})).trim();
|
|
619
|
+
const chosenVisibility = answered(await selectOne(terminal, {
|
|
620
|
+
frame: frame('sync'),
|
|
621
|
+
question: 'Visibility of the new repository:',
|
|
622
|
+
choices: [
|
|
623
|
+
{ value: 'private', label: 'Private (recommended)', description: 'only people you invite can see it' },
|
|
624
|
+
{ value: 'public', label: 'Public', description: 'anyone can see the code and its whole history' },
|
|
625
|
+
],
|
|
626
|
+
defaultValue: 'private',
|
|
627
|
+
}));
|
|
628
|
+
const target = github(repository);
|
|
629
|
+
choice = { ...target, visibility: chosenVisibility };
|
|
630
|
+
const shownName = `${target.owner ?? login.login ?? '<you>'}/${target.name}`;
|
|
631
|
+
const ok = answered(await confirm(terminal, { frame: frame('sync'), question: `Create github.com/${shownName} (${chosenVisibility}) and publish this project after the local setup is committed?`, defaultYes: true }));
|
|
632
|
+
if (!ok)
|
|
633
|
+
throw new WizardBack('back');
|
|
634
|
+
}
|
|
545
635
|
}
|
|
546
636
|
}
|
|
637
|
+
if (!choice) {
|
|
638
|
+
missingDecisions.push(origin ? '--sync-provider existing (reuse the existing origin)' : '--github-repository <owner>/<name> or --origin-url <url> (automatic synchronization was enabled)');
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
state.sync = { enabled: true, choice, reused: false };
|
|
642
|
+
answers.set('sync', `Synchronization: ${choice.kind === 'existing' ? `existing origin ${redactUrl(origin ?? '')}` : choice.kind === 'origin' ? `origin ${redactUrl(choice.url)}` : `GitHub ${choice.owner ? `${choice.owner}/` : ''}${choice.name} (${choice.visibility})`}`);
|
|
547
643
|
},
|
|
548
644
|
};
|
|
549
645
|
/**
|
|
@@ -554,27 +650,28 @@ export async function initializeProject(context, options = {}) {
|
|
|
554
650
|
if (!interactive)
|
|
555
651
|
return;
|
|
556
652
|
const destination = projectAt(state.root);
|
|
557
|
-
const raw =
|
|
653
|
+
const raw = await savedConfiguration(state.root);
|
|
558
654
|
const local = await inspectLocalInstallation(state.root);
|
|
559
655
|
const saved = await loadSettings(destination, { env: context.env }).catch(() => undefined);
|
|
560
656
|
const remembered = (raw.initialized ?? {}).installation === 'global';
|
|
561
|
-
const fresh = !(await isDirectory(state.root)) || (await isFreshDirectory(state.root));
|
|
562
657
|
steps.set('installation', !(local.usable || local.declaredIn || option(context, 'installation') || remembered));
|
|
563
658
|
steps.set('harnesses', !(option(context, 'harness') || flag(context, 'no-harness') || (saved && enabledHarnesses(saved).length)));
|
|
564
|
-
steps.set('browser', !(raw
|
|
565
|
-
const tracesSaved = ['traces.
|
|
659
|
+
steps.set('browser', !(hasBrowserTool(raw) || option(context, 'browser-profile')));
|
|
660
|
+
const tracesSaved = ['traces.isCommitted', 'traces.isVideoIgnored', 'traces.isVideoRecordingEnabled'].every((key) => typeof getPath(raw, key) === 'boolean');
|
|
566
661
|
const tracesFlagged = flag(context, 'commit-traces') !== undefined && flag(context, 'ignore-trace-videos') !== undefined && flag(context, 'record-video') !== undefined;
|
|
567
662
|
steps.set('traces', !(tracesSaved || tracesFlagged));
|
|
568
|
-
|
|
569
|
-
steps.set('validation', !(fresh || option(context, 'check') || (check !== undefined && (await validateCheckCommand(state.root, check, context.env)).ok)));
|
|
663
|
+
steps.set('sync', !(typeof raw.isAutoSync === 'boolean' || flag(context, 'auto-sync') !== undefined || flag(context, 'auto-pull') || flag(context, 'auto-push')));
|
|
570
664
|
};
|
|
571
665
|
/**
|
|
572
666
|
* Installs, logs in and updates selected harnesses (interactive sub-steps of the Harnesses step).
|
|
573
667
|
*/
|
|
574
668
|
const setUpHarnesses = async (selected) => {
|
|
575
|
-
const
|
|
669
|
+
const projectRoot = await nearestExisting(state.root);
|
|
576
670
|
for (const id of [...selected]) {
|
|
577
671
|
const plugin = harnessPlugin(id);
|
|
672
|
+
// The simple init instance: the native account context of the global installation (never an ambient API key).
|
|
673
|
+
const nativeTool = { type: 'HARNESS', variant: plugin.variant, name: plugin.id, isEnabled: true, authentication: 'NATIVE' };
|
|
674
|
+
const context2 = { projectRoot, env: instanceEnvironment(plugin, nativeTool, undefined, context.env), tool: nativeTool };
|
|
578
675
|
let inspection = await plugin.inspect(context2);
|
|
579
676
|
if (!inspection.installed) {
|
|
580
677
|
let install = flag(context, 'install-harnesses') === true;
|
|
@@ -620,6 +717,7 @@ export async function initializeProject(context, options = {}) {
|
|
|
620
717
|
continue;
|
|
621
718
|
state.inspections.set(id, inspection);
|
|
622
719
|
}
|
|
720
|
+
const context2 = { projectRoot, env: context.env };
|
|
623
721
|
const outdated = [];
|
|
624
722
|
const canDecideUpdates = interactive || option(context, 'update-harnesses') === 'update';
|
|
625
723
|
for (const id of canDecideUpdates ? selected : []) {
|
|
@@ -674,7 +772,7 @@ export async function initializeProject(context, options = {}) {
|
|
|
674
772
|
alreadyReady.push(`${item.plugin.displayName} kept at ${item.current}`);
|
|
675
773
|
};
|
|
676
774
|
// Questions, with Esc going back to the previous screen.
|
|
677
|
-
const order = ['intent', 'update', 'destination', 'installation', 'harnesses', 'browser', 'traces', '
|
|
775
|
+
const order = ['intent', 'update', 'destination', 'installation', 'harnesses', 'browser', 'traces', 'sync'];
|
|
678
776
|
for (let index = 0; index < order.length;) {
|
|
679
777
|
const id = order[index];
|
|
680
778
|
try {
|
|
@@ -723,8 +821,16 @@ export async function initializeProject(context, options = {}) {
|
|
|
723
821
|
changed.push('Git repository initialized');
|
|
724
822
|
else
|
|
725
823
|
alreadyReady.push(`Git repository ${repository.repositoryRoot}`);
|
|
824
|
+
// Required configuration evolution of an existing project is its own committed pass, before any setup write.
|
|
825
|
+
if (await pathExists(project.configPath)) {
|
|
826
|
+
const maintenance = await maintainProjectConfiguration(project, { commit, env: context.env });
|
|
827
|
+
if (maintenance.migrated)
|
|
828
|
+
changed.push(`Migrated ptbk.json from ${maintenance.migrated.from} to ${maintenance.migrated.to}${maintenance.migrated.commit ? ` (${maintenance.migrated.commit.slice(0, 8)})` : ''}`);
|
|
829
|
+
if (maintenance.normalized)
|
|
830
|
+
changed.push(`Normalized ptbk.json spelling${maintenance.normalized.commit ? ` (${maintenance.normalized.commit.slice(0, 8)})` : ''}`);
|
|
831
|
+
}
|
|
726
832
|
const configBefore = (await pathExists(project.configPath)) ? await loadSettings(project, { env: context.env }) : undefined;
|
|
727
|
-
|
|
833
|
+
const savedCheck = configBefore ? (setting(configBefore, 'execution.check') ?? undefined) : undefined;
|
|
728
834
|
const installation = state.installation ?? 'existing';
|
|
729
835
|
if (installation === 'existing') {
|
|
730
836
|
const local = await inspectLocalInstallation(root);
|
|
@@ -740,17 +846,18 @@ export async function initializeProject(context, options = {}) {
|
|
|
740
846
|
else
|
|
741
847
|
unresolved.push(`${plugin.displayName}: ${inspection?.summary ?? 'not inspected'}${inspection?.nextAction ? ` → ${inspection.nextAction}` : ''}`);
|
|
742
848
|
}
|
|
743
|
-
if (!selected.length)
|
|
744
|
-
|
|
849
|
+
if (!selected.length && !(configBefore && enabledHarnesses(configBefore).length))
|
|
850
|
+
notes.push('No harness is selected: the agenda runs but waits for tools until one is added (`ptbk tool add HARNESS …`).');
|
|
745
851
|
const tasksDirectory = configBefore ? setting(configBefore, 'paths.tasks') : 'tasks';
|
|
746
852
|
const agentsDirectory = configBefore ? setting(configBefore, 'paths.agents') : 'agents';
|
|
747
853
|
const tracePolicy = state.traces ?? { commit: false, ignoreVideos: true, recordVideo: true };
|
|
748
854
|
const createdManifest = installation === 'local' && !(await pathExists(join(root, 'package.json')));
|
|
749
|
-
|
|
855
|
+
// Runtime state and local secrets stay ignored independently of every trace choice.
|
|
856
|
+
const ignore = await ensureRuntimeIgnore(root, [...(createdManifest || (await pathExists(join(root, 'node_modules'))) ? ['node_modules/'] : []), '.env']);
|
|
750
857
|
if (ignore)
|
|
751
858
|
createdPaths.push(ignore);
|
|
752
|
-
const savedPolicy = configBefore ? { commit: setting(configBefore, 'traces.
|
|
753
|
-
const traceIgnore = await ensureTraceIgnoreRules(root, savedPolicy && configBefore?.
|
|
859
|
+
const savedPolicy = configBefore ? { commit: setting(configBefore, 'traces.isCommitted'), ignoreVideos: setting(configBefore, 'traces.isVideoIgnored') } : undefined;
|
|
860
|
+
const traceIgnore = await ensureTraceIgnoreRules(root, savedPolicy && configBefore?.interpreted.traces ? savedPolicy : tracePolicy);
|
|
754
861
|
if (traceIgnore)
|
|
755
862
|
createdPaths.push(traceIgnore);
|
|
756
863
|
// Default roles first: a missing Chatter's initial TEAM covers the completed role set.
|
|
@@ -777,7 +884,7 @@ export async function initializeProject(context, options = {}) {
|
|
|
777
884
|
createdPaths.push(specification, ...files);
|
|
778
885
|
}
|
|
779
886
|
if (installation === 'local' || createdManifest) {
|
|
780
|
-
if (await ensureManifest(root, project.name
|
|
887
|
+
if (await ensureManifest(root, project.name))
|
|
781
888
|
createdPaths.push(join(root, 'package.json'));
|
|
782
889
|
}
|
|
783
890
|
if (installation === 'local') {
|
|
@@ -803,59 +910,47 @@ export async function initializeProject(context, options = {}) {
|
|
|
803
910
|
else
|
|
804
911
|
notes.push(global.note);
|
|
805
912
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
913
|
+
// No check question: a real existing check is kept (even a failing one: that is ordinary repair work); a supplied
|
|
914
|
+
// --check fills a missing one; otherwise the explicit informational placeholder records that validation is absent.
|
|
915
|
+
const suppliedCheck = option(context, 'check');
|
|
916
|
+
let check = savedCheck;
|
|
917
|
+
let saveCheck;
|
|
918
|
+
if (savedCheck !== undefined) {
|
|
919
|
+
if (isCheckPlaceholder(savedCheck))
|
|
920
|
+
notes.push('Real automated validation is not configured yet (execution.check holds the informational placeholder).');
|
|
921
|
+
else {
|
|
922
|
+
const validation = await validateCheckCommand(root, savedCheck, context.env);
|
|
923
|
+
if (validation.ok)
|
|
924
|
+
alreadyReady.push(`Check: ${savedCheck}`);
|
|
925
|
+
else
|
|
926
|
+
notes.push(`The configured check \`${savedCheck}\` is kept but does not look runnable (${validation.reason}); it is repaired through ordinary work, never replaced by the placeholder.`);
|
|
927
|
+
}
|
|
928
|
+
if (suppliedCheck && suppliedCheck !== savedCheck)
|
|
929
|
+
notes.push(`Check override for this run only: ${suppliedCheck}`);
|
|
812
930
|
}
|
|
813
|
-
if (
|
|
814
|
-
|
|
931
|
+
else if (suppliedCheck) {
|
|
932
|
+
saveCheck = suppliedCheck;
|
|
933
|
+
const validation = await validateCheckCommand(root, suppliedCheck, context.env);
|
|
815
934
|
if (!validation.ok)
|
|
816
|
-
|
|
817
|
-
changed.push(`Check: ${
|
|
818
|
-
check = state.saveCheck;
|
|
935
|
+
notes.push(`The supplied check \`${suppliedCheck}\` does not look runnable yet (${validation.reason}).`);
|
|
936
|
+
changed.push(`Check: ${suppliedCheck}`);
|
|
819
937
|
}
|
|
820
|
-
else
|
|
821
|
-
|
|
938
|
+
else {
|
|
939
|
+
saveCheck = CHECK_PLACEHOLDER;
|
|
940
|
+
changed.push('Check: informational placeholder (real automated validation is not configured)');
|
|
822
941
|
}
|
|
942
|
+
if (flag(context, 'defer-check'))
|
|
943
|
+
notes.push('--defer-check is no longer needed: init does not ask for a check.');
|
|
944
|
+
if (saveCheck !== undefined)
|
|
945
|
+
check = saveCheck;
|
|
823
946
|
const instructions = await ensureProjectInstructions(root, project.name, check);
|
|
824
947
|
if (instructions)
|
|
825
948
|
createdPaths.push(instructions);
|
|
826
|
-
// ptbk.json
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
const usableSelected = selected.filter((id) => state.inspections.get(id)?.usable);
|
|
830
|
-
const settingsChanges = {};
|
|
831
|
-
if (!routingHarness && (usableSelected[0] ?? selected[0]))
|
|
832
|
-
settingsChanges['routing.harness'] = usableSelected[0] ?? selected[0];
|
|
833
|
-
if (state.saveCheck !== undefined)
|
|
834
|
-
settingsChanges['execution.check'] = state.saveCheck;
|
|
835
|
-
if (!rawConfig.browser || option(context, 'browser-profile') !== undefined)
|
|
836
|
-
settingsChanges['browser.profile'] = state.browserProfile ?? 'global';
|
|
837
|
-
if (!rawConfig.browser || option(context, 'browser-mode') !== undefined)
|
|
838
|
-
settingsChanges['browser.mode'] = state.browserMode ?? 'headless';
|
|
839
|
-
for (const [key, value] of [
|
|
840
|
-
['traces.commit', tracePolicy.commit],
|
|
841
|
-
['traces.ignoreVideos', tracePolicy.ignoreVideos],
|
|
842
|
-
['traces.recordVideo', tracePolicy.recordVideo],
|
|
843
|
-
]) {
|
|
844
|
-
if (getPath(rawConfig, key) === undefined)
|
|
845
|
-
settingsChanges[key] = value;
|
|
846
|
-
}
|
|
847
|
-
const harnessChanges = {};
|
|
848
|
-
const configured = configBefore ? enabledHarnesses(configBefore) : [];
|
|
849
|
-
for (const id of selected)
|
|
850
|
-
harnessChanges[id] = { enabled: true };
|
|
851
|
-
for (const id of configured)
|
|
852
|
-
if (!selected.includes(id))
|
|
853
|
-
harnessChanges[id] = { enabled: false };
|
|
854
|
-
if (await updateProjectConfig(project, { settings: settingsChanges, harnesses: harnessChanges }))
|
|
855
|
-
createdPaths.push(project.configPath);
|
|
856
|
-
// The first task: one ordinary Expert task referring to the preserved specification, never duplicated.
|
|
949
|
+
// ptbk.json in one write: settings, the simple initial tools (through the shared tool operations), synchronization
|
|
950
|
+
// choice and the initialization marker. A new configuration records the running engine version.
|
|
951
|
+
const file = await readProjectConfigFile(project);
|
|
857
952
|
const timeZone = setting(await loadSettings(project, { env: context.env }), 'timezone');
|
|
858
|
-
const marker = (
|
|
953
|
+
const marker = (file.raw.initialized ?? {});
|
|
859
954
|
let firstTask;
|
|
860
955
|
const tasksRoot = join(root, tasksDirectory);
|
|
861
956
|
if (newPurpose && !marker.starterTask && !(await hasFirstTask({ root, tasksDirectory, timeZone }))) {
|
|
@@ -870,19 +965,65 @@ export async function initializeProject(context, options = {}) {
|
|
|
870
965
|
createdPaths.push(draftPath);
|
|
871
966
|
firstTask = { title, path: draftPath };
|
|
872
967
|
}
|
|
968
|
+
const raw = structuredClone(file.raw);
|
|
969
|
+
if (!file.exists)
|
|
970
|
+
raw.promptbookVersion = ENGINE_VERSION;
|
|
971
|
+
const set = (key, value) => setPath(raw, key, serializeSettingValue(SETTING_SPEC_BY_KEY.get(key), value));
|
|
972
|
+
const routingHarness = getPath(raw, 'routing.harness');
|
|
973
|
+
const usableSelected = selected.filter((id) => state.inspections.get(id)?.usable);
|
|
974
|
+
if (routingHarness === undefined && (usableSelected[0] ?? selected[0]))
|
|
975
|
+
set('routing.harness', usableSelected[0] ?? selected[0]);
|
|
976
|
+
if (saveCheck !== undefined)
|
|
977
|
+
set('execution.check', saveCheck);
|
|
978
|
+
for (const [key, value] of [
|
|
979
|
+
['traces.isCommitted', tracePolicy.commit],
|
|
980
|
+
['traces.isVideoIgnored', tracePolicy.ignoreVideos],
|
|
981
|
+
['traces.isVideoRecordingEnabled', tracePolicy.recordVideo],
|
|
982
|
+
]) {
|
|
983
|
+
if (getPath(raw, key) === undefined)
|
|
984
|
+
set(key, value);
|
|
985
|
+
}
|
|
986
|
+
if (state.sync && (!state.sync.reused || raw.isAutoSync === undefined))
|
|
987
|
+
raw.isAutoSync = state.sync.enabled;
|
|
988
|
+
const existingTools = Array.isArray(raw.tools) ? raw.tools : [];
|
|
989
|
+
const typedTools = interpretConfig({ raw, exists: true, diagnostics: [] }).tools;
|
|
990
|
+
const operations = [];
|
|
991
|
+
// One simple browser from the familiar picker; advanced profiles/accounts are managed later.
|
|
992
|
+
const browserProfile = profileFromSpelling(state.browserProfile ?? 'global');
|
|
993
|
+
const firstBrowser = typedTools.find((tool) => tool.type === 'BROWSER');
|
|
994
|
+
if (!firstBrowser) {
|
|
995
|
+
operations.push({ kind: 'add', tool: { type: 'BROWSER', variant: 'CHROME', name: MIGRATED_BROWSER_NAME, profileSource: browserProfile.source, ...(browserProfile.path ? { profilePath: browserProfile.path } : {}), isHeadless: (state.browserMode ?? 'headless') !== 'headful' } });
|
|
996
|
+
}
|
|
997
|
+
else if (option(context, 'browser-profile') !== undefined || option(context, 'browser-mode') !== undefined) {
|
|
998
|
+
operations.push({ kind: 'modify', name: firstBrowser.name, changes: { ...(option(context, 'browser-profile') !== undefined ? { profileSource: browserProfile.source, profilePath: browserProfile.path ?? null } : {}), ...(option(context, 'browser-mode') !== undefined ? { isHeadless: state.browserMode !== 'headful' } : {}) } });
|
|
999
|
+
}
|
|
1000
|
+
// Each selected integration gets one native instance named after it (existing instances are kept as they are).
|
|
1001
|
+
for (const id of selected) {
|
|
1002
|
+
const variant = harnessVariantOf(id);
|
|
1003
|
+
const instances = typedTools.filter((tool) => tool.type === 'HARNESS' && tool.variant === variant);
|
|
1004
|
+
if (!instances.length)
|
|
1005
|
+
operations.push({ kind: 'add', tool: { type: 'HARNESS', variant, name: typedTools.some((tool) => tool.name === id) ? `${id}-native` : id, authentication: 'NATIVE' } });
|
|
1006
|
+
else if (!instances.some((tool) => tool.isEnabled))
|
|
1007
|
+
operations.push({ kind: 'enable', name: instances[0].name });
|
|
1008
|
+
}
|
|
1009
|
+
const configured = operations.length ? applyToolOperationsToConfig({ ...raw, tools: existingTools }, operations) : canonicalizeConfig(raw).raw;
|
|
873
1010
|
const effectiveInstallation = installation === 'existing' ? (marker.installation ?? 'local') : installation;
|
|
874
1011
|
if (firstTask || !marker.engineVersion || marker.installation !== effectiveInstallation) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1012
|
+
configured.initialized = { ...configured.initialized, engineVersion: marker.engineVersion ?? ENGINE_VERSION, ...(firstTask ? { starterTask: firstTask.title } : {}), installation: effectiveInstallation };
|
|
1013
|
+
}
|
|
1014
|
+
const text = configText(configured);
|
|
1015
|
+
if (text !== file.text) {
|
|
1016
|
+
await writeFileAtomic(project.configPath, text);
|
|
878
1017
|
createdPaths.push(project.configPath);
|
|
879
1018
|
}
|
|
1019
|
+
for (const operation of operations)
|
|
1020
|
+
changed.push(operation.kind === 'add' ? `Tool ${operation.tool.name} (${operation.tool.type} ${operation.tool.variant})` : `Tool ${operation.name}: ${operation.kind}`);
|
|
880
1021
|
for (const path of createdPaths) {
|
|
881
1022
|
const label = toProjectRelative(root, path);
|
|
882
1023
|
if (!changed.includes(label))
|
|
883
1024
|
changed.push(label);
|
|
884
1025
|
}
|
|
885
|
-
// Commit authorized setup artifacts only (never unrelated changes), with the first task's
|
|
1026
|
+
// Commit authorized setup artifacts only (never unrelated changes), with the first task's name.
|
|
886
1027
|
let commitSha;
|
|
887
1028
|
const uniquePaths = [...new Set(createdPaths)];
|
|
888
1029
|
if (uniquePaths.length) {
|
|
@@ -922,6 +1063,26 @@ export async function initializeProject(context, options = {}) {
|
|
|
922
1063
|
notes.push(`Parent repository changes left uncommitted: ${parentChanges.map((path) => toProjectRelative(state.nested.parentRoot, path) || '.').join(', ')}`);
|
|
923
1064
|
if (!traceIgnore && !createdPaths.length)
|
|
924
1065
|
alreadyReady.push(`Trace policy: ${traceIgnoreLines(tracePolicy).join(' ')}`);
|
|
1066
|
+
// The initial browser must actually be usable (inspected without launching it).
|
|
1067
|
+
const browserTool = interpretConfig({ raw: configured, exists: true, diagnostics: [] }).tools.find((tool) => tool.type === 'BROWSER' && tool.isEnabled);
|
|
1068
|
+
if (browserTool) {
|
|
1069
|
+
const browserReadiness = await inspectBrowserTool(project, browserTool, context.env);
|
|
1070
|
+
if (browserReadiness.usable)
|
|
1071
|
+
alreadyReady.push(`Browser ${browserTool.name}: ${browserReadiness.summary}`);
|
|
1072
|
+
else
|
|
1073
|
+
unresolved.push(`Browser ${browserTool.name}: ${browserReadiness.summary}${browserReadiness.nextAction ? ` → ${browserReadiness.nextAction}` : ''}`);
|
|
1074
|
+
}
|
|
1075
|
+
// Remote publication is a separate, honestly reported outcome after the committed local setup.
|
|
1076
|
+
let publication;
|
|
1077
|
+
if (state.sync?.enabled && state.sync.choice && commitSha !== undefined && !unresolved.length) {
|
|
1078
|
+
say('Setting up automatic synchronization…');
|
|
1079
|
+
publication = await publishProject(project, repository.repositoryRoot, state.sync.choice, context.env);
|
|
1080
|
+
(publication.ok ? changed : notes).push(publication.ok ? publication.message : `Remote publication did not succeed: ${publication.message} Local setup is complete; synchronization stays enabled and retries in the background (or rerun \`ptbk init\`).`);
|
|
1081
|
+
}
|
|
1082
|
+
else if (state.sync?.enabled && state.sync.choice && !commitSha && !unresolved.length && !uniquePaths.length) {
|
|
1083
|
+
publication = await publishProject(project, repository.repositoryRoot, state.sync.choice, context.env);
|
|
1084
|
+
(publication.ok ? alreadyReady : notes).push(publication.ok ? publication.message : `Remote publication did not succeed: ${publication.message}`);
|
|
1085
|
+
}
|
|
925
1086
|
// Readiness from the saved settings the service will use.
|
|
926
1087
|
project = projectAt(root);
|
|
927
1088
|
const readiness = await assessReadiness(project, await loadSettings(project, { env: context.env }), context.env);
|
|
@@ -929,7 +1090,7 @@ export async function initializeProject(context, options = {}) {
|
|
|
929
1090
|
if (!unresolved.some((item) => item.startsWith(problem.message.slice(0, 30))))
|
|
930
1091
|
unresolved.push(`${problem.message} → ${problem.action}`);
|
|
931
1092
|
const ready = readiness.ready && !unresolved.length;
|
|
932
|
-
return { outcome: ready ? 'ready' : 'incomplete', project, changed, alreadyReady, unresolved, notes, commit: commitSha, firstTask };
|
|
1093
|
+
return { outcome: ready ? 'ready' : 'incomplete', project, changed, alreadyReady, unresolved, notes, commit: commitSha, firstTask, publication, synchronization: state.sync?.enabled ?? false };
|
|
933
1094
|
}
|
|
934
1095
|
/**
|
|
935
1096
|
* Performs the ordinary background start with the init invocation's applicable overrides (one service, never a second
|
|
@@ -943,7 +1104,7 @@ async function startService(context, project) {
|
|
|
943
1104
|
return { kind: 'reused', snapshot: await liveSnapshot(project, existing), note: `The ${existing.record.mode} service (PID ${existing.record.pid}) was already running and keeps its configuration.` };
|
|
944
1105
|
}
|
|
945
1106
|
const settings = await loadSettings(project, { env: context.env, cli: overrides });
|
|
946
|
-
const persistent = setting(settings, 'service.
|
|
1107
|
+
const persistent = setting(settings, 'service.isPersistent');
|
|
947
1108
|
const launched = await launchDaemon(project, { cli: overrides, tasksDirectory: tasksOverride(context), persistent, env: context.env });
|
|
948
1109
|
let snapshot = await liveSnapshot(project, launched.lookup);
|
|
949
1110
|
const deadline = Date.now() + 10_000;
|