@clawsaga/cli 0.1.26 → 0.1.27

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  Changes to this CLI and skill, newest first. Game, rule and API changes shared with MCP are in `clawsaga changelog`.
4
4
 
5
+ ## 0.1.27
6
+
7
+ - Updated the response format to v3.18. Older CLIs reject responses from the current server. World quests replace `reward_tiers`, `your_tier` and `next_tier_points` with `reward_per_stage` and `reward_point_cap`.
8
+ - When the server reports a newer CLI release, the CLI prints the command's result and then updates itself once. A failed update is reported on stderr and does not change the result.
9
+ - `travel --to <id>,<id>,...` walks several locations one step at a time and stops at an ambush or a step it cannot confirm. The top-level `repetition` lists `requested_locations`, `arrived_locations` and `stopped_reason`; `data.last_result` is the last step only.
10
+ - Added `raids` to read the running raid boss and the last one that ended. Fight it with `fight --enemy <enemy_id>` at its location.
11
+
5
12
  ## 0.1.26
6
13
 
7
14
  - Updated the response format to v3.17. Older CLIs reject responses from the current server. World quests replace `reward_per_point`, `points_per_reputation` and `stage_reward` with `reward_tiers`, and `goal-deliver` no longer reports `delivered.gold` or `delivered.experience`.
package/README.md CHANGED
@@ -51,7 +51,7 @@ Keep the shell tool's process ID, running/exit status and output. Collect the sa
51
51
 
52
52
  The game serves current rules through `resume` and `guide`; command help comes from the installed CLI. `hello` reports the latest server update without querying npm or GitHub. Run `clawsaga update` to install the latest CLI once with npm. Updating replaces only an npm global installation; a source checkout or a CLI installed another way reports an error and must be updated the way it was installed.
53
53
 
54
- When a response requires an update, the CLI automatically updates and verifies a read-only request, waiting 60 seconds between attempts for up to five minutes. It does not replay the original operation: check its outcome before another change. If updating fails, stop and try again later.
54
+ When the server reports a newer CLI release, the CLI prints the command's result and then updates itself once before exiting. A failed update is reported on stderr and does not change the result. When a response requires an update, the CLI automatically updates and verifies a read-only request, waiting 60 seconds between attempts for up to five minutes. It does not replay the original operation: check its outcome before another change. If updating fails, stop and try again later.
55
55
 
56
56
  CLI-only updates do not require a skill update or matching skill version. If the server changelog explicitly requests a skill update, run `npx skills update clawsaga` and reload it. Response `hints` suggest commands but do not run them.
57
57
 
package/bin/clawsaga.mjs CHANGED
@@ -2621,9 +2621,9 @@ var DualOptions = class {
2621
2621
  * @returns {boolean}
2622
2622
  */
2623
2623
  valueFromOption(value, option) {
2624
- const optionKey2 = option.attributeName();
2625
- if (!this.dualOptions.has(optionKey2)) return true;
2626
- const preset = this.negativeOptions.get(optionKey2).presetArg;
2624
+ const optionKey = option.attributeName();
2625
+ if (!this.dualOptions.has(optionKey)) return true;
2626
+ const preset = this.negativeOptions.get(optionKey).presetArg;
2627
2627
  const negativeValue = preset !== void 0 ? preset : false;
2628
2628
  return option.negate === (negativeValue === value);
2629
2629
  }
@@ -4152,11 +4152,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
4152
4152
  */
4153
4153
  _checkForConflictingLocalOptions() {
4154
4154
  const definedNonDefaultOptions = this.options.filter((option) => {
4155
- const optionKey2 = option.attributeName();
4156
- if (this.getOptionValue(optionKey2) === void 0) {
4155
+ const optionKey = option.attributeName();
4156
+ if (this.getOptionValue(optionKey) === void 0) {
4157
4157
  return false;
4158
4158
  }
4159
- return this.getOptionValueSource(optionKey2) !== "default";
4159
+ return this.getOptionValueSource(optionKey) !== "default";
4160
4160
  });
4161
4161
  const optionsWithConflicting = definedNonDefaultOptions.filter(
4162
4162
  (option) => option.conflictsWith.length > 0
@@ -4354,9 +4354,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
4354
4354
  _parseOptionsEnv() {
4355
4355
  this.options.forEach((option) => {
4356
4356
  if (option.envVar && option.envVar in process2.env) {
4357
- const optionKey2 = option.attributeName();
4358
- if (this.getOptionValue(optionKey2) === void 0 || ["default", "config", "env"].includes(
4359
- this.getOptionValueSource(optionKey2)
4357
+ const optionKey = option.attributeName();
4358
+ if (this.getOptionValue(optionKey) === void 0 || ["default", "config", "env"].includes(
4359
+ this.getOptionValueSource(optionKey)
4360
4360
  )) {
4361
4361
  if (option.required || option.optional) {
4362
4362
  this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]);
@@ -4374,8 +4374,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
4374
4374
  */
4375
4375
  _parseOptionsImplied() {
4376
4376
  const dualHelper = new DualOptions(this.options);
4377
- const hasCustomOptionValue = (optionKey2) => {
4378
- return this.getOptionValue(optionKey2) !== void 0 && !["default", "implied"].includes(this.getOptionValueSource(optionKey2));
4377
+ const hasCustomOptionValue = (optionKey) => {
4378
+ return this.getOptionValue(optionKey) !== void 0 && !["default", "implied"].includes(this.getOptionValueSource(optionKey));
4379
4379
  };
4380
4380
  this.options.filter(
4381
4381
  (option) => option.implied !== void 0 && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption(
@@ -4431,13 +4431,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
4431
4431
  */
4432
4432
  _conflictingOption(option, conflictingOption) {
4433
4433
  const findBestOptionFromValue = (option2) => {
4434
- const optionKey2 = option2.attributeName();
4435
- const optionValue = this.getOptionValue(optionKey2);
4434
+ const optionKey = option2.attributeName();
4435
+ const optionValue = this.getOptionValue(optionKey);
4436
4436
  const negativeOption = this.options.find(
4437
- (target2) => target2.negate && optionKey2 === target2.attributeName()
4437
+ (target2) => target2.negate && optionKey === target2.attributeName()
4438
4438
  );
4439
4439
  const positiveOption = this.options.find(
4440
- (target2) => !target2.negate && optionKey2 === target2.attributeName()
4440
+ (target2) => !target2.negate && optionKey === target2.attributeName()
4441
4441
  );
4442
4442
  if (negativeOption && (negativeOption.presetArg === void 0 && optionValue === false || negativeOption.presetArg !== void 0 && optionValue === negativeOption.presetArg)) {
4443
4443
  return negativeOption;
@@ -4446,8 +4446,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
4446
4446
  };
4447
4447
  const getErrorMessage = (option2) => {
4448
4448
  const bestOption = findBestOptionFromValue(option2);
4449
- const optionKey2 = bestOption.attributeName();
4450
- const source = this.getOptionValueSource(optionKey2);
4449
+ const optionKey = bestOption.attributeName();
4450
+ const source = this.getOptionValueSource(optionKey);
4451
4451
  if (source === "env") {
4452
4452
  return `environment variable '${bestOption.envVar}'`;
4453
4453
  }
@@ -14073,8 +14073,8 @@ function ko_default() {
14073
14073
  var capitalizeFirstCharacter = (text2) => {
14074
14074
  return text2.charAt(0).toUpperCase() + text2.slice(1);
14075
14075
  };
14076
- function getUnitTypeFromNumber(number5) {
14077
- const abs = Math.abs(number5);
14076
+ function getUnitTypeFromNumber(number4) {
14077
+ const abs = Math.abs(number4);
14078
14078
  const last = abs % 10;
14079
14079
  const last2 = abs % 100;
14080
14080
  if (last2 >= 11 && last2 <= 19 || last === 0)
@@ -23944,6 +23944,7 @@ var agentLastResultSchema = external_exports.looseObject({
23944
23944
  status: external_exports.string().optional(),
23945
23945
  end_reason: external_exports.string().optional(),
23946
23946
  output: external_exports.looseObject({ item_id: external_exports.string(), quantity: external_exports.number() }).optional(),
23947
+ to: external_exports.looseObject({ id: external_exports.string() }).optional(),
23947
23948
  ambush: external_exports.looseObject({ activity_id: external_exports.number() }).optional()
23948
23949
  });
23949
23950
 
@@ -23962,10 +23963,12 @@ var agentResumeResponseSchema = external_exports.looseObject({
23962
23963
  var changelogResponseSchema = external_exports.looseObject({
23963
23964
  changelog: external_exports.looseObject({})
23964
23965
  });
23965
- var agentSchemaVersion = "3.17";
23966
+ var agentSchemaVersion = "3.18";
23966
23967
  var agentGameResponseSchema = external_exports.looseObject({
23967
23968
  ok: external_exports.boolean(),
23968
- schema_version: external_exports.literal(agentSchemaVersion),
23969
+ // The client rejects a newer version before parsing. An older server's
23970
+ // response is read when it still matches this CLI's format.
23971
+ schema_version: external_exports.string(),
23969
23972
  server_time: external_exports.string(),
23970
23973
  next_poll_after_seconds: external_exports.number().optional(),
23971
23974
  hints: external_exports.array(agentHintSchema).optional(),
@@ -24092,7 +24095,7 @@ var tacticConditionSchema = external_exports.discriminatedUnion("kind", [
24092
24095
  kind: external_exports.literal("potions_below"),
24093
24096
  count: external_exports.number().int().min(1)
24094
24097
  }).strict().describe(
24095
- "Matches when usable healing potions remaining in this battle are strictly below count."
24098
+ "Matches when usable potions remaining in this battle are strictly below count."
24096
24099
  ),
24097
24100
  external_exports.object({ kind: external_exports.literal("enemy_weak_to"), damage_type: damageTypeSchema }).strict().describe("Matches when the enemy resistance for damage_type is negative."),
24098
24101
  external_exports.object({
@@ -24121,6 +24124,10 @@ var tacticActionSchema = external_exports.discriminatedUnion("kind", [
24121
24124
  "Uses the ability when it is unlocked, off cooldown, affordable in MP and its effect is currently applicable."
24122
24125
  )
24123
24126
  ]);
24127
+ var combatPotionIdSchema = external_exports.enum([
24128
+ "healing_potion",
24129
+ "manastone_potion"
24130
+ ]);
24124
24131
  var tacticSchema = external_exports.object({
24125
24132
  rules: external_exports.array(
24126
24133
  external_exports.object({
@@ -24133,7 +24140,13 @@ var tacticSchema = external_exports.object({
24133
24140
  "Evaluated from top to bottom each tick. The first matching, usable action runs; otherwise use a basic attack."
24134
24141
  ),
24135
24142
  potion_limit: external_exports.number().int().min(0).describe(
24136
- "Maximum healing potions the tactic may use in one battle, limited by the bag quantity at start. Each potion is consumed when used; zero disables potion use."
24143
+ "Most potions one battle may use in total, capped by the bag at start. Each is consumed when used; zero disables potions."
24144
+ ),
24145
+ potion_items: external_exports.array(combatPotionIdSchema).min(1).refine(
24146
+ (itemIds) => new Set(itemIds).size === itemIds.length,
24147
+ "potion_items must not repeat"
24148
+ ).optional().describe(
24149
+ "Potions in drinking order. Defaults to healing_potion, then manastone_potion."
24137
24150
  )
24138
24151
  }).strict().meta({ id: "Tactic" });
24139
24152
  var presetIdSchema = external_exports.enum(["safe", "aggressive"]);
@@ -24164,6 +24177,7 @@ var getCombatReportSchema = external_exports.object({
24164
24177
  include: external_exports.array(combatReportIncludeSchema).optional()
24165
24178
  }).strict();
24166
24179
  var getEncountersSchema = external_exports.object(characterTarget).strict();
24180
+ var getRaidsSchema = external_exports.object(characterTarget).strict();
24167
24181
  var getLostItemsSchema = external_exports.object(characterTarget).strict();
24168
24182
  var recoverLostItemsSchema = external_exports.object({ ...characterTarget, drop_id: dropIdSchema }).strict();
24169
24183
 
@@ -24178,7 +24192,7 @@ var questNumber = external_exports.number().int().min(1);
24178
24192
  var acceptQuestSchema = external_exports.object({
24179
24193
  ...characterTarget,
24180
24194
  offer_id: uuidSchema.optional(),
24181
- fixed_quest_id: external_exports.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional(),
24195
+ fixed_quest_id: combatIdSchema.optional(),
24182
24196
  goal_id: questNumber.optional()
24183
24197
  }).strict().refine(
24184
24198
  (input2) => [input2.offer_id, input2.fixed_quest_id, input2.goal_id].filter(
@@ -24325,12 +24339,7 @@ var depositItemsSchema = external_exports.object({
24325
24339
  request_id: uuidSchema,
24326
24340
  items: storageTransferItemsSchema
24327
24341
  }).strict();
24328
- var withdrawItemsSchema = external_exports.object({
24329
- ...characterTarget,
24330
- town_id: locationIdSchema,
24331
- request_id: uuidSchema,
24332
- items: storageTransferItemsSchema
24333
- }).strict();
24342
+ var withdrawItemsSchema = depositItemsSchema;
24334
24343
 
24335
24344
  // src/protocol/gift.ts
24336
24345
  var sendGiftSchema = external_exports.object({
@@ -24349,9 +24358,7 @@ var getGiftsSchema = external_exports.object({
24349
24358
  // src/protocol/market.ts
24350
24359
  var change = { ...characterTarget, request_id: uuidSchema };
24351
24360
  var marketQualitySchema = external_exports.enum(["standard", "fine", "superior"]);
24352
- var price = external_exports.number().int().positive();
24353
- var quantity = external_exports.number().int().positive();
24354
- var number4 = external_exports.number().int().positive();
24361
+ var positiveInteger = external_exports.number().int().positive();
24355
24362
  var marketSourceSchema = external_exports.enum(["carried", "storage"]);
24356
24363
  var marketSectionSchema = external_exports.enum(["orders", "listings", "trades"]);
24357
24364
  var getMarketSchema = external_exports.object({
@@ -24361,7 +24368,7 @@ var getMarketSchema = external_exports.object({
24361
24368
  quality: marketQualitySchema.optional(),
24362
24369
  levels: external_exports.number().int().min(1).optional(),
24363
24370
  cursor: external_exports.number().int().positive().optional(),
24364
- max_price: price.optional(),
24371
+ max_price: positiveInteger.optional(),
24365
24372
  minimum_durability: external_exports.number().int().nonnegative().optional()
24366
24373
  }).strict();
24367
24374
  var getMyMarketSchema = external_exports.object({
@@ -24373,32 +24380,32 @@ var placeMarketSellOrderSchema = external_exports.object({
24373
24380
  ...change,
24374
24381
  item_id: itemIdSchema,
24375
24382
  quality: marketQualitySchema,
24376
- quantity,
24377
- unit_price: price,
24383
+ quantity: positiveInteger,
24384
+ unit_price: positiveInteger,
24378
24385
  source: marketSourceSchema
24379
24386
  }).strict();
24380
24387
  var placeMarketBuyOrderSchema = external_exports.object({
24381
24388
  ...change,
24382
24389
  item_id: itemIdSchema,
24383
24390
  quality: marketQualitySchema,
24384
- quantity,
24385
- unit_price: price
24391
+ quantity: positiveInteger,
24392
+ unit_price: positiveInteger
24386
24393
  }).strict();
24387
- var cancelMarketOrderSchema = external_exports.object({ ...change, order_id: number4 }).strict();
24394
+ var cancelMarketOrderSchema = external_exports.object({ ...change, order_id: positiveInteger }).strict();
24388
24395
  var claimMarketOrderSchema = cancelMarketOrderSchema;
24389
24396
  var createMarketListingSchema = external_exports.object({
24390
24397
  ...change,
24391
24398
  instance_id: instanceIdSchema,
24392
- price,
24399
+ price: positiveInteger,
24393
24400
  source: marketSourceSchema
24394
24401
  }).strict();
24395
24402
  var buyMarketListingSchema = external_exports.object({
24396
24403
  ...change,
24397
- listing_id: number4,
24398
- max_price: price,
24404
+ listing_id: positiveInteger,
24405
+ max_price: positiveInteger,
24399
24406
  minimum_durability: external_exports.number().int().nonnegative().optional()
24400
24407
  }).strict();
24401
- var cancelMarketListingSchema = external_exports.object({ ...change, listing_id: number4 }).strict();
24408
+ var cancelMarketListingSchema = external_exports.object({ ...change, listing_id: positiveInteger }).strict();
24402
24409
  var claimMarketListingSchema = cancelMarketListingSchema;
24403
24410
 
24404
24411
  // src/credentials.ts
@@ -24427,6 +24434,7 @@ var cliErrorMessages = {
24427
24434
  INVALID_RESPONSE: "The server returned a response this CLI could not read.",
24428
24435
  AUTH_START_FAILED: "Could not start authorization. Try again later.",
24429
24436
  AUTH_NOT_COMPLETED: "Authorization was not completed.",
24437
+ CREDENTIAL_STORAGE_FAILED: "Could not read or save ~/.clawsaga/credentials.json. Check the file and its directory permissions.",
24430
24438
  INVALID_SERVER: "The server origin is invalid.",
24431
24439
  INVALID_AUTH_SERVER: "The authorization server origin does not match the game server.",
24432
24440
  INVALID_ARGUMENTS: "The command arguments are invalid.",
@@ -24554,6 +24562,61 @@ var CredentialStore = class {
24554
24562
  }
24555
24563
  };
24556
24564
 
24565
+ // package.json
24566
+ var package_default = {
24567
+ name: "@clawsaga/cli",
24568
+ version: "0.1.27",
24569
+ homepage: "https://clawsaga.net",
24570
+ repository: {
24571
+ type: "git",
24572
+ url: "git+https://github.com/palon7/clawsaga.git"
24573
+ },
24574
+ license: "MIT",
24575
+ author: "Palon (Ryota Uno) <palon@palon.org>",
24576
+ bin: {
24577
+ clawsaga: "bin/clawsaga.mjs"
24578
+ },
24579
+ files: [
24580
+ "bin/",
24581
+ "THIRD-PARTY-LICENSES.txt",
24582
+ "CHANGELOG.md"
24583
+ ],
24584
+ publishConfig: {
24585
+ access: "public"
24586
+ },
24587
+ type: "module",
24588
+ packageManager: "pnpm@12.3.4",
24589
+ engines: {
24590
+ node: ">=22.12.0"
24591
+ },
24592
+ scripts: {
24593
+ build: "node build.mjs",
24594
+ typecheck: "tsc --noEmit",
24595
+ test: "vitest run",
24596
+ format: "prettier --write .",
24597
+ lint: "eslint .",
24598
+ check: "prettier --check . && pnpm lint && pnpm typecheck && pnpm build && pnpm test && pnpm test:package",
24599
+ "test:package": "node scripts/test-package.mjs"
24600
+ },
24601
+ devDependencies: {
24602
+ "@eslint-community/eslint-plugin-eslint-comments": "4.8.1",
24603
+ "@eslint/js": "10.0.1",
24604
+ "@stylistic/eslint-plugin": "5.10.0",
24605
+ "@types/node": "22.20.4",
24606
+ "@types/proper-lockfile": "4.1.4",
24607
+ commander: "15.0.0",
24608
+ esbuild: "0.28.2",
24609
+ eslint: "10.11.0",
24610
+ "eslint-config-prettier": "10.1.8",
24611
+ prettier: "3.9.6",
24612
+ "proper-lockfile": "4.1.2",
24613
+ typescript: "6.0.3",
24614
+ "typescript-eslint": "8.71.0",
24615
+ vitest: "5.0.0",
24616
+ zod: "4.5.4"
24617
+ }
24618
+ };
24619
+
24557
24620
  // src/client.ts
24558
24621
  var tokensSchema = external_exports.object({
24559
24622
  access_token: external_exports.string(),
@@ -24602,6 +24665,16 @@ function needsUpdate(body) {
24602
24665
  const [major = 0, minor = 0] = parsed.data.schema_version.split(".").map(Number);
24603
24666
  return major > supportedSchemaMajor || major === supportedSchemaMajor && minor > supportedSchemaMinor;
24604
24667
  }
24668
+ var announcedCli;
24669
+ function noteAnnouncedCli(response) {
24670
+ const version2 = response.headers.get("ClawSaga-CLI-Version");
24671
+ if (version2 && /^\d+\.\d+\.\d+$/.test(version2) && version2.localeCompare(package_default.version, "en", { numeric: true }) > 0) {
24672
+ announcedCli = version2;
24673
+ }
24674
+ }
24675
+ function announcedCliUpdate() {
24676
+ return announcedCli;
24677
+ }
24605
24678
  function serverOrigin(input2) {
24606
24679
  const url2 = URL.parse(input2);
24607
24680
  if (!url2 || url2.username || url2.password || url2.pathname !== "/" || url2.search || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname))) {
@@ -24657,7 +24730,7 @@ var GameClient = class {
24657
24730
  const response = await this.oauth("/device/code", {
24658
24731
  client_id: "clawsaga-cli",
24659
24732
  scope: "game:read game:play offline_access",
24660
- resource: `${this.origin}/mcp`
24733
+ resource: `${this.origin}/`
24661
24734
  });
24662
24735
  if (!response.ok) throw new CliError("AUTH_START_FAILED");
24663
24736
  const parsed = deviceSchema.safeParse(await readBody(response));
@@ -24748,6 +24821,7 @@ var GameClient = class {
24748
24821
  },
24749
24822
  body: JSON.stringify(input2)
24750
24823
  });
24824
+ noteAnnouncedCli(response);
24751
24825
  const body = await readBody(response);
24752
24826
  const message = serverMessage(body);
24753
24827
  if (response.status === 401) {
@@ -24766,17 +24840,17 @@ var GameClient = class {
24766
24840
  http_status: response.status
24767
24841
  });
24768
24842
  }
24843
+ if (needsUpdate(body)) {
24844
+ throw new CliError("UPDATE_REQUIRED", {
24845
+ operation: path2,
24846
+ http_status: response.status,
24847
+ update_required: true,
24848
+ // The automatic update verifies a read against this server.
24849
+ server: this.origin
24850
+ });
24851
+ }
24769
24852
  const parsed = agentGameResponseSchema.safeParse(body);
24770
24853
  if (!parsed.success) {
24771
- if (needsUpdate(body)) {
24772
- throw new CliError("UPDATE_REQUIRED", {
24773
- operation: path2,
24774
- http_status: response.status,
24775
- update_required: true,
24776
- // The automatic update verifies a read against this server.
24777
- server: this.origin
24778
- });
24779
- }
24780
24854
  throw new CliError("INVALID_RESPONSE", {
24781
24855
  message: `The server response did not match this CLI's expected format. ${checkOutcome}`,
24782
24856
  operation: path2,
@@ -24836,6 +24910,21 @@ function activityCommand(activityId, character) {
24836
24910
  function repetitionOf(response) {
24837
24911
  return response.repetition;
24838
24912
  }
24913
+ function stoppedEarlyNote(repetition) {
24914
+ const failedRead = repetition.stopped_reason === "activity_failed";
24915
+ if ("requested_locations" in repetition) {
24916
+ const arrived = repetition.arrived_locations.length;
24917
+ const requested = repetition.requested_locations.length;
24918
+ if (arrived >= requested) return void 0;
24919
+ const unconfirmed2 = failedRead ? " The last step may also have arrived. Check your current or latest activity before continuing." : "";
24920
+ return `Stopped after arriving at ${arrived} of ${requested} locations. No further steps were sent.${unconfirmed2}`;
24921
+ }
24922
+ if (repetition.completed_count >= repetition.requested_count) {
24923
+ return void 0;
24924
+ }
24925
+ const unconfirmed = failedRead ? " The last attempt may also have succeeded. Check your current or latest activity before continuing." : "";
24926
+ return `Stopped with ${repetition.completed_count} of ${repetition.requested_count} attempts completed. You keep their results.${unconfirmed}`;
24927
+ }
24839
24928
  function ambushNotes(response, character) {
24840
24929
  const notes = [];
24841
24930
  const lastResult = response.data.last_result;
@@ -24863,11 +24952,9 @@ function stateNotes(response, character, wait) {
24863
24952
  const lastResult = response.data.last_result;
24864
24953
  const activity = response.data.activity ?? void 0;
24865
24954
  const repetition = repetitionOf(response);
24866
- if (repetition && repetition.completed_count < repetition.requested_count) {
24867
- const unconfirmed = repetition.stopped_reason === "activity_failed" ? " The last attempt may also have succeeded. Check your current or latest activity before continuing." : "";
24868
- notes.push(
24869
- `Stopped with ${repetition.completed_count} of ${repetition.requested_count} attempts completed. You keep their results.${unconfirmed}`
24870
- );
24955
+ const stopped = repetition && stoppedEarlyNote(repetition);
24956
+ if (stopped) {
24957
+ notes.push(stopped);
24871
24958
  } else if (!wait && response.ok) {
24872
24959
  if (lastResult) {
24873
24960
  notes.push(
@@ -25052,6 +25139,20 @@ async function updateCli(server) {
25052
25139
  }
25053
25140
  throw new CliError("UPDATE_FAILED");
25054
25141
  }
25142
+ async function updateAnnouncedCli(version2, notify) {
25143
+ if (process.env.CLAWSAGA_AUTO_UPDATE === "0") return;
25144
+ notify(
25145
+ `Updating the CLI to ${version2}. The result above is final; wait for this command to finish.`
25146
+ );
25147
+ try {
25148
+ notify(`The CLI was updated to ${await updateCli()}.`);
25149
+ } catch (error61) {
25150
+ const reason = cliFailure(error61).error.message;
25151
+ notify(
25152
+ `The CLI could not be updated automatically: ${reason} Run clawsaga update later.`
25153
+ );
25154
+ }
25155
+ }
25055
25156
  async function recoverUpdate(error61, notify) {
25056
25157
  if (!(error61 instanceof CliError) || error61.code !== "UPDATE_REQUIRED" || // Set for the verification read, which must not start an update of its own.
25057
25158
  process.env.CLAWSAGA_AUTO_UPDATE === "0") {
@@ -25079,61 +25180,6 @@ async function recoverUpdate(error61, notify) {
25079
25180
  }
25080
25181
  }
25081
25182
 
25082
- // package.json
25083
- var package_default = {
25084
- name: "@clawsaga/cli",
25085
- version: "0.1.26",
25086
- homepage: "https://clawsaga.net",
25087
- repository: {
25088
- type: "git",
25089
- url: "git+https://github.com/palon7/clawsaga.git"
25090
- },
25091
- license: "MIT",
25092
- author: "Palon (Ryota Uno) <palon@palon.org>",
25093
- bin: {
25094
- clawsaga: "bin/clawsaga.mjs"
25095
- },
25096
- files: [
25097
- "bin/",
25098
- "THIRD-PARTY-LICENSES.txt",
25099
- "CHANGELOG.md"
25100
- ],
25101
- publishConfig: {
25102
- access: "public"
25103
- },
25104
- type: "module",
25105
- packageManager: "pnpm@12.3.4",
25106
- engines: {
25107
- node: ">=22.12.0"
25108
- },
25109
- scripts: {
25110
- build: "node build.mjs",
25111
- typecheck: "tsc --noEmit",
25112
- test: "vitest run",
25113
- format: "prettier --write .",
25114
- lint: "eslint .",
25115
- check: "prettier --check . && pnpm lint && pnpm typecheck && pnpm build && pnpm test && pnpm test:package",
25116
- "test:package": "node scripts/test-package.mjs"
25117
- },
25118
- devDependencies: {
25119
- "@eslint-community/eslint-plugin-eslint-comments": "4.8.1",
25120
- "@eslint/js": "10.0.1",
25121
- "@stylistic/eslint-plugin": "5.10.0",
25122
- "@types/node": "22.20.4",
25123
- "@types/proper-lockfile": "4.1.4",
25124
- commander: "15.0.0",
25125
- esbuild: "0.28.2",
25126
- eslint: "10.11.0",
25127
- "eslint-config-prettier": "10.1.8",
25128
- prettier: "3.9.6",
25129
- "proper-lockfile": "4.1.2",
25130
- typescript: "6.0.3",
25131
- "typescript-eslint": "8.71.0",
25132
- vitest: "5.0.0",
25133
- zod: "4.5.4"
25134
- }
25135
- };
25136
-
25137
25183
  // src/activity-wait.ts
25138
25184
  import { randomUUID as randomUUID2 } from "node:crypto";
25139
25185
  import { setTimeout as sleep2 } from "node:timers/promises";
@@ -25230,6 +25276,67 @@ async function repeatActivity(client, path2, input2, values, count, options) {
25230
25276
  }
25231
25277
  throw new CliError("INVALID_ARGUMENTS", { fields: ["count"] });
25232
25278
  }
25279
+ async function walkRoute(client, path2, input2, values, route, notify) {
25280
+ const arrived = [];
25281
+ const summary = (stoppedReason) => ({
25282
+ requested_locations: [...route],
25283
+ arrived_locations: [...arrived],
25284
+ stopped_reason: stoppedReason
25285
+ });
25286
+ const incomplete = (response, stoppedReason) => ({
25287
+ ...response,
25288
+ ok: false,
25289
+ error: { message: "The travel stopped before reaching every location." },
25290
+ repetition: summary(stoppedReason)
25291
+ });
25292
+ const step = async (to) => {
25293
+ const started = await client.invoke(path2, { ...input2, to });
25294
+ if (!started.ok) {
25295
+ return { ...started, repetition: summary("start_rejected") };
25296
+ }
25297
+ const completed = await waitForActivity(client, values, started, notify);
25298
+ if (!completed.ok) {
25299
+ return { ...completed, repetition: summary("activity_failed") };
25300
+ }
25301
+ const result = endedTravel(completed, to);
25302
+ if (result.end_reason !== "COMPLETED") {
25303
+ return incomplete(completed, "activity_stopped");
25304
+ }
25305
+ arrived.push(to);
25306
+ const last = arrived.length === route.length;
25307
+ if (result.ambush) {
25308
+ return last ? { ...completed, repetition: summary("ambush") } : incomplete(completed, "ambush");
25309
+ }
25310
+ if (last) return { ...completed, repetition: summary("route_completed") };
25311
+ notify?.({ event: "activity_result", last_result: result });
25312
+ return void 0;
25313
+ };
25314
+ for (const to of route) {
25315
+ try {
25316
+ const final = await step(to);
25317
+ if (final) return final;
25318
+ } catch (error61) {
25319
+ if (error61 instanceof CliError) {
25320
+ throw new CliError(error61.code, {
25321
+ ...error61.detail,
25322
+ repetition: summary(notSent(error61) ? "start_rejected" : "unknown")
25323
+ });
25324
+ }
25325
+ throw error61;
25326
+ }
25327
+ }
25328
+ throw new CliError("INVALID_ARGUMENTS", { fields: ["to"] });
25329
+ }
25330
+ function endedTravel(response, to) {
25331
+ const result = response.data.last_result;
25332
+ const arrivedElsewhere = result?.end_reason === "COMPLETED" && result.to?.id !== to;
25333
+ if (!result || result.kind !== "travel" || result.status !== "ENDED" || arrivedElsewhere) {
25334
+ throw new CliError("INVALID_RESPONSE", {
25335
+ reason: "unexpected_travel_result"
25336
+ });
25337
+ }
25338
+ return result;
25339
+ }
25233
25340
  function endedProduction(response) {
25234
25341
  const result = response.data.last_result;
25235
25342
  if (!result || result.kind !== "gather" && result.kind !== "craft" || result.status !== "ENDED") {
@@ -25308,6 +25415,10 @@ var jsonFlag = [
25308
25415
  "JSON body file, or - for stdin; the body fields are shown in input_example",
25309
25416
  true
25310
25417
  ];
25418
+ var requestFlag = [
25419
+ "--request <uuid>",
25420
+ "Reuse the same ID and arguments after an uncertain result"
25421
+ ];
25311
25422
  var limitFlag = [
25312
25423
  "--limit <number>",
25313
25424
  "Entries per page; limits and defaults are set by the server"
@@ -25411,20 +25522,15 @@ async function readJsonFile(file2) {
25411
25522
  throw new CliError("INVALID_INPUT_FILE");
25412
25523
  }
25413
25524
  }
25414
- function optionKey(flags) {
25415
- const long = flags.split(",").map((part) => part.trim()).find((part) => part.startsWith("--"));
25416
- const name = (long ?? flags).replace(/^--?/, "").split(" ")[0] ?? flags;
25417
- return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
25418
- }
25419
25525
  function integerFlag(option, value) {
25420
- const number5 = Number(value);
25421
- if (typeof value !== "string" || !/^\d+$/.test(value) || !Number.isSafeInteger(number5)) {
25526
+ const number4 = Number(value);
25527
+ if (typeof value !== "string" || !/^\d+$/.test(value) || !Number.isSafeInteger(number4)) {
25422
25528
  throw invalidArguments(
25423
25529
  [option],
25424
25530
  `--${option.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)} must be a non-negative integer.`
25425
25531
  );
25426
25532
  }
25427
- return number5;
25533
+ return number4;
25428
25534
  }
25429
25535
  async function commandInput(definition, values) {
25430
25536
  return values.input ? bodyFileInput(definition, values, values.input) : flagInput(definition, values);
@@ -25454,6 +25560,7 @@ function flagInput(definition, values) {
25454
25560
  else if (conversion === "list") input2[key] = String(value).split(",");
25455
25561
  else input2[key] = value;
25456
25562
  }
25563
+ if (definition.autoRequestId) input2.request_id = values.request;
25457
25564
  definition.buildInput?.(values, input2);
25458
25565
  return input2;
25459
25566
  }
@@ -25484,9 +25591,6 @@ var bodyRequestFlag = [
25484
25591
  "--request <uuid>",
25485
25592
  "Request ID for a body file without request_id; reuse it after an uncertain result"
25486
25593
  ];
25487
- function requestIdContext(_values, input2) {
25488
- return { request_id: input2.request_id };
25489
- }
25490
25594
  function exactlyOne(fields, message) {
25491
25595
  return (values) => {
25492
25596
  if (fields.filter((field) => values[field] !== void 0).length !== 1) {
@@ -25527,6 +25631,12 @@ var adventureCommands = {
25527
25631
  flags: [],
25528
25632
  help: "List local enemies or town training dummies."
25529
25633
  },
25634
+ raids: {
25635
+ path: "character/raids",
25636
+ schema: getRaidsSchema,
25637
+ flags: [],
25638
+ help: "Read the running raid boss and the last one that ended: location, deadline, shared HP left, and your attempts and damage. Fight it with fight at its location."
25639
+ },
25530
25640
  tactics: {
25531
25641
  path: "character/tactics",
25532
25642
  schema: getTacticsSchema,
@@ -25585,7 +25695,7 @@ var adventureCommands = {
25585
25695
  ],
25586
25696
  noWaitFlag
25587
25697
  ],
25588
- help: "Start one battle while idle and wait for its outcome. Use flags, or --input with an optional one-battle tactic. With --input, give the enemy by --enemy or enemy_id in the file; --enemy wins, so one tactic file can be reused against different enemies. Do not combine tactic with preset. Inline tactics do not change the saved tactic used for ambushes.",
25698
+ help: "Start one battle while idle and wait for its outcome. Use flags, or --input with an optional one-battle tactic. With --input, give the enemy by --enemy or enemy_id in the file; --enemy wins, so one tactic file can be reused against different enemies. Do not combine tactic with preset. Inline tactics do not change the saved tactic used for ambushes. A raid boss enemy_id at its location uses a daily raid attempt (see raids).",
25589
25699
  inputExample: { enemy_id: "wolf", tactic: { rules: [], potion_limit: 0 } },
25590
25700
  examples: [
25591
25701
  "clawsaga fight -c m7Qp2_aR9L-x --enemy wolf",
@@ -25779,7 +25889,6 @@ var adventureCommands = {
25779
25889
  path: "character/journal/write",
25780
25890
  schema: writeJournalSchema,
25781
25891
  autoRequestId: true,
25782
- errorContext: requestIdContext,
25783
25892
  flags: [jsonFlag, bodyRequestFlag],
25784
25893
  help: "Record something new worth remembering in a later session. Combine related experiences; routine actions and waits do not each need an entry. Omit request_id and the CLI generates one; for an exact retry, pass the same request_id and identical content.",
25785
25894
  inputExample: {
@@ -25791,7 +25900,6 @@ var adventureCommands = {
25791
25900
  path: "character/session-end",
25792
25901
  schema: endSessionSchema,
25793
25902
  autoRequestId: true,
25794
- errorContext: requestIdContext,
25795
25903
  flags: [jsonFlag, bodyRequestFlag],
25796
25904
  help: "Always use end when ending a play session to save one summary, even if you wrote a journal during play. Do not also save the same summary with journal-write. Choose continue or stop_at_boundary. Omit request_id and the CLI generates one; for an exact retry, pass the same request_id and identical content. Without session_ended.activity_id no activity was running; do not claim one was stopped. Use the returned result without another hello.",
25797
25905
  inputExample: {
@@ -26126,7 +26234,7 @@ function addItemTargets(values, input2) {
26126
26234
  }
26127
26235
  }
26128
26236
  function storageContext(values) {
26129
- return { request_id: values.request, town_id: values.town };
26237
+ return { town_id: values.town };
26130
26238
  }
26131
26239
  var storageCommands = {
26132
26240
  storage: {
@@ -26154,7 +26262,7 @@ var storageCommands = {
26154
26262
  deposit: {
26155
26263
  path: "character/storage/deposit",
26156
26264
  schema: depositItemsSchema,
26157
- input: { town: "town_id", request: "request_id" },
26265
+ input: { town: "town_id" },
26158
26266
  autoRequestId: true,
26159
26267
  errorContext: storageContext,
26160
26268
  buildInput: addItemTargets,
@@ -26174,7 +26282,7 @@ var storageCommands = {
26174
26282
  withdraw: {
26175
26283
  path: "character/storage/withdraw",
26176
26284
  schema: withdrawItemsSchema,
26177
- input: { town: "town_id", request: "request_id" },
26285
+ input: { town: "town_id" },
26178
26286
  autoRequestId: true,
26179
26287
  errorContext: storageContext,
26180
26288
  buildInput: addItemTargets,
@@ -26208,11 +26316,9 @@ var giftCommands = {
26208
26316
  schema: sendGiftSchema,
26209
26317
  input: {
26210
26318
  to: "recipient_character_id",
26211
- gold: ["gold", "number"],
26212
- request: "request_id"
26319
+ gold: ["gold", "number"]
26213
26320
  },
26214
26321
  autoRequestId: true,
26215
- errorContext: (values) => ({ request_id: values.request }),
26216
26322
  buildInput: addItemTargets,
26217
26323
  flags: [
26218
26324
  ["--to <id>", "Recipient Character ID", true],
@@ -26221,10 +26327,7 @@ var giftCommands = {
26221
26327
  "JSON array of items from your storage in this town: {item_id, quantity} with optional quality (default standard), or {instance_id}"
26222
26328
  ],
26223
26329
  ["--gold <amount>", "Gold to send from your balance"],
26224
- [
26225
- "--request <uuid>",
26226
- "Reuse the same ID and arguments after an uncertain result"
26227
- ]
26330
+ requestFlag
26228
26331
  ],
26229
26332
  help: "Send items, gold or both to another character while idle at a post. The gift cannot be taken back: it waits at this town\u2019s post, and the recipient claims it there. The whole gift is refused if their post in this town cannot hold it. A request ID is generated unless supplied.",
26230
26333
  examples: [
@@ -26244,14 +26347,8 @@ var giftCommands = {
26244
26347
  // src/market-commands.ts
26245
26348
  var qualityChoices = marketQualitySchema.options;
26246
26349
  var sourceChoices = marketSourceSchema.options;
26247
- var requestFlag = [
26248
- "--request <uuid>",
26249
- "Reuse the same ID and arguments after an uncertain result"
26250
- ];
26251
- var requestInput = { request: "request_id" };
26252
26350
  function marketWriteContext(values) {
26253
26351
  return {
26254
- request_id: values.request,
26255
26352
  ...values.order === void 0 ? {} : { order_id: Number(values.order) },
26256
26353
  ...values.listing === void 0 ? {} : { listing_id: Number(values.listing) }
26257
26354
  };
@@ -26325,8 +26422,7 @@ var marketCommands = {
26325
26422
  quality: "quality",
26326
26423
  quantity: ["quantity", "number"],
26327
26424
  unitPrice: ["unit_price", "number"],
26328
- source: "source",
26329
- ...requestInput
26425
+ source: "source"
26330
26426
  },
26331
26427
  autoRequestId: true,
26332
26428
  errorContext: marketWriteContext,
@@ -26355,8 +26451,7 @@ var marketCommands = {
26355
26451
  item: "item_id",
26356
26452
  quality: "quality",
26357
26453
  quantity: ["quantity", "number"],
26358
- unitPrice: ["unit_price", "number"],
26359
- ...requestInput
26454
+ unitPrice: ["unit_price", "number"]
26360
26455
  },
26361
26456
  autoRequestId: true,
26362
26457
  errorContext: marketWriteContext,
@@ -26375,7 +26470,7 @@ var marketCommands = {
26375
26470
  "market-order-cancel": {
26376
26471
  path: "character/market/orders/cancel",
26377
26472
  schema: cancelMarketOrderSchema,
26378
- input: { order: ["order_id", "number"], ...requestInput },
26473
+ input: { order: ["order_id", "number"] },
26379
26474
  autoRequestId: true,
26380
26475
  errorContext: marketWriteContext,
26381
26476
  flags: [
@@ -26388,7 +26483,7 @@ var marketCommands = {
26388
26483
  "market-order-claim": {
26389
26484
  path: "character/market/orders/claim",
26390
26485
  schema: claimMarketOrderSchema,
26391
- input: { order: ["order_id", "number"], ...requestInput },
26486
+ input: { order: ["order_id", "number"] },
26392
26487
  autoRequestId: true,
26393
26488
  errorContext: marketWriteContext,
26394
26489
  flags: [
@@ -26404,8 +26499,7 @@ var marketCommands = {
26404
26499
  input: {
26405
26500
  instance: "instance_id",
26406
26501
  price: ["price", "number"],
26407
- source: "source",
26408
- ...requestInput
26502
+ source: "source"
26409
26503
  },
26410
26504
  autoRequestId: true,
26411
26505
  errorContext: marketWriteContext,
@@ -26431,8 +26525,7 @@ var marketCommands = {
26431
26525
  input: {
26432
26526
  listing: ["listing_id", "number"],
26433
26527
  maxPrice: ["max_price", "number"],
26434
- minDurability: ["minimum_durability", "number"],
26435
- ...requestInput
26528
+ minDurability: ["minimum_durability", "number"]
26436
26529
  },
26437
26530
  autoRequestId: true,
26438
26531
  errorContext: marketWriteContext,
@@ -26450,7 +26543,7 @@ var marketCommands = {
26450
26543
  "market-listing-cancel": {
26451
26544
  path: "character/market/listings/cancel",
26452
26545
  schema: cancelMarketListingSchema,
26453
- input: { listing: ["listing_id", "number"], ...requestInput },
26546
+ input: { listing: ["listing_id", "number"] },
26454
26547
  autoRequestId: true,
26455
26548
  errorContext: marketWriteContext,
26456
26549
  flags: [
@@ -26463,7 +26556,7 @@ var marketCommands = {
26463
26556
  "market-listing-claim": {
26464
26557
  path: "character/market/listings/claim",
26465
26558
  schema: claimMarketListingSchema,
26466
- input: { listing: ["listing_id", "number"], ...requestInput },
26559
+ input: { listing: ["listing_id", "number"] },
26467
26560
  autoRequestId: true,
26468
26561
  errorContext: marketWriteContext,
26469
26562
  flags: [
@@ -26482,13 +26575,22 @@ var productionCommands = {
26482
26575
  schema: travelSchema,
26483
26576
  input: { to: "to" },
26484
26577
  startsActivity: true,
26578
+ walksRoute: true,
26485
26579
  flags: [
26486
- ["--to <id>", "Adjacent destination location ID", true],
26487
- noWaitFlag
26580
+ [
26581
+ "--to <ids>",
26582
+ "Adjacent destination location ID, or comma-separated location IDs to walk in order from your current location",
26583
+ true
26584
+ ],
26585
+ [
26586
+ "--no-wait",
26587
+ "Return after acceptance without waiting for arrival; --to must name one location"
26588
+ ]
26488
26589
  ],
26489
- help: "Walk one step to an adjacent location while idle and wait for arrival.",
26590
+ help: "Walk one step to an adjacent location while idle and wait for arrival. With comma-separated locations, walk them in order, sending each step only after the previous arrival is confirmed. An ambush, a rejected or cancelled step, or an unconfirmed result stops the walk; repetition lists the arrived locations and the stop reason. The CLI does not search or change the route; read route and choose the locations yourself. Finish the whole command before starting another main activity.",
26490
26591
  examples: [
26491
26592
  "clawsaga travel -c m7Qp2_aR9L-x --to openpit",
26593
+ "clawsaga travel -c m7Qp2_aR9L-x --to darras,openpit",
26492
26594
  "clawsaga travel -c m7Qp2_aR9L-x --to openpit --no-wait"
26493
26595
  ]
26494
26596
  },
@@ -26555,13 +26657,11 @@ var productionCommands = {
26555
26657
  schema: craftSchema,
26556
26658
  input: {
26557
26659
  recipe: "recipe_id",
26558
- maxFeePerLot: ["max_fee_per_lot", "number"],
26559
- request: "request_id"
26660
+ maxFeePerLot: ["max_fee_per_lot", "number"]
26560
26661
  },
26561
26662
  startsActivity: true,
26562
26663
  repeat: { requestIdPerLot: true },
26563
26664
  autoRequestId: true,
26564
- errorContext: (values) => ({ request_id: values.request }),
26565
26665
  flags: [
26566
26666
  ["--recipe <id>", "Recipe ID from recipes", true],
26567
26667
  [
@@ -26606,12 +26706,10 @@ var productionCommands = {
26606
26706
  input: {
26607
26707
  item: "item_id",
26608
26708
  quantity: ["quantity", "number"],
26609
- maxPayment: ["max_payment", "number"],
26610
- request: "request_id"
26709
+ maxPayment: ["max_payment", "number"]
26611
26710
  },
26612
26711
  autoRequestId: true,
26613
26712
  errorContext: (values) => ({
26614
- request_id: values.request,
26615
26713
  item_id: values.item,
26616
26714
  quantity: values.quantity === void 0 ? 1 : Number(values.quantity),
26617
26715
  ...values.maxPayment === void 0 ? {} : { max_payment: Number(values.maxPayment) }
@@ -26731,6 +26829,7 @@ var commands = {
26731
26829
  var responseDataKeys = {
26732
26830
  monologue: ["monologue"],
26733
26831
  encounters: ["encounters"],
26832
+ raids: ["raids"],
26734
26833
  tactics: ["tactics"],
26735
26834
  "tactics-set": ["validation", "tactics"],
26736
26835
  "tactics-check": ["validation"],
@@ -26742,8 +26841,13 @@ var responseDataKeys = {
26742
26841
  "change-job": ["capacity", "status", "combat_stats", "equipment_change"],
26743
26842
  "lost-items": ["lost_items"],
26744
26843
  recover: ["capacity", "status"],
26745
- "quest-board": ["quest_board", "quest_board_budget", "community_goals"],
26746
- quests: ["quests", "community_goals"],
26844
+ "quest-board": [
26845
+ "quest_board",
26846
+ "quest_board_budget",
26847
+ "community_goals",
26848
+ "raids"
26849
+ ],
26850
+ quests: ["quests", "community_goals", "raids"],
26747
26851
  "quest-accept": ["quest", "community_goal", "status", "reputation_change"],
26748
26852
  "goal-deliver": ["community_goal", "delivered", "status"],
26749
26853
  "quest-claim": ["quest", "community_goal", "status", "reputation_change"],
@@ -27132,15 +27236,21 @@ function createProgram(run, notify) {
27132
27236
  }
27133
27237
  return program2;
27134
27238
  }
27239
+ function trackHelp(command2, run, target2, helpCommand) {
27240
+ command2.configureOutput({
27241
+ outputError: () => {
27242
+ run.helpCommand = helpCommand;
27243
+ }
27244
+ }).on("--help", () => {
27245
+ run.helpTarget = target2;
27246
+ }).hook("preAction", () => {
27247
+ run.helpCommand = helpCommand;
27248
+ });
27249
+ }
27135
27250
  function addSchemaCommand(program2, run) {
27136
27251
  const schemaCommand = program2.command("schema").description(
27137
27252
  "Read the local input structure for a command. The server validates input limits; read clawsaga guide for current rules and error.fields when input is rejected."
27138
- ).argument("<command>", "Game command name").configureOutput({
27139
- outputError: () => {
27140
- run.helpCommand = "clawsaga schema --help";
27141
- }
27142
- }).action((name) => {
27143
- run.helpCommand = "clawsaga schema --help";
27253
+ ).argument("<command>", "Game command name").action((name) => {
27144
27254
  const definition = commands[name];
27145
27255
  if (!definition) {
27146
27256
  throw invalidArguments(
@@ -27157,9 +27267,7 @@ function addSchemaCommand(program2, run) {
27157
27267
  )
27158
27268
  };
27159
27269
  });
27160
- schemaCommand.on("--help", () => {
27161
- run.helpTarget = "schema";
27162
- });
27270
+ trackHelp(schemaCommand, run, "schema", "clawsaga schema --help");
27163
27271
  }
27164
27272
  var documentCommands = [
27165
27273
  {
@@ -27191,20 +27299,13 @@ var documentCommands = [
27191
27299
  ];
27192
27300
  function addDocumentCommands(program2, run) {
27193
27301
  for (const document of documentCommands) {
27194
- const command2 = program2.command(document.name).description(document.description).configureOutput({
27195
- outputError: () => {
27196
- run.helpCommand = `clawsaga ${document.name} --help`;
27197
- }
27198
- });
27302
+ const command2 = program2.command(document.name).description(document.description);
27199
27303
  for (const option of document.options) {
27200
27304
  command2.option(option.flags, option.description);
27201
27305
  }
27202
- command2.on("--help", () => {
27203
- run.helpTarget = `clawsaga ${document.name}`;
27204
- run.helpCommand = `clawsaga ${document.name} --help`;
27205
- });
27306
+ const target2 = `clawsaga ${document.name}`;
27307
+ trackHelp(command2, run, target2, `${target2} --help`);
27206
27308
  command2.action(async () => {
27207
- run.helpCommand = `clawsaga ${document.name} --help`;
27208
27309
  const values = optionsSchema.parse(command2.optsWithGlobals());
27209
27310
  run.result = await clientFor(values).readDocument(
27210
27311
  document.path(values),
@@ -27212,14 +27313,8 @@ function addDocumentCommands(program2, run) {
27212
27313
  );
27213
27314
  });
27214
27315
  }
27215
- const login = program2.command("auth").description("Manage authorization").command("login").description("Return a device verification URL without waiting").configureOutput({
27216
- outputError: () => {
27217
- run.helpCommand = "clawsaga auth login --help";
27218
- }
27219
- });
27220
- login.on("--help", () => {
27221
- run.helpTarget = "auth login";
27222
- });
27316
+ const login = program2.command("auth").description("Manage authorization").command("login").description("Return a device verification URL without waiting");
27317
+ trackHelp(login, run, "auth login", "clawsaga auth login --help");
27223
27318
  login.action(async () => {
27224
27319
  run.result = await clientFor(
27225
27320
  optionsSchema.parse(login.optsWithGlobals())
@@ -27227,11 +27322,7 @@ function addDocumentCommands(program2, run) {
27227
27322
  });
27228
27323
  }
27229
27324
  function addGameCommand(program2, run, notify, name, definition) {
27230
- const command2 = program2.command(name).description(definition.help).configureOutput({
27231
- outputError: () => {
27232
- run.helpCommand = `clawsaga ${name} --help`;
27233
- }
27234
- });
27325
+ const command2 = program2.command(name).description(definition.help);
27235
27326
  for (const [flags, description] of definition.flags) {
27236
27327
  command2.option(flags, description);
27237
27328
  }
@@ -27242,11 +27333,8 @@ function addGameCommand(program2, run, notify, name, definition) {
27242
27333
  JSON body: use input_example below with your own content. Full schema: clawsaga schema ${name}.`
27243
27334
  );
27244
27335
  }
27245
- command2.on("--help", () => {
27246
- run.helpTarget = `clawsaga ${name}`;
27247
- });
27336
+ trackHelp(command2, run, `clawsaga ${name}`, `clawsaga ${name} --help`);
27248
27337
  command2.action(async () => {
27249
- run.helpCommand = `clawsaga ${name} --help`;
27250
27338
  const executed = {
27251
27339
  name,
27252
27340
  definition,
@@ -27258,6 +27346,7 @@ JSON body: use input_example below with your own content. Full schema: clawsaga
27258
27346
  executed.character = values.character;
27259
27347
  executed.wait = values.wait !== false;
27260
27348
  assertRequiredOptions(definition, values, command2.opts());
27349
+ const route = definition.walksRoute ? routeLocations(values) : void 0;
27261
27350
  const requestedId = values.request;
27262
27351
  if (definition.autoRequestId && !values.request) {
27263
27352
  values.request = randomUUID3();
@@ -27277,6 +27366,17 @@ JSON body: use input_example below with your own content. Full schema: clawsaga
27277
27366
  ) : await invokeGame(client, definition, input2, values);
27278
27367
  return;
27279
27368
  }
27369
+ if (route) {
27370
+ run.result = await walkRoute(
27371
+ client,
27372
+ definition.path,
27373
+ input2,
27374
+ activityValues(values),
27375
+ route,
27376
+ notify
27377
+ );
27378
+ return;
27379
+ }
27280
27380
  const response = await invokeGame(client, definition, input2, values);
27281
27381
  run.result = definition.startsActivity && response.ok && executed.wait ? await waitForActivity(
27282
27382
  client,
@@ -27297,9 +27397,10 @@ function assertRequiredOptions(definition, values, provided) {
27297
27397
  );
27298
27398
  }
27299
27399
  for (const [flags, , required2] of definition.flags) {
27300
- if (!required2 || provided[optionKey(flags)] !== void 0) continue;
27400
+ const key = new Option(flags).attributeName();
27401
+ if (!required2 || provided[key] !== void 0) continue;
27301
27402
  throw invalidArguments(
27302
- [optionKey(flags)],
27403
+ [key],
27303
27404
  `Required option '${flags}' was not provided.`
27304
27405
  );
27305
27406
  }
@@ -27308,15 +27409,33 @@ async function invokeGame(client, definition, input2, values) {
27308
27409
  try {
27309
27410
  return await client.invoke(definition.path, input2);
27310
27411
  } catch (error61) {
27311
- if (definition.errorContext && error61 instanceof CliError) {
27412
+ if (definition.autoRequestId && error61 instanceof CliError) {
27312
27413
  throw new CliError(error61.code, {
27313
27414
  ...error61.detail,
27314
- ...definition.errorContext(values, input2)
27415
+ request_id: input2.request_id,
27416
+ ...definition.errorContext?.(values, input2)
27315
27417
  });
27316
27418
  }
27317
27419
  throw error61;
27318
27420
  }
27319
27421
  }
27422
+ function routeLocations(values) {
27423
+ const route = values.to?.split(",") ?? [];
27424
+ if (route.length < 2) return void 0;
27425
+ if (!route.every((id) => locationIdSchema.safeParse(id).success)) {
27426
+ throw invalidArguments(
27427
+ ["to"],
27428
+ "--to takes location IDs separated by commas, without spaces or empty entries."
27429
+ );
27430
+ }
27431
+ if (values.wait === false) {
27432
+ throw invalidArguments(
27433
+ ["to"],
27434
+ "--no-wait starts one step; give one location with --to or omit --no-wait."
27435
+ );
27436
+ }
27437
+ return route;
27438
+ }
27320
27439
  function repeatCount(requestIdPerLot, values, requestedId) {
27321
27440
  const count = values.count === void 0 ? 1 : Number(values.count);
27322
27441
  if (!Number.isSafeInteger(count) || count < 1) {
@@ -27348,6 +27467,9 @@ function print(stream, value) {
27348
27467
  stream.write(`${JSON.stringify(output2)}
27349
27468
  `);
27350
27469
  }
27470
+ function printNote(note) {
27471
+ print(process.stderr, { hints: [{ note }] });
27472
+ }
27351
27473
  function printResult(value) {
27352
27474
  for (const hint of value.hints ?? []) {
27353
27475
  if (hint.note !== void 0) process.stderr.write(`Hint: ${hint.note}
@@ -27366,14 +27488,9 @@ try {
27366
27488
  );
27367
27489
  printResult(result);
27368
27490
  process.exitCode = "ok" in result && !result.ok ? 1 : 0;
27491
+ const announced = announcedCliUpdate();
27492
+ if (announced) await updateAnnouncedCli(announced, printNote);
27369
27493
  } catch (error61) {
27370
- printResult(
27371
- cliFailure(
27372
- await recoverUpdate(
27373
- error61,
27374
- (note) => print(process.stderr, { hints: [{ note }] })
27375
- )
27376
- )
27377
- );
27494
+ printResult(cliFailure(await recoverUpdate(error61, printNote)));
27378
27495
  process.exitCode = 1;
27379
27496
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawsaga/cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "homepage": "https://clawsaga.net",
5
5
  "repository": {
6
6
  "type": "git",