|
|
@@ -1,12 +1,21 @@
|
|
|
import { type Command } from 'commander';
|
|
|
|
|
|
import { readCliVersion } from '../lib/version.js';
|
|
|
-import { isProjectRoot, installFiles } from '../lib/installer.js';
|
|
|
-import { getPackageRoot, listBundledFiles } from '../lib/bundled.js';
|
|
|
-import { writeManifest } from '../lib/manifest.js';
|
|
|
+import { isProjectRoot, installFiles, syncFiles, type InstallResult } from '../lib/installer.js';
|
|
|
+import { getPackageRoot, listBundledFiles, classifyBundledFile } from '../lib/bundled.js';
|
|
|
+import { readManifest, writeManifest, type ManifestFile } from '../lib/manifest.js';
|
|
|
import { readConfig, writeConfig, createDefaultConfig } from '../lib/config.js';
|
|
|
import { detectIdes } from '../lib/ide-detect.js';
|
|
|
-import { log, info, warn, error, success, setVerbose, verbose } from '../ui/logger.js';
|
|
|
+import {
|
|
|
+ loadRegistry,
|
|
|
+ listProfiles,
|
|
|
+ getProfile,
|
|
|
+ resolveProfile,
|
|
|
+ DEFAULT_PROFILE_ID,
|
|
|
+ FULL_PROFILE_ID,
|
|
|
+ type Registry,
|
|
|
+} from '../lib/registry.js';
|
|
|
+import { log, info, warn, error, success, setVerbose, verbose, dim } from '../ui/logger.js';
|
|
|
import { createSpinner } from '../ui/spinner.js';
|
|
|
|
|
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
|
@@ -15,81 +24,154 @@ export type InitOptions = {
|
|
|
yolo: boolean;
|
|
|
dryRun: boolean;
|
|
|
verbose: boolean;
|
|
|
+ /** Install tier from registry.json `profiles`. Defaults to `essential`. */
|
|
|
+ profile: string;
|
|
|
+ /** Keep only the profile's context files. */
|
|
|
+ contextOnly: boolean;
|
|
|
+ /** Print the available profiles and exit. */
|
|
|
+ listProfiles: boolean;
|
|
|
};
|
|
|
|
|
|
-// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
+/** The files `oac init` will copy, plus anything worth telling the user about the choice. */
|
|
|
+export type InstallPlan = {
|
|
|
+ profileId: string;
|
|
|
+ profileName: string;
|
|
|
+ files: string[];
|
|
|
+ unresolved: string[];
|
|
|
+ refused: string[];
|
|
|
+};
|
|
|
|
|
|
-/** Counts files by type prefix. Pure function. */
|
|
|
-const countByType = (
|
|
|
- files: string[],
|
|
|
-): { agents: number; context: number; skills: number; other: number } => ({
|
|
|
- agents: files.filter((f) => f.startsWith('.opencode/agent/')).length,
|
|
|
- context: files.filter((f) => f.startsWith('.opencode/context/')).length,
|
|
|
- skills: files.filter((f) => f.startsWith('.opencode/skills/')).length,
|
|
|
- other: files.filter(
|
|
|
- (f) =>
|
|
|
- !f.startsWith('.opencode/agent/') &&
|
|
|
- !f.startsWith('.opencode/context/') &&
|
|
|
- !f.startsWith('.opencode/skills/'),
|
|
|
- ).length,
|
|
|
-});
|
|
|
+// ── Pure helpers ──────────────────────────────────────────────────────────────
|
|
|
+
|
|
|
+const CONTEXT_PREFIX = '.opencode/context/';
|
|
|
+
|
|
|
+/** Counts files by bundled kind. Pure function. */
|
|
|
+export const countByType = (files: string[]): Record<string, number> =>
|
|
|
+ files.reduce<Record<string, number>>((acc, file) => {
|
|
|
+ const kind = classifyBundledFile(file);
|
|
|
+ return { ...acc, [kind]: (acc[kind] ?? 0) + 1 };
|
|
|
+ }, {});
|
|
|
+
|
|
|
+const LABELS: Record<string, [string, string]> = {
|
|
|
+ agent: ['agent', 'agents'],
|
|
|
+ context: ['context file', 'context files'],
|
|
|
+ skill: ['skill file', 'skill files'],
|
|
|
+ command: ['command', 'commands'],
|
|
|
+ tool: ['tool file', 'tool files'],
|
|
|
+ plugin: ['plugin', 'plugins'],
|
|
|
+ config: ['config file', 'config files'],
|
|
|
+};
|
|
|
|
|
|
/** Formats a file-count summary string. Pure function. */
|
|
|
-const formatFileSummary = (counts: ReturnType<typeof countByType>): string => {
|
|
|
- const parts: string[] = [];
|
|
|
- if (counts.agents > 0) parts.push(`${counts.agents} agent${counts.agents !== 1 ? 's' : ''}`);
|
|
|
- if (counts.context > 0) parts.push(`${counts.context} context file${counts.context !== 1 ? 's' : ''}`);
|
|
|
- if (counts.skills > 0) parts.push(`${counts.skills} skill${counts.skills !== 1 ? 's' : ''}`);
|
|
|
- if (counts.other > 0) parts.push(`${counts.other} other file${counts.other !== 1 ? 's' : ''}`);
|
|
|
+export const formatFileSummary = (counts: Record<string, number>): string => {
|
|
|
+ const parts = Object.keys(LABELS)
|
|
|
+ .filter((kind) => (counts[kind] ?? 0) > 0)
|
|
|
+ .map((kind) => {
|
|
|
+ const n = counts[kind]!;
|
|
|
+ const [one, many] = LABELS[kind]!;
|
|
|
+ return `${n} ${n === 1 ? one : many}`;
|
|
|
+ });
|
|
|
return parts.join(', ') || '0 files';
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Decides which bundled files a profile installs.
|
|
|
+ *
|
|
|
+ * `full` is the bundle itself (every agent, context and skill file), so it needs no registry
|
|
|
+ * resolution. Every other profile is the closure of its `type:id` refs.
|
|
|
+ *
|
|
|
+ * Pure apart from reading the bundle listing, which the caller supplies.
|
|
|
+ */
|
|
|
+export const planInstall = (
|
|
|
+ registry: Registry,
|
|
|
+ bundledFiles: string[],
|
|
|
+ options: Pick<InitOptions, 'profile' | 'contextOnly'>,
|
|
|
+): InstallPlan | null => {
|
|
|
+ const profile = getProfile(registry, options.profile);
|
|
|
+ if (profile === null) return null;
|
|
|
+
|
|
|
+ if (options.profile === FULL_PROFILE_ID) {
|
|
|
+ const files = options.contextOnly
|
|
|
+ ? bundledFiles.filter((f) => f.startsWith(CONTEXT_PREFIX))
|
|
|
+ : bundledFiles;
|
|
|
+ return { profileId: options.profile, profileName: profile.name, files: [...files].sort(), unresolved: [], refused: [] };
|
|
|
+ }
|
|
|
+
|
|
|
+ const resolved = resolveProfile(registry, profile, { contextOnly: options.contextOnly });
|
|
|
+ return {
|
|
|
+ profileId: options.profile,
|
|
|
+ profileName: profile.name,
|
|
|
+ files: resolved.files,
|
|
|
+ unresolved: resolved.unresolved,
|
|
|
+ refused: resolved.refused,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+// ── Output ────────────────────────────────────────────────────────────────────
|
|
|
+
|
|
|
+/** Prints every profile the bundle offers. Side-effect only. */
|
|
|
+const printProfiles = (registry: Registry): void => {
|
|
|
+ log('');
|
|
|
+ log('Available profiles (oac init --profile <id>):');
|
|
|
+ log('');
|
|
|
+ for (const p of listProfiles(registry)) {
|
|
|
+ const marker = p.id === DEFAULT_PROFILE_ID ? ' (default)' : '';
|
|
|
+ log(` ${p.id}${marker} — ${p.name}, ${p.components.length} components`);
|
|
|
+ if (p.description) dim(` ${p.description}`);
|
|
|
+ }
|
|
|
+ log('');
|
|
|
+};
|
|
|
+
|
|
|
/** Prints the pre-install plan. Side-effect only. */
|
|
|
const printPlan = (
|
|
|
- bundledFiles: string[],
|
|
|
+ plan: InstallPlan,
|
|
|
ides: Awaited<ReturnType<typeof detectIdes>>,
|
|
|
- dryRun: boolean,
|
|
|
+ options: InitOptions,
|
|
|
): void => {
|
|
|
- const counts = countByType(bundledFiles);
|
|
|
const detectedIdes = ides.filter((i) => i.detected).map((i) => i.type);
|
|
|
|
|
|
log('');
|
|
|
- log(dryRun ? ' [dry-run] oac init — no files will be written' : ' oac init');
|
|
|
+ log(options.dryRun ? ' [dry-run] oac init — no files will be written' : ' oac init');
|
|
|
log('');
|
|
|
- info(`Will install: ${formatFileSummary(counts)}`);
|
|
|
+ info(`Profile: ${plan.profileId} (${plan.profileName})${options.contextOnly ? ', context files only' : ''}`);
|
|
|
+ info(`Will install: ${formatFileSummary(countByType(plan.files))}`);
|
|
|
info(`Destination: .opencode/ (relative to project root)`);
|
|
|
|
|
|
+ for (const ref of plan.unresolved) warn(`Profile lists '${ref}', which this bundle does not have — skipping.`);
|
|
|
+ for (const ref of plan.refused) warn(`Profile lists '${ref}', which lives outside .opencode/ — skipping.`);
|
|
|
+
|
|
|
if (detectedIdes.length > 0) {
|
|
|
info(`IDEs detected: ${detectedIdes.join(', ')} — run \`oac apply\` after init`);
|
|
|
} else {
|
|
|
- info('No IDEs detected — run `oac apply <ide>` to generate IDE-specific files');
|
|
|
+ info('No IDEs detected — run `oac apply claude` to generate Claude Code files');
|
|
|
}
|
|
|
|
|
|
log('');
|
|
|
};
|
|
|
|
|
|
/** Prints the post-install summary. Side-effect only. */
|
|
|
-const printSummary = (
|
|
|
- installed: number,
|
|
|
- skipped: number,
|
|
|
- errors: number,
|
|
|
- dryRun: boolean,
|
|
|
-): void => {
|
|
|
+const printSummary = (result: InstallResult, reinstall: boolean, dryRun: boolean): void => {
|
|
|
+ const written = result.installed.length + result.updated.length;
|
|
|
log('');
|
|
|
if (dryRun) {
|
|
|
- info(`[dry-run] Would install ${installed} file${installed !== 1 ? 's' : ''}.`);
|
|
|
+ info(`[dry-run] Would install ${written} file${written !== 1 ? 's' : ''}.`);
|
|
|
+ if (result.skipped.length > 0) {
|
|
|
+ info(`[dry-run] Would skip ${result.skipped.length} (modified since install — use --yolo to overwrite).`);
|
|
|
+ }
|
|
|
info('No changes were made. Remove --dry-run to apply.');
|
|
|
return;
|
|
|
}
|
|
|
- if (errors > 0) {
|
|
|
- warn(`Completed with ${errors} error${errors !== 1 ? 's' : ''}.`);
|
|
|
+ if (result.errors.length > 0) {
|
|
|
+ warn(`Completed with ${result.errors.length} error${result.errors.length !== 1 ? 's' : ''}.`);
|
|
|
+ }
|
|
|
+ if (reinstall && result.skipped.length > 0) {
|
|
|
+ warn(`Skipped ${result.skipped.length} file${result.skipped.length !== 1 ? 's' : ''} you have modified (use --yolo to back up and overwrite):`);
|
|
|
+ for (const f of result.skipped) dim(` ${f}`);
|
|
|
}
|
|
|
- if (skipped > 0) {
|
|
|
- info(`Skipped ${skipped} file${skipped !== 1 ? 's' : ''} (already modified — use --yolo to overwrite).`);
|
|
|
+ if (result.backed_up.length > 0) {
|
|
|
+ info(`Backed up ${result.backed_up.length} file${result.backed_up.length !== 1 ? 's' : ''} to .oac/backups/.`);
|
|
|
}
|
|
|
- success(
|
|
|
- `Done! ${installed} file${installed !== 1 ? 's' : ''} installed. Run \`oac doctor\` to verify.`,
|
|
|
- );
|
|
|
+ success(`Done! ${written} file${written !== 1 ? 's' : ''} installed. Run \`oac doctor\` to verify.`);
|
|
|
log('');
|
|
|
};
|
|
|
|
|
|
@@ -99,9 +181,7 @@ const printSummary = (
|
|
|
const assertProjectRoot = async (cwd: string): Promise<void> => {
|
|
|
const isRoot = await isProjectRoot(cwd);
|
|
|
if (!isRoot) {
|
|
|
- error(
|
|
|
- 'Not a project root — no package.json or .git found in the current directory.',
|
|
|
- );
|
|
|
+ error('Not a project root — no package.json or .git found in the current directory.');
|
|
|
error('Fix: run `oac init` from your project root (where package.json lives).');
|
|
|
process.exit(1);
|
|
|
}
|
|
|
@@ -127,89 +207,138 @@ const ensureConfig = async (projectRoot: string, dryRun: boolean): Promise<void>
|
|
|
verbose('Wrote .oac/config.json with defaults.');
|
|
|
};
|
|
|
|
|
|
+// ── Install step ──────────────────────────────────────────────────────────────
|
|
|
+
|
|
|
+type InstallOutcome = { result: InstallResult; updatedManifest: ManifestFile; reinstall: boolean };
|
|
|
+
|
|
|
+/**
|
|
|
+ * Fresh project: copy everything. Existing manifest: reconcile against it so a file the
|
|
|
+ * user edited is skipped, not clobbered, and `oac add` entries survive.
|
|
|
+ */
|
|
|
+const runInstall = async (
|
|
|
+ files: string[],
|
|
|
+ existing: ManifestFile | null,
|
|
|
+ projectRoot: string,
|
|
|
+ packageRoot: string,
|
|
|
+ options: InitOptions,
|
|
|
+): Promise<InstallOutcome> => {
|
|
|
+ const installOpts = {
|
|
|
+ projectRoot,
|
|
|
+ packageRoot,
|
|
|
+ dryRun: options.dryRun,
|
|
|
+ yolo: options.yolo,
|
|
|
+ verbose: options.verbose,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (existing === null) {
|
|
|
+ const { result, updatedManifest } = await installFiles(files, installOpts);
|
|
|
+ return { result, updatedManifest, reinstall: false };
|
|
|
+ }
|
|
|
+
|
|
|
+ const { result, updatedManifest } = await syncFiles(files, existing, installOpts);
|
|
|
+ return { result, updatedManifest, reinstall: true };
|
|
|
+};
|
|
|
+
|
|
|
// ── Main command ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
|
* Implements `oac init`:
|
|
|
* 1. Validates we are in a project root
|
|
|
- * 2. Detects IDEs and prints the install plan
|
|
|
- * 3. Copies all bundled files via installFiles()
|
|
|
- * 4. Writes .oac/manifest.json
|
|
|
- * 5. Writes .oac/config.json (only if absent)
|
|
|
- * 6. Prints a completion summary
|
|
|
+ * 2. Loads the bundled registry and resolves the chosen profile
|
|
|
+ * 3. Detects IDEs and prints the install plan
|
|
|
+ * 4. Copies the profile's files (reconciling against an existing manifest)
|
|
|
+ * 5. Writes .oac/manifest.json
|
|
|
+ * 6. Writes .oac/config.json (only if absent)
|
|
|
+ * 7. Prints a completion summary
|
|
|
*/
|
|
|
export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
// Respect CI=true as implicit --yolo
|
|
|
- const effectiveYolo = options.yolo || process.env['CI'] === 'true';
|
|
|
- const effectiveOptions = { ...options, yolo: effectiveYolo };
|
|
|
+ const effectiveOptions: InitOptions = { ...options, yolo: options.yolo || process.env['CI'] === 'true' };
|
|
|
|
|
|
if (effectiveOptions.verbose) setVerbose(true);
|
|
|
|
|
|
const projectRoot = process.cwd();
|
|
|
|
|
|
- // Step 1: validate project root
|
|
|
- await assertProjectRoot(projectRoot);
|
|
|
-
|
|
|
- // Step 2: locate bundled files
|
|
|
+ // Step 1: locate the bundle and its registry
|
|
|
let packageRoot: string;
|
|
|
+ let registry: Registry;
|
|
|
let bundledFiles: string[];
|
|
|
try {
|
|
|
packageRoot = getPackageRoot();
|
|
|
+ registry = await loadRegistry(packageRoot);
|
|
|
bundledFiles = await listBundledFiles(packageRoot);
|
|
|
} catch (err) {
|
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
|
error(`Could not locate bundled files: ${msg}`);
|
|
|
error('Fix: ensure @controlstack/oac is installed correctly (try reinstalling).');
|
|
|
process.exit(1);
|
|
|
- return;
|
|
|
}
|
|
|
|
|
|
- if (bundledFiles.length === 0) {
|
|
|
- warn('No bundled files found — nothing to install.');
|
|
|
- warn('Fix: the @controlstack/oac package may be missing its bundled assets.');
|
|
|
+ if (effectiveOptions.listProfiles) {
|
|
|
+ printProfiles(registry);
|
|
|
+ process.exit(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Step 2: validate project root
|
|
|
+ await assertProjectRoot(projectRoot);
|
|
|
+
|
|
|
+ // Step 3: resolve the profile
|
|
|
+ const plan = planInstall(registry, bundledFiles, effectiveOptions);
|
|
|
+ if (plan === null) {
|
|
|
+ const known = listProfiles(registry).map((p) => p.id).join(', ');
|
|
|
+ error(`Unknown profile '${effectiveOptions.profile}'. Available: ${known}.`);
|
|
|
+ error('Fix: run `oac init --list-profiles` to see what each one installs.');
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
- // Step 3: detect IDEs and print plan
|
|
|
+ if (plan.files.length === 0) {
|
|
|
+ warn(`Profile '${plan.profileId}' resolves to no files — nothing to install.`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Step 4: detect IDEs and print plan
|
|
|
const ides = await detectIdes(projectRoot);
|
|
|
- printPlan(bundledFiles, ides, effectiveOptions.dryRun);
|
|
|
+ printPlan(plan, ides, effectiveOptions);
|
|
|
+
|
|
|
+ // Step 5: install files
|
|
|
+ const existing = await readManifest(projectRoot).catch((err: unknown): never => {
|
|
|
+ const msg = err instanceof Error ? err.message : String(err);
|
|
|
+ error(`Could not read .oac/manifest.json: ${msg}`);
|
|
|
+ error('Fix: repair the JSON, or delete .oac/manifest.json to start fresh.');
|
|
|
+ return process.exit(1);
|
|
|
+ });
|
|
|
+ if (existing !== null) {
|
|
|
+ info('Existing install found — files you have modified will be kept.');
|
|
|
+ }
|
|
|
|
|
|
- // Step 4: install files
|
|
|
const spinner = createSpinner('Installing files…', { dryRun: effectiveOptions.dryRun });
|
|
|
spinner.start();
|
|
|
|
|
|
- let installResult: Awaited<ReturnType<typeof installFiles>>;
|
|
|
+ let outcome: InstallOutcome;
|
|
|
try {
|
|
|
- installResult = await installFiles(bundledFiles, {
|
|
|
- projectRoot,
|
|
|
- packageRoot,
|
|
|
- dryRun: effectiveOptions.dryRun,
|
|
|
- yolo: effectiveOptions.yolo,
|
|
|
- verbose: effectiveOptions.verbose,
|
|
|
- });
|
|
|
+ outcome = await runInstall(plan.files, existing, projectRoot, packageRoot, effectiveOptions);
|
|
|
} catch (err) {
|
|
|
spinner.fail('Installation failed.');
|
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
|
error(`Installation failed: ${msg}`);
|
|
|
error('Fix: check file permissions in your project directory.');
|
|
|
process.exit(1);
|
|
|
- return;
|
|
|
}
|
|
|
- const { result, updatedManifest } = installResult;
|
|
|
+ const { result, updatedManifest, reinstall } = outcome;
|
|
|
|
|
|
// Report per-file errors (non-fatal — partial installs are still useful)
|
|
|
for (const fileError of result.errors) {
|
|
|
warn(`Error: ${fileError}`);
|
|
|
}
|
|
|
|
|
|
- spinner.succeed(`Installed ${result.installed.length} file${result.installed.length !== 1 ? 's' : ''}.`);
|
|
|
+ const written = result.installed.length + result.updated.length;
|
|
|
+ spinner.succeed(`Installed ${written} file${written !== 1 ? 's' : ''}.`);
|
|
|
|
|
|
- // Step 5: write manifest (skip in dry-run)
|
|
|
+ // Step 6: write manifest (skip in dry-run)
|
|
|
if (effectiveOptions.dryRun) {
|
|
|
info('[dry-run] Would write .oac/manifest.json');
|
|
|
} else {
|
|
|
- const cliVersion = readCliVersion();
|
|
|
- const finalManifest = { ...updatedManifest, oacVersion: cliVersion };
|
|
|
+ const finalManifest = { ...updatedManifest, oacVersion: readCliVersion() };
|
|
|
|
|
|
await writeManifest(projectRoot, finalManifest).catch((err: unknown) => {
|
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
|
@@ -220,7 +349,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
verbose('Wrote .oac/manifest.json');
|
|
|
}
|
|
|
|
|
|
- // Step 6: write config (only if absent)
|
|
|
+ // Step 7: write config (only if absent)
|
|
|
await ensureConfig(projectRoot, effectiveOptions.dryRun).catch((err: unknown) => {
|
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
|
error(`Failed to write config: ${msg}`);
|
|
|
@@ -228,13 +357,8 @@ export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
process.exit(1) as never;
|
|
|
});
|
|
|
|
|
|
- // Step 7: print summary
|
|
|
- printSummary(
|
|
|
- result.installed.length,
|
|
|
- result.skipped.length,
|
|
|
- result.errors.length,
|
|
|
- effectiveOptions.dryRun,
|
|
|
- );
|
|
|
+ // Step 8: print summary
|
|
|
+ printSummary(result, reinstall, effectiveOptions.dryRun);
|
|
|
|
|
|
// Exit 0 on success (explicit for clarity)
|
|
|
process.exit(0);
|
|
|
@@ -250,14 +374,29 @@ export function registerInitCommand(program: Command): void {
|
|
|
program
|
|
|
.command('init')
|
|
|
.description('Set up OAC agents and context files in the current project')
|
|
|
- .option('--yolo', 'Skip conflict checks and overwrite user-modified files', false)
|
|
|
+ .option('-p, --profile <id>', `Install tier: essential, developer, business, advanced or full`, DEFAULT_PROFILE_ID)
|
|
|
+ .option('--context-only', "Install only the profile's context files (no agents, skills or commands)", false)
|
|
|
+ .option('--list-profiles', 'Show the available profiles and exit', false)
|
|
|
+ .option('--yolo', 'Back up and overwrite files you have modified (re-runs only)', false)
|
|
|
.option('--dry-run', 'Print what would happen without making any changes', false)
|
|
|
.option('--verbose', 'Show each file being copied', false)
|
|
|
- .action(async (opts: { yolo: boolean; dryRun: boolean; verbose: boolean }) => {
|
|
|
- await initCommand({
|
|
|
- yolo: opts.yolo,
|
|
|
- dryRun: opts.dryRun,
|
|
|
- verbose: opts.verbose,
|
|
|
- });
|
|
|
- });
|
|
|
+ .action(
|
|
|
+ async (opts: {
|
|
|
+ profile: string;
|
|
|
+ contextOnly: boolean;
|
|
|
+ listProfiles: boolean;
|
|
|
+ yolo: boolean;
|
|
|
+ dryRun: boolean;
|
|
|
+ verbose: boolean;
|
|
|
+ }) => {
|
|
|
+ await initCommand({
|
|
|
+ profile: opts.profile,
|
|
|
+ contextOnly: opts.contextOnly,
|
|
|
+ listProfiles: opts.listProfiles,
|
|
|
+ yolo: opts.yolo,
|
|
|
+ dryRun: opts.dryRun,
|
|
|
+ verbose: opts.verbose,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ );
|
|
|
}
|