@cleocode/cleo 2026.6.2 → 2026.6.4

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/cli/index.js CHANGED
@@ -12302,6 +12302,13 @@ var init_plugin_llm = __esm({
12302
12302
  }
12303
12303
  });
12304
12304
 
12305
+ // packages/contracts/src/llm/system-resolver.ts
12306
+ var init_system_resolver = __esm({
12307
+ "packages/contracts/src/llm/system-resolver.ts"() {
12308
+ "use strict";
12309
+ }
12310
+ });
12311
+
12305
12312
  // packages/contracts/src/memory/observe.ts
12306
12313
  var init_observe = __esm({
12307
12314
  "packages/contracts/src/memory/observe.ts"() {
@@ -14224,6 +14231,7 @@ var init_src2 = __esm({
14224
14231
  init_invariants();
14225
14232
  init_lafs();
14226
14233
  init_plugin_llm();
14234
+ init_system_resolver();
14227
14235
  init_observe();
14228
14236
  init_migration_parity();
14229
14237
  init_mvi();
@@ -28898,12 +28906,12 @@ async function orchestrateClassify(request, context, projectRoot, taskId) {
28898
28906
  }
28899
28907
  try {
28900
28908
  const { getCleoCantWorkflowsDir } = await import("@cleocode/core/internal");
28901
- const { readFileSync: readFileSync22, readdirSync: readdirSync3, existsSync: existsSync21 } = await import("node:fs");
28909
+ const { readFileSync: readFileSync22, readdirSync: readdirSync3, existsSync: existsSync22 } = await import("node:fs");
28902
28910
  const { join: join38 } = await import("node:path");
28903
28911
  const workflowsDir = getCleoCantWorkflowsDir();
28904
28912
  const combined = `${request} ${context ?? ""}`.toLowerCase();
28905
28913
  const matches = [];
28906
- if (existsSync21(workflowsDir)) {
28914
+ if (existsSync22(workflowsDir)) {
28907
28915
  const files = readdirSync3(workflowsDir).filter((f) => f.endsWith(".cant"));
28908
28916
  for (const file of files) {
28909
28917
  try {
@@ -28923,7 +28931,7 @@ async function orchestrateClassify(request, context, projectRoot, taskId) {
28923
28931
  }
28924
28932
  }
28925
28933
  const localCantDir = join38(projectRoot, CLEO_DIR_NAME2, WORKFLOWS_SUBDIR);
28926
- if (existsSync21(localCantDir)) {
28934
+ if (existsSync22(localCantDir)) {
28927
28935
  const files = readdirSync3(localCantDir).filter((f) => f.endsWith(".cant"));
28928
28936
  for (const file of files) {
28929
28937
  try {
@@ -36988,12 +36996,12 @@ var init_agent = __esm({
36988
36996
  transportConfig: {},
36989
36997
  isActive: true
36990
36998
  });
36991
- const { existsSync: existsSync21, mkdirSync: mkdirSync7, writeFileSync: writeFileSync7 } = await import("node:fs");
36999
+ const { existsSync: existsSync22, mkdirSync: mkdirSync7, writeFileSync: writeFileSync7 } = await import("node:fs");
36992
37000
  const { join: join38 } = await import("node:path");
36993
37001
  const cantDir = join38(CLEO_DIR_NAME3, AGENTS_SUBDIR);
36994
37002
  const cantPath = join38(cantDir, `${agentId}.cant`);
36995
37003
  let cantScaffolded = false;
36996
- if (!existsSync21(cantPath)) {
37004
+ if (!existsSync22(cantPath)) {
36997
37005
  mkdirSync7(cantDir, { recursive: true });
36998
37006
  const role = classification ?? "specialist";
36999
37007
  const cantContent = `---
@@ -37053,7 +37061,7 @@ agent ${agentId}:
37053
37061
  data: {
37054
37062
  agentId: credential.agentId,
37055
37063
  displayName: credential.displayName,
37056
- cantFile: cantScaffolded ? cantPath : existsSync21(cantPath) ? cantPath : null,
37064
+ cantFile: cantScaffolded ? cantPath : existsSync22(cantPath) ? cantPath : null,
37057
37065
  cantScaffolded
37058
37066
  }
37059
37067
  },
@@ -37172,7 +37180,7 @@ agent ${agentId}:
37172
37180
  try {
37173
37181
  const { AgentRegistryAccessor } = await import("@cleocode/core/agents");
37174
37182
  const { createRuntime } = await import("@cleocode/runtime");
37175
- const { existsSync: existsSync21, readFileSync: readFileSync22 } = await import("node:fs");
37183
+ const { existsSync: existsSync22, readFileSync: readFileSync22 } = await import("node:fs");
37176
37184
  const { join: join38 } = await import("node:path");
37177
37185
  await openCleoDb("project");
37178
37186
  const registry = new AgentRegistryAccessor(getProjectRoot24());
@@ -37194,7 +37202,7 @@ agent ${agentId}:
37194
37202
  let profile = null;
37195
37203
  let cantValidation = null;
37196
37204
  const cantPath = args.cant ?? join38(CLEO_DIR_NAME3, AGENTS_SUBDIR, `${args.agentId}.cant`);
37197
- if (existsSync21(cantPath)) {
37205
+ if (existsSync22(cantPath)) {
37198
37206
  profile = readFileSync22(cantPath, "utf-8");
37199
37207
  try {
37200
37208
  const cantModule = await import("@cleocode/cant");
@@ -37718,7 +37726,7 @@ Task ${args.taskId} reassigned to you by ${active.agentId}. Run: cleo show ${arg
37718
37726
  try {
37719
37727
  const { AgentRegistryAccessor } = await import("@cleocode/core/agents");
37720
37728
  const { createRuntime } = await import("@cleocode/runtime");
37721
- const { existsSync: existsSync21 } = await import("node:fs");
37729
+ const { existsSync: existsSync22 } = await import("node:fs");
37722
37730
  const { join: join38 } = await import("node:path");
37723
37731
  await openCleoDb("project");
37724
37732
  const registry = new AgentRegistryAccessor(getProjectRoot24());
@@ -37737,7 +37745,7 @@ Task ${args.taskId} reassigned to you by ${active.agentId}. Run: cleo show ${arg
37737
37745
  await registry.update(args.agentId, { isActive: true });
37738
37746
  await registry.markUsed(args.agentId);
37739
37747
  const cantPath = join38(CLEO_DIR_NAME3, AGENTS_SUBDIR, `${args.agentId}.cant`);
37740
- const hasProfile = existsSync21(cantPath);
37748
+ const hasProfile = existsSync22(cantPath);
37741
37749
  const runtime = await createRuntime(registry, {
37742
37750
  agentId: args.agentId,
37743
37751
  pollIntervalMs: 5e3,
@@ -38530,10 +38538,10 @@ Task ${args.taskId} reassigned to you by ${active.agentId}. Run: cleo show ${arg
38530
38538
  async run({ args }) {
38531
38539
  let tempDir = null;
38532
38540
  try {
38533
- const { existsSync: existsSync21 } = await import("node:fs");
38541
+ const { existsSync: existsSync22 } = await import("node:fs");
38534
38542
  const { basename, resolve: resolve11 } = await import("node:path");
38535
38543
  const resolvedPath = resolve11(args.path);
38536
- if (!existsSync21(resolvedPath)) {
38544
+ if (!existsSync22(resolvedPath)) {
38537
38545
  cliOutput(
38538
38546
  {
38539
38547
  success: false,
@@ -38658,11 +38666,11 @@ Task ${args.taskId} reassigned to you by ${active.agentId}. Run: cleo show ${arg
38658
38666
  },
38659
38667
  async run({ args }) {
38660
38668
  try {
38661
- const { existsSync: existsSync21, statSync: statSync2 } = await import("node:fs");
38669
+ const { existsSync: existsSync22, statSync: statSync2 } = await import("node:fs");
38662
38670
  const { resolve: resolve11, basename, dirname: dirname12 } = await import("node:path");
38663
38671
  const { execFileSync: execFileSync5 } = await import("node:child_process");
38664
38672
  const resolvedDir = resolve11(args.dir);
38665
- if (!existsSync21(resolvedDir) || !statSync2(resolvedDir).isDirectory()) {
38673
+ if (!existsSync22(resolvedDir) || !statSync2(resolvedDir).isDirectory()) {
38666
38674
  cliOutput(
38667
38675
  {
38668
38676
  success: false,
@@ -38678,7 +38686,7 @@ Task ${args.taskId} reassigned to you by ${active.agentId}. Run: cleo show ${arg
38678
38686
  }
38679
38687
  const { join: join38 } = await import("node:path");
38680
38688
  const personaPath = join38(resolvedDir, "persona.cant");
38681
- if (!existsSync21(personaPath)) {
38689
+ if (!existsSync22(personaPath)) {
38682
38690
  cliOutput(
38683
38691
  {
38684
38692
  success: false,
@@ -38973,10 +38981,10 @@ Task ${args.taskId} reassigned to you by ${active.agentId}. Run: cleo show ${arg
38973
38981
  },
38974
38982
  async run({ args }) {
38975
38983
  try {
38976
- const { existsSync: existsSync21, readFileSync: readFileSync22, mkdirSync: mkdirSync7 } = await import("node:fs");
38984
+ const { existsSync: existsSync22, readFileSync: readFileSync22, mkdirSync: mkdirSync7 } = await import("node:fs");
38977
38985
  const { resolve: resolve11, join: join38 } = await import("node:path");
38978
38986
  const specPath = resolve11(args.spec);
38979
- if (!existsSync21(specPath)) {
38987
+ if (!existsSync22(specPath)) {
38980
38988
  cliError(`spec file not found: ${specPath}`, 4, { name: "E_NOT_FOUND" });
38981
38989
  process.exitCode = 4;
38982
38990
  return;
@@ -41928,11 +41936,11 @@ var init_caamp = __esm({
41928
41936
  }
41929
41937
  if (args["dry-run"]) {
41930
41938
  const { parseCaampBlocks } = await import("@cleocode/caamp");
41931
- const { existsSync: existsSync21 } = await import("node:fs");
41939
+ const { existsSync: existsSync22 } = await import("node:fs");
41932
41940
  const { readFile: readFile9 } = await import("node:fs/promises");
41933
41941
  const dryResults = [];
41934
41942
  for (const filePath of filePaths) {
41935
- if (!existsSync21(filePath)) {
41943
+ if (!existsSync22(filePath)) {
41936
41944
  dryResults.push({ filePath, exists: false, blockCount: 0, wouldRemove: 0 });
41937
41945
  continue;
41938
41946
  }
@@ -43026,7 +43034,7 @@ var init_check2 = __esm({
43026
43034
  },
43027
43035
  async run({ args }) {
43028
43036
  const { spawnSync } = await import("node:child_process");
43029
- const { existsSync: existsSync21 } = await import("node:fs");
43037
+ const { existsSync: existsSync22 } = await import("node:fs");
43030
43038
  const { join: join38, resolve: resolve11 } = await import("node:path");
43031
43039
  const strict = Boolean(args.strict);
43032
43040
  const jsonOnly = Boolean(args.json);
@@ -43068,7 +43076,7 @@ var init_check2 = __esm({
43068
43076
  let anyFailed = false;
43069
43077
  for (const gate of gates) {
43070
43078
  const scriptPath = join38(repoRoot, gate.script);
43071
- if (!existsSync21(scriptPath)) {
43079
+ if (!existsSync22(scriptPath)) {
43072
43080
  results.push({
43073
43081
  id: gate.id,
43074
43082
  task: gate.task,
@@ -50684,6 +50692,97 @@ ${this.prefix}: \u2717 ${message}
50684
50692
  }
50685
50693
  });
50686
50694
 
50695
+ // packages/cleo/src/cli/commands/doctor-exodus-residue.ts
50696
+ import {
50697
+ archiveStrandedResidue,
50698
+ buildExodusPlan,
50699
+ detectStrandedResidue
50700
+ } from "@cleocode/core/store/exodus/index.js";
50701
+ var doctorExodusResidueCommand;
50702
+ var init_doctor_exodus_residue = __esm({
50703
+ "packages/cleo/src/cli/commands/doctor-exodus-residue.ts"() {
50704
+ "use strict";
50705
+ init_define_cli_command();
50706
+ init_renderers();
50707
+ doctorExodusResidueCommand = defineCommand({
50708
+ meta: {
50709
+ name: "exodus-residue",
50710
+ description: "Detect legacy exodus source DBs still present after a cutover (stranded residue that re-arms the exodus-on-open corruption trigger). Use --fix to archive them into _archive/ (reversible move, never deletes)."
50711
+ },
50712
+ args: {
50713
+ fix: {
50714
+ type: "boolean",
50715
+ description: "Archive stranded legacy source DBs into _archive/ (move, never delete)",
50716
+ default: false
50717
+ }
50718
+ },
50719
+ run({ args }) {
50720
+ const fix = args.fix === true;
50721
+ const cwd = process.cwd();
50722
+ const plan = buildExodusPlan(cwd);
50723
+ const stranded = detectStrandedResidue(plan.sources, cwd);
50724
+ if (stranded.length === 0) {
50725
+ cliOutput(
50726
+ {
50727
+ kind: "generic",
50728
+ ok: true,
50729
+ strandedCount: 0,
50730
+ stranded: [],
50731
+ fixed: false
50732
+ },
50733
+ {
50734
+ command: "doctor exodus-residue",
50735
+ message: "No stranded legacy exodus source DBs (clean cutover or pre-migration install)."
50736
+ }
50737
+ );
50738
+ return;
50739
+ }
50740
+ for (const entry of stranded) {
50741
+ humanInfo(` STRANDED [${entry.scope}] ${entry.name} \u2192 ${entry.path}`);
50742
+ }
50743
+ if (!fix) {
50744
+ cliOutput(
50745
+ {
50746
+ kind: "generic",
50747
+ ok: false,
50748
+ strandedCount: stranded.length,
50749
+ stranded: stranded.map((s) => ({ name: s.name, path: s.path, scope: s.scope })),
50750
+ fixed: false
50751
+ },
50752
+ {
50753
+ command: "doctor exodus-residue",
50754
+ message: `${stranded.length} stranded legacy source DB(s) found. Run \`cleo doctor exodus-residue --fix\` to archive them.`
50755
+ }
50756
+ );
50757
+ process.exitCode = 1;
50758
+ return;
50759
+ }
50760
+ const archived = archiveStrandedResidue(stranded, plan.sources, cwd);
50761
+ const movedCount = archived.filter((a) => a.action === "archived").length;
50762
+ humanInfo(` Archived ${movedCount} stranded source DB(s) \u2192 _archive/.`);
50763
+ cliOutput(
50764
+ {
50765
+ kind: "generic",
50766
+ ok: true,
50767
+ strandedCount: stranded.length,
50768
+ stranded: stranded.map((s) => ({ name: s.name, path: s.path, scope: s.scope })),
50769
+ fixed: true,
50770
+ archived: archived.map((a) => ({
50771
+ name: a.name,
50772
+ action: a.action,
50773
+ archivedTo: a.archivedTo
50774
+ }))
50775
+ },
50776
+ {
50777
+ command: "doctor exodus-residue",
50778
+ message: `Archived ${movedCount} stranded legacy source DB(s) into _archive/.`
50779
+ }
50780
+ );
50781
+ }
50782
+ });
50783
+ }
50784
+ });
50785
+
50687
50786
  // packages/cleo/src/cli/commands/migrate-agents-v2.ts
50688
50787
  var migrate_agents_v2_exports = {};
50689
50788
  __export(migrate_agents_v2_exports, {
@@ -51048,6 +51147,7 @@ var init_doctor = __esm({
51048
51147
  init_progress();
51049
51148
  init_renderers();
51050
51149
  init_doctor_db_substrate();
51150
+ init_doctor_exodus_residue();
51051
51151
  init_doctor_legacy_backups();
51052
51152
  init_doctor_projects();
51053
51153
  init_doctor_release_readiness();
@@ -51066,6 +51166,8 @@ var init_doctor = __esm({
51066
51166
  "db-substrate": doctorDbSubstrateCommand,
51067
51167
  // T10309 / Saga T10281 / Epic T10282 — Legacy-backup walker
51068
51168
  "legacy-backups": doctorLegacyBackupsCommand,
51169
+ // T11777 / Saga T11242 / Epic T11249 — exodus stranded-residue check (+ --fix)
51170
+ "exodus-residue": doctorExodusResidueCommand,
51069
51171
  // T10458 / Saga T10431 / Epic T10436 — Release-readiness preflight
51070
51172
  "release-readiness": doctorReleaseReadinessCommand
51071
51173
  },
@@ -51268,7 +51370,7 @@ var init_doctor = __esm({
51268
51370
  if (args["check-worktree-config"]) {
51269
51371
  const { execFileSync: execFile2 } = await import("node:child_process");
51270
51372
  const { join: pathJoin } = await import("node:path");
51271
- const { existsSync: existsSync21 } = await import("node:fs");
51373
+ const { existsSync: existsSync22 } = await import("node:fs");
51272
51374
  const { detectAndHealCoreWorktreeLeak } = await import("@cleocode/worktree");
51273
51375
  const projectRoot = getProjectRoot42();
51274
51376
  let gitRoot = projectRoot;
@@ -51285,7 +51387,7 @@ var init_doctor = __esm({
51285
51387
  if (isDryRun) {
51286
51388
  const gitConfigPath = pathJoin(gitRoot, ".git", "config");
51287
51389
  let leakedValue;
51288
- if (existsSync21(gitConfigPath)) {
51390
+ if (existsSync22(gitConfigPath)) {
51289
51391
  try {
51290
51392
  leakedValue = execFile2("git", ["config", "--file", gitConfigPath, "--get", "core.worktree"], {
51291
51393
  encoding: "utf-8",
@@ -52188,14 +52290,18 @@ var exodus_exports = {};
52188
52290
  __export(exodus_exports, {
52189
52291
  exodusCommand: () => exodusCommand
52190
52292
  });
52293
+ import { existsSync as existsSync15 } from "node:fs";
52191
52294
  import { resolveDualScopeDbPath } from "@cleocode/core/store/dual-scope-db.js";
52192
52295
  import {
52193
- buildExodusPlan,
52296
+ archiveMigratedSources,
52297
+ buildExodusPlan as buildExodusPlan2,
52194
52298
  runExodusMigrate,
52195
52299
  runExodusStatus,
52196
52300
  runExodusVerify,
52197
- sourcesPresent
52301
+ sourcesPresent,
52302
+ verifyMigration
52198
52303
  } from "@cleocode/core/store/exodus/index.js";
52304
+ import { isDataContinuityOk } from "@cleocode/core/store/exodus/on-open.js";
52199
52305
  function fmtBytes2(n) {
52200
52306
  if (n < 1024) return `${n} B`;
52201
52307
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
@@ -52249,7 +52355,7 @@ var init_exodus = __esm({
52249
52355
  async run({ args }) {
52250
52356
  const dryRun = args["dry-run"] === true;
52251
52357
  const forceCrossVersion = args["force-cross-version"] === true;
52252
- const plan = buildExodusPlan(process.cwd());
52358
+ const plan = buildExodusPlan2(process.cwd());
52253
52359
  humanInfo(`Exodus migration plan:`);
52254
52360
  humanInfo(
52255
52361
  ` Source DBs (${plan.sources.length} total, ${fmtBytes2(plan.totalSourceBytes)} combined):`
@@ -52323,6 +52429,25 @@ var init_exodus = __esm({
52323
52429
  }
52324
52430
  const copied = result.tables.filter((t) => !t.skipped).reduce((n, t) => n + t.rowsCopied, 0);
52325
52431
  const skipped = result.tables.filter((t) => t.skipped).length;
52432
+ const verifyResult = verifyMigration(
52433
+ plan.sources,
52434
+ plan.projectDbPath,
52435
+ plan.globalDbPath,
52436
+ (msg) => humanInfo(` verify: ${msg}`)
52437
+ );
52438
+ let archived = [];
52439
+ if (isDataContinuityOk(verifyResult)) {
52440
+ const consumed = plan.sources.filter((s) => existsSync15(s.path));
52441
+ const archiveResult = archiveMigratedSources(consumed, process.cwd());
52442
+ archived = archiveResult.sources.filter((s) => s.action === "archived").map((s) => s.name);
52443
+ humanInfo(
52444
+ ` Archived ${archived.length} legacy source DB(s) \u2192 _archive/ and sealed completion marker(s): ${archiveResult.markersWritten.join(", ")}`
52445
+ );
52446
+ } else {
52447
+ humanInfo(
52448
+ " Parity validation did NOT pass \u2014 legacy sources LEFT IN PLACE (not archived). Run `cleo exodus verify` to inspect."
52449
+ );
52450
+ }
52326
52451
  cliOutput(
52327
52452
  {
52328
52453
  kind: "generic",
@@ -52332,7 +52457,9 @@ var init_exodus = __esm({
52332
52457
  rowsCopied: copied,
52333
52458
  tablesSkipped: skipped,
52334
52459
  stagingDir: result.stagingDir,
52335
- backupCount: result.backupPaths.length
52460
+ backupCount: result.backupPaths.length,
52461
+ archivedSources: archived,
52462
+ archived: archived.length > 0
52336
52463
  },
52337
52464
  tables: result.tables
52338
52465
  },
@@ -52357,7 +52484,7 @@ var init_exodus = __esm({
52357
52484
  },
52358
52485
  run({ args }) {
52359
52486
  const showPassing = args["show-passing"] === true;
52360
- const plan = buildExodusPlan(process.cwd());
52487
+ const plan = buildExodusPlan2(process.cwd());
52361
52488
  const projectDbPath = resolveDualScopeDbPath("project", process.cwd());
52362
52489
  const globalDbPath = resolveDualScopeDbPath("global");
52363
52490
  humanInfo("Running equivalence verification\u2026");
@@ -54571,7 +54698,7 @@ __export(init_exports, {
54571
54698
  getWorkflowTemplatesDir: () => getWorkflowTemplatesDir2,
54572
54699
  initCommand: () => initCommand2
54573
54700
  });
54574
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "node:fs";
54701
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
54575
54702
  import { join as join27 } from "node:path";
54576
54703
  import { fileURLToPath as fileURLToPath5 } from "node:url";
54577
54704
  import {
@@ -54588,8 +54715,8 @@ function getGitignoreTemplate() {
54588
54715
  const packageRoot = join27(thisFile, "..", "..", "..", "..");
54589
54716
  const localTemplatePath = join27(packageRoot, "templates", "cleo-gitignore");
54590
54717
  const monorepoTemplatePath = join27(packageRoot, "..", "..", "templates", "cleo-gitignore");
54591
- const templatePath = existsSync15(localTemplatePath) ? localTemplatePath : monorepoTemplatePath;
54592
- if (existsSync15(templatePath)) {
54718
+ const templatePath = existsSync16(localTemplatePath) ? localTemplatePath : monorepoTemplatePath;
54719
+ if (existsSync16(templatePath)) {
54593
54720
  return readFileSync14(templatePath, "utf-8");
54594
54721
  }
54595
54722
  } catch {
@@ -55892,7 +56019,7 @@ import {
55892
56019
  refreshPkceToken
55893
56020
  } from "@cleocode/core/llm/oauth/pkce.js";
55894
56021
  import { getKimiCodeMshHeaders } from "@cleocode/core/llm/provider-registry/builtin/kimi-code.js";
55895
- import { getProviderProfile } from "@cleocode/core/llm/provider-registry/index.js";
56022
+ import { getProviderProfile, listProviders } from "@cleocode/core/llm/provider-registry/index.js";
55896
56023
  async function runLlmLogin(provider, opts) {
55897
56024
  const meta = { operation: "llm.login", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
55898
56025
  const profile = await getProviderProfile(provider);
@@ -55901,34 +56028,48 @@ async function runLlmLogin(provider, opts) {
55901
56028
  return _runKimiCodeLogin(opts, meta);
55902
56029
  }
55903
56030
  if (oauthMode === "pkce") {
55904
- return _runPkceLogin(provider, profile.oauth, opts, meta);
56031
+ return _runPkceLogin(profile.name, profile.oauth, opts, meta);
55905
56032
  }
55906
56033
  return {
55907
56034
  success: false,
55908
56035
  error: {
55909
56036
  code: "E_NOT_IMPLEMENTED",
55910
56037
  codeName: "E_NOT_IMPLEMENTED",
55911
- message: `OAuth login for '${provider}' is not yet wired. Supported providers: 'anthropic' (PKCE), 'kimi-code' (device-code). To add credentials for other providers use 'cleo llm add <provider> --api-key-stdin'.`
56038
+ message: `OAuth login for '${provider}' is not yet wired. ${await _supportedOauthProvidersHint()} For any other provider, add an API key with 'cleo llm add <provider> --api-key-stdin'.`
55912
56039
  },
55913
56040
  meta
55914
56041
  };
55915
56042
  }
56043
+ async function _supportedOauthProvidersHint() {
56044
+ try {
56045
+ const profiles = await listProviders();
56046
+ const oauthable = profiles.flatMap((p) => p.oauth ? [`'${p.name}' (${p.oauth.mode})`] : []);
56047
+ return oauthable.length > 0 ? `Providers with OAuth login: ${oauthable.join(", ")}.` : "No providers currently expose OAuth login.";
56048
+ } catch {
56049
+ return "Providers with OAuth login: 'anthropic' (pkce), 'openai' (pkce), 'kimi-code' (device-code).";
56050
+ }
56051
+ }
55916
56052
  async function _runPkceLogin(provider, oauthCfg, opts, meta) {
55917
56053
  const { codeVerifier, codeChallenge } = await generatePkcePair();
55918
56054
  const state = _generateState();
55919
56055
  const isHeadless = opts.headless || process.env["CLEO_HEADLESS"] === "1";
55920
- const port = isHeadless ? 0 : await _findFreePort();
55921
- const redirectUri = isHeadless ? oauthCfg.redirectUri ?? "http://localhost" : `http://localhost:${port}/callback`;
56056
+ const fixedPort = isHeadless ? null : _parseFixedLoopbackPort(oauthCfg.redirectUri);
56057
+ const isNonLoopbackPasteBack = !isHeadless && fixedPort === null && !_isLoopbackUri(oauthCfg.redirectUri);
56058
+ const effectiveHeadless = isHeadless || isNonLoopbackPasteBack;
56059
+ const port = effectiveHeadless ? 0 : fixedPort ?? await _findFreePort();
56060
+ const randomRedirect = `http://localhost:${port}/callback`;
56061
+ const redirectUri = effectiveHeadless ? oauthCfg.redirectUri ?? "http://localhost" : fixedPort != null ? oauthCfg.redirectUri ?? randomRedirect : randomRedirect;
55922
56062
  const authUrl = buildAuthorizationUrl({
55923
56063
  authorizationEndpoint: oauthCfg.authorizationEndpoint ?? "",
55924
56064
  clientId: oauthCfg.clientId,
55925
56065
  redirectUri,
55926
56066
  scope: oauthCfg.scope ?? "",
55927
56067
  codeChallenge,
55928
- state
56068
+ state,
56069
+ extraParams: oauthCfg.extraAuthParams
55929
56070
  });
55930
56071
  let code;
55931
- if (isHeadless) {
56072
+ if (effectiveHeadless) {
55932
56073
  code = await _headlessPkceFlow(provider, authUrl);
55933
56074
  } else {
55934
56075
  const result = await _localCallbackPkceFlow(provider, authUrl, state, port);
@@ -55962,6 +56103,7 @@ async function _runPkceLogin(provider, oauthCfg, opts, meta) {
55962
56103
  process.stderr.write("\r Authorization approved. \n\n");
55963
56104
  const label = opts.label ?? "oauth-login";
55964
56105
  const expiresAt = typeof tokens.expiresIn === "number" ? Date.now() + tokens.expiresIn * 1e3 : void 0;
56106
+ const oauthExtraHeaders = provider === "anthropic" ? { "anthropic-beta": "oauth-2025-04-20" } : void 0;
55965
56107
  try {
55966
56108
  await addCredential({
55967
56109
  provider,
@@ -55972,7 +56114,7 @@ async function _runPkceLogin(provider, oauthCfg, opts, meta) {
55972
56114
  expiresAt,
55973
56115
  priority: 10,
55974
56116
  source: "oauth-pkce",
55975
- extraHeaders: { "anthropic-beta": "oauth-2025-04-20" }
56117
+ ...oauthExtraHeaders ? { extraHeaders: oauthExtraHeaders } : {}
55976
56118
  });
55977
56119
  } catch (err) {
55978
56120
  const msg = err instanceof Error ? err.message : String(err);
@@ -55992,6 +56134,27 @@ async function _runPkceLogin(provider, oauthCfg, opts, meta) {
55992
56134
  meta
55993
56135
  };
55994
56136
  }
56137
+ function _parseFixedLoopbackPort(redirectUri) {
56138
+ if (!redirectUri) return null;
56139
+ try {
56140
+ const u = new URL(redirectUri);
56141
+ const isLoopback = u.hostname === "localhost" || u.hostname === "127.0.0.1";
56142
+ if (!isLoopback || !u.port) return null;
56143
+ const port = Number(u.port);
56144
+ return Number.isInteger(port) && port > 0 ? port : null;
56145
+ } catch {
56146
+ return null;
56147
+ }
56148
+ }
56149
+ function _isLoopbackUri(redirectUri) {
56150
+ if (!redirectUri) return false;
56151
+ try {
56152
+ const u = new URL(redirectUri);
56153
+ return u.hostname === "localhost" || u.hostname === "127.0.0.1";
56154
+ } catch {
56155
+ return false;
56156
+ }
56157
+ }
55995
56158
  async function _headlessPkceFlow(provider, authUrl) {
55996
56159
  process.stderr.write("\n");
55997
56160
  process.stderr.write(` Provider: ${provider}
@@ -56278,11 +56441,11 @@ __export(llm_exports, {
56278
56441
  });
56279
56442
  import { pushWarning as pushWarning7 } from "@cleocode/core";
56280
56443
  async function getListProviders() {
56281
- const { listProviders } = await import(
56444
+ const { listProviders: listProviders2 } = await import(
56282
56445
  /* webpackIgnore: true */
56283
56446
  "@cleocode/core/llm/provider-registry"
56284
56447
  );
56285
- return listProviders;
56448
+ return listProviders2;
56286
56449
  }
56287
56450
  async function readApiKeyFromStdin() {
56288
56451
  if (process.stdin.isTTY) return "";
@@ -56593,8 +56756,8 @@ var init_llm3 = __esm({
56593
56756
  }
56594
56757
  },
56595
56758
  async run() {
56596
- const listProviders = await getListProviders();
56597
- const profiles = await listProviders();
56759
+ const listProviders2 = await getListProviders();
56760
+ const profiles = await listProviders2();
56598
56761
  const providers = profiles.map((p) => ({
56599
56762
  name: p.name,
56600
56763
  displayName: p.displayName,
@@ -56654,12 +56817,12 @@ var init_llm3 = __esm({
56654
56817
  loginCommand = defineCommand({
56655
56818
  meta: {
56656
56819
  name: "login",
56657
- description: "Authenticate with a provider via OAuth device-code flow. Supported providers: anthropic (MVP). Prints the verification URL and user code, then polls until the user approves."
56820
+ description: "Authenticate an LLM provider via OAuth. PKCE (browser): anthropic, openai/codex. Device-code: kimi-code. Example: `cleo llm login openai`. For any other provider use `cleo llm add <provider> --api-key-stdin`. Prompts/URLs go to stderr; the result is a human line on a terminal or a JSON envelope when piped/--json."
56658
56821
  },
56659
56822
  args: {
56660
56823
  provider: {
56661
56824
  type: "positional",
56662
- description: "Provider to authenticate with (e.g. anthropic)",
56825
+ description: "Provider to authenticate: anthropic | openai | codex | kimi-code",
56663
56826
  required: true
56664
56827
  },
56665
56828
  label: {
@@ -63395,7 +63558,7 @@ var project_exports = {};
63395
63558
  __export(project_exports, {
63396
63559
  projectCommand: () => projectCommand
63397
63560
  });
63398
- import { existsSync as existsSync16, statSync } from "node:fs";
63561
+ import { existsSync as existsSync17, statSync } from "node:fs";
63399
63562
  import { resolve as resolve7 } from "node:path";
63400
63563
  import { moveProject, projectLifecycle, renameProject } from "@cleocode/core";
63401
63564
  function formatSuccessSection(header, icon, items) {
@@ -63438,7 +63601,7 @@ var init_project = __esm({
63438
63601
  const newPathRaw = args["newPath"];
63439
63602
  const dryRun = args["dry-run"] ?? false;
63440
63603
  const newPath = resolve7(newPathRaw);
63441
- if (!dryRun && existsSync16(newPath) && statSync(newPath).isFile()) {
63604
+ if (!dryRun && existsSync17(newPath) && statSync(newPath).isFile()) {
63442
63605
  cliOutput(formatErrorSection("E_INVALID_PATH", `newPath is not a directory: ${newPath}`), {
63443
63606
  command: "project",
63444
63607
  operation: "project.move"
@@ -71524,7 +71687,7 @@ var init_lib = __esm({
71524
71687
  });
71525
71688
 
71526
71689
  // packages/cleo/src/cli/commands/templates/diff.ts
71527
- import { existsSync as existsSync17, readFileSync as readFileSync16 } from "node:fs";
71690
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
71528
71691
  import { join as join31 } from "node:path";
71529
71692
  import { getTemplateById } from "@cleocode/core/templates/registry";
71530
71693
  function unifiedDiff(a, b) {
@@ -71606,7 +71769,7 @@ var init_diff = __esm({
71606
71769
  process.exit(1 /* GENERAL_ERROR */);
71607
71770
  return;
71608
71771
  }
71609
- if (!existsSync17(installPath)) {
71772
+ if (!existsSync18(installPath)) {
71610
71773
  const missingResult = {
71611
71774
  id,
71612
71775
  installPath,
@@ -71645,7 +71808,7 @@ ${unifiedDiff("", rendered)}`
71645
71808
  });
71646
71809
 
71647
71810
  // packages/cleo/src/cli/commands/templates/install.ts
71648
- import { existsSync as existsSync18, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "node:fs";
71811
+ import { existsSync as existsSync19, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "node:fs";
71649
71812
  import { dirname as dirname9, join as join32 } from "node:path";
71650
71813
  import { getTemplateById as getTemplateById2 } from "@cleocode/core/templates/registry";
71651
71814
  var templatesInstallCommand;
@@ -71713,7 +71876,7 @@ var init_install = __esm({
71713
71876
  process.exit(1 /* GENERAL_ERROR */);
71714
71877
  return;
71715
71878
  }
71716
- if (existsSync18(installPath)) {
71879
+ if (existsSync19(installPath)) {
71717
71880
  const current = readFileSync17(installPath, "utf8");
71718
71881
  if (current === rendered) {
71719
71882
  const noopResult = {
@@ -71862,7 +72025,7 @@ var init_show3 = __esm({
71862
72025
  });
71863
72026
 
71864
72027
  // packages/cleo/src/cli/commands/templates/upgrade.ts
71865
- import { existsSync as existsSync19, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "node:fs";
72028
+ import { existsSync as existsSync20, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "node:fs";
71866
72029
  import { dirname as dirname10, join as join33 } from "node:path";
71867
72030
  import { getTemplateById as getTemplateById4 } from "@cleocode/core/templates/registry";
71868
72031
  var templatesUpgradeCommand;
@@ -71938,7 +72101,7 @@ var init_upgrade2 = __esm({
71938
72101
  }
71939
72102
  const previewOnly = args["diff"] === true;
71940
72103
  const accept = args["accept"] === true;
71941
- const current = existsSync19(installPath) ? readFileSync18(installPath, "utf8") : null;
72104
+ const current = existsSync20(installPath) ? readFileSync18(installPath, "utf8") : null;
71942
72105
  const diffBody = current === null ? "" : current === rendered ? "" : unifiedDiff(current, rendered);
71943
72106
  let outcome;
71944
72107
  let reason;
@@ -75735,14 +75898,14 @@ function lazyCommand(meta, loader2) {
75735
75898
  init_did_you_mean();
75736
75899
 
75737
75900
  // packages/cleo/src/cli/lib/first-run-detection.ts
75738
- import { existsSync as existsSync20 } from "node:fs";
75901
+ import { existsSync as existsSync21 } from "node:fs";
75739
75902
  import { join as join36 } from "node:path";
75740
75903
  async function detectFirstRun() {
75741
75904
  const envKey = process.env["ANTHROPIC_API_KEY"];
75742
75905
  if (typeof envKey === "string" && envKey.length > 0) return false;
75743
75906
  const { getCleoPlatformPaths } = await import("@cleocode/paths");
75744
75907
  const configPath = join36(getCleoPlatformPaths().config, "config.json");
75745
- if (existsSync20(configPath)) return false;
75908
+ if (existsSync21(configPath)) return false;
75746
75909
  try {
75747
75910
  const { getCredentialPool } = await import("@cleocode/core/llm/credential-pool.js");
75748
75911
  const pool = getCredentialPool();