screenci 0.0.23 → 0.0.25

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/Dockerfile CHANGED
@@ -10,15 +10,23 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
10
10
  && rm -rf /var/lib/apt/lists/*
11
11
 
12
12
  # ── Dependency layer (cached until package.json changes) ──────────────────────
13
- # Install screenci as a workspace package so npm creates the bin link.
13
+ # Install through a workspace to keep dependency resolution unchanged, then
14
+ # replace the workspace symlink with a real package under node_modules. Playwright
15
+ # skips transforms for node_modules packages, but follows workspace symlinks to
16
+ # /app/screenci and can rewrite compiled ESM as CJS while loading TS configs.
14
17
  COPY package.json ./screenci/
15
- RUN printf '{"private":true,"workspaces":["screenci"]}' > package.json && npm install
18
+ RUN printf '{"private":true,"workspaces":["screenci"]}' > package.json && \
19
+ npm install && \
20
+ rm /app/node_modules/screenci && \
21
+ mkdir -p /app/node_modules/screenci
16
22
 
17
23
  # Playwright browser download: only re-runs when the playwright version changes.
18
24
  RUN npx playwright install chromium --with-deps
19
25
 
20
26
  # ── screenci build output ─────────────────────────────────────────────────────
21
27
  COPY dist ./screenci/dist/
28
+ COPY package.json ./node_modules/screenci/package.json
29
+ COPY dist ./node_modules/screenci/dist/
22
30
 
23
31
  # Explicit bin wrapper — no npm bin-linking magic needed.
24
32
  RUN printf '#!/bin/sh\nexec node /app/screenci/dist/cli.js "$@"\n' > /app/node_modules/.bin/screenci && \
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AAwBA,OAAO,KAAK,EACV,uBAAuB,EACvB,aAAa,EAEd,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AA4D/C,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAiBD,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,GAAG,KAAK,GAAG,OAAO,CAAC,EACtD,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,GACxC,MAAM,IAAI,CAiBZ;AAuED,QAAA,MAAM,mCAAmC;;;CAG/B,CAAA;AAEV,KAAK,oBAAoB,GAAG,MAAM,OAAO,mCAAmC,CAAA;AAmc5E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,QAAQ,GAAG,uBAAuB,GACxC,QAAQ,GAAG,uBAAuB,CAKpC;AAED,wBAAgB,oCAAoC,CAClD,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,mBAAmB,EAAE,GAC5B,aAAa,CAqEf;AAQD,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,MAAM,CAMR;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,IAAI,CAcN;AAgRD,wBAAgB,gBAAgB,IAAI,MAAM,CAKzC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAK1C;AA0FD,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,aAAa,GAAG,SAAS,GAClC,MAAM,GAAG,SAAS,CAepB;AAsjBD,wBAAsB,IAAI,kBA8OzB;AAoJD,wBAAgB,sBAAsB,CACpC,aAAa,CAAC,EAAE,oBAAoB,GACnC,oBAAoB,CAoBtB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AAwBA,OAAO,KAAK,EACV,uBAAuB,EACvB,aAAa,EAEd,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AA4D/C,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAiBD,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,GAAG,KAAK,GAAG,OAAO,CAAC,EACtD,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,GACxC,MAAM,IAAI,CAiBZ;AAuED,QAAA,MAAM,mCAAmC;;;CAG/B,CAAA;AAEV,KAAK,oBAAoB,GAAG,MAAM,OAAO,mCAAmC,CAAA;AAmc5E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,QAAQ,GAAG,uBAAuB,GACxC,QAAQ,GAAG,uBAAuB,CAKpC;AAED,wBAAgB,oCAAoC,CAClD,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,mBAAmB,EAAE,GAC5B,aAAa,CAqEf;AAQD,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,MAAM,CAeR;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,IAAI,CAcN;AAgRD,wBAAgB,gBAAgB,IAAI,MAAM,CAKzC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAK1C;AA0FD,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,aAAa,GAAG,SAAS,GAClC,MAAM,GAAG,SAAS,CAepB;AAuvBD,wBAAsB,IAAI,kBAoQzB;AAoJD,wBAAgB,sBAAsB,CACpC,aAAa,CAAC,EAAE,oBAAoB,GACnC,oBAAoB,CAoBtB"}
package/dist/cli.js CHANGED
@@ -570,6 +570,15 @@ function hint401(status, secret) {
570
570
  }
571
571
  export function formatUploadStartFailureMessage(videoName, status, responseText, secret) {
572
572
  if (responseText.trim().length > 0) {
573
+ try {
574
+ const parsed = JSON.parse(responseText);
575
+ if (typeof parsed.error === 'string' && parsed.error.trim().length > 0) {
576
+ return `${parsed.error}${hint401(status, secret)}`;
577
+ }
578
+ }
579
+ catch {
580
+ // fall back to raw response text
581
+ }
573
582
  return responseText;
574
583
  }
575
584
  return `Failed to start upload for "${videoName}": ${status}${hint401(status, secret)}`;
@@ -973,11 +982,10 @@ export default defineConfig({
973
982
  })
974
983
  `;
975
984
  }
976
- function generatePackageJson(projectName, includePlaywrightCli = false) {
985
+ function generatePackageJson(projectName, includePlaywrightCli = false, screenciDependency = 'latest') {
977
986
  const npmName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
978
987
  const devDependencies = {
979
988
  '@types/node': '^25.0.0',
980
- tsx: '^4.21.0',
981
989
  };
982
990
  if (includePlaywrightCli) {
983
991
  devDependencies['@playwright/cli'] = 'latest';
@@ -993,7 +1001,7 @@ function generatePackageJson(projectName, includePlaywrightCli = false) {
993
1001
  test: 'screenci test',
994
1002
  },
995
1003
  dependencies: {
996
- screenci: 'latest',
1004
+ screenci: screenciDependency,
997
1005
  },
998
1006
  devDependencies,
999
1007
  }, null, 2) + '\n');
@@ -1011,13 +1019,17 @@ Learn more: https://screenci.com/docs/intro/
1011
1019
 
1012
1020
  ## Quick start
1013
1021
 
1014
- 1. Run tests in UI mode:
1022
+ 1. Create your own videos in \`videos/*.video.ts\`, either manually or with an AI agent using your source code or a URL.
1015
1023
 
1016
- \`npx screenci test\` or \`npx screenci test --ui\`
1024
+ 2. Run videos locally to test the script working:
1017
1025
 
1018
- 2. Record and upload videos:
1026
+ \`npx screenci test\` or with UI mode: \`npx screenci test --ui\`
1027
+
1028
+ 3. Record videos:
1019
1029
 
1020
1030
  \`npx screenci record\`
1031
+
1032
+ 4. View results on screenci.com and optionally enable a public URL to embed the video on your site.
1021
1033
  `;
1022
1034
  }
1023
1035
  function generateDockerfile() {
@@ -1053,25 +1065,23 @@ jobs:
1053
1065
  SCREENCI_SECRET: \${{ secrets.SCREENCI_SECRET }}
1054
1066
  run: |
1055
1067
  if [ -z "$SCREENCI_SECRET" ]; then
1056
- echo "::error::SCREENCI_SECRET is not set. Add it under Settings → Secrets and variables → Actions."
1068
+ echo "::error::SCREENCI_SECRET is not set. Copy it from https://app.screenci.com/secrets and add it under Settings → Secrets and variables → Actions → Repository secrets."
1057
1069
  exit 1
1058
1070
  fi
1059
1071
 
1060
1072
  - uses: actions/checkout@v4
1061
1073
 
1062
- - name: Build Docker image
1063
- run: docker build -t screenci-project .
1074
+ - uses: actions/setup-node@v6
1075
+ with:
1076
+ node-version: latest
1077
+
1078
+ - name: Install dependencies
1079
+ run: npm install
1064
1080
 
1065
1081
  - name: Record
1066
1082
  env:
1067
1083
  SCREENCI_SECRET: \${{ secrets.SCREENCI_SECRET }}
1068
- run: |
1069
- docker run --rm \\
1070
- -e SCREENCI_SECRET \\
1071
- -e SCREENCI_IN_CONTAINER=true \\
1072
- -e SCREENCI_RECORD=true \\
1073
- screenci-project \\
1074
- npm run record
1084
+ run: npm run record
1075
1085
  `;
1076
1086
  }
1077
1087
  function openBrowser(url) {
@@ -1090,7 +1100,7 @@ async function performBrowserLogin(appUrl) {
1090
1100
  const secret = reqUrl.searchParams.get('secret');
1091
1101
  if (secret) {
1092
1102
  res.writeHead(200, { 'Content-Type': 'text/html' });
1093
- res.end('<html><body style="font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0"><p style="font-size:1.2rem">Authentication successful! You can close this tab.</p></body></html>');
1103
+ res.end('<html><body style="font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0"><p style="font-size:1.2rem">Setup complete! You can close this tab.</p></body></html>');
1094
1104
  server.close();
1095
1105
  resolve(secret);
1096
1106
  }
@@ -1120,28 +1130,115 @@ async function performBrowserLogin(appUrl) {
1120
1130
  const timeout = setTimeout(() => {
1121
1131
  server.close();
1122
1132
  reject(new Error('Authentication timed out after 5 minutes'));
1123
- }, 5 * 60 * 1000);
1133
+ }, 15 * 60 * 1000);
1124
1134
  server.on('close', () => clearTimeout(timeout));
1125
1135
  });
1126
1136
  }
1127
1137
  function generateExampleVideo() {
1128
- return `import { video } from 'screenci'
1138
+ return `import { createNarration, hide, video, voices } from 'screenci'
1139
+
1140
+ const narration = createNarration({
1141
+ voice: { name: voices.Sophie, style: 'Clear, friendly product walkthrough' },
1142
+ languages: {
1143
+ en: {
1144
+ cues: {
1145
+ intro:
1146
+ 'Here is how to find instructions for starting to create your own ScreenCI [pronounce: screen see eye] videos. [short pause] Start on the homepage, then open the documentation from the hero section.',
1147
+ docs: 'The documentation opens with the guide sidebar on the left. In the Guides group, choose AI-Supported Editing.',
1148
+ },
1149
+ },
1150
+ es: {
1151
+ cues: {
1152
+ intro:
1153
+ 'Aqui se muestra como encontrar instrucciones para empezar a crear tus propios videos de ScreenCI [pronounce: screen see eye]. [short pause] Comienza en la pagina principal y abre la documentacion desde la seccion principal.',
1154
+ docs: 'La documentacion se abre con la barra lateral de guias a la izquierda. En el grupo Guias, elige Edicion asistida por IA.',
1155
+ },
1156
+ },
1157
+ },
1158
+ })
1159
+
1160
+ video('Navigate to AI editing documentation', async ({ page }) => {
1161
+ await hide(async () => {
1162
+ await page.goto('https://screenci.com/')
1163
+ await page.getByText('ScreenCI', { exact: true }).first().waitFor()
1164
+ })
1129
1165
 
1130
- video('Example video', async ({ page }) => {
1131
- await page.goto('https://example.com')
1132
- await page.waitForTimeout(3000)
1166
+ await narration.intro
1167
+ await page.getByRole('link', { name: 'View Documentation' }).click()
1168
+
1169
+ await narration.docs
1170
+ await page
1171
+ .getByRole('link', { name: 'AI-Supported Editing', exact: true })
1172
+ .click()
1133
1173
  })
1134
1174
  `;
1135
1175
  }
1136
1176
  async function promptProjectName() {
1137
1177
  return input({ message: 'Project name:' });
1138
1178
  }
1179
+ async function promptInitGitRepository() {
1180
+ return confirm({
1181
+ message: 'Initialize a git repository? (Y/n)',
1182
+ default: true,
1183
+ });
1184
+ }
1185
+ async function promptInitDependencies() {
1186
+ return confirm({
1187
+ message: 'Install dependencies now, including Chromium for Playwright? (Y/n)',
1188
+ default: true,
1189
+ });
1190
+ }
1191
+ async function promptInitAiAuthoring() {
1192
+ return confirm({
1193
+ message: 'Do you want to write videos with an AI agent based on a URL and not just source code? If yes, playwright-cli will be also installed.',
1194
+ default: true,
1195
+ });
1196
+ }
1197
+ async function promptInitGithubActionCi() {
1198
+ return confirm({
1199
+ message: 'Do you want to add Github Action CI? (Y/n)',
1200
+ default: true,
1201
+ });
1202
+ }
1139
1203
  function getProjectDirName(name) {
1140
1204
  return name.toLowerCase().replace(/\s+/g, '-');
1141
1205
  }
1142
1206
  function getInitProjectRoot() {
1143
1207
  return process.env['SCREENCI_INIT_CWD'] ?? process.cwd();
1144
1208
  }
1209
+ function isSourceCliEntrypoint(entrypoint) {
1210
+ return (entrypoint?.endsWith('/cli.ts') === true ||
1211
+ entrypoint?.endsWith('\\cli.ts') === true);
1212
+ }
1213
+ function getDevScreenciPackageRoot() {
1214
+ const explicitRoot = process.env.SCREENCI_DEV_PACKAGE_ROOT?.trim();
1215
+ if (explicitRoot)
1216
+ return resolve(explicitRoot);
1217
+ if (!isSourceCliEntrypoint(process.argv[1]))
1218
+ return undefined;
1219
+ return dirname(fileURLToPath(import.meta.url));
1220
+ }
1221
+ function getLocalScreenciDependency(packageRoot, projectDir) {
1222
+ const relativePath = pathRelative(projectDir, packageRoot) || '.';
1223
+ const normalizedPath = relativePath.replace(/\\/g, '/');
1224
+ return `file:${normalizedPath.startsWith('.') ? normalizedPath : `./${normalizedPath}`}`;
1225
+ }
1226
+ async function buildLocalScreenciPackage(packageRoot) {
1227
+ logger.info(`Using local screenci package: ${packageRoot}`);
1228
+ logger.info("Running 'npm run build' for local screenci package...");
1229
+ await spawnInherited('npm', ['run', 'build'], packageRoot, 'screenci dev build');
1230
+ }
1231
+ async function installLocalScreenciPackage(projectDir, packageRoot) {
1232
+ const packageJsonPath = resolve(projectDir, 'package.json');
1233
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
1234
+ packageJson.dependencies = {
1235
+ ...packageJson.dependencies,
1236
+ screenci: getLocalScreenciDependency(packageRoot, projectDir),
1237
+ };
1238
+ await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
1239
+ logger.info('Installing local screenci package into this project...');
1240
+ await spawnInherited('npm', ['install', '--install-links'], projectDir, 'screenci dev install');
1241
+ }
1145
1242
  function buildChildEnv() {
1146
1243
  const { PATH, HOME, USER, LOGNAME, TMPDIR, TEMP, TMP } = process.env;
1147
1244
  return {
@@ -1160,6 +1257,7 @@ function buildChildEnv() {
1160
1257
  SCREENCI_IN_CONTAINER: process.env.SCREENCI_IN_CONTAINER,
1161
1258
  SCREENCI_RECORD: process.env.SCREENCI_RECORD,
1162
1259
  SCREENCI_SIGNAL_LOGGING: process.env.SCREENCI_SIGNAL_LOGGING,
1260
+ SCREENCI_DEV_PACKAGE_ROOT: process.env.SCREENCI_DEV_PACKAGE_ROOT,
1163
1261
  };
1164
1262
  }
1165
1263
  function requireContainerRuntime() {
@@ -1211,7 +1309,8 @@ function checkNodeVersion() {
1211
1309
  process.exit(1);
1212
1310
  }
1213
1311
  }
1214
- async function runInit(projectNameArg, verbose = false) {
1312
+ async function runInit(projectNameArg, options) {
1313
+ const { verbose, git, install, yes, skill, ci } = options;
1215
1314
  checkNodeVersion();
1216
1315
  checkContainerRuntimeForInit();
1217
1316
  const initCwd = getInitProjectRoot();
@@ -1229,10 +1328,26 @@ async function runInit(projectNameArg, verbose = false) {
1229
1328
  logger.error(`Error: Directory "${dirName}" already exists`);
1230
1329
  process.exit(1);
1231
1330
  }
1232
- const shouldAddPlaywrightCli = await confirm({
1233
- message: 'Do you want to write videos with an AI agent based on a URL and not just source code? If yes, playwright-cli will be also installed.',
1234
- default: true,
1235
- });
1331
+ const shouldInitializeGitRepository = yes
1332
+ ? true
1333
+ : git
1334
+ ? true
1335
+ : await promptInitGitRepository();
1336
+ const shouldInstallDependencies = yes
1337
+ ? true
1338
+ : install
1339
+ ? true
1340
+ : await promptInitDependencies();
1341
+ const shouldAddPlaywrightCli = yes
1342
+ ? true
1343
+ : skill
1344
+ ? true
1345
+ : await promptInitAiAuthoring();
1346
+ const shouldAddGithubActionCi = yes
1347
+ ? true
1348
+ : ci
1349
+ ? true
1350
+ : await promptInitGithubActionCi();
1236
1351
  const skillsArgs = [
1237
1352
  '--yes',
1238
1353
  'skills',
@@ -1244,15 +1359,28 @@ async function runInit(projectNameArg, verbose = false) {
1244
1359
  '-y',
1245
1360
  ];
1246
1361
  const skillsCommand = `npx ${skillsArgs.join(' ')}`;
1362
+ const devScreenciPackageRoot = getDevScreenciPackageRoot();
1363
+ const screenciDependency = devScreenciPackageRoot
1364
+ ? getLocalScreenciDependency(devScreenciPackageRoot, projectDir)
1365
+ : 'latest';
1366
+ if (devScreenciPackageRoot) {
1367
+ await buildLocalScreenciPackage(devScreenciPackageRoot);
1368
+ }
1247
1369
  await mkdir(resolve(projectDir, 'videos'), { recursive: true });
1248
- await mkdir(resolve(projectDir, '.github', 'workflows'), { recursive: true });
1370
+ if (shouldAddGithubActionCi) {
1371
+ await mkdir(resolve(projectDir, '.github', 'workflows'), {
1372
+ recursive: true,
1373
+ });
1374
+ }
1249
1375
  await writeFile(resolve(projectDir, 'screenci.config.ts'), generateConfig(projectName));
1250
- await writeFile(resolve(projectDir, 'package.json'), generatePackageJson(dirName, shouldAddPlaywrightCli));
1376
+ await writeFile(resolve(projectDir, 'package.json'), generatePackageJson(dirName, shouldAddPlaywrightCli, screenciDependency));
1251
1377
  await writeFile(resolve(projectDir, 'README.md'), generateReadme(projectName));
1252
1378
  await writeFile(resolve(projectDir, 'Dockerfile'), generateDockerfile());
1253
1379
  await writeFile(resolve(projectDir, '.gitignore'), generateGitignore());
1254
1380
  await writeFile(resolve(projectDir, 'videos', 'example.video.ts'), generateExampleVideo());
1255
- await writeFile(resolve(projectDir, '.github', 'workflows', 'record.yml'), generateGithubAction());
1381
+ if (shouldAddGithubActionCi) {
1382
+ await writeFile(resolve(projectDir, '.github', 'workflows', 'record.yml'), generateGithubAction());
1383
+ }
1256
1384
  await writeFile(resolve(projectDir, '.env'), '');
1257
1385
  logger.info(`Initialized screenci project "${projectName}" in ${projectDir}/`);
1258
1386
  logger.info('Files created:');
@@ -1262,44 +1390,77 @@ async function runInit(projectNameArg, verbose = false) {
1262
1390
  logger.info(' Dockerfile');
1263
1391
  logger.info(' .gitignore');
1264
1392
  logger.info(' videos/example.video.ts');
1265
- logger.info(' .github/workflows/record.yml');
1393
+ if (shouldAddGithubActionCi) {
1394
+ logger.info(' .github/workflows/record.yml');
1395
+ }
1266
1396
  logger.info(' .env (empty placeholder)');
1267
1397
  logger.info('');
1268
- logger.info('screenci requires dependencies to be installed.');
1269
- if (verbose) {
1270
- logger.info(`Running '${skillsCommand}'...`);
1271
- await spawnInherited('npx', skillsArgs, projectDir, 'screenci init');
1272
- }
1273
- else {
1274
- const spinner = ora('Adding ScreenCI skills...').start();
1275
- try {
1276
- await spawnSilent('npx', skillsArgs, projectDir);
1277
- spinner.succeed('ScreenCI skills added');
1398
+ if (shouldInitializeGitRepository) {
1399
+ if (verbose) {
1400
+ logger.info("Running 'git init'...");
1401
+ await spawnInherited('git', ['init'], projectDir, 'screenci init');
1278
1402
  }
1279
- catch (err) {
1280
- spinner.fail('ScreenCI skills install failed');
1281
- throw err;
1403
+ else {
1404
+ const spinner = ora('Initializing git repository...').start();
1405
+ try {
1406
+ await spawnSilent('git', ['init'], projectDir);
1407
+ spinner.succeed('Git repository initialized');
1408
+ }
1409
+ catch (err) {
1410
+ spinner.fail('git init failed');
1411
+ throw err;
1412
+ }
1282
1413
  }
1283
1414
  }
1284
- if (verbose) {
1285
- logger.info("Running 'npm install'...");
1286
- await spawnInherited('npm', ['install'], projectDir, 'screenci init');
1287
- }
1288
- else {
1289
- const spinner = ora('Running npm install...').start();
1290
- try {
1291
- await spawnSilent('npm', ['install', '--prefix', projectDir]);
1292
- spinner.succeed('npm install complete');
1415
+ if (shouldInstallDependencies) {
1416
+ logger.info('screenci requires dependencies to be installed.');
1417
+ if (verbose) {
1418
+ logger.info(`Running '${skillsCommand}'...`);
1419
+ await spawnInherited('npx', skillsArgs, projectDir, 'screenci init');
1293
1420
  }
1294
- catch (err) {
1295
- spinner.fail('npm install failed');
1296
- throw err;
1421
+ else {
1422
+ const spinner = ora('Adding ScreenCI skills...').start();
1423
+ try {
1424
+ await spawnSilent('npx', skillsArgs, projectDir);
1425
+ spinner.succeed('ScreenCI skills added');
1426
+ }
1427
+ catch (err) {
1428
+ spinner.fail('ScreenCI skills install failed');
1429
+ throw err;
1430
+ }
1297
1431
  }
1432
+ if (verbose) {
1433
+ const installArgs = devScreenciPackageRoot
1434
+ ? ['install', '--install-links']
1435
+ : ['install'];
1436
+ logger.info(`Running 'npm ${installArgs.join(' ')}'...`);
1437
+ await spawnInherited('npm', installArgs, projectDir, 'screenci init');
1438
+ }
1439
+ else {
1440
+ const spinner = ora('Running npm install...').start();
1441
+ try {
1442
+ const installArgs = devScreenciPackageRoot
1443
+ ? ['install', '--install-links', '--prefix', projectDir]
1444
+ : ['install', '--prefix', projectDir];
1445
+ await spawnSilent('npm', installArgs);
1446
+ spinner.succeed('npm install complete');
1447
+ }
1448
+ catch (err) {
1449
+ spinner.fail('npm install failed');
1450
+ throw err;
1451
+ }
1452
+ }
1453
+ logger.info("Local development requires Chromium for Playwright, running 'npx playwright install chromium --with-deps'...");
1454
+ await spawnInherited('npx', ['playwright', 'install', 'chromium', '--with-deps'], projectDir, 'screenci init');
1455
+ logger.info(`${pc.green('✔')} Playwright installed successfully`);
1456
+ }
1457
+ else {
1458
+ logger.info('Dependencies were not installed automatically.');
1459
+ logger.info('Run these commands when you are ready:');
1460
+ logger.info(` ${skillsCommand}`);
1461
+ logger.info(' npm install');
1462
+ logger.info(' npx playwright install chromium --with-deps');
1298
1463
  }
1299
- logger.info('Local development requires Chromium for Playwright.');
1300
- logger.info("Running 'npx playwright install chromium --with-deps'...");
1301
- await spawnInherited('npx', ['playwright', 'install', 'chromium', '--with-deps'], projectDir, 'screenci init');
1302
- logger.info('Warming the container image for faster first record...');
1303
1464
  const cliDir = dirname(fileURLToPath(import.meta.url));
1304
1465
  await buildRecordImages(requireContainerRuntime(), cliDir, resolve(projectDir, 'Dockerfile'), projectDir, verbose);
1305
1466
  logger.info('');
@@ -1356,6 +1517,9 @@ export async function main() {
1356
1517
  }
1357
1518
  }
1358
1519
  }
1520
+ if (useContainer) {
1521
+ await ensureScreenciSecret();
1522
+ }
1359
1523
  if (useContainer) {
1360
1524
  await runWithContainer(parsed.otherArgs, parsed.configPath, parsed.imageTag, parsed.verbose, parsed.forcedRuntime);
1361
1525
  }
@@ -1416,6 +1580,9 @@ export async function main() {
1416
1580
  .action(async () => {
1417
1581
  const parsed = parseConfigCliArgs(getSubcommandArgv('test'));
1418
1582
  await run('test', parsed.otherArgs, parsed.configPath);
1583
+ if (process.env.SCREENCI_IN_CONTAINER === 'true')
1584
+ return;
1585
+ logger.info('Tests passed. Run `npx screenci record` to render the videos.');
1419
1586
  });
1420
1587
  program
1421
1588
  .command('info')
@@ -1451,13 +1618,25 @@ export async function main() {
1451
1618
  program
1452
1619
  .command('init [name]')
1453
1620
  .description('Initialize a new screenci project')
1621
+ .option('--git', 'initialize a git repository without prompting')
1622
+ .option('--install', 'install skills, dependencies, and Chromium without prompting')
1623
+ .option('--ci', 'add GitHub Action CI without prompting')
1624
+ .option('--skill', 'enable playwright-cli without prompting')
1625
+ .option('-y, --yes', 'answer yes to all init prompts')
1454
1626
  .option('-v, --verbose', 'verbose output')
1455
1627
  .action(async (name, options) => {
1456
1628
  if (name === 'auth') {
1457
1629
  await runInitAuth();
1458
1630
  }
1459
1631
  else {
1460
- await runInit(name, options['verbose'] ?? false);
1632
+ await runInit(name, {
1633
+ verbose: options['verbose'] ?? false,
1634
+ git: options['git'] ?? false,
1635
+ install: options['install'] ?? false,
1636
+ yes: options['yes'] ?? false,
1637
+ skill: options['skill'] ?? false,
1638
+ ci: options['ci'] ?? false,
1639
+ });
1461
1640
  }
1462
1641
  });
1463
1642
  try {
@@ -1810,9 +1989,9 @@ async function run(command, additionalArgs, customConfigPath) {
1810
1989
  logger.error(errorMsg);
1811
1990
  process.exit(1);
1812
1991
  }
1813
- await ensureScreenciSecret();
1814
1992
  // Only validate args for record command
1815
1993
  if (command === 'record') {
1994
+ await ensureScreenciSecret();
1816
1995
  validateArgs(additionalArgs);
1817
1996
  const screenciDir = resolve(dirname(configPath), '.screenci');
1818
1997
  clearDirectory(screenciDir);
@@ -1822,6 +2001,12 @@ async function run(command, additionalArgs, customConfigPath) {
1822
2001
  logger.info(`Running ScreenCI ${mode} with npx...`);
1823
2002
  logger.info(`Using config: ${configPath}`);
1824
2003
  }
2004
+ const devScreenciPackageRoot = getDevScreenciPackageRoot();
2005
+ if (devScreenciPackageRoot) {
2006
+ const configDir = dirname(configPath);
2007
+ await buildLocalScreenciPackage(devScreenciPackageRoot);
2008
+ await installLocalScreenciPackage(configDir, devScreenciPackageRoot);
2009
+ }
1825
2010
  const playwrightArgs = [
1826
2011
  'playwright',
1827
2012
  'test',