@przeprogramowani/10x-cli 1.19.1 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,7 +68,7 @@ Once installed, just tell your agent to **set up 10x-cli** and it will pick up t
68
68
 
69
69
  | Flag | Description |
70
70
  |------|-------------|
71
- | `--tool <tool>` | AI coding tool: `claude-code`, `cursor`, `copilot`, `codex`, `windsurf`, `gemini`, `generic` |
71
+ | `--tool <tool>` | AI coding tool: `claude-code`, `cursor`, `copilot`, `codex`, `devin-desktop`, `gemini`, `generic` |
72
72
  | `--print` | Output artifact content to stdout instead of writing files |
73
73
  | `--type <type>` | Filter by artifact type: `skills`, `prompts`, `rules`, `configs` |
74
74
  | `--name <name>` | Filter by artifact name (requires `--type`) |
@@ -197,7 +197,7 @@ scoring live in the template and the instance.
197
197
  | Flag | Description |
198
198
  |------|-------------|
199
199
  | `--template-version <tag>` | Template tag to install (default: latest) |
200
- | `--tool <id>` | Agent tool for skill placement (`claude-code`, `cursor`, `copilot`, `codex`, `windsurf`, `gemini`, `generic`) |
200
+ | `--tool <id>` | Agent tool for skill placement (`claude-code`, `cursor`, `copilot`, `codex`, `devin-desktop`, `gemini`, `generic`) |
201
201
  | `--yes` | Run non-interactively, accepting defaults |
202
202
 
203
203
  ```bash
@@ -239,11 +239,14 @@ On first run, the CLI prompts you to choose your AI coding tool. Artifacts are w
239
239
  | Cursor | `.cursor/` | `.cursor/rules/10x-course.mdc` |
240
240
  | GitHub Copilot | `.github/` | `.github/copilot-instructions.md` |
241
241
  | Codex CLI | `.agents/` | `AGENTS.md` |
242
- | Windsurf | `.windsurf/` | `.windsurfrules` |
242
+ | Devin Desktop | `.devin/` | `AGENTS.md` |
243
243
  | Gemini CLI | `.gemini/` | `GEMINI.md` |
244
244
  | Generic | `.ai/` | `AGENTS.md` |
245
245
 
246
246
  Override anytime with `--tool <name>`. Your choice is saved in `~/.config/10x-cli/config.json`.
247
+ The former `windsurf` ID remains accepted as an alias and is upgraded to
248
+ `devin-desktop`; existing `.windsurf/` artifacts can be migrated by the normal
249
+ tool-switch prompt.
247
250
 
248
251
  ## Development
249
252
 
package/dist/index.mjs CHANGED
@@ -3183,7 +3183,7 @@ var cac = (name = "") => new CAC(name);
3183
3183
  // package.json
3184
3184
  var package_default = {
3185
3185
  name: "@przeprogramowani/10x-cli",
3186
- version: "1.19.1",
3186
+ version: "1.20.0",
3187
3187
  description: "Open-source CLI for 10xDevs course content",
3188
3188
  repository: {
3189
3189
  type: "git",
@@ -4296,15 +4296,16 @@ var PROFILES = {
4296
4296
  sentinelBegin: SENTINEL_BEGIN,
4297
4297
  sentinelEnd: SENTINEL_END
4298
4298
  },
4299
- windsurf: {
4300
- toolId: "windsurf",
4301
- displayName: "Windsurf",
4302
- skillPath: (n) => `.windsurf/skills/${n}/SKILL.md`,
4303
- skillDir: (n) => `.windsurf/skills/${n}`,
4304
- promptPath: (n) => `.windsurf/prompts/${n}.md`,
4305
- configPath: (n) => `.windsurf/config-templates/${n}`,
4306
- rulesFile: ".windsurfrules",
4307
- manifestDir: ".windsurf",
4299
+ "devin-desktop": {
4300
+ toolId: "devin-desktop",
4301
+ contentToolId: "windsurf",
4302
+ displayName: "Devin Desktop",
4303
+ skillPath: (n) => `.devin/skills/${n}/SKILL.md`,
4304
+ skillDir: (n) => `.devin/skills/${n}`,
4305
+ promptPath: (n) => `.devin/prompts/${n}.md`,
4306
+ configPath: (n) => `.devin/config-templates/${n}`,
4307
+ rulesFile: "AGENTS.md",
4308
+ manifestDir: ".devin",
4308
4309
  sentinelBegin: SENTINEL_BEGIN,
4309
4310
  sentinelEnd: SENTINEL_END
4310
4311
  },
@@ -4333,6 +4334,32 @@ var PROFILES = {
4333
4334
  sentinelEnd: SENTINEL_END
4334
4335
  }
4335
4336
  };
4337
+ var TOOL_ALIASES = {
4338
+ windsurf: "devin-desktop"
4339
+ };
4340
+ var LEGACY_PROFILES = {
4341
+ windsurf: {
4342
+ toolId: "windsurf",
4343
+ displayName: "Windsurf",
4344
+ skillPath: (n) => `.windsurf/skills/${n}/SKILL.md`,
4345
+ skillDir: (n) => `.windsurf/skills/${n}`,
4346
+ promptPath: (n) => `.windsurf/prompts/${n}.md`,
4347
+ configPath: (n) => `.windsurf/config-templates/${n}`,
4348
+ rulesFile: ".windsurfrules",
4349
+ manifestDir: ".windsurf",
4350
+ sentinelBegin: SENTINEL_BEGIN,
4351
+ sentinelEnd: SENTINEL_END
4352
+ }
4353
+ };
4354
+ function canonicalToolId(toolId) {
4355
+ return TOOL_ALIASES[toolId] ?? toolId;
4356
+ }
4357
+ function getToolProfile(toolId) {
4358
+ return PROFILES[canonicalToolId(toolId)];
4359
+ }
4360
+ function contentToolId(profile) {
4361
+ return profile.contentToolId ?? profile.toolId;
4362
+ }
4336
4363
  var DEFAULT_TOOL = "claude-code";
4337
4364
 
4338
4365
  // src/lib/tool-detect.ts
@@ -4391,16 +4418,34 @@ function detectTools(projectRoot) {
4391
4418
  } else if (hit("AGENTS.md") && !hit(".ai")) {
4392
4419
  signals.push({ profileId: "codex", confidence: "medium", reason: "AGENTS.md" });
4393
4420
  }
4394
- if (hit(".windsurf/" + MANIFEST_FILENAME)) {
4421
+ if (hit(".devin/" + MANIFEST_FILENAME)) {
4422
+ signals.push({
4423
+ profileId: "devin-desktop",
4424
+ confidence: "strong",
4425
+ reason: ".devin/.10x-cli-manifest.json"
4426
+ });
4427
+ } else if (hit(".devin/rules")) {
4428
+ signals.push({ profileId: "devin-desktop", confidence: "strong", reason: ".devin/rules/" });
4429
+ } else if (hit(".devin")) {
4430
+ signals.push({ profileId: "devin-desktop", confidence: "medium", reason: ".devin/ directory" });
4431
+ } else if (hit(".windsurf/" + MANIFEST_FILENAME)) {
4395
4432
  signals.push({
4396
- profileId: "windsurf",
4433
+ profileId: "devin-desktop",
4397
4434
  confidence: "strong",
4398
- reason: ".windsurf/.10x-cli-manifest.json"
4435
+ reason: ".windsurf/.10x-cli-manifest.json (legacy)"
4399
4436
  });
4400
4437
  } else if (hit(".windsurfrules")) {
4401
- signals.push({ profileId: "windsurf", confidence: "strong", reason: ".windsurfrules" });
4438
+ signals.push({
4439
+ profileId: "devin-desktop",
4440
+ confidence: "strong",
4441
+ reason: ".windsurfrules (legacy)"
4442
+ });
4402
4443
  } else if (hit(".windsurf")) {
4403
- signals.push({ profileId: "windsurf", confidence: "medium", reason: ".windsurf/ directory" });
4444
+ signals.push({
4445
+ profileId: "devin-desktop",
4446
+ confidence: "medium",
4447
+ reason: ".windsurf/ directory (legacy)"
4448
+ });
4404
4449
  }
4405
4450
  if (hit(".gemini/" + MANIFEST_FILENAME)) {
4406
4451
  signals.push({
@@ -4427,7 +4472,15 @@ function detectTools(projectRoot) {
4427
4472
  return rankSignals(signals);
4428
4473
  }
4429
4474
  var CONFIDENCE_ORDER = { strong: 3, medium: 2, weak: 1 };
4430
- var PROFILE_ORDER = ["claude-code", "cursor", "copilot", "codex", "windsurf", "gemini", "generic"];
4475
+ var PROFILE_ORDER = [
4476
+ "claude-code",
4477
+ "cursor",
4478
+ "copilot",
4479
+ "codex",
4480
+ "devin-desktop",
4481
+ "gemini",
4482
+ "generic"
4483
+ ];
4431
4484
  function rankSignals(signals) {
4432
4485
  return [...signals].sort((a2, b2) => {
4433
4486
  const c2 = CONFIDENCE_ORDER[b2.confidence] - CONFIDENCE_ORDER[a2.confidence];
@@ -4541,7 +4594,7 @@ async function runBenchKitInit(ctx, dirArg, options, deps = defaultDeps) {
4541
4594
  ...res.runnerDeps === "failed" ? ["Runner dependencies did not install — run 'npm ci --prefix .bench-kit/runner' yourself."] : res.runnerDeps === "installed" ? ["Runner dependencies installed (.bench-kit/runner/node_modules)."] : [],
4542
4595
  res.committed === true ? "Initialized a fresh git repository with an initial commit." : "Initialized a fresh git repository (initial commit skipped — commit the files yourself).",
4543
4596
  ...(res.warnings ?? []).map((warning) => `Warning: ${warning}`),
4544
- "Next: wire up secrets, then run 'bench validate' before the first run."
4597
+ `Next: go to ${targetDir}, launch you preferred agent and apply skill *bench-wiring*`
4545
4598
  ];
4546
4599
  output(ctx, humanLines.join(`
4547
4600
  `), {
@@ -4648,10 +4701,11 @@ function describeSync(counts) {
4648
4701
  }
4649
4702
  async function resolveInstanceTool(ctx, options, deps, existingInstance) {
4650
4703
  if (options.tool !== undefined) {
4651
- if (!PROFILES[options.tool]) {
4704
+ const canonicalId = canonicalToolId(options.tool);
4705
+ if (!getToolProfile(options.tool)) {
4652
4706
  outputError(ctx, "unknown_tool", `'${options.tool}' is not a supported agent tool.`, ExitCodes.USAGE, `Supported: ${Object.keys(PROFILES).join(", ")}.`);
4653
4707
  }
4654
- return { id: options.tool, explicit: true };
4708
+ return { id: canonicalId, explicit: true };
4655
4709
  }
4656
4710
  const signals = deps.detectToolSignals(process.cwd());
4657
4711
  const top = signals[0];
@@ -4666,7 +4720,7 @@ async function resolveInstanceTool(ctx, options, deps, existingInstance) {
4666
4720
  return { id: choice !== null && PROFILES[choice] ? choice : initial, explicit: choice !== null };
4667
4721
  }
4668
4722
  function skillRootFor(toolId) {
4669
- const profile = PROFILES[toolId] ?? PROFILES[DEFAULT_TOOL];
4723
+ const profile = getToolProfile(toolId) ?? PROFILES[DEFAULT_TOOL];
4670
4724
  return `${profile.manifestDir}/skills`;
4671
4725
  }
4672
4726
  function allToolProfiles() {
@@ -5304,7 +5358,7 @@ async function checkCliVersion() {
5304
5358
  function checkToolDirectory() {
5305
5359
  const cwd = process.cwd();
5306
5360
  const toolId = readToolConfig()?.tool ?? DEFAULT_TOOL;
5307
- const profile = PROFILES[toolId] ?? PROFILES[DEFAULT_TOOL];
5361
+ const profile = getToolProfile(toolId) ?? PROFILES[DEFAULT_TOOL];
5308
5362
  const dirName = profile.manifestDir;
5309
5363
  const toolDir = join5(cwd, dirName);
5310
5364
  if (!existsSync5(toolDir)) {
@@ -6009,7 +6063,7 @@ function removeEmptyParentDirs(filePath, skillDirAbs) {
6009
6063
  }
6010
6064
  function findOrphanedManifests(projectRoot, currentProfile) {
6011
6065
  const out = [];
6012
- for (const profile of Object.values(PROFILES)) {
6066
+ for (const profile of [...Object.values(PROFILES), ...Object.values(LEGACY_PROFILES)]) {
6013
6067
  if (profile.toolId === currentProfile.toolId)
6014
6068
  continue;
6015
6069
  const manifestPath = join6(projectRoot, profile.manifestDir, MANIFEST_FILENAME);
@@ -6282,13 +6336,14 @@ async function resolveToolProfile(flagOverride, projectRoot = process.cwd()) {
6282
6336
  }
6283
6337
  async function resolveProfileOnly(flagOverride, projectRoot) {
6284
6338
  if (flagOverride) {
6285
- const profile = PROFILES[flagOverride];
6339
+ const canonicalId = canonicalToolId(flagOverride);
6340
+ const profile = getToolProfile(flagOverride);
6286
6341
  if (!profile) {
6287
6342
  throw new Error(`Unknown tool '${flagOverride}'. Supported: ${Object.keys(PROFILES).join(", ")}`);
6288
6343
  }
6289
6344
  const existing = readToolConfig();
6290
- if (existing?.tool !== flagOverride) {
6291
- saveToolConfig({ ...existing ?? {}, tool: flagOverride });
6345
+ if (existing?.tool !== canonicalId) {
6346
+ saveToolConfig({ ...existing ?? {}, tool: canonicalId });
6292
6347
  if (process.stdout.isTTY) {
6293
6348
  process.stderr.write(`Default tool set to ${profile.displayName}.
6294
6349
  `);
@@ -6297,8 +6352,15 @@ async function resolveProfileOnly(flagOverride, projectRoot) {
6297
6352
  return profile;
6298
6353
  }
6299
6354
  const config = readToolConfig();
6300
- if (config?.tool && PROFILES[config.tool]) {
6301
- return PROFILES[config.tool];
6355
+ if (config?.tool) {
6356
+ const profile = getToolProfile(config.tool);
6357
+ if (profile) {
6358
+ const canonicalId = canonicalToolId(config.tool);
6359
+ if (canonicalId !== config.tool) {
6360
+ saveToolConfig({ ...config, tool: canonicalId });
6361
+ }
6362
+ return profile;
6363
+ }
6302
6364
  }
6303
6365
  if (process.stdout.isTTY) {
6304
6366
  const signals = detectTools(projectRoot);
@@ -6413,7 +6475,7 @@ function resolveCourseRulesFlag(argv) {
6413
6475
  }
6414
6476
  var DEFAULT_COURSE2 = "10xdevs3";
6415
6477
  function registerGetCommand(cli) {
6416
- cli.command("get <ref>", "Fetch and apply a lesson pack").option("--dry-run", "Show what would be written without touching the filesystem").option("--course <course>", "Override the course slug (default: 10xdevs3)").option("--tool <tool>", "AI coding tool (claude-code, cursor, copilot, codex, windsurf, gemini, generic)").option("--print", "Print artifact content to stdout instead of writing to files").option("--type <type>", "Artifact type filter: skills, prompts, rules, configs").option("--name <name>", "Artifact name filter (requires --type)").option("--lang <lang>", "Content language: en (default) or pl").option("--no-course-rules", "Skip applying the course rules block to your rules file (CLAUDE.md/AGENTS.md)").action(async (ref, options) => {
6478
+ cli.command("get <ref>", "Fetch and apply a lesson pack").option("--dry-run", "Show what would be written without touching the filesystem").option("--course <course>", "Override the course slug (default: 10xdevs3)").option("--tool <tool>", "AI coding tool (claude-code, cursor, copilot, codex, devin-desktop, gemini, generic)").option("--print", "Print artifact content to stdout instead of writing to files").option("--type <type>", "Artifact type filter: skills, prompts, rules, configs").option("--name <name>", "Artifact name filter (requires --type)").option("--lang <lang>", "Content language: en (default) or pl").option("--no-course-rules", "Skip applying the course rules block to your rules file (CLAUDE.md/AGENTS.md)").action(async (ref, options) => {
6417
6479
  const ctx = resolveContext(options);
6418
6480
  await runGet(ctx, ref, options);
6419
6481
  });
@@ -6456,7 +6518,7 @@ async function runGet(ctx, ref, options) {
6456
6518
  verbose(ctx, `fetching lesson ${course}/${parsed.lessonId}`);
6457
6519
  const result = await fetchLesson(course, parsed.lessonId, auth.access_token, {
6458
6520
  lang,
6459
- tool: profile.toolId
6521
+ tool: contentToolId(profile)
6460
6522
  });
6461
6523
  if (!result.ok) {
6462
6524
  handleLessonError(ctx, result.status, result.code, result.error, result.payload);
@@ -6528,7 +6590,7 @@ async function runPrintMode(ctx, lessonId, course, profile, token, lang, options
6528
6590
  }
6529
6591
  if (options.name) {
6530
6592
  verbose(ctx, `fetching artifact ${course}/${lessonId}/${options.type}/${options.name}`);
6531
- const result = await fetchArtifact(course, lessonId, options.type, options.name, profile.toolId, token, { lang });
6593
+ const result = await fetchArtifact(course, lessonId, options.type, options.name, contentToolId(profile), token, { lang });
6532
6594
  if (!result.ok) {
6533
6595
  handleLessonError(ctx, result.status, result.code, result.error, result.payload);
6534
6596
  }
@@ -6546,7 +6608,7 @@ async function runPrintMode(ctx, lessonId, course, profile, token, lang, options
6546
6608
  }
6547
6609
  } else {
6548
6610
  verbose(ctx, `fetching lesson ${course}/${lessonId} (filtering by ${options.type})`);
6549
- const result = await fetchLesson(course, lessonId, token, { lang, tool: profile.toolId });
6611
+ const result = await fetchLesson(course, lessonId, token, { lang, tool: contentToolId(profile) });
6550
6612
  if (!result.ok) {
6551
6613
  handleLessonError(ctx, result.status, result.code, result.error, result.payload);
6552
6614
  }
@@ -6848,7 +6910,7 @@ import { join as join9 } from "node:path";
6848
6910
  var DEFAULT_COURSE4 = "10xdevs3";
6849
6911
  var SUPPORTED_LANGS = ["en", "pl"];
6850
6912
  function registerSyncCommand(cli) {
6851
- cli.command("sync", "Bulk-download and update lessons, reporting what changed upstream").option("--all", "Sync every unlocked lesson (default: only lessons you've already downloaded)").option("--dry-run", "Show what would change without writing").option("--force", "Ignore the cheap-skip digest and overwrite local edits with upstream").option("--module <module>", "Limit to one module (e.g. 'm2' or '2')").option("--course <course>", "Override the course slug (default: 10xdevs3)").option("--tool <tool>", "AI coding tool (claude-code, cursor, copilot, codex, windsurf, gemini, generic)").option("--lang <lang>", "Content language: en (default) or pl").option("--no-course-rules", "Skip applying the course rules block to your rules file (CLAUDE.md/AGENTS.md)").action(async (options) => {
6913
+ cli.command("sync", "Bulk-download and update lessons, reporting what changed upstream").option("--all", "Sync every unlocked lesson (default: only lessons you've already downloaded)").option("--dry-run", "Show what would change without writing").option("--force", "Ignore the cheap-skip digest and overwrite local edits with upstream").option("--module <module>", "Limit to one module (e.g. 'm2' or '2')").option("--course <course>", "Override the course slug (default: 10xdevs3)").option("--tool <tool>", "AI coding tool (claude-code, cursor, copilot, codex, devin-desktop, gemini, generic)").option("--lang <lang>", "Content language: en (default) or pl").option("--no-course-rules", "Skip applying the course rules block to your rules file (CLAUDE.md/AGENTS.md)").action(async (options) => {
6852
6914
  const ctx = resolveContext(options);
6853
6915
  await runSync(ctx, options);
6854
6916
  });
@@ -6949,7 +7011,7 @@ async function syncLesson(ctx, lesson, opts) {
6949
7011
  verbose(ctx, `${lesson.lessonId}: fetching`);
6950
7012
  const result = await fetchLesson(opts.course, lesson.lessonId, opts.token, {
6951
7013
  lang: opts.lang,
6952
- tool: opts.profile.toolId,
7014
+ tool: contentToolId(opts.profile),
6953
7015
  signal: opts.signal
6954
7016
  });
6955
7017
  if (!result.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@przeprogramowani/10x-cli",
3
- "version": "1.19.1",
3
+ "version": "1.20.0",
4
4
  "description": "Open-source CLI for 10xDevs course content",
5
5
  "repository": {
6
6
  "type": "git",
@@ -81,6 +81,7 @@ The primary daily command. Fetches a lesson bundle from the API and writes skill
81
81
  | Cursor | `.cursor/skills/<name>/SKILL.md` | `.cursor/prompts/<name>.md` | `.cursor/rules/10x-course.mdc` | `.cursor/config-templates/<name>` |
82
82
  | GitHub Copilot | `.github/skills/<name>/SKILL.md` | `.github/prompts/<name>.md` | `.github/copilot-instructions.md` | `.github/config-templates/<name>` |
83
83
  | Codex CLI | `.agents/skills/<name>/SKILL.md` | `.agents/prompts/<name>.md` | `AGENTS.md` | `.agents/config-templates/<name>` |
84
+ | Devin Desktop | `.devin/skills/<name>/SKILL.md` | `.devin/prompts/<name>.md` | `AGENTS.md` | `.devin/config-templates/<name>` |
84
85
  | Generic | `.ai/skills/<name>/SKILL.md` | `.ai/prompts/<name>.md` | `AGENTS.md` | `.ai/config-templates/<name>` |
85
86
 
86
87
  **Re-applying a lesson** overwrites skills and prompts if content changed, updates the rules sentinel block, but never overwrites config templates (they may contain user edits).