chofex-cli 0.1.173 → 0.1.178

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.
Files changed (3) hide show
  1. package/README.md +16 -1
  2. package/dist/index.js +716 -48
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,17 +24,32 @@ chofex challenge notebook
24
24
  chofex challenge test --source ./shipping.js
25
25
  chofex challenge evaluate --source ./shipping.js
26
26
  chofex challenge ranking
27
+ chofex challenge init --challenge broken-agent
28
+ cd broken-agent && npm test
29
+ chofex challenge test --challenge broken-agent --source ./scheduler.js
30
+ chofex challenge evaluate --challenge broken-agent --source ./scheduler.js
31
+ chofex challenge ranking --challenge broken-agent
27
32
  chofex confirm
33
+ chofex badge
34
+ chofex badge regenerate
28
35
  ```
29
36
 
30
37
  `chofex update` and `chofex upgrade` are interchangeable; both update the CLI
31
38
  to the latest published version.
32
39
 
40
+ Las copias instaladas también consultan npm cada vez que se inicia `chofex` e
41
+ instalan una versión publicada más reciente antes de ejecutar el comando. Si
42
+ npm o la red no están disponibles temporalmente, se continúa con la versión
43
+ actual. Usa `CHOFEX_AUTO_UPDATE=0` para desactivar esta comprobación.
44
+
33
45
  `chofex register` collects and submits an application with full name, role,
34
46
  optional phone number, bio, portfolio URL, shipped project, LinkedIn and GitHub
35
47
  URLs, and Terms and Conditions. Use `--input` to submit a completed JSON
36
48
  application.
37
- The Black Box does not block submission, but every challenge winner must submit
49
+ After attendance confirmation, `chofex badge regenerate` updates the public
50
+ badge name, one-line description, picture, and QR destination without changing
51
+ the submitted application.
52
+ The technical challenges do not block submission, but every challenge winner must submit
38
53
  an application before being accepted. Top results receive a direct pass; other
39
54
  scores give organizers another review metric.
40
55
 
package/dist/index.js CHANGED
@@ -27645,6 +27645,7 @@ var require_throttleit = __commonJS((exports, module) => {
27645
27645
 
27646
27646
  // src/index.ts
27647
27647
  import { readFileSync } from "node:fs";
27648
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
27648
27649
  // ../../node_modules/@effect/platform-node/dist/NodeRuntime.js
27649
27650
  var exports_NodeRuntime = {};
27650
27651
  __export(exports_NodeRuntime, {
@@ -58552,6 +58553,7 @@ var runWith2 = (command, config) => {
58552
58553
  };
58553
58554
  // ../../packages/challenges-contract/src/index.ts
58554
58555
  var blackBoxChallengeSlug = "black-box";
58556
+ var brokenAgentChallengeSlug = "broken-agent";
58555
58557
  var challengeCatalog = [
58556
58558
  {
58557
58559
  slug: blackBoxChallengeSlug,
@@ -58573,21 +58575,21 @@ var challengeCatalog = [
58573
58575
  solutionKind: "javascript_source"
58574
58576
  },
58575
58577
  {
58576
- slug: "last-mile",
58578
+ slug: brokenAgentChallengeSlug,
58577
58579
  number: 2,
58578
58580
  code: "02",
58579
- theme: "Last Mile",
58580
- title: "The Delivery Plan",
58581
- summary: "Build a delivery plan under capacity, traffic, and deadline constraints. Better heuristics score higher.",
58582
- coreSkill: "Optimization & heuristics",
58583
- format: "optimization",
58584
- formatLabel: "Optimization score",
58581
+ theme: "Broken Agent",
58582
+ title: "The Scheduler",
58583
+ summary: "Take a plausible AI-generated job scheduler whose public tests already pass and harden it against production reality.",
58584
+ coreSkill: "Production correctness & reliability",
58585
+ format: "accuracy",
58586
+ formatLabel: "Production readiness score",
58585
58587
  opensAt: "2026-09-25T05:00:00.000Z",
58586
58588
  queryLimit: 0,
58587
- evaluationLimit: 3,
58588
- hiddenSampleSize: 1,
58589
- playable: false,
58590
- solutionKind: "json_plan"
58589
+ evaluationLimit: 5,
58590
+ hiddenSampleSize: 100,
58591
+ playable: true,
58592
+ solutionKind: "javascript_source"
58591
58593
  },
58592
58594
  {
58593
58595
  slug: "make-it-fast",
@@ -58690,7 +58692,7 @@ var formatChallengeOpeningInPeru = (opensAt) => {
58690
58692
  var challengeOpeningNotice = (title, opensAt) => `${title} opens ${formatChallengeOpeningInPeru(opensAt)}. Queries and evaluations are disabled until then; no attempts will be consumed.`;
58691
58693
  var ChallengeSlugSchema = exports_Schema.Literals([
58692
58694
  "black-box",
58693
- "last-mile",
58695
+ "broken-agent",
58694
58696
  "make-it-fast",
58695
58697
  "power-grid",
58696
58698
  "agent-arena"
@@ -58718,7 +58720,38 @@ var ChallengeScoreSchema = exports_Schema.Struct({
58718
58720
  sampleSize: exports_Schema.Number,
58719
58721
  meanError: exports_Schema.Number,
58720
58722
  queriesUsed: exports_Schema.Number,
58721
- runtimeMs: exports_Schema.Number
58723
+ runtimeMs: exports_Schema.Number,
58724
+ evaluationsUsed: exports_Schema.optional(exports_Schema.Number),
58725
+ breakdown: exports_Schema.optional(exports_Schema.Struct({
58726
+ coreBehavior: exports_Schema.Struct({
58727
+ earned: exports_Schema.Number,
58728
+ available: exports_Schema.Number
58729
+ }),
58730
+ persistence: exports_Schema.Struct({
58731
+ earned: exports_Schema.Number,
58732
+ available: exports_Schema.Number
58733
+ }),
58734
+ concurrency: exports_Schema.Struct({
58735
+ earned: exports_Schema.Number,
58736
+ available: exports_Schema.Number
58737
+ }),
58738
+ failureRecovery: exports_Schema.Struct({
58739
+ earned: exports_Schema.Number,
58740
+ available: exports_Schema.Number
58741
+ }),
58742
+ idempotency: exports_Schema.Struct({
58743
+ earned: exports_Schema.Number,
58744
+ available: exports_Schema.Number
58745
+ }),
58746
+ regressionSafety: exports_Schema.Struct({
58747
+ earned: exports_Schema.Number,
58748
+ available: exports_Schema.Number
58749
+ }),
58750
+ performance: exports_Schema.Struct({
58751
+ earned: exports_Schema.Number,
58752
+ available: exports_Schema.Number
58753
+ })
58754
+ }))
58722
58755
  });
58723
58756
  var ChallengeProgressStatus = exports_Schema.Literals([
58724
58757
  "not_started",
@@ -58785,7 +58818,8 @@ var ChallengeEvaluationSummarySchema = exports_Schema.Struct({
58785
58818
  shareCode: exports_Schema.String,
58786
58819
  rank: exports_Schema.optional(exports_Schema.Number),
58787
58820
  percentile: exports_Schema.optional(exports_Schema.Number),
58788
- createdAt: exports_Schema.String
58821
+ createdAt: exports_Schema.String,
58822
+ breakdown: exports_Schema.optional(ChallengeScoreSchema.fields.breakdown)
58789
58823
  });
58790
58824
  var ChallengeAttemptViewSchema = exports_Schema.Struct({
58791
58825
  challenge: ChallengeCatalogItemSchema,
@@ -58802,6 +58836,7 @@ var ChallengeQueryResultSchema = exports_Schema.Struct({
58802
58836
  queriesLimit: exports_Schema.Number
58803
58837
  });
58804
58838
  var ChallengeLocalTestResultSchema = exports_Schema.Struct({
58839
+ kind: exports_Schema.optional(exports_Schema.Literals(["black_box", "broken_agent"])),
58805
58840
  matchedObservations: exports_Schema.Number,
58806
58841
  observationCount: exports_Schema.Number,
58807
58842
  accuracy: exports_Schema.Number,
@@ -58827,7 +58862,8 @@ var ChallengeEvaluationResultSchema = exports_Schema.Struct({
58827
58862
  evaluationsRemaining: exports_Schema.Number,
58828
58863
  evaluationsLimit: exports_Schema.Number,
58829
58864
  rankingPath: exports_Schema.String,
58830
- shareText: exports_Schema.String
58865
+ shareText: exports_Schema.String,
58866
+ breakdown: exports_Schema.optional(ChallengeScoreSchema.fields.breakdown)
58831
58867
  });
58832
58868
  var ChallengeRankingEntrySchema = exports_Schema.Struct({
58833
58869
  rank: exports_Schema.Number,
@@ -59156,10 +59192,25 @@ var BadgeStatus = exports_Schema.Literals([
59156
59192
  "completed",
59157
59193
  "failed"
59158
59194
  ]);
59195
+ var BadgeProfileSchema = exports_Schema.Struct({
59196
+ fullName: nonBlank(200),
59197
+ oneLiner: nonBlank(30),
59198
+ linkUrl: url,
59199
+ placement: exports_Schema.String
59200
+ });
59159
59201
  var BadgeResultSchema = exports_Schema.Struct({
59160
59202
  status: BadgeStatus,
59161
- url: exports_Schema.optional(exports_Schema.String)
59203
+ url: exports_Schema.optional(exports_Schema.String),
59204
+ profile: exports_Schema.optional(BadgeProfileSchema)
59162
59205
  });
59206
+ var badgeRegenerationInputFields = {
59207
+ fullName: nonBlank(200),
59208
+ oneLiner: nonBlank(30),
59209
+ linkUrl: url,
59210
+ pictureSource: exports_Schema.optional(PictureSource)
59211
+ };
59212
+ var BadgeRegenerationInput = exports_Schema.Struct(badgeRegenerationInputFields);
59213
+ var badgeRegenerationInputFieldNames = Object.keys(badgeRegenerationInputFields);
59163
59214
  var PictureUploadSchema = exports_Schema.Struct({
59164
59215
  url: exports_Schema.String,
59165
59216
  contentType: PictureContentType,
@@ -59697,6 +59748,7 @@ var register = (options, input) => request3(options, "/api/v1/registrations", {
59697
59748
  var getCurrentUser = (options) => request3(options, "/api/v1/me", { method: "GET" }, decodeCurrentUser);
59698
59749
  var getRegistration = (options) => request3(options, "/api/v1/registration", { method: "GET" }, decodeRegistrationResult);
59699
59750
  var getBadge = (options) => request3(options, "/api/v1/badge", { method: "GET" }, decodeBadgeResult);
59751
+ var regenerateBadge = (options, input) => request3(options, "/api/v1/badge", { method: "PATCH", body: JSON.stringify(input) }, decodeBadgeResult);
59700
59752
  var confirmAttendance = (options, input) => request3(options, "/api/v1/registration/attendance", { method: "PUT", body: JSON.stringify(input) }, decodeRegistrationResult);
59701
59753
  var beginPictureUpload = (options, input) => request3(options, "/api/v1/profile-picture", { method: "POST", body: JSON.stringify(input) }, decodePictureUploadGrant);
59702
59754
  var completePictureUpload = (options, input) => request3(options, "/api/v1/profile-picture", { method: "PUT", body: JSON.stringify(input) }, decodePictureUpload);
@@ -59709,6 +59761,7 @@ var getChallengeRanking = (options, slug) => publicRequest(options, `/api/v1/cha
59709
59761
 
59710
59762
  // src/challenge-input.ts
59711
59763
  import { readFile as readFile3 } from "node:fs/promises";
59764
+ var defaultChallengeSlug = "black-box";
59712
59765
  var readStdin = async () => {
59713
59766
  const chunks = [];
59714
59767
  for await (const chunk of process.stdin) {
@@ -59724,9 +59777,12 @@ var readTextFile = (path4) => exports_Effect.tryPromise({
59724
59777
  },
59725
59778
  catch: (error2) => cliError("INVALID_INPUT_FILE", `Could not read ${path4}: ${String(error2)}`)
59726
59779
  });
59727
- var javascriptSourceFromPath = (path4) => {
59780
+ var javascriptSourceFromPath = (path4, challenge = defaultChallengeSlug) => {
59728
59781
  if (!path4) {
59729
- return exports_Effect.fail(cliError("SOURCE_REQUIRED", "Pass --source <file.js> with function calculateShipping(input)"));
59782
+ let expectedFunction = "calculateShipping(input)";
59783
+ if (challenge === "broken-agent")
59784
+ expectedFunction = "createScheduler(dependencies)";
59785
+ return exports_Effect.fail(cliError("SOURCE_REQUIRED", `Pass --source <file.js> with function ${expectedFunction}`));
59730
59786
  }
59731
59787
  return readTextFile(path4).pipe(exports_Effect.map((source) => ({ kind: "javascript_source", source })));
59732
59788
  };
@@ -59778,7 +59834,6 @@ var shipmentInput = (path4, flags) => {
59778
59834
  }
59779
59835
  return interactiveShipment();
59780
59836
  };
59781
- var defaultChallengeSlug = "black-box";
59782
59837
 
59783
59838
  // src/brand.ts
59784
59839
  var eventName = "Hack the Andes";
@@ -59834,6 +59889,32 @@ var challengeShowText = (attempt) => {
59834
59889
  const { challenge, progress } = attempt;
59835
59890
  const queriesRemaining = progress.queriesLimit - progress.queriesUsed;
59836
59891
  const evaluationsRemaining = progress.evaluationsLimit - progress.evaluationsUsed;
59892
+ if (challenge.slug === "broken-agent") {
59893
+ let caseStatus2 = "READY TO AUDIT";
59894
+ if (attempt.latestEvaluation)
59895
+ caseStatus2 = "EVALUATED";
59896
+ const lines3 = [
59897
+ `${challenge.title.toUpperCase()} — CASE #${challenge.code}`,
59898
+ challenge.summary,
59899
+ "",
59900
+ `CASE STATUS: ${caseStatus2}`,
59901
+ "",
59902
+ "The public tests are green. Your job is to make the scheduler trustworthy under production conditions.",
59903
+ "AI tools are allowed.",
59904
+ "",
59905
+ `Evaluations ${evaluationsRemaining} / ${progress.evaluationsLimit} remaining`
59906
+ ];
59907
+ if (progress.bestAccuracy !== undefined) {
59908
+ lines3.push("", `Best score ${percent(progress.bestAccuracy)}`);
59909
+ if (progress.rank !== undefined)
59910
+ lines3.push(`Rank #${progress.rank}`);
59911
+ if (progress.shareCode)
59912
+ lines3.push(`Share code #${progress.shareCode}`);
59913
+ }
59914
+ lines3.push("", "YOUR NEXT MOVE", "Read the contract, audit scheduler.js, and keep the visible suite green.", " cd broken-agent && npm test", " chofex challenge test --challenge broken-agent --source ./broken-agent/scheduler.js");
59915
+ return lines3.join(`
59916
+ `);
59917
+ }
59837
59918
  let evidence = `${attempt.observations.length} observations`;
59838
59919
  if (attempt.observations.length === 1)
59839
59920
  evidence = "1 observation";
@@ -59996,6 +60077,26 @@ var notebookCsvText = (observations) => {
59996
60077
  `);
59997
60078
  };
59998
60079
  var challengeTestText = (result3) => {
60080
+ if (result3.kind === "broken_agent") {
60081
+ const lines3 = [
60082
+ "PUBLIC SUITE — BROKEN AGENT",
60083
+ `${result3.matchedObservations} / ${result3.observationCount} visible behaviors passing`
60084
+ ];
60085
+ if (result3.accuracy === 1) {
60086
+ lines3.push("", "Everything passes.", "Unfortunately, that does not mean the scheduler is production-correct.", "Reason about concurrency, restarts, failures, and idempotency before evaluating.", "", "Next: spend one official evaluation when you would ship it", " chofex challenge evaluate --challenge broken-agent --source ./broken-agent/scheduler.js");
60087
+ return lines3.join(`
60088
+ `);
60089
+ }
60090
+ if (result3.mismatches.length > 0) {
60091
+ lines3.push("", "Visible failures:");
60092
+ for (const mismatch of result3.mismatches) {
60093
+ lines3.push(` ${String(mismatch.actual)}`);
60094
+ }
60095
+ }
60096
+ lines3.push("", "Fix the public regressions before using an official evaluation.");
60097
+ return lines3.join(`
60098
+ `);
60099
+ }
59999
60100
  let verdict = "KEEP WORKING";
60000
60101
  if (result3.accuracy === 1)
60001
60102
  verdict = "NOTEBOOK MATCHED";
@@ -60022,6 +60123,34 @@ var challengeTestText = (result3) => {
60022
60123
  `);
60023
60124
  };
60024
60125
  var challengeEvaluateText = (result3) => {
60126
+ if (result3.breakdown) {
60127
+ const capabilityLines = [
60128
+ ["Core behavior", result3.breakdown.coreBehavior],
60129
+ ["Persistence", result3.breakdown.persistence],
60130
+ ["Concurrency", result3.breakdown.concurrency],
60131
+ ["Failure recovery", result3.breakdown.failureRecovery],
60132
+ ["Idempotency", result3.breakdown.idempotency],
60133
+ ["Regression safety", result3.breakdown.regressionSafety],
60134
+ ["Performance", result3.breakdown.performance]
60135
+ ];
60136
+ const lines3 = [
60137
+ "OFFICIAL VERDICT — BROKEN AGENT — PRODUCTION READINESS",
60138
+ `Score ${(result3.accuracy * 100).toFixed(2)} / ${result3.sampleSize}`,
60139
+ `Runtime ${result3.runtimeMs} ms`,
60140
+ "",
60141
+ "Capability scores",
60142
+ ...capabilityLines.map(([label, score]) => `${label.padEnd(20)}${score.earned.toFixed(2)} / ${score.available}`)
60143
+ ];
60144
+ if (result3.rank !== undefined) {
60145
+ lines3.push(`Rank #${result3.rank}`);
60146
+ }
60147
+ lines3.push(`Official evaluations remaining ${result3.evaluationsRemaining} / ${result3.evaluationsLimit}`, "", result3.shareText, "", "Next: inspect the leaderboard", " chofex challenge ranking --challenge broken-agent");
60148
+ if (result3.evaluationsRemaining > 0) {
60149
+ lines3.push("", "The evaluator reports capabilities, not individual hidden cases. Reason before submitting again.", " chofex challenge test --challenge broken-agent --source ./broken-agent/scheduler.js");
60150
+ }
60151
+ return lines3.join(`
60152
+ `);
60153
+ }
60025
60154
  const lines2 = [
60026
60155
  "OFFICIAL VERDICT — BLACK BOX REPLICATION",
60027
60156
  `Accuracy ${percent(result3.accuracy)}`,
@@ -60050,6 +60179,18 @@ var challengeRankingText = (ranking, now3 = new Date) => {
60050
60179
  if (ranking.entries.length === 0) {
60051
60180
  lines2.push("No official evaluations yet.");
60052
60181
  return lines2.join(`
60182
+ `);
60183
+ }
60184
+ if (ranking.challenge.slug === "broken-agent") {
60185
+ lines2.push("Rank Score Points Runtime Name");
60186
+ for (const entry of ranking.entries.slice(0, 20)) {
60187
+ const rank = String(entry.rank).padStart(4, " ");
60188
+ const accuracy = percent(entry.accuracy).padStart(8, " ");
60189
+ const points = `${entry.exactCount}/${entry.sampleSize}`.padStart(11, " ");
60190
+ const runtime = `${entry.runtimeMs}ms`.padStart(8, " ");
60191
+ lines2.push(`${rank} ${accuracy} ${points} ${runtime} ${entry.displayName} #${entry.shareCode}`);
60192
+ }
60193
+ return lines2.join(`
60053
60194
  `);
60054
60195
  }
60055
60196
  lines2.push("Rank Accuracy Exact Queries Name");
@@ -60086,7 +60227,216 @@ var registrationPartsText = (result3) => {
60086
60227
  };
60087
60228
 
60088
60229
  // src/challenge-scaffold.ts
60089
- import { writeFile as writeFile3 } from "node:fs/promises";
60230
+ import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
60231
+
60232
+ // ../../packages/challenges-contract/src/broken-agent.ts
60233
+ var brokenAgentScaffoldDirectory = "broken-agent";
60234
+ var brokenAgentSolutionPath = `${brokenAgentScaffoldDirectory}/scheduler.js`;
60235
+ var brokenAgentPackageJson = `${JSON.stringify({
60236
+ name: "broken-agent-challenge",
60237
+ private: true,
60238
+ type: "commonjs",
60239
+ scripts: { test: "node --test scheduler.test.js" }
60240
+ }, null, 2)}
60241
+ `;
60242
+ var brokenAgentStarterSource = `/**
60243
+ * Broken Agent — The Scheduler
60244
+ *
60245
+ * A coding agent implemented this scheduler and declared the feature complete.
60246
+ * Every public test passes. The implementation is not production-correct.
60247
+ * Keep the createScheduler interface intact and harden the implementation.
60248
+ */
60249
+ function createScheduler({ store, clock, execute }) {
60250
+ return {
60251
+ async schedule(input) {
60252
+ const job = { ...input, status: "pending" };
60253
+ await store.transaction((tx) => tx.put(job));
60254
+ return job;
60255
+ },
60256
+
60257
+ async cancel(id) {
60258
+ return store.transaction((tx) => {
60259
+ const job = tx.get(id);
60260
+ if (!job || job.status !== "pending") return false;
60261
+ tx.put({ ...job, status: "cancelled" });
60262
+ return true;
60263
+ });
60264
+ },
60265
+
60266
+ async list() {
60267
+ return store.transaction((tx) => tx.list());
60268
+ },
60269
+
60270
+ async runDue() {
60271
+ const now = clock.now().getTime();
60272
+ const jobs = await store.transaction((tx) => tx.list());
60273
+ for (const job of jobs) {
60274
+ if (job.status !== "pending" || Date.parse(job.runAt) > now) continue;
60275
+ await execute(job);
60276
+ await store.transaction((tx) =>
60277
+ tx.put({ ...job, status: "completed" }),
60278
+ );
60279
+ }
60280
+ },
60281
+ };
60282
+ }
60283
+
60284
+ module.exports = { createScheduler };
60285
+ `;
60286
+ var brokenAgentReadme = `# Broken Agent — The Scheduler
60287
+
60288
+ > A coding agent was asked to implement a feature. It says it is done. It is
60289
+ > not. Your job is to ship it.
60290
+
60291
+ The repository starts with all public tests passing. Repair \`scheduler.js\`
60292
+ without changing its exported \`createScheduler(dependencies)\` interface.
60293
+
60294
+ ## Commands
60295
+
60296
+ \`\`\`sh
60297
+ npm test
60298
+ chofex challenge test --challenge broken-agent --source ./broken-agent/scheduler.js
60299
+ chofex challenge evaluate --challenge broken-agent --source ./broken-agent/scheduler.js
60300
+ \`\`\`
60301
+
60302
+ Local and public tests are unlimited. You have **5 official evaluations**
60303
+ against hidden production scenarios. AI tools are explicitly allowed.
60304
+
60305
+ ## Public contract
60306
+
60307
+ \`createScheduler({ store, clock, execute, workerId })\` returns:
60308
+
60309
+ - \`schedule({ id, runAt, payload })\`
60310
+ - \`cancel(id)\`
60311
+ - \`list()\`
60312
+ - \`runDue()\`
60313
+
60314
+ ### Jobs
60315
+
60316
+ - \`id\` matches \`^[A-Za-z0-9_-]{1,64}$\`.
60317
+ - \`runAt\` is an ISO-8601 timestamp with an explicit timezone.
60318
+ - \`payload\` is JSON-serializable.
60319
+ - Scheduling an identical job ID is idempotent. Reusing an ID for different
60320
+ work must reject without changing the original job.
60321
+ - Past timestamps are valid and become due immediately.
60322
+ - \`list()\` returns jobs ordered by \`runAt\`, then \`id\`.
60323
+
60324
+ ### Durability and concurrency
60325
+
60326
+ - The supplied store survives scheduler instances and process restarts.
60327
+ - Several workers may call \`runDue()\` concurrently against the same store.
60328
+ - A worker claim lasts 30 seconds. Another worker may reclaim the job when
60329
+ \`clock.now()\` reaches that lease deadline.
60330
+ - A pending job may be cancelled. A running or terminal job may not.
60331
+ - Failed executions retry, without preventing other due jobs from running.
60332
+ - A job becomes \`failed\` after 3 unsuccessful attempts.
60333
+ - The execution adapter applies effects idempotently by job ID. A worker may
60334
+ stop after the effect is applied but before completion is recorded; retrying
60335
+ must eventually complete the job without duplicating the effect.
60336
+
60337
+ The store exposes only \`transaction(action)\`. Inside an action, \`tx\`
60338
+ provides synchronous \`get(id)\`, \`put(job)\`, \`delete(id)\`, and \`list()\`
60339
+ operations. Transactions are serialized. Do not keep a transaction open while
60340
+ executing a job.
60341
+
60342
+ \`clock.now()\` returns a \`Date\`. \`execute(job)\` returns a promise.
60343
+
60344
+ ## Evaluation
60345
+
60346
+ The hidden evaluator checks the published contract under deterministic
60347
+ concurrency, restart, failure, idempotency, compatibility, and load scenarios.
60348
+ It reports capability-level scores, not individual hidden-test failures.
60349
+
60350
+ - Core behavior: 10 points
60351
+ - Persistence: 15 points
60352
+ - Concurrency: 20 points
60353
+ - Failure recovery: 20 points
60354
+ - Idempotency: 15 points
60355
+ - Regression safety: 15 points
60356
+ - Performance: 5 points, available only when the load jobs complete correctly
60357
+
60358
+ Ranking order is total score, fewer official evaluations used, runtime, then
60359
+ official submission time. Submission time is only the final tie-breaker.
60360
+ `;
60361
+ var brokenAgentPublicTestSource = `const test = require("node:test");
60362
+ const assert = require("node:assert/strict");
60363
+ const { createScheduler } = require("./scheduler.js");
60364
+
60365
+ const clone = (value) => {
60366
+ if (value === undefined) return undefined;
60367
+ return JSON.parse(JSON.stringify(value));
60368
+ };
60369
+
60370
+ class MemoryStore {
60371
+ constructor() {
60372
+ this.jobs = new Map();
60373
+ this.tail = Promise.resolve();
60374
+ }
60375
+
60376
+ transaction(action) {
60377
+ const run = this.tail.then(() => action({
60378
+ get: (id) => clone(this.jobs.get(id)),
60379
+ put: (job) => {
60380
+ this.jobs.set(job.id, clone(job));
60381
+ return clone(job);
60382
+ },
60383
+ delete: (id) => this.jobs.delete(id),
60384
+ list: () => [...this.jobs.values()].map(clone),
60385
+ }));
60386
+ this.tail = run.then(() => undefined, () => undefined);
60387
+ return run;
60388
+ }
60389
+ }
60390
+
60391
+ const setup = () => {
60392
+ const store = new MemoryStore();
60393
+ const now = new Date("2026-10-17T15:00:00.000Z");
60394
+ const executed = [];
60395
+ const scheduler = createScheduler({
60396
+ store,
60397
+ clock: { now: () => new Date(now) },
60398
+ execute: async (job) => executed.push(job.id),
60399
+ workerId: "public-worker",
60400
+ });
60401
+ return { store, scheduler, executed };
60402
+ };
60403
+
60404
+ const job = (id, runAt = "2026-10-17T15:00:00.000Z") => ({
60405
+ id,
60406
+ runAt,
60407
+ payload: { message: id },
60408
+ });
60409
+
60410
+ test("schedules and lists a job", async () => {
60411
+ const { scheduler } = setup();
60412
+ await scheduler.schedule(job("job-1"));
60413
+ assert.equal((await scheduler.list()).length, 1);
60414
+ });
60415
+
60416
+ test("executes a due job", async () => {
60417
+ const { scheduler, executed } = setup();
60418
+ await scheduler.schedule(job("job-1"));
60419
+ await scheduler.runDue();
60420
+ assert.deepEqual(executed, ["job-1"]);
60421
+ });
60422
+
60423
+ test("does not execute a future job", async () => {
60424
+ const { scheduler, executed } = setup();
60425
+ await scheduler.schedule(job("future", "2026-10-17T16:00:00.000Z"));
60426
+ await scheduler.runDue();
60427
+ assert.deepEqual(executed, []);
60428
+ });
60429
+
60430
+ test("cancels a pending job", async () => {
60431
+ const { scheduler, executed } = setup();
60432
+ await scheduler.schedule(job("cancel-me"));
60433
+ assert.equal(await scheduler.cancel("cancel-me"), true);
60434
+ await scheduler.runDue();
60435
+ assert.deepEqual(executed, []);
60436
+ });
60437
+ `;
60438
+
60439
+ // src/challenge-scaffold.ts
60090
60440
  var solutionPath = "shipping.js";
60091
60441
  var starterSource = `/**
60092
60442
  * The Shipping Machine
@@ -60107,7 +60457,36 @@ function calculateShipping(input) {
60107
60457
  return 0;
60108
60458
  }
60109
60459
  `;
60110
- var createChallengeScaffold = exports_Effect.fn("createChallengeScaffold")(function* () {
60460
+ var createChallengeScaffold = exports_Effect.fn("createChallengeScaffold")(function* (challengeSlug = "black-box") {
60461
+ const challenge = challengeBySlug(challengeSlug);
60462
+ if (!challenge?.playable) {
60463
+ return yield* exports_Effect.fail(cliError("CHALLENGE_NOT_AVAILABLE", `Challenge ${challengeSlug} is not available`));
60464
+ }
60465
+ if (challengeSlug === "broken-agent") {
60466
+ const status2 = yield* exports_Effect.tryPromise({
60467
+ try: async () => {
60468
+ await mkdir2(brokenAgentScaffoldDirectory);
60469
+ await Promise.all([
60470
+ writeFile3(brokenAgentSolutionPath, brokenAgentStarterSource, "utf8"),
60471
+ writeFile3(`${brokenAgentScaffoldDirectory}/scheduler.test.js`, brokenAgentPublicTestSource, "utf8"),
60472
+ writeFile3(`${brokenAgentScaffoldDirectory}/README.md`, brokenAgentReadme, "utf8"),
60473
+ writeFile3(`${brokenAgentScaffoldDirectory}/package.json`, brokenAgentPackageJson, "utf8")
60474
+ ]);
60475
+ return "created";
60476
+ },
60477
+ catch: (error2) => error2
60478
+ }).pipe(exports_Effect.catch((error2) => {
60479
+ if (exports_Predicate.hasProperty(error2, "code") && error2.code === "EEXIST") {
60480
+ return exports_Effect.succeed("exists");
60481
+ }
60482
+ return exports_Effect.fail(cliError("SCAFFOLD_FAILED", `Could not create ${brokenAgentScaffoldDirectory}: ${String(error2)}`));
60483
+ }));
60484
+ return {
60485
+ challenge: challengeSlug,
60486
+ path: brokenAgentScaffoldDirectory,
60487
+ status: status2
60488
+ };
60489
+ }
60111
60490
  const status = yield* exports_Effect.tryPromise({
60112
60491
  try: async () => {
60113
60492
  await writeFile3(solutionPath, starterSource, {
@@ -60123,14 +60502,19 @@ var createChallengeScaffold = exports_Effect.fn("createChallengeScaffold")(funct
60123
60502
  }
60124
60503
  return exports_Effect.fail(cliError("SCAFFOLD_FAILED", `Could not create ${solutionPath}: ${String(error2)}`));
60125
60504
  }));
60126
- return { path: solutionPath, status };
60505
+ return { challenge: challengeSlug, path: solutionPath, status };
60127
60506
  });
60128
60507
 
60129
60508
  // src/cli-root.ts
60509
+ var rootValueFlagNames = {
60510
+ apiUrl: "api-url",
60511
+ output: "output",
60512
+ token: "token"
60513
+ };
60130
60514
  var root = exports_Command.make("chofex").pipe(exports_Command.withSharedFlags({
60131
- apiUrl: exports_Flag.string("api-url").pipe(exports_Flag.withDefault(config.apiUrl), exports_Flag.withDescription("Chofex API base URL")),
60132
- output: exports_Flag.choice("output", ["human", "json"]).pipe(exports_Flag.withDefault("human"), exports_Flag.withDescription("Output format")),
60133
- token: exports_Flag.string("token").pipe(exports_Flag.optional, exports_Flag.withDescription("Clerk OAuth token (prefer CHOFEX_TOKEN to avoid shell history)"))
60515
+ apiUrl: exports_Flag.string(rootValueFlagNames.apiUrl).pipe(exports_Flag.withDefault(config.apiUrl), exports_Flag.withDescription("Chofex API base URL")),
60516
+ output: exports_Flag.choice(rootValueFlagNames.output, ["human", "json"]).pipe(exports_Flag.withDefault("human"), exports_Flag.withDescription("Output format")),
60517
+ token: exports_Flag.string(rootValueFlagNames.token).pipe(exports_Flag.optional, exports_Flag.withDescription("Clerk OAuth token (prefer CHOFEX_TOKEN to avoid shell history)"))
60134
60518
  }), exports_Command.withDescription(`Register for and manage your ${eventName} application`));
60135
60519
 
60136
60520
  // src/output.ts
@@ -60252,7 +60636,13 @@ var requirementsOnlyText = (result3) => requirementsText(result3);
60252
60636
  var badgeText = (result3) => {
60253
60637
  if (result3.url)
60254
60638
  return result3.url;
60255
- return "You don't have a badge yet.";
60639
+ if (result3.status === "pending" || result3.status === "running") {
60640
+ return "Tu carnet se está generando.";
60641
+ }
60642
+ if (result3.status === "failed") {
60643
+ return "No se pudo generar tu carnet. Ejecuta `chofex badge regenerate` para intentarlo de nuevo.";
60644
+ }
60645
+ return "Todavía no tienes un carnet.";
60256
60646
  };
60257
60647
 
60258
60648
  // src/challenge-commands.ts
@@ -60364,7 +60754,7 @@ var challengeQuickstartText = (participationState, launchNotice) => {
60364
60754
  };
60365
60755
  var optionalString = (name, description) => exports_Flag.string(name).pipe(exports_Flag.optional, exports_Flag.withDescription(description));
60366
60756
  var challengeFlag = exports_Flag.string("challenge").pipe(exports_Flag.withDefault(defaultChallengeSlug), exports_Flag.withDescription("Challenge slug (default: black-box)"));
60367
- var sourceFlag = optionalString("source", "JavaScript file defining function calculateShipping(input)");
60757
+ var sourceFlag = optionalString("source", "JavaScript challenge solution file");
60368
60758
  var inputFlag = optionalString("input", "JSON file, or - for stdin");
60369
60759
  var formatFlag = exports_Flag.choice("format", ["table", "json", "csv"]).pipe(exports_Flag.withDefault("table"), exports_Flag.withDescription("Notebook format"));
60370
60760
  var numberFromOption = (value3) => {
@@ -60381,6 +60771,30 @@ var booleanFromOption = (value3) => {
60381
60771
  return false;
60382
60772
  };
60383
60773
  var challengeInitText = (result3) => {
60774
+ if (result3.challenge === "broken-agent") {
60775
+ if (result3.status === "exists") {
60776
+ return [
60777
+ `${result3.path} already exists. Left it unchanged.`,
60778
+ "",
60779
+ "Next: keep hardening your scheduler, then run the public tests",
60780
+ ` cd ${result3.path} && npm test`,
60781
+ ` chofex challenge test --challenge broken-agent --source ./${result3.path}/scheduler.js`
60782
+ ].join(`
60783
+ `);
60784
+ }
60785
+ return [
60786
+ `Created ${result3.path}`,
60787
+ "The previous agent says the scheduler is done. Everything passes.",
60788
+ "Unfortunately, the agent was wrong.",
60789
+ "",
60790
+ "Start with the public contract and tests:",
60791
+ ` cd ${result3.path} && npm test`,
60792
+ "",
60793
+ "Then repair scheduler.js and test it safely:",
60794
+ ` chofex challenge test --challenge broken-agent --source ./${result3.path}/scheduler.js`
60795
+ ].join(`
60796
+ `);
60797
+ }
60384
60798
  if (result3.status === "exists") {
60385
60799
  return [
60386
60800
  `${result3.path} already exists. Left it unchanged.`,
@@ -60412,28 +60826,32 @@ var listCommand = exports_Command.make("list", {}, exports_Effect.fn("challengeL
60412
60826
  description: "See which challenge is currently playable"
60413
60827
  }
60414
60828
  ]));
60415
- var initCommand = exports_Command.make("init", {}, exports_Effect.fn("challengeInitCommand")(function* () {
60829
+ var initCommand = exports_Command.make("init", { challenge: challengeFlag }, exports_Effect.fn("challengeInitCommand")(function* ({ challenge }) {
60416
60830
  const options = yield* root;
60417
- const participationState = participationStateFor(defaultChallengeSlug);
60831
+ const participationState = participationStateFor(challenge);
60418
60832
  if (participationState !== "open") {
60419
- const message = launchNoticeFor(defaultChallengeSlug) ?? "El challenge todavía no está disponible.";
60833
+ const message = launchNoticeFor(challenge) ?? "El challenge todavía no está disponible.";
60420
60834
  let code = "CHALLENGE_NOT_OPEN";
60421
60835
  if (participationState === "closed")
60422
60836
  code = "CHALLENGE_CLOSED";
60423
60837
  yield* execute(options.output, exports_Effect.fail(cliError(code, message)), challengeInitText);
60424
60838
  return;
60425
60839
  }
60426
- const operation = createChallengeScaffold().pipe(exports_Effect.map((data) => ({
60840
+ const operation = createChallengeScaffold(challenge).pipe(exports_Effect.map((data) => ({
60427
60841
  version: 1,
60428
60842
  ok: true,
60429
60843
  requestId: crypto.randomUUID(),
60430
60844
  data
60431
60845
  })));
60432
60846
  yield* execute(options.output, operation, challengeInitText);
60433
- })).pipe(exports_Command.withDescription("Create a documented shipping.js starter in the current directory. Existing files are never overwritten."), exports_Command.withExamples([
60847
+ })).pipe(exports_Command.withDescription("Create a challenge starter without overwriting existing work."), exports_Command.withExamples([
60434
60848
  {
60435
60849
  command: "chofex challenge init",
60436
60850
  description: "Prepare a safe JavaScript solution file"
60851
+ },
60852
+ {
60853
+ command: "chofex challenge init --challenge broken-agent",
60854
+ description: "Prepare the Broken Agent starter repository"
60437
60855
  }
60438
60856
  ]));
60439
60857
  var showCommand = exports_Command.make("show", { challenge: challengeFlag }, exports_Effect.fn("challengeShowCommand")(function* ({ challenge }) {
@@ -60512,11 +60930,11 @@ var testCommand = exports_Command.make("test", { challenge: challengeFlag, sourc
60512
60930
  const options = yield* root;
60513
60931
  const token = exports_Option.getOrUndefined(options.token);
60514
60932
  const operation = exports_Effect.gen(function* () {
60515
- const solution = yield* javascriptSourceFromPath(exports_Option.getOrUndefined(source));
60933
+ const solution = yield* javascriptSourceFromPath(exports_Option.getOrUndefined(source), challenge);
60516
60934
  return yield* testChallenge({ apiUrl: options.apiUrl, token }, challenge, solution);
60517
60935
  });
60518
60936
  yield* execute(options.output, operation, challengeTestText);
60519
- })).pipe(exports_Command.withDescription("Check calculateShipping against your saved observations. Safe to repeat; official evaluations are not consumed. Requires sign-in."), exports_Command.withExamples([
60937
+ })).pipe(exports_Command.withDescription("Run the challenge's public tests. Safe to repeat; official evaluations are not consumed. Requires sign-in."), exports_Command.withExamples([
60520
60938
  {
60521
60939
  command: "chofex challenge test --source ./shipping.js",
60522
60940
  description: "Test a JavaScript replacement against your notebook"
@@ -60526,11 +60944,11 @@ var evaluateCommand = exports_Command.make("evaluate", { challenge: challengeFla
60526
60944
  const options = yield* root;
60527
60945
  const token = exports_Option.getOrUndefined(options.token);
60528
60946
  const operation = exports_Effect.gen(function* () {
60529
- const solution = yield* javascriptSourceFromPath(exports_Option.getOrUndefined(source));
60947
+ const solution = yield* javascriptSourceFromPath(exports_Option.getOrUndefined(source), challenge);
60530
60948
  return yield* evaluateChallenge({ apiUrl: options.apiUrl, token }, challenge, solution);
60531
60949
  });
60532
60950
  yield* execute(options.output, operation, challengeEvaluateText);
60533
- })).pipe(exports_Command.withDescription("Score calculateShipping on hidden cases. This consumes one limited official evaluation. Requires sign-in."), exports_Command.withExamples([
60951
+ })).pipe(exports_Command.withDescription("Score a solution on hidden cases. This consumes one limited official evaluation. Requires sign-in."), exports_Command.withExamples([
60534
60952
  {
60535
60953
  command: "chofex challenge evaluate --source ./shipping.js",
60536
60954
  description: "Spend one official evaluation when your solution is ready"
@@ -60975,6 +61393,34 @@ var picturePathInput = (suppliedPath) => {
60975
61393
  }
60976
61394
  return exports_Prompt.run(requiredText("Path to a JPEG, PNG, or WebP picture (5 MB maximum)", exports_Schema.String.pipe(exports_Schema.check(exports_Schema.isMinLength(1))))).pipe(exports_Effect.mapError(() => cliError("PROMPT_CANCELLED", "Interactive input was cancelled")));
60977
61395
  };
61396
+ var badgeProfileInput = (path4, defaults2, pictures) => {
61397
+ const interactive = exports_Effect.gen(function* () {
61398
+ const publicFields = yield* exports_Prompt.run(exports_Prompt.all({
61399
+ fullName: requiredText("Nombre impreso en el carnet", exports_Schema.Trim.pipe(exports_Schema.check(exports_Schema.isMinLength(1), exports_Schema.isMaxLength(200))), defaults2.fullName),
61400
+ oneLiner: requiredText("Presentación de una línea", exports_Schema.Trim.pipe(exports_Schema.check(exports_Schema.isMinLength(1), exports_Schema.isMaxLength(30))), defaults2.oneLiner),
61401
+ linkUrl: requiredText("Destino del QR", exports_Schema.Trim.pipe(exports_Schema.check(exports_Schema.isMinLength(1), exports_Schema.isMaxLength(2048))), defaults2.linkUrl)
61402
+ }));
61403
+ const pictureChoices = [{ title: "Mantener mi foto actual", value: "keep" }];
61404
+ if (pictures.clerkPictureUrl) {
61405
+ pictureChoices.push({ title: "Usar mi foto de Clerk", value: "clerk" });
61406
+ }
61407
+ if (pictures.githubUrl) {
61408
+ pictureChoices.push({ title: "Usar mi foto de GitHub", value: "github" });
61409
+ }
61410
+ pictureChoices.push({
61411
+ title: "Subir otra foto",
61412
+ value: "upload"
61413
+ });
61414
+ const pictureChoice = yield* exports_Prompt.run(exports_Prompt.select({
61415
+ message: "Foto impresa en el carnet",
61416
+ choices: pictureChoices
61417
+ }));
61418
+ if (pictureChoice === "keep")
61419
+ return publicFields;
61420
+ return { ...publicFields, pictureSource: pictureChoice };
61421
+ });
61422
+ return inputOrInteractive(path4, interactive).pipe(exports_Effect.flatMap((input) => decode2(BadgeRegenerationInput, input, badgeRegenerationInputFieldNames)));
61423
+ };
60978
61424
 
60979
61425
  // src/picture-upload.ts
60980
61426
  import { readFile as readFile5, stat as stat3 } from "node:fs/promises";
@@ -62643,13 +63089,14 @@ var uploadPicture = exports_Effect.fn("uploadPicture")(function* (options, path4
62643
63089
  import { spawn as spawn3 } from "node:child_process";
62644
63090
  import { mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
62645
63091
  import { tmpdir as tmpdir2 } from "node:os";
62646
- import { dirname as dirname2, join as join3 } from "node:path";
63092
+ import { dirname as dirname2, isAbsolute, join as join3, relative, sep } from "node:path";
62647
63093
  var cliPackageName = "chofex-cli";
62648
63094
  var upgradeVersion = "latest";
62649
- var npmUpgradeArguments = (cacheDirectory) => [
63095
+ var npmRegistryUrl = `https://registry.npmjs.org/${cliPackageName}/latest`;
63096
+ var npmUpgradeArguments = (cacheDirectory, version) => [
62650
63097
  "install",
62651
63098
  "--global",
62652
- `${cliPackageName}@${upgradeVersion}`,
63099
+ `${cliPackageName}@${version}`,
62653
63100
  "--force",
62654
63101
  "--prefer-online",
62655
63102
  `--cache=${cacheDirectory}`
@@ -62657,22 +63104,32 @@ var npmUpgradeArguments = (cacheDirectory) => [
62657
63104
  var runProcess = (executable, arguments_, input) => new Promise((resolve4, reject) => {
62658
63105
  const stdin = input === undefined ? "ignore" : "pipe";
62659
63106
  const child = spawn3(executable, arguments_, {
62660
- stdio: [stdin, "ignore", "pipe"]
63107
+ stdio: [stdin, "pipe", "pipe"]
62661
63108
  });
63109
+ let stdout = "";
62662
63110
  let stderr = "";
63111
+ const stdoutStream = child.stdout;
62663
63112
  const stderrStream = child.stderr;
62664
- if (stderrStream === null) {
63113
+ if (stdoutStream === null || stderrStream === null) {
62665
63114
  child.kill();
62666
- reject(new Error(`Could not capture stderr from ${executable}`));
63115
+ reject(new Error(`Could not capture output from ${executable}`));
62667
63116
  return;
62668
63117
  }
63118
+ stdoutStream.setEncoding("utf8");
63119
+ stdoutStream.on("data", (chunk) => {
63120
+ stdout += chunk;
63121
+ });
62669
63122
  stderrStream.setEncoding("utf8");
62670
63123
  stderrStream.on("data", (chunk) => {
62671
63124
  stderr += chunk;
62672
63125
  });
62673
63126
  child.on("error", reject);
62674
63127
  child.on("close", (exitCode) => {
62675
- resolve4({ exitCode: exitCode ?? 1, stderr: stderr.trim() });
63128
+ resolve4({
63129
+ exitCode: exitCode ?? 1,
63130
+ stdout: stdout.trim(),
63131
+ stderr: stderr.trim()
63132
+ });
62676
63133
  });
62677
63134
  if (input !== undefined) {
62678
63135
  const stdinStream = child.stdin;
@@ -62689,7 +63146,7 @@ var runNpm = (arguments_) => {
62689
63146
  return runProcess(executable, arguments_);
62690
63147
  };
62691
63148
  var installerUrl = "https://hacktheandes.com/install";
62692
- var runInstaller = async (installDirectory) => {
63149
+ var runInstaller = async (installDirectory, version) => {
62693
63150
  const response = await fetch(installerUrl);
62694
63151
  if (!response.ok) {
62695
63152
  throw new Error(`installer download returned HTTP ${response.status}`);
@@ -62698,6 +63155,8 @@ var runInstaller = async (installDirectory) => {
62698
63155
  const arguments_ = [
62699
63156
  "-s",
62700
63157
  "--",
63158
+ "--version",
63159
+ version,
62701
63160
  "--install-dir",
62702
63161
  installDirectory,
62703
63162
  "--no-modify-path"
@@ -62717,22 +63176,160 @@ var assertSuccessful = (result3, program) => {
62717
63176
  throw new Error(`${program} exited with code ${result3.exitCode}${detail}`);
62718
63177
  };
62719
63178
  var upgradeCli = async (options = {}) => {
63179
+ const version = options.version ?? upgradeVersion;
62720
63180
  const standalone = options.standalone ?? isStandaloneExecutable();
62721
63181
  if (standalone) {
62722
63182
  const runner2 = options.installerRunner ?? runInstaller;
62723
- const result3 = await runner2(dirname2(process.execPath));
63183
+ const result3 = await runner2(dirname2(process.execPath), version);
62724
63184
  assertSuccessful(result3, "installer");
62725
63185
  return;
62726
63186
  }
62727
63187
  const runner = options.npmRunner ?? runNpm;
62728
63188
  const cacheDirectory = await mkdtemp2(join3(tmpdir2(), "chofex-npm-cache-"));
62729
63189
  try {
62730
- const result3 = await runner(npmUpgradeArguments(cacheDirectory));
63190
+ const result3 = await runner(npmUpgradeArguments(cacheDirectory, version));
62731
63191
  assertSuccessful(result3, "npm");
62732
63192
  } finally {
62733
63193
  await rm2(cacheDirectory, { recursive: true, force: true });
62734
63194
  }
62735
63195
  };
63196
+ var parseVersion = (version) => {
63197
+ const match9 = version.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
63198
+ if (match9 === null)
63199
+ throw new Error(`Invalid CLI version: ${version}`);
63200
+ return {
63201
+ core: [Number(match9[1]), Number(match9[2]), Number(match9[3])],
63202
+ prerelease: match9[4]?.split(".") ?? []
63203
+ };
63204
+ };
63205
+ var comparePrereleaseIdentifier = (left, right) => {
63206
+ const leftNumber = /^\d+$/.test(left) ? Number(left) : undefined;
63207
+ const rightNumber = /^\d+$/.test(right) ? Number(right) : undefined;
63208
+ if (leftNumber !== undefined && rightNumber !== undefined) {
63209
+ return leftNumber - rightNumber;
63210
+ }
63211
+ if (leftNumber !== undefined)
63212
+ return -1;
63213
+ if (rightNumber !== undefined)
63214
+ return 1;
63215
+ return left.localeCompare(right);
63216
+ };
63217
+ var compareVersions = (left, right) => {
63218
+ const parsedLeft = parseVersion(left);
63219
+ const parsedRight = parseVersion(right);
63220
+ const [leftMajor, leftMinor, leftPatch] = parsedLeft.core;
63221
+ const [rightMajor, rightMinor, rightPatch] = parsedRight.core;
63222
+ for (const difference3 of [
63223
+ leftMajor - rightMajor,
63224
+ leftMinor - rightMinor,
63225
+ leftPatch - rightPatch
63226
+ ]) {
63227
+ if (difference3 !== 0)
63228
+ return difference3;
63229
+ }
63230
+ if (parsedLeft.prerelease.length === 0) {
63231
+ return parsedRight.prerelease.length === 0 ? 0 : 1;
63232
+ }
63233
+ if (parsedRight.prerelease.length === 0)
63234
+ return -1;
63235
+ const identifierCount = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
63236
+ for (let index2 = 0;index2 < identifierCount; index2 += 1) {
63237
+ const leftIdentifier = parsedLeft.prerelease[index2];
63238
+ const rightIdentifier = parsedRight.prerelease[index2];
63239
+ if (leftIdentifier === undefined)
63240
+ return -1;
63241
+ if (rightIdentifier === undefined)
63242
+ return 1;
63243
+ const difference3 = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);
63244
+ if (difference3 !== 0)
63245
+ return difference3;
63246
+ }
63247
+ return 0;
63248
+ };
63249
+ var fetchLatestVersion = async () => {
63250
+ const response = await fetch(npmRegistryUrl, {
63251
+ headers: { accept: "application/json" },
63252
+ signal: AbortSignal.timeout(5000)
63253
+ });
63254
+ if (!response.ok) {
63255
+ throw new Error(`npm registry returned HTTP ${response.status}`);
63256
+ }
63257
+ const metadata = await response.json();
63258
+ if (typeof metadata !== "object" || metadata === null || !("version" in metadata) || typeof metadata.version !== "string") {
63259
+ throw new Error("npm registry returned an invalid package version");
63260
+ }
63261
+ parseVersion(metadata.version);
63262
+ return metadata.version;
63263
+ };
63264
+ var autoUpdateCli = async (currentVersion, options = {}) => {
63265
+ const latestVersion = await (options.fetchLatestVersion ?? fetchLatestVersion)();
63266
+ if (compareVersions(latestVersion, currentVersion) <= 0) {
63267
+ return { status: "current", version: currentVersion };
63268
+ }
63269
+ const upgrade = options.upgrade ?? ((version) => upgradeCli({ version }));
63270
+ await upgrade(latestVersion);
63271
+ return {
63272
+ status: "updated",
63273
+ previousVersion: currentVersion,
63274
+ version: latestVersion
63275
+ };
63276
+ };
63277
+ var npmGlobalNodeModulesPath = async (runner = runNpm) => {
63278
+ const result3 = await runner(["root", "--global"]);
63279
+ assertSuccessful(result3, "npm");
63280
+ if (!result3.stdout)
63281
+ throw new Error("npm did not report its global root");
63282
+ return result3.stdout;
63283
+ };
63284
+ var pathIsInside = (parent, child) => {
63285
+ const pathFromParent = relative(parent, child);
63286
+ return pathFromParent !== "" && pathFromParent !== ".." && !pathFromParent.startsWith(`..${sep}`) && !isAbsolute(pathFromParent);
63287
+ };
63288
+ var rootFlagsWithValues = new Set(Object.values(rootValueFlagNames).map((name) => `--${name}`));
63289
+ var requestedCommand = (arguments_) => {
63290
+ for (let index2 = 0;index2 < arguments_.length; index2 += 1) {
63291
+ const argument = arguments_[index2];
63292
+ if (argument === undefined)
63293
+ continue;
63294
+ if (rootFlagsWithValues.has(argument)) {
63295
+ index2 += 1;
63296
+ continue;
63297
+ }
63298
+ if (argument.startsWith("-"))
63299
+ continue;
63300
+ return argument;
63301
+ }
63302
+ return;
63303
+ };
63304
+ var shouldAutoUpdateCli = async ({
63305
+ arguments: arguments_,
63306
+ entryPath,
63307
+ environmentValue,
63308
+ globalNodeModulesPath,
63309
+ npmRunner,
63310
+ standalone
63311
+ }) => {
63312
+ if (environmentValue === "0" || environmentValue === "false")
63313
+ return false;
63314
+ const command = requestedCommand(arguments_);
63315
+ if (command === "update" || command === "upgrade") {
63316
+ return false;
63317
+ }
63318
+ if (standalone)
63319
+ return true;
63320
+ if (!entryPath.split(/[\\/]/).includes("node_modules"))
63321
+ return false;
63322
+ const npmRoot = globalNodeModulesPath ?? await npmGlobalNodeModulesPath(npmRunner);
63323
+ return pathIsInside(join3(npmRoot, cliPackageName), entryPath);
63324
+ };
63325
+ var runUpdatedCli = () => new Promise((resolve4, reject) => {
63326
+ const child = spawn3(process.execPath, process.argv.slice(1), {
63327
+ env: { ...process.env, CHOFEX_AUTO_UPDATE: "0" },
63328
+ stdio: "inherit"
63329
+ });
63330
+ child.on("error", reject);
63331
+ child.on("close", (exitCode) => resolve4(exitCode ?? 1));
63332
+ });
62736
63333
 
62737
63334
  // src/commands.ts
62738
63335
  var inputFlag2 = exports_Flag.string("input").pipe(exports_Flag.optional, exports_Flag.withDescription("Read JSON from a file, or use - for stdin"));
@@ -62819,12 +63416,50 @@ var requirementsCommand = exports_Command.make("requirements", {}, exports_Effec
62819
63416
  });
62820
63417
  yield* execute(options.output, operation, requirementsOnlyText, registrationLookupErrorText);
62821
63418
  })).pipe(exports_Command.withDescription("Show information you still need to provide"));
63419
+ var badgeRegenerateCommand = exports_Command.make("regenerate", {
63420
+ input: inputFlag2,
63421
+ picture: exports_Flag.string("picture").pipe(exports_Flag.optional, exports_Flag.withDescription("Ruta a una foto JPEG, PNG o WebP (máximo 5 MB)"))
63422
+ }, exports_Effect.fn("badgeRegenerateCommand")(function* ({ input, picture }) {
63423
+ const options = yield* root;
63424
+ const operation = exports_Effect.gen(function* () {
63425
+ const inputPath = exports_Option.getOrUndefined(input);
63426
+ if (options.output === "json" && inputPath === undefined) {
63427
+ return yield* exports_Effect.fail(cliError("INPUT_REQUIRED", "El modo JSON requiere --input <archivo> o --input -"));
63428
+ }
63429
+ const token = exports_Option.getOrUndefined(options.token);
63430
+ const client = { apiUrl: options.apiUrl, token };
63431
+ const badge = yield* getBadge(client);
63432
+ if (!badge.data.profile) {
63433
+ return yield* exports_Effect.fail(cliError("INVALID_APPLICATION_STATE", "Confirma tu asistencia antes de regenerar tu carnet"));
63434
+ }
63435
+ const [current, currentUser] = yield* exports_Effect.all([
63436
+ getRegistration(client),
63437
+ getCurrentUser(client)
63438
+ ]);
63439
+ const body = yield* badgeProfileInput(inputPath, badge.data.profile, {
63440
+ clerkPictureUrl: currentUser.data.clerkPictureUrl,
63441
+ githubUrl: current.data.registration.githubUrl
63442
+ });
63443
+ const picturePath = exports_Option.getOrUndefined(picture);
63444
+ if (body.pictureSource === "upload") {
63445
+ if (options.output === "json" && picturePath === undefined) {
63446
+ return yield* exports_Effect.fail(cliError("PICTURE_PATH_REQUIRED", "Usa --picture <ruta> cuando pictureSource es upload"));
63447
+ }
63448
+ const path4 = yield* picturePathInput(picturePath);
63449
+ yield* uploadPicture(client, path4);
63450
+ } else if (picturePath) {
63451
+ return yield* exports_Effect.fail(cliError("UNEXPECTED_PICTURE_PATH", "--picture solo se puede usar cuando pictureSource es upload"));
63452
+ }
63453
+ return yield* regenerateBadge(client, body);
63454
+ });
63455
+ yield* execute(options.output, operation, badgeText);
63456
+ })).pipe(exports_Command.withDescription("Actualiza la foto, el nombre, la presentación y el enlace QR del carnet"));
62822
63457
  var badgeCommand = exports_Command.make("badge", {}, exports_Effect.fn("badgeCommand")(function* () {
62823
63458
  const options = yield* root;
62824
63459
  const token = exports_Option.getOrUndefined(options.token);
62825
63460
  const operation = getBadge({ apiUrl: options.apiUrl, token });
62826
63461
  yield* execute(options.output, operation, badgeText);
62827
- })).pipe(exports_Command.withDescription("Show your generated participant badge"));
63462
+ })).pipe(exports_Command.withDescription("Muestra o regenera tu carnet de participante"), exports_Command.withSubcommands([badgeRegenerateCommand]));
62828
63463
  var confirmCommand = exports_Command.make("confirm", {
62829
63464
  input: inputFlag2,
62830
63465
  picture: exports_Flag.string("picture").pipe(exports_Flag.optional, exports_Flag.withDescription("Path to a JPEG, PNG, or WebP picture (5 MB max)"))
@@ -63259,4 +63894,37 @@ var programForArguments = () => {
63259
63894
  }
63260
63895
  return commandProgram;
63261
63896
  };
63262
- programForArguments().pipe(exports_Effect.provide(exports_NodeServices.layer), (program) => exports_NodeRuntime.runMain(program, { disableErrorReporting: true }));
63897
+ var runAutomaticUpdate = async () => {
63898
+ try {
63899
+ const standalone = isStandaloneExecutable();
63900
+ const enabled = await shouldAutoUpdateCli({
63901
+ arguments: arguments_,
63902
+ entryPath: fileURLToPath2(import.meta.url),
63903
+ environmentValue: process.env.CHOFEX_AUTO_UPDATE,
63904
+ standalone
63905
+ });
63906
+ if (!enabled)
63907
+ return "continue";
63908
+ const result3 = await autoUpdateCli(cliVersion);
63909
+ if (result3.status === "current")
63910
+ return "continue";
63911
+ process.stderr.write(`${cliPackageName} se actualizó de ${result3.previousVersion} a ${result3.version}.
63912
+ `);
63913
+ if (standalone && process.platform === "win32") {
63914
+ process.stderr.write(`La actualización se aplicará cuando termine este comando.
63915
+ `);
63916
+ return "continue";
63917
+ }
63918
+ process.exitCode = await runUpdatedCli();
63919
+ return "command-completed";
63920
+ } catch {
63921
+ process.stderr.write(`No se pudo comprobar o instalar una actualización de ${cliPackageName}; se continuará con ${cliVersion}.
63922
+ `);
63923
+ return "continue";
63924
+ }
63925
+ };
63926
+ exports_Effect.promise(runAutomaticUpdate).pipe(exports_Effect.flatMap((outcome) => {
63927
+ if (outcome === "command-completed")
63928
+ return exports_Effect.void;
63929
+ return programForArguments();
63930
+ }), exports_Effect.provide(exports_NodeServices.layer), (program) => exports_NodeRuntime.runMain(program, { disableErrorReporting: true }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chofex-cli",
3
- "version": "0.1.173",
3
+ "version": "0.1.178",
4
4
  "description": "Command-line client for Hack the Andes",
5
5
  "type": "module",
6
6
  "bin": {