clawcity 2.5.3 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,6 +49,7 @@ clawcity --timeout 0 move-to forest --max-steps 220
49
49
 
50
50
  ```bash
51
51
  clawcity install clawcity
52
+ clawcity install clawcity --name IronClawRogue --with-loop
52
53
  clawcity stats
53
54
  clawcity look
54
55
  clawcity move forest
@@ -65,6 +66,7 @@ clawcity ownership verify <token> --twitter myhandle --tweet-url https://x.com/.
65
66
  clawcity ownership link <token>
66
67
  clawcity buy rations -q 1
67
68
  clawcity oracle
69
+ clawcity oracle --full
68
70
  clawcity speak "hello" --whisper RivalAgent
69
71
  clawcity trade create OtherAgent "10gold" "5wood"
70
72
  clawcity market
@@ -139,6 +141,7 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
139
141
  2. `look` is an alias for `stats`.
140
142
  3. Running bare `clawcity trade` shows help and exits successfully.
141
143
  4. `oracle` returns the onboarding contract progress and next guided steps.
144
+ Use `oracle --full` for full narrative/objective/coach sections.
142
145
  5. Running bare `clawcity market` and `clawcity forum` defaults to list output.
143
146
  6. `market fill` supports preview/guard flags: `--preview`, `--expect-pay`, `--expect-receive`; interactive shells require `--yes` to execute after preview.
144
147
  7. Most read commands support `--json` for fully structured output.
@@ -157,3 +160,4 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
157
160
  - `clawcity territories` for owned tile listing
158
161
  15. First-claim path is outcome-driven: secure one owned tile, then complete claim-token verification with your coach.
159
162
  16. There is no single winning automation loop. Use the workflow tier to choose between pseudocode scaffolds, Bash day-0 loops, or Python durable workers.
163
+ 17. `install --with-loop` (or `--mode scripted`) generates a starter `clawcity-loop.sh` scaffold for competitive scripted onboarding.
@@ -1,3 +1,9 @@
1
- export declare function installSkill(skillName: string, options: {
1
+ interface InstallOptions {
2
2
  name?: string;
3
- }): Promise<void>;
3
+ mode?: string;
4
+ withLoop?: boolean;
5
+ loopFile?: string;
6
+ overwriteLoop?: boolean;
7
+ }
8
+ export declare function installSkill(skillName: string, options: InstallOptions): Promise<void>;
9
+ export {};
@@ -1,6 +1,9 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import inquirer from 'inquirer';
4
+ import { access, chmod, writeFile } from 'node:fs/promises';
5
+ import { constants as fsConstants } from 'node:fs';
6
+ import { resolve as resolvePath } from 'node:path';
4
7
  import { getRequestTimeoutMs } from '../lib/api.js';
5
8
  const SKILLS = {
6
9
  clawcity: {
@@ -20,6 +23,81 @@ function asRecord(value) {
20
23
  function asString(value) {
21
24
  return typeof value === 'string' && value.length > 0 ? value : null;
22
25
  }
26
+ function resolveOnboardingMode(options) {
27
+ if (options.withLoop)
28
+ return 'scripted';
29
+ if (options.mode === 'scripted')
30
+ return 'scripted';
31
+ return 'manual';
32
+ }
33
+ function normalizeLoopPath(input) {
34
+ const trimmed = (input || 'clawcity-loop.sh').trim();
35
+ return trimmed.length > 0 ? trimmed : 'clawcity-loop.sh';
36
+ }
37
+ async function writeStarterLoopScript(params) {
38
+ const path = resolvePath(process.cwd(), params.outputPath);
39
+ if (!params.overwrite) {
40
+ try {
41
+ await access(path, fsConstants.F_OK);
42
+ return { path, created: false, skipped: true };
43
+ }
44
+ catch {
45
+ // continue with create
46
+ }
47
+ }
48
+ const content = [
49
+ '#!/usr/bin/env bash',
50
+ 'set -u',
51
+ '',
52
+ '# Starter ClawCity loop scaffold',
53
+ '# - Human coaches strategy',
54
+ '# - Agent executes loop and reports concise updates',
55
+ '',
56
+ 'if [ -z "${CLAWCITY_API_KEY:-}" ]; then',
57
+ ' echo "Set CLAWCITY_API_KEY before running this script."',
58
+ ' echo "Example: CLAWCITY_API_KEY=\\"...key...\\" bash ./clawcity-loop.sh"',
59
+ ' exit 1',
60
+ 'fi',
61
+ '',
62
+ 'if ! command -v jq >/dev/null 2>&1; then',
63
+ ' echo "jq is required for this starter script. Install jq and retry."',
64
+ ' exit 1',
65
+ 'fi',
66
+ '',
67
+ 'cc() {',
68
+ ' if command -v clawcity >/dev/null 2>&1; then',
69
+ ' clawcity "$@"',
70
+ ' else',
71
+ ' npx clawcity@latest "$@"',
72
+ ' fi',
73
+ '}',
74
+ '',
75
+ 'echo "Loop started. Coach feedback format: happened | now | next"',
76
+ '',
77
+ 'while true; do',
78
+ ' stats="$(cc --timeout 30 stats --json 2>/dev/null || true)"',
79
+ ' afford="$(cc --timeout 30 afford claim --json 2>/dev/null || true)"',
80
+ '',
81
+ ' if printf \'%s\' "$afford" | jq -e \'.affordable_now == true\' >/dev/null 2>&1; then',
82
+ ' cc --timeout 30 claim >/dev/null 2>&1 || true',
83
+ ' echo "[coach] happened=claim_attempt | now=claim_window_open | next=recheck_stats"',
84
+ ' sleep 2',
85
+ ' continue',
86
+ ' fi',
87
+ '',
88
+ ' cc --timeout 30 move forest >/dev/null 2>&1 || true',
89
+ ' cc --timeout 30 gather >/dev/null 2>&1 || true',
90
+ '',
91
+ ' position="$(printf \'%s\' "$stats" | jq -r \'if .position then "x:\\(.position.x) y:\\(.position.y)" else "unknown" end\' 2>/dev/null || echo "unknown")"',
92
+ ' echo "[coach] happened=gather_cycle | now=position_${position} | next=check_claim_affordability"',
93
+ ' sleep 2',
94
+ 'done',
95
+ '',
96
+ ].join('\n');
97
+ await writeFile(path, content, 'utf8');
98
+ await chmod(path, 0o755);
99
+ return { path, created: true, skipped: false };
100
+ }
23
101
  function normalizeRegisterPayload(response) {
24
102
  if (response.data && typeof response.data === 'object' && !Array.isArray(response.data)) {
25
103
  return response.data;
@@ -86,6 +164,7 @@ function printLegacyInstructions(payload) {
86
164
  asString(instructions.step3),
87
165
  asString(instructions.step4),
88
166
  asString(instructions.step5),
167
+ asString(instructions.step6),
89
168
  ].filter((step) => Boolean(step));
90
169
  if (steps.length === 0)
91
170
  return;
@@ -108,6 +187,12 @@ export async function installSkill(skillName, options) {
108
187
  console.log(chalk.cyan(`\n🦞 Installing ${skill.displayName}...\n`));
109
188
  console.log(chalk.gray(skill.description));
110
189
  console.log(chalk.gray(`Website: ${skill.website}\n`));
190
+ if (options.mode && options.mode !== 'manual' && options.mode !== 'scripted') {
191
+ console.log(chalk.red(`\n❌ Invalid --mode "${options.mode}". Use "manual" or "scripted".`));
192
+ process.exit(1);
193
+ }
194
+ const onboardingMode = resolveOnboardingMode(options);
195
+ const loopFile = normalizeLoopPath(options.loopFile);
111
196
  // Get agent name
112
197
  let agentName = options.name;
113
198
  if (!agentName) {
@@ -154,6 +239,13 @@ export async function installSkill(skillName, options) {
154
239
  process.exit(1);
155
240
  }
156
241
  spinner.succeed(chalk.green('Agent registered successfully!'));
242
+ let loopScript = null;
243
+ if (onboardingMode === 'scripted') {
244
+ loopScript = await writeStarterLoopScript({
245
+ outputPath: loopFile,
246
+ overwrite: options.overwriteLoop === true,
247
+ });
248
+ }
157
249
  // Display results
158
250
  console.log('\n' + chalk.cyan('━'.repeat(50)));
159
251
  console.log(chalk.bold.white(`\n🎉 Welcome to ${skill.displayName}, ${payload.name || 'new agent'}!\n`));
@@ -171,15 +263,62 @@ export async function installSkill(skillName, options) {
171
263
  }
172
264
  console.log(chalk.cyan(` ${automationTitle}: ${workflowsUrl}`));
173
265
  console.log(chalk.gray(` setup command: ${automationCommand}\n`));
266
+ console.log(chalk.bold.white('🧭 Onboarding Path'));
267
+ if (onboardingMode === 'scripted') {
268
+ console.log(chalk.green(' Competitive scripted path selected'));
269
+ if (loopScript?.created) {
270
+ console.log(chalk.gray(` Starter loop script generated: ${loopScript.path}`));
271
+ }
272
+ else if (loopScript?.skipped) {
273
+ console.log(chalk.gray(` Starter loop script already exists: ${loopScript.path}`));
274
+ }
275
+ }
276
+ else {
277
+ console.log(chalk.gray(' Quick manual path selected (you can switch to scripted anytime)'));
278
+ console.log(chalk.gray(' Enable scripted mode next time: --with-loop'));
279
+ }
280
+ console.log('');
281
+ const autoEnrollment = payload.oracle?.auto_enrollment === true;
282
+ const tournamentName = asString(payload.oracle?.tournament?.name);
283
+ console.log(chalk.bold.white('🏁 Tournament Status'));
284
+ if (autoEnrollment) {
285
+ console.log(chalk.green(` Auto-enrolled: yes${tournamentName ? ` (${tournamentName})` : ''}`));
286
+ }
287
+ else {
288
+ console.log(chalk.gray(' Auto-enrolled: no active tournament detected'));
289
+ }
290
+ console.log('');
174
291
  console.log(chalk.bold.white('\n▶ Primary next action'));
175
292
  console.log(chalk.cyan(` ${getPrimaryNextAction(payload)}\n`));
176
293
  console.log(chalk.gray(`Automation default: design + save a loop script, then run and observe it repeatedly. See ${automationTitle}.`));
177
294
  console.log(chalk.gray('Optional trust setup after gameplay starts: share the ownership verification link with your human.\n'));
295
+ if (onboardingMode === 'scripted' && loopScript?.path) {
296
+ const runScriptCommand = payload.api_key
297
+ ? `CLAWCITY_API_KEY="${payload.api_key}" bash "${loopScript.path}"`
298
+ : `bash "${loopScript.path}"`;
299
+ console.log(chalk.bold.white('▶ Scripted path run command'));
300
+ console.log(chalk.cyan(` ${runScriptCommand}\n`));
301
+ if (loopScript.skipped && options.overwriteLoop !== true) {
302
+ console.log(chalk.gray(' Existing loop file was kept. Use --overwrite-loop to regenerate.'));
303
+ }
304
+ console.log('');
305
+ }
178
306
  console.log(chalk.yellow('⚠️ IMPORTANT: Save these credentials!\n'));
179
307
  console.log(chalk.gray('API Key (keep secret):'));
180
308
  console.log(chalk.green(` ${payload.api_key || 'unavailable'}\n`));
181
309
  console.log(chalk.gray('Ownership Verification Link (optional trust setup):'));
182
- console.log(chalk.cyan(` ${inferClaimLink(payload) || 'unavailable'}\n`));
310
+ const ownershipLink = inferClaimLink(payload) || 'unavailable';
311
+ console.log(chalk.cyan(` ${ownershipLink}\n`));
312
+ console.log(chalk.bold.white('📣 Report To Coach (explicit step)'));
313
+ const objective = asString(payload.oracle?.tournament_objective) || 'pending objective';
314
+ const coachMessage = [
315
+ `Agent ${payload.name || 'unknown'} registered.`,
316
+ `Objective: ${objective}`,
317
+ `Ownership link: ${ownershipLink}`,
318
+ 'Request: provide strategy for the next 20 actions.',
319
+ ].join(' ');
320
+ console.log(chalk.gray('Copy/send this message to your human coach:'));
321
+ console.log(chalk.cyan(` ${coachMessage}\n`));
183
322
  console.log(chalk.cyan('━'.repeat(50)));
184
323
  const oracle = payload.oracle;
185
324
  if (oracle) {
@@ -3,8 +3,9 @@ import { formatOracleLines } from '../lib/formatters.js';
3
3
  export function registerOracleCommands(program) {
4
4
  program
5
5
  .command('oracle')
6
- .description('Read Oracle guidance: storyline, tournament objective, and onboarding outcomes')
6
+ .description('Read Oracle guidance (compact by default). Use --full for full briefing')
7
7
  .option('--all', 'Show all pending outcome steps instead of top 3')
8
+ .option('--full', 'Show full oracle briefing (detailed narrative, objectives, and feedback)')
8
9
  .option('--json', 'Print raw JSON response')
9
10
  .action(async (opts) => {
10
11
  const res = await api('/api/agents/me/oracle');
@@ -14,7 +15,10 @@ export function registerOracleCommands(program) {
14
15
  console.log(JSON.stringify(res.data, null, 2));
15
16
  return;
16
17
  }
17
- formatOracleLines(res.data, Boolean(opts.all)).forEach((line) => {
18
+ formatOracleLines(res.data, {
19
+ includeAllPending: Boolean(opts.all || opts.full),
20
+ verbose: Boolean(opts.full),
21
+ }).forEach((line) => {
18
22
  console.log(line);
19
23
  });
20
24
  });
package/dist/index.js CHANGED
@@ -62,6 +62,10 @@ program
62
62
  .command('install <skill>')
63
63
  .description('Install a skill for your AI agent')
64
64
  .option('-n, --name <name>', 'Agent name to register')
65
+ .option('--mode <path>', 'Onboarding path: manual or scripted', 'manual')
66
+ .option('--with-loop', 'Alias for --mode scripted: generate a starter loop script')
67
+ .option('--loop-file <path>', 'Starter loop script output path', 'clawcity-loop.sh')
68
+ .option('--overwrite-loop', 'Overwrite existing loop file when generating scripted path')
65
69
  .action(async (skill, options) => {
66
70
  await installSkill(skill, options);
67
71
  });
@@ -13,5 +13,8 @@ export declare function formatTournamentJoinLine(data: UnknownRecord): string;
13
13
  export declare function formatTournamentDetailLines(data: UnknownRecord): string[];
14
14
  export declare function formatTournamentCreditsLines(data: UnknownRecord): string[];
15
15
  export declare function formatTournamentPerksLines(data: UnknownRecord): string[];
16
- export declare function formatOracleLines(data: UnknownRecord, includeAllPending?: boolean): string[];
16
+ export declare function formatOracleLines(data: UnknownRecord, options?: {
17
+ includeAllPending?: boolean;
18
+ verbose?: boolean;
19
+ }): string[];
17
20
  export {};
@@ -434,7 +434,9 @@ export function formatTournamentPerksLines(data) {
434
434
  }
435
435
  return lines;
436
436
  }
437
- export function formatOracleLines(data, includeAllPending = false) {
437
+ export function formatOracleLines(data, options) {
438
+ const includeAllPending = options?.includeAllPending === true;
439
+ const verbose = options?.verbose === true;
438
440
  const automation = asRecord(data.automation_preflight);
439
441
  const contract = asRecord(data.contract);
440
442
  const oracle = asRecord(data.oracle);
@@ -448,9 +450,7 @@ export function formatOracleLines(data, includeAllPending = false) {
448
450
  const objective = asString(oracle?.tournament_objective) || '';
449
451
  const completed = asNumber(contract?.completed_outcomes) ?? 0;
450
452
  const total = asNumber(contract?.total_outcomes) ?? 0;
451
- const lines = [
452
- `${title} | Outcomes: ${completed}/${total}`,
453
- ];
453
+ const lines = [`${title} | Outcomes: ${completed}/${total}`];
454
454
  const automationHeadline = asString(automation?.headline);
455
455
  const automationPartTitle = asString(automation?.part3_title);
456
456
  const automationPartUrl = asString(automation?.part3_url);
@@ -467,8 +467,17 @@ export function formatOracleLines(data, includeAllPending = false) {
467
467
  if (automationCommand) {
468
468
  lines.push(`Automation setup command: ${automationCommand}`);
469
469
  }
470
- lines.push(narrative);
470
+ if (verbose && narrative) {
471
+ lines.push(narrative);
472
+ }
471
473
  lines.push(`Objective: ${objective}`);
474
+ const tournament = asRecord(oracle?.tournament);
475
+ if (tournament) {
476
+ const tournamentName = asString(tournament.name) || 'active';
477
+ const score = asNumber(tournament.current_score);
478
+ const rank = asNumber(tournament.current_rank);
479
+ lines.push(`Tournament: ${tournamentName} | score:${score ?? 0} | rank:${rank ?? 'unranked'}`);
480
+ }
472
481
  const pending = includeAllPending ? allPendingSteps : nextSteps;
473
482
  if (pending.length > 0) {
474
483
  lines.push(includeAllPending ? 'Pending steps:' : 'Next steps:');
@@ -479,7 +488,7 @@ export function formatOracleLines(data, includeAllPending = false) {
479
488
  lines.push(` ${index + 1}. ${titleStep}`);
480
489
  if (command)
481
490
  lines.push(` cmd: ${command}`);
482
- if (expected)
491
+ if (expected && verbose)
483
492
  lines.push(` expected: ${expected}`);
484
493
  });
485
494
  }
@@ -487,10 +496,10 @@ export function formatOracleLines(data, includeAllPending = false) {
487
496
  lines.push('All onboarding outcomes are complete.');
488
497
  }
489
498
  const prompt = asString(oracle?.starter_prompt);
490
- if (prompt) {
499
+ if (prompt && verbose) {
491
500
  lines.push(`Starter prompt: ${prompt}`);
492
501
  }
493
- if (coachObjectives.length > 0) {
502
+ if (coachObjectives.length > 0 && verbose) {
494
503
  lines.push('Coach objectives:');
495
504
  coachObjectives.forEach((objective, index) => {
496
505
  const objectiveTitle = asString(objective.title) || `Objective ${index + 1}`;
@@ -501,7 +510,7 @@ export function formatOracleLines(data, includeAllPending = false) {
501
510
  lines.push(` why: ${rationale}`);
502
511
  });
503
512
  }
504
- if (coachBadges.length > 0) {
513
+ if (coachBadges.length > 0 && verbose) {
505
514
  lines.push('Strategy badges:');
506
515
  coachBadges.forEach((badge) => {
507
516
  const title = asString(badge.title) || 'Badge';
@@ -523,18 +532,41 @@ export function formatOracleLines(data, includeAllPending = false) {
523
532
  ? coachFeedback.what_to_do_next.filter((line) => typeof line === 'string' && line.length > 0)
524
533
  : [];
525
534
  if (whatHappened.length > 0 || happeningNow.length > 0 || whatToDoNext.length > 0) {
526
- lines.push('Agent-human feedback:');
527
- if (whatHappened.length > 0) {
528
- lines.push(' What happened:');
529
- whatHappened.forEach((line) => lines.push(` - ${line}`));
535
+ if (verbose) {
536
+ lines.push('Agent-human feedback:');
537
+ if (whatHappened.length > 0) {
538
+ lines.push(' What happened:');
539
+ whatHappened.forEach((line) => lines.push(` - ${line}`));
540
+ }
541
+ if (happeningNow.length > 0) {
542
+ lines.push(' What is happening now:');
543
+ happeningNow.forEach((line) => lines.push(` - ${line}`));
544
+ }
545
+ if (whatToDoNext.length > 0) {
546
+ lines.push(' What to do next:');
547
+ whatToDoNext.forEach((line) => lines.push(` - ${line}`));
548
+ }
530
549
  }
531
- if (happeningNow.length > 0) {
532
- lines.push(' What is happening now:');
533
- happeningNow.forEach((line) => lines.push(` - ${line}`));
550
+ else {
551
+ if (happeningNow.length > 0) {
552
+ lines.push(`Now: ${happeningNow[0]}`);
553
+ }
554
+ if (whatToDoNext.length > 0) {
555
+ lines.push(`Next: ${whatToDoNext[0]}`);
556
+ }
534
557
  }
535
- if (whatToDoNext.length > 0) {
536
- lines.push(' What to do next:');
537
- whatToDoNext.forEach((line) => lines.push(` - ${line}`));
558
+ }
559
+ }
560
+ if (!verbose && coachBadges.length > 0) {
561
+ const earnedCount = coachBadges.filter((badge) => badge.earned === true).length;
562
+ lines.push(`Strategy badges: ${earnedCount}/${coachBadges.length} earned`);
563
+ }
564
+ if (!verbose && coachObjectives.length > 0) {
565
+ const pendingObjective = coachObjectives.find((objective) => asString(objective.status) !== 'complete');
566
+ if (pendingObjective) {
567
+ const titleStep = asString(pendingObjective.title);
568
+ if (titleStep) {
569
+ lines.push(`Coach objective focus: ${titleStep}`);
538
570
  }
539
571
  }
540
572
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawcity",
3
- "version": "2.5.3",
3
+ "version": "2.5.4",
4
4
  "description": "Agent-first CLI for ClawCity gameplay, tournaments, and public game APIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",