@clawsaga/cli 0.1.26 → 0.1.28

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,17 @@
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.28
6
+
7
+ - Internal code cleanup. Commands, output and skill instructions are unchanged.
8
+
9
+ ## 0.1.27
10
+
11
+ - 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`.
12
+ - 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.
13
+ - `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.
14
+ - Added `raids` to read the running raid boss and the last one that ended. Fight it with `fight --enemy <enemy_id>` at its location.
15
+
5
16
  ## 0.1.26
6
17
 
7
18
  - 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.28",
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(),
@@ -24577,7 +24640,7 @@ var retryAfterSchema = external_exports.object({
24577
24640
  var schemaVersionSchema = external_exports.object({ schema_version: external_exports.string() });
24578
24641
  var [supportedSchemaMajor = 0, supportedSchemaMinor = 0] = agentSchemaVersion.split(".").map(Number);
24579
24642
  var checkOutcome = "The action may still have been applied; check its outcome before another change.";
24580
- async function readBody(response) {
24643
+ async function readJsonOrUndefined(response) {
24581
24644
  return response.json().catch(() => void 0);
24582
24645
  }
24583
24646
  function serverMessage(body) {
@@ -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)) return;
24672
+ const announcedIsNewer = version2.localeCompare(package_default.version, "en", { numeric: true }) > 0;
24673
+ if (announcedIsNewer) announcedCli = version2;
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))) {
@@ -24640,13 +24713,13 @@ var GameClient = class {
24640
24713
  });
24641
24714
  if (response.status >= 500) throw new CliError("SERVICE_UNAVAILABLE");
24642
24715
  if (response.status === 429) {
24643
- throw rateLimited(response, await readBody(response));
24716
+ throw rateLimited(response, await readJsonOrUndefined(response));
24644
24717
  }
24645
24718
  return response;
24646
24719
  }
24647
24720
  async decodeTokens(response) {
24648
24721
  if (!response.ok) throw new CliError("AUTH_REQUIRED");
24649
- const parsed = tokensSchema.safeParse(await readBody(response));
24722
+ const parsed = tokensSchema.safeParse(await readJsonOrUndefined(response));
24650
24723
  if (!parsed.success) throw new CliError("INVALID_RESPONSE");
24651
24724
  return {
24652
24725
  ...parsed.data,
@@ -24657,10 +24730,10 @@ 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
- const parsed = deviceSchema.safeParse(await readBody(response));
24736
+ const parsed = deviceSchema.safeParse(await readJsonOrUndefined(response));
24664
24737
  if (!parsed.success) throw new CliError("INVALID_RESPONSE");
24665
24738
  const device = parsed.data;
24666
24739
  if (new URL(device.verification_uri_complete).origin !== this.origin) {
@@ -24705,7 +24778,9 @@ var GameClient = class {
24705
24778
  entries[this.origin] = credential;
24706
24779
  return credential.access_token;
24707
24780
  }
24708
- const failure = errorSchema.safeParse(await readBody(polled));
24781
+ const failure = errorSchema.safeParse(
24782
+ await readJsonOrUndefined(polled)
24783
+ );
24709
24784
  if (!failure.success) {
24710
24785
  throw new CliError("AUTH_NOT_COMPLETED", {
24711
24786
  reason: "invalid_response"
@@ -24748,7 +24823,8 @@ var GameClient = class {
24748
24823
  },
24749
24824
  body: JSON.stringify(input2)
24750
24825
  });
24751
- const body = await readBody(response);
24826
+ noteAnnouncedCli(response);
24827
+ const body = await readJsonOrUndefined(response);
24752
24828
  const message = serverMessage(body);
24753
24829
  if (response.status === 401) {
24754
24830
  throw new CliError("AUTH_REQUIRED", {
@@ -24766,17 +24842,17 @@ var GameClient = class {
24766
24842
  http_status: response.status
24767
24843
  });
24768
24844
  }
24845
+ if (needsUpdate(body)) {
24846
+ throw new CliError("UPDATE_REQUIRED", {
24847
+ operation: path2,
24848
+ http_status: response.status,
24849
+ update_required: true,
24850
+ // The automatic update verifies a read against this server.
24851
+ server: this.origin
24852
+ });
24853
+ }
24769
24854
  const parsed = agentGameResponseSchema.safeParse(body);
24770
24855
  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
24856
  throw new CliError("INVALID_RESPONSE", {
24781
24857
  message: `The server response did not match this CLI's expected format. ${checkOutcome}`,
24782
24858
  operation: path2,
@@ -24788,10 +24864,13 @@ var GameClient = class {
24788
24864
  }
24789
24865
  return parsed.data;
24790
24866
  }
24791
- // Public reads stay usable before authorization and are not agent responses.
24867
+ /**
24868
+ * Reads a public document. It works before authorization, and the body is
24869
+ * not an agent game response.
24870
+ */
24792
24871
  async readDocument(path2, schema) {
24793
24872
  const response = await this.send(`/api/v1/${path2}`, { method: "GET" });
24794
- const body = await readBody(response);
24873
+ const body = await readJsonOrUndefined(response);
24795
24874
  if (response.status === 429) throw rateLimited(response, body);
24796
24875
  if (response.status >= 500) throw new CliError("SERVICE_UNAVAILABLE");
24797
24876
  const parsed = schema.safeParse(body);
@@ -24836,13 +24915,29 @@ function activityCommand(activityId, character) {
24836
24915
  function repetitionOf(response) {
24837
24916
  return response.repetition;
24838
24917
  }
24918
+ function stoppedEarlyNote(repetition) {
24919
+ const failedRead = repetition.stopped_reason === "activity_failed";
24920
+ if ("requested_locations" in repetition) {
24921
+ const arrived = repetition.arrived_locations.length;
24922
+ const requested = repetition.requested_locations.length;
24923
+ if (arrived >= requested) return void 0;
24924
+ const unconfirmed2 = failedRead ? " The last step may also have arrived. Check your current or latest activity before continuing." : "";
24925
+ return `Stopped after arriving at ${arrived} of ${requested} locations. No further steps were sent.${unconfirmed2}`;
24926
+ }
24927
+ if (repetition.completed_count >= repetition.requested_count) {
24928
+ return void 0;
24929
+ }
24930
+ const unconfirmed = failedRead ? " The last attempt may also have succeeded. Check your current or latest activity before continuing." : "";
24931
+ return `Stopped with ${repetition.completed_count} of ${repetition.requested_count} attempts completed. You keep their results.${unconfirmed}`;
24932
+ }
24839
24933
  function ambushNotes(response, character) {
24840
24934
  const notes = [];
24841
24935
  const lastResult = response.data.last_result;
24842
24936
  const activity = response.data.activity ?? void 0;
24843
24937
  const ambush = lastResult?.ambush;
24844
24938
  if (!lastResult || !ambush) return { notes };
24845
- if (activity?.kind === "combat" && activity.activity_id === ambush.activity_id) {
24939
+ const ambushIsCurrentActivity = activity?.kind === "combat" && activity.activity_id === ambush.activity_id;
24940
+ if (ambushIsCurrentActivity) {
24846
24941
  notes.push(
24847
24942
  `The ${lastResult.kind} activity is complete. Continue the ambush battle or retreat.`
24848
24943
  );
@@ -24863,11 +24958,9 @@ function stateNotes(response, character, wait) {
24863
24958
  const lastResult = response.data.last_result;
24864
24959
  const activity = response.data.activity ?? void 0;
24865
24960
  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
- );
24961
+ const stopped = repetition && stoppedEarlyNote(repetition);
24962
+ if (stopped) {
24963
+ notes.push(stopped);
24871
24964
  } else if (!wait && response.ok) {
24872
24965
  if (lastResult) {
24873
24966
  notes.push(
@@ -25052,9 +25145,25 @@ async function updateCli(server) {
25052
25145
  }
25053
25146
  throw new CliError("UPDATE_FAILED");
25054
25147
  }
25148
+ function isVerificationRead() {
25149
+ return process.env.CLAWSAGA_AUTO_UPDATE === "0";
25150
+ }
25151
+ async function updateAnnouncedCli(version2, notify) {
25152
+ if (isVerificationRead()) return;
25153
+ notify(
25154
+ `Updating the CLI to ${version2}. The result above is final; wait for this command to finish.`
25155
+ );
25156
+ try {
25157
+ notify(`The CLI was updated to ${await updateCli()}.`);
25158
+ } catch (error61) {
25159
+ const reason = cliFailure(error61).error.message;
25160
+ notify(
25161
+ `The CLI could not be updated automatically: ${reason} Run clawsaga update later.`
25162
+ );
25163
+ }
25164
+ }
25055
25165
  async function recoverUpdate(error61, notify) {
25056
- if (!(error61 instanceof CliError) || error61.code !== "UPDATE_REQUIRED" || // Set for the verification read, which must not start an update of its own.
25057
- process.env.CLAWSAGA_AUTO_UPDATE === "0") {
25166
+ if (!(error61 instanceof CliError) || error61.code !== "UPDATE_REQUIRED" || isVerificationRead()) {
25058
25167
  return error61;
25059
25168
  }
25060
25169
  const { server } = error61.detail;
@@ -25079,61 +25188,6 @@ async function recoverUpdate(error61, notify) {
25079
25188
  }
25080
25189
  }
25081
25190
 
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
25191
  // src/activity-wait.ts
25138
25192
  import { randomUUID as randomUUID2 } from "node:crypto";
25139
25193
  import { setTimeout as sleep2 } from "node:timers/promises";
@@ -25146,7 +25200,6 @@ function acceptedActivityNote(activity, nextPollAfterSeconds, requestId) {
25146
25200
  activity_id: activity.activity_id,
25147
25201
  kind: activity.kind,
25148
25202
  started_at: activity.started_at,
25149
- // 出力のJSONでは値のない項目が落ちる。
25150
25203
  completes_at: activity.completes_at,
25151
25204
  arrives_at: activity.arrives_at,
25152
25205
  time_limit_at: activity.time_limit_at,
@@ -25173,7 +25226,7 @@ async function repeatActivity(client, path2, input2, values, count, options) {
25173
25226
  },
25174
25227
  repetition: summary(stoppedReason)
25175
25228
  });
25176
- const attempt = async (lotInput, requestId) => {
25229
+ const finalResponseOfLot = async (lotInput, requestId) => {
25177
25230
  const started = await client.invoke(path2, lotInput);
25178
25231
  if (!started.ok) {
25179
25232
  return { ...started, repetition: summary("start_rejected") };
@@ -25214,7 +25267,7 @@ async function repeatActivity(client, path2, input2, values, count, options) {
25214
25267
  lastRequestId = requestId;
25215
25268
  const lotInput = requestId === void 0 ? input2 : { ...input2, request_id: requestId };
25216
25269
  try {
25217
- const final = await attempt(lotInput, requestId);
25270
+ const final = await finalResponseOfLot(lotInput, requestId);
25218
25271
  if (final) return final;
25219
25272
  } catch (error61) {
25220
25273
  if (error61 instanceof CliError) {
@@ -25230,6 +25283,67 @@ async function repeatActivity(client, path2, input2, values, count, options) {
25230
25283
  }
25231
25284
  throw new CliError("INVALID_ARGUMENTS", { fields: ["count"] });
25232
25285
  }
25286
+ async function walkRoute(client, path2, input2, values, route, notify) {
25287
+ const arrived = [];
25288
+ const summary = (stoppedReason) => ({
25289
+ requested_locations: [...route],
25290
+ arrived_locations: [...arrived],
25291
+ stopped_reason: stoppedReason
25292
+ });
25293
+ const incomplete = (response, stoppedReason) => ({
25294
+ ...response,
25295
+ ok: false,
25296
+ error: { message: "The travel stopped before reaching every location." },
25297
+ repetition: summary(stoppedReason)
25298
+ });
25299
+ const finalResponseOfStep = async (to) => {
25300
+ const started = await client.invoke(path2, { ...input2, to });
25301
+ if (!started.ok) {
25302
+ return { ...started, repetition: summary("start_rejected") };
25303
+ }
25304
+ const completed = await waitForActivity(client, values, started, notify);
25305
+ if (!completed.ok) {
25306
+ return { ...completed, repetition: summary("activity_failed") };
25307
+ }
25308
+ const result = endedTravel(completed, to);
25309
+ if (result.end_reason !== "COMPLETED") {
25310
+ return incomplete(completed, "activity_stopped");
25311
+ }
25312
+ arrived.push(to);
25313
+ const last = arrived.length === route.length;
25314
+ if (result.ambush) {
25315
+ return last ? { ...completed, repetition: summary("ambush") } : incomplete(completed, "ambush");
25316
+ }
25317
+ if (last) return { ...completed, repetition: summary("route_completed") };
25318
+ notify?.({ event: "activity_result", last_result: result });
25319
+ return void 0;
25320
+ };
25321
+ for (const to of route) {
25322
+ try {
25323
+ const final = await finalResponseOfStep(to);
25324
+ if (final) return final;
25325
+ } catch (error61) {
25326
+ if (error61 instanceof CliError) {
25327
+ throw new CliError(error61.code, {
25328
+ ...error61.detail,
25329
+ repetition: summary(notSent(error61) ? "start_rejected" : "unknown")
25330
+ });
25331
+ }
25332
+ throw error61;
25333
+ }
25334
+ }
25335
+ throw new CliError("INVALID_ARGUMENTS", { fields: ["to"] });
25336
+ }
25337
+ function endedTravel(response, to) {
25338
+ const result = response.data.last_result;
25339
+ const arrivedElsewhere = result?.end_reason === "COMPLETED" && result.to?.id !== to;
25340
+ if (!result || result.kind !== "travel" || result.status !== "ENDED" || arrivedElsewhere) {
25341
+ throw new CliError("INVALID_RESPONSE", {
25342
+ reason: "unexpected_travel_result"
25343
+ });
25344
+ }
25345
+ return result;
25346
+ }
25233
25347
  function endedProduction(response) {
25234
25348
  const result = response.data.last_result;
25235
25349
  if (!result || result.kind !== "gather" && result.kind !== "craft" || result.status !== "ENDED") {
@@ -25308,6 +25422,10 @@ var jsonFlag = [
25308
25422
  "JSON body file, or - for stdin; the body fields are shown in input_example",
25309
25423
  true
25310
25424
  ];
25425
+ var requestFlag = [
25426
+ "--request <uuid>",
25427
+ "Reuse the same ID and arguments after an uncertain result"
25428
+ ];
25311
25429
  var limitFlag = [
25312
25430
  "--limit <number>",
25313
25431
  "Entries per page; limits and defaults are set by the server"
@@ -25394,12 +25512,13 @@ var optionsSchema = external_exports.object({
25394
25512
  function isJsonObject(value) {
25395
25513
  return typeof value === "object" && value !== null && !Array.isArray(value);
25396
25514
  }
25515
+ var maxStdinLength = 128 * 1024;
25397
25516
  async function readStdin() {
25398
25517
  process.stdin.setEncoding("utf8");
25399
25518
  let text2 = "";
25400
25519
  for await (const chunk of process.stdin) {
25401
25520
  text2 += String(chunk);
25402
- if (text2.length > 128 * 1024) throw new CliError("INVALID_INPUT_FILE");
25521
+ if (text2.length > maxStdinLength) throw new CliError("INVALID_INPUT_FILE");
25403
25522
  }
25404
25523
  return text2;
25405
25524
  }
@@ -25411,20 +25530,15 @@ async function readJsonFile(file2) {
25411
25530
  throw new CliError("INVALID_INPUT_FILE");
25412
25531
  }
25413
25532
  }
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
25533
  function integerFlag(option, value) {
25420
- const number5 = Number(value);
25421
- if (typeof value !== "string" || !/^\d+$/.test(value) || !Number.isSafeInteger(number5)) {
25534
+ const number4 = Number(value);
25535
+ if (typeof value !== "string" || !/^\d+$/.test(value) || !Number.isSafeInteger(number4)) {
25422
25536
  throw invalidArguments(
25423
25537
  [option],
25424
25538
  `--${option.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)} must be a non-negative integer.`
25425
25539
  );
25426
25540
  }
25427
- return number5;
25541
+ return number4;
25428
25542
  }
25429
25543
  async function commandInput(definition, values) {
25430
25544
  return values.input ? bodyFileInput(definition, values, values.input) : flagInput(definition, values);
@@ -25454,6 +25568,7 @@ function flagInput(definition, values) {
25454
25568
  else if (conversion === "list") input2[key] = String(value).split(",");
25455
25569
  else input2[key] = value;
25456
25570
  }
25571
+ if (definition.autoRequestId) input2.request_id = values.request;
25457
25572
  definition.buildInput?.(values, input2);
25458
25573
  return input2;
25459
25574
  }
@@ -25484,9 +25599,6 @@ var bodyRequestFlag = [
25484
25599
  "--request <uuid>",
25485
25600
  "Request ID for a body file without request_id; reuse it after an uncertain result"
25486
25601
  ];
25487
- function requestIdContext(_values, input2) {
25488
- return { request_id: input2.request_id };
25489
- }
25490
25602
  function exactlyOne(fields, message) {
25491
25603
  return (values) => {
25492
25604
  if (fields.filter((field) => values[field] !== void 0).length !== 1) {
@@ -25527,6 +25639,12 @@ var adventureCommands = {
25527
25639
  flags: [],
25528
25640
  help: "List local enemies or town training dummies."
25529
25641
  },
25642
+ raids: {
25643
+ path: "character/raids",
25644
+ schema: getRaidsSchema,
25645
+ flags: [],
25646
+ 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."
25647
+ },
25530
25648
  tactics: {
25531
25649
  path: "character/tactics",
25532
25650
  schema: getTacticsSchema,
@@ -25585,7 +25703,7 @@ var adventureCommands = {
25585
25703
  ],
25586
25704
  noWaitFlag
25587
25705
  ],
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.",
25706
+ 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
25707
  inputExample: { enemy_id: "wolf", tactic: { rules: [], potion_limit: 0 } },
25590
25708
  examples: [
25591
25709
  "clawsaga fight -c m7Qp2_aR9L-x --enemy wolf",
@@ -25779,7 +25897,6 @@ var adventureCommands = {
25779
25897
  path: "character/journal/write",
25780
25898
  schema: writeJournalSchema,
25781
25899
  autoRequestId: true,
25782
- errorContext: requestIdContext,
25783
25900
  flags: [jsonFlag, bodyRequestFlag],
25784
25901
  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
25902
  inputExample: {
@@ -25791,7 +25908,6 @@ var adventureCommands = {
25791
25908
  path: "character/session-end",
25792
25909
  schema: endSessionSchema,
25793
25910
  autoRequestId: true,
25794
- errorContext: requestIdContext,
25795
25911
  flags: [jsonFlag, bodyRequestFlag],
25796
25912
  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
25913
  inputExample: {
@@ -26126,7 +26242,7 @@ function addItemTargets(values, input2) {
26126
26242
  }
26127
26243
  }
26128
26244
  function storageContext(values) {
26129
- return { request_id: values.request, town_id: values.town };
26245
+ return { town_id: values.town };
26130
26246
  }
26131
26247
  var storageCommands = {
26132
26248
  storage: {
@@ -26154,7 +26270,7 @@ var storageCommands = {
26154
26270
  deposit: {
26155
26271
  path: "character/storage/deposit",
26156
26272
  schema: depositItemsSchema,
26157
- input: { town: "town_id", request: "request_id" },
26273
+ input: { town: "town_id" },
26158
26274
  autoRequestId: true,
26159
26275
  errorContext: storageContext,
26160
26276
  buildInput: addItemTargets,
@@ -26174,7 +26290,7 @@ var storageCommands = {
26174
26290
  withdraw: {
26175
26291
  path: "character/storage/withdraw",
26176
26292
  schema: withdrawItemsSchema,
26177
- input: { town: "town_id", request: "request_id" },
26293
+ input: { town: "town_id" },
26178
26294
  autoRequestId: true,
26179
26295
  errorContext: storageContext,
26180
26296
  buildInput: addItemTargets,
@@ -26208,11 +26324,9 @@ var giftCommands = {
26208
26324
  schema: sendGiftSchema,
26209
26325
  input: {
26210
26326
  to: "recipient_character_id",
26211
- gold: ["gold", "number"],
26212
- request: "request_id"
26327
+ gold: ["gold", "number"]
26213
26328
  },
26214
26329
  autoRequestId: true,
26215
- errorContext: (values) => ({ request_id: values.request }),
26216
26330
  buildInput: addItemTargets,
26217
26331
  flags: [
26218
26332
  ["--to <id>", "Recipient Character ID", true],
@@ -26221,10 +26335,7 @@ var giftCommands = {
26221
26335
  "JSON array of items from your storage in this town: {item_id, quantity} with optional quality (default standard), or {instance_id}"
26222
26336
  ],
26223
26337
  ["--gold <amount>", "Gold to send from your balance"],
26224
- [
26225
- "--request <uuid>",
26226
- "Reuse the same ID and arguments after an uncertain result"
26227
- ]
26338
+ requestFlag
26228
26339
  ],
26229
26340
  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
26341
  examples: [
@@ -26244,14 +26355,8 @@ var giftCommands = {
26244
26355
  // src/market-commands.ts
26245
26356
  var qualityChoices = marketQualitySchema.options;
26246
26357
  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
26358
  function marketWriteContext(values) {
26253
26359
  return {
26254
- request_id: values.request,
26255
26360
  ...values.order === void 0 ? {} : { order_id: Number(values.order) },
26256
26361
  ...values.listing === void 0 ? {} : { listing_id: Number(values.listing) }
26257
26362
  };
@@ -26325,8 +26430,7 @@ var marketCommands = {
26325
26430
  quality: "quality",
26326
26431
  quantity: ["quantity", "number"],
26327
26432
  unitPrice: ["unit_price", "number"],
26328
- source: "source",
26329
- ...requestInput
26433
+ source: "source"
26330
26434
  },
26331
26435
  autoRequestId: true,
26332
26436
  errorContext: marketWriteContext,
@@ -26355,8 +26459,7 @@ var marketCommands = {
26355
26459
  item: "item_id",
26356
26460
  quality: "quality",
26357
26461
  quantity: ["quantity", "number"],
26358
- unitPrice: ["unit_price", "number"],
26359
- ...requestInput
26462
+ unitPrice: ["unit_price", "number"]
26360
26463
  },
26361
26464
  autoRequestId: true,
26362
26465
  errorContext: marketWriteContext,
@@ -26375,7 +26478,7 @@ var marketCommands = {
26375
26478
  "market-order-cancel": {
26376
26479
  path: "character/market/orders/cancel",
26377
26480
  schema: cancelMarketOrderSchema,
26378
- input: { order: ["order_id", "number"], ...requestInput },
26481
+ input: { order: ["order_id", "number"] },
26379
26482
  autoRequestId: true,
26380
26483
  errorContext: marketWriteContext,
26381
26484
  flags: [
@@ -26388,7 +26491,7 @@ var marketCommands = {
26388
26491
  "market-order-claim": {
26389
26492
  path: "character/market/orders/claim",
26390
26493
  schema: claimMarketOrderSchema,
26391
- input: { order: ["order_id", "number"], ...requestInput },
26494
+ input: { order: ["order_id", "number"] },
26392
26495
  autoRequestId: true,
26393
26496
  errorContext: marketWriteContext,
26394
26497
  flags: [
@@ -26404,8 +26507,7 @@ var marketCommands = {
26404
26507
  input: {
26405
26508
  instance: "instance_id",
26406
26509
  price: ["price", "number"],
26407
- source: "source",
26408
- ...requestInput
26510
+ source: "source"
26409
26511
  },
26410
26512
  autoRequestId: true,
26411
26513
  errorContext: marketWriteContext,
@@ -26431,8 +26533,7 @@ var marketCommands = {
26431
26533
  input: {
26432
26534
  listing: ["listing_id", "number"],
26433
26535
  maxPrice: ["max_price", "number"],
26434
- minDurability: ["minimum_durability", "number"],
26435
- ...requestInput
26536
+ minDurability: ["minimum_durability", "number"]
26436
26537
  },
26437
26538
  autoRequestId: true,
26438
26539
  errorContext: marketWriteContext,
@@ -26450,7 +26551,7 @@ var marketCommands = {
26450
26551
  "market-listing-cancel": {
26451
26552
  path: "character/market/listings/cancel",
26452
26553
  schema: cancelMarketListingSchema,
26453
- input: { listing: ["listing_id", "number"], ...requestInput },
26554
+ input: { listing: ["listing_id", "number"] },
26454
26555
  autoRequestId: true,
26455
26556
  errorContext: marketWriteContext,
26456
26557
  flags: [
@@ -26463,7 +26564,7 @@ var marketCommands = {
26463
26564
  "market-listing-claim": {
26464
26565
  path: "character/market/listings/claim",
26465
26566
  schema: claimMarketListingSchema,
26466
- input: { listing: ["listing_id", "number"], ...requestInput },
26567
+ input: { listing: ["listing_id", "number"] },
26467
26568
  autoRequestId: true,
26468
26569
  errorContext: marketWriteContext,
26469
26570
  flags: [
@@ -26482,13 +26583,22 @@ var productionCommands = {
26482
26583
  schema: travelSchema,
26483
26584
  input: { to: "to" },
26484
26585
  startsActivity: true,
26586
+ walksRoute: true,
26485
26587
  flags: [
26486
- ["--to <id>", "Adjacent destination location ID", true],
26487
- noWaitFlag
26588
+ [
26589
+ "--to <ids>",
26590
+ "Adjacent destination location ID, or comma-separated location IDs to walk in order from your current location",
26591
+ true
26592
+ ],
26593
+ [
26594
+ "--no-wait",
26595
+ "Return after acceptance without waiting for arrival; --to must name one location"
26596
+ ]
26488
26597
  ],
26489
- help: "Walk one step to an adjacent location while idle and wait for arrival.",
26598
+ 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
26599
  examples: [
26491
26600
  "clawsaga travel -c m7Qp2_aR9L-x --to openpit",
26601
+ "clawsaga travel -c m7Qp2_aR9L-x --to darras,openpit",
26492
26602
  "clawsaga travel -c m7Qp2_aR9L-x --to openpit --no-wait"
26493
26603
  ]
26494
26604
  },
@@ -26555,13 +26665,11 @@ var productionCommands = {
26555
26665
  schema: craftSchema,
26556
26666
  input: {
26557
26667
  recipe: "recipe_id",
26558
- maxFeePerLot: ["max_fee_per_lot", "number"],
26559
- request: "request_id"
26668
+ maxFeePerLot: ["max_fee_per_lot", "number"]
26560
26669
  },
26561
26670
  startsActivity: true,
26562
26671
  repeat: { requestIdPerLot: true },
26563
26672
  autoRequestId: true,
26564
- errorContext: (values) => ({ request_id: values.request }),
26565
26673
  flags: [
26566
26674
  ["--recipe <id>", "Recipe ID from recipes", true],
26567
26675
  [
@@ -26606,12 +26714,10 @@ var productionCommands = {
26606
26714
  input: {
26607
26715
  item: "item_id",
26608
26716
  quantity: ["quantity", "number"],
26609
- maxPayment: ["max_payment", "number"],
26610
- request: "request_id"
26717
+ maxPayment: ["max_payment", "number"]
26611
26718
  },
26612
26719
  autoRequestId: true,
26613
26720
  errorContext: (values) => ({
26614
- request_id: values.request,
26615
26721
  item_id: values.item,
26616
26722
  quantity: values.quantity === void 0 ? 1 : Number(values.quantity),
26617
26723
  ...values.maxPayment === void 0 ? {} : { max_payment: Number(values.maxPayment) }
@@ -26731,6 +26837,7 @@ var commands = {
26731
26837
  var responseDataKeys = {
26732
26838
  monologue: ["monologue"],
26733
26839
  encounters: ["encounters"],
26840
+ raids: ["raids"],
26734
26841
  tactics: ["tactics"],
26735
26842
  "tactics-set": ["validation", "tactics"],
26736
26843
  "tactics-check": ["validation"],
@@ -26742,8 +26849,13 @@ var responseDataKeys = {
26742
26849
  "change-job": ["capacity", "status", "combat_stats", "equipment_change"],
26743
26850
  "lost-items": ["lost_items"],
26744
26851
  recover: ["capacity", "status"],
26745
- "quest-board": ["quest_board", "quest_board_budget", "community_goals"],
26746
- quests: ["quests", "community_goals"],
26852
+ "quest-board": [
26853
+ "quest_board",
26854
+ "quest_board_budget",
26855
+ "community_goals",
26856
+ "raids"
26857
+ ],
26858
+ quests: ["quests", "community_goals", "raids"],
26747
26859
  "quest-accept": ["quest", "community_goal", "status", "reputation_change"],
26748
26860
  "goal-deliver": ["community_goal", "delivered", "status"],
26749
26861
  "quest-claim": ["quest", "community_goal", "status", "reputation_change"],
@@ -27132,15 +27244,21 @@ function createProgram(run, notify) {
27132
27244
  }
27133
27245
  return program2;
27134
27246
  }
27247
+ function trackHelp(command2, run, target2, helpCommand) {
27248
+ command2.configureOutput({
27249
+ outputError: () => {
27250
+ run.helpCommand = helpCommand;
27251
+ }
27252
+ }).on("--help", () => {
27253
+ run.helpTarget = target2;
27254
+ }).hook("preAction", () => {
27255
+ run.helpCommand = helpCommand;
27256
+ });
27257
+ }
27135
27258
  function addSchemaCommand(program2, run) {
27136
27259
  const schemaCommand = program2.command("schema").description(
27137
27260
  "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";
27261
+ ).argument("<command>", "Game command name").action((name) => {
27144
27262
  const definition = commands[name];
27145
27263
  if (!definition) {
27146
27264
  throw invalidArguments(
@@ -27157,9 +27275,7 @@ function addSchemaCommand(program2, run) {
27157
27275
  )
27158
27276
  };
27159
27277
  });
27160
- schemaCommand.on("--help", () => {
27161
- run.helpTarget = "schema";
27162
- });
27278
+ trackHelp(schemaCommand, run, "schema", "clawsaga schema --help");
27163
27279
  }
27164
27280
  var documentCommands = [
27165
27281
  {
@@ -27191,20 +27307,13 @@ var documentCommands = [
27191
27307
  ];
27192
27308
  function addDocumentCommands(program2, run) {
27193
27309
  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
- });
27310
+ const command2 = program2.command(document.name).description(document.description);
27199
27311
  for (const option of document.options) {
27200
27312
  command2.option(option.flags, option.description);
27201
27313
  }
27202
- command2.on("--help", () => {
27203
- run.helpTarget = `clawsaga ${document.name}`;
27204
- run.helpCommand = `clawsaga ${document.name} --help`;
27205
- });
27314
+ const target2 = `clawsaga ${document.name}`;
27315
+ trackHelp(command2, run, target2, `${target2} --help`);
27206
27316
  command2.action(async () => {
27207
- run.helpCommand = `clawsaga ${document.name} --help`;
27208
27317
  const values = optionsSchema.parse(command2.optsWithGlobals());
27209
27318
  run.result = await clientFor(values).readDocument(
27210
27319
  document.path(values),
@@ -27212,14 +27321,8 @@ function addDocumentCommands(program2, run) {
27212
27321
  );
27213
27322
  });
27214
27323
  }
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
- });
27324
+ const login = program2.command("auth").description("Manage authorization").command("login").description("Return a device verification URL without waiting");
27325
+ trackHelp(login, run, "auth login", "clawsaga auth login --help");
27223
27326
  login.action(async () => {
27224
27327
  run.result = await clientFor(
27225
27328
  optionsSchema.parse(login.optsWithGlobals())
@@ -27227,11 +27330,7 @@ function addDocumentCommands(program2, run) {
27227
27330
  });
27228
27331
  }
27229
27332
  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
- });
27333
+ const command2 = program2.command(name).description(definition.help);
27235
27334
  for (const [flags, description] of definition.flags) {
27236
27335
  command2.option(flags, description);
27237
27336
  }
@@ -27242,11 +27341,8 @@ function addGameCommand(program2, run, notify, name, definition) {
27242
27341
  JSON body: use input_example below with your own content. Full schema: clawsaga schema ${name}.`
27243
27342
  );
27244
27343
  }
27245
- command2.on("--help", () => {
27246
- run.helpTarget = `clawsaga ${name}`;
27247
- });
27344
+ trackHelp(command2, run, `clawsaga ${name}`, `clawsaga ${name} --help`);
27248
27345
  command2.action(async () => {
27249
- run.helpCommand = `clawsaga ${name} --help`;
27250
27346
  const executed = {
27251
27347
  name,
27252
27348
  definition,
@@ -27258,6 +27354,7 @@ JSON body: use input_example below with your own content. Full schema: clawsaga
27258
27354
  executed.character = values.character;
27259
27355
  executed.wait = values.wait !== false;
27260
27356
  assertRequiredOptions(definition, values, command2.opts());
27357
+ const route = definition.walksRoute ? routeLocations(values) : void 0;
27261
27358
  const requestedId = values.request;
27262
27359
  if (definition.autoRequestId && !values.request) {
27263
27360
  values.request = randomUUID3();
@@ -27277,6 +27374,17 @@ JSON body: use input_example below with your own content. Full schema: clawsaga
27277
27374
  ) : await invokeGame(client, definition, input2, values);
27278
27375
  return;
27279
27376
  }
27377
+ if (route) {
27378
+ run.result = await walkRoute(
27379
+ client,
27380
+ definition.path,
27381
+ input2,
27382
+ activityValues(values),
27383
+ route,
27384
+ notify
27385
+ );
27386
+ return;
27387
+ }
27280
27388
  const response = await invokeGame(client, definition, input2, values);
27281
27389
  run.result = definition.startsActivity && response.ok && executed.wait ? await waitForActivity(
27282
27390
  client,
@@ -27297,9 +27405,10 @@ function assertRequiredOptions(definition, values, provided) {
27297
27405
  );
27298
27406
  }
27299
27407
  for (const [flags, , required2] of definition.flags) {
27300
- if (!required2 || provided[optionKey(flags)] !== void 0) continue;
27408
+ const key = new Option(flags).attributeName();
27409
+ if (!required2 || provided[key] !== void 0) continue;
27301
27410
  throw invalidArguments(
27302
- [optionKey(flags)],
27411
+ [key],
27303
27412
  `Required option '${flags}' was not provided.`
27304
27413
  );
27305
27414
  }
@@ -27308,15 +27417,33 @@ async function invokeGame(client, definition, input2, values) {
27308
27417
  try {
27309
27418
  return await client.invoke(definition.path, input2);
27310
27419
  } catch (error61) {
27311
- if (definition.errorContext && error61 instanceof CliError) {
27420
+ if (definition.autoRequestId && error61 instanceof CliError) {
27312
27421
  throw new CliError(error61.code, {
27313
27422
  ...error61.detail,
27314
- ...definition.errorContext(values, input2)
27423
+ request_id: input2.request_id,
27424
+ ...definition.errorContext?.(values, input2)
27315
27425
  });
27316
27426
  }
27317
27427
  throw error61;
27318
27428
  }
27319
27429
  }
27430
+ function routeLocations(values) {
27431
+ const route = values.to?.split(",") ?? [];
27432
+ if (route.length < 2) return void 0;
27433
+ if (!route.every((id) => locationIdSchema.safeParse(id).success)) {
27434
+ throw invalidArguments(
27435
+ ["to"],
27436
+ "--to takes location IDs separated by commas, without spaces or empty entries."
27437
+ );
27438
+ }
27439
+ if (values.wait === false) {
27440
+ throw invalidArguments(
27441
+ ["to"],
27442
+ "--no-wait starts one step; give one location with --to or omit --no-wait."
27443
+ );
27444
+ }
27445
+ return route;
27446
+ }
27320
27447
  function repeatCount(requestIdPerLot, values, requestedId) {
27321
27448
  const count = values.count === void 0 ? 1 : Number(values.count);
27322
27449
  if (!Number.isSafeInteger(count) || count < 1) {
@@ -27348,6 +27475,9 @@ function print(stream, value) {
27348
27475
  stream.write(`${JSON.stringify(output2)}
27349
27476
  `);
27350
27477
  }
27478
+ function printNote(note) {
27479
+ print(process.stderr, { hints: [{ note }] });
27480
+ }
27351
27481
  function printResult(value) {
27352
27482
  for (const hint of value.hints ?? []) {
27353
27483
  if (hint.note !== void 0) process.stderr.write(`Hint: ${hint.note}
@@ -27366,14 +27496,9 @@ try {
27366
27496
  );
27367
27497
  printResult(result);
27368
27498
  process.exitCode = "ok" in result && !result.ok ? 1 : 0;
27499
+ const announced = announcedCliUpdate();
27500
+ if (announced) await updateAnnouncedCli(announced, printNote);
27369
27501
  } catch (error61) {
27370
- printResult(
27371
- cliFailure(
27372
- await recoverUpdate(
27373
- error61,
27374
- (note) => print(process.stderr, { hints: [{ note }] })
27375
- )
27376
- )
27377
- );
27502
+ printResult(cliFailure(await recoverUpdate(error61, printNote)));
27378
27503
  process.exitCode = 1;
27379
27504
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawsaga/cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "homepage": "https://clawsaga.net",
5
5
  "repository": {
6
6
  "type": "git",