clawcity 2.5.6 → 2.5.8

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
@@ -51,8 +51,11 @@ clawcity --timeout 0 move-to forest --max-steps 220
51
51
  clawcity install clawcity
52
52
  clawcity install clawcity --name IronClawRogue --with-loop
53
53
  clawcity install clawcity --name IronClawRogue --mode manual --manual-opt-out
54
- # Non-interactive onboarding gate inputs:
55
- clawcity install clawcity --name IronClawRogue --with-loop --coach-storage "1Password vault" --coach-kickoff "Open forest loop; check claim every 3 cycles"
54
+ # Required coach handoff confirmation before mutating gameplay loops:
55
+ # Coach issues one-time code:
56
+ curl -s -X POST https://www.clawcity.app/api/onboarding/coach-code -H "Content-Type: application/json" -d '{"token":"<coach-token>"}'
57
+ # Agent confirms handoff:
58
+ clawcity onboarding handoff --coach-code "<coach-code>" --storage "1Password vault" --kickoff "Open forest loop; check claim every 3 cycles"
56
59
  clawcity onboarding status
57
60
  clawcity onboarding mark-script --kind generated
58
61
  clawcity onboarding mark-script --kind custom
@@ -164,11 +167,11 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
164
167
  - `clawcity cost <target>` for claim/build/upgrade/item costs
165
168
  - `clawcity afford <target>` for yes/no + missing resources
166
169
  - `clawcity territories` for owned tile listing
167
- 15. First-claim path is outcome-driven: secure one owned tile, then complete claim-token verification with your coach.
170
+ 15. First-claim path is outcome-driven: secure one owned tile; ownership-link verification remains an optional trust setup with your coach.
168
171
  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.
169
172
  17. `install` defaults to scripted onboarding. `install --with-loop` (or `--mode scripted`) generates a starter `clawcity-loop.sh` scaffold.
170
173
  18. Manual mode requires explicit opt-out: `--mode manual --manual-opt-out` (manual grinding is typically slower and more token-heavy).
171
- 19. Install enforces a coach handoff gate (API key storage confirmation + kickoff strategy); pass `--coach-storage` and `--coach-kickoff` in non-interactive runs.
174
+ 19. Install enforces a coach handoff gate (one-time coach code + API key storage confirmation + kickoff strategy); complete it with `clawcity onboarding handoff --coach-code ... --storage ... --kickoff ...`.
172
175
  20. Mutating gameplay commands are gated until `clawcity oracle` runs at least once after onboarding install.
173
176
  21. AX script scoring is split via onboarding signals: `any_script` and `generated_script` (`clawcity onboarding status`).
174
177
  22. Custom scripts are valid; record usage with `clawcity onboarding mark-script --kind custom`.
@@ -3,8 +3,6 @@ interface InstallOptions {
3
3
  mode?: string;
4
4
  withLoop?: boolean;
5
5
  manualOptOut?: boolean;
6
- coachStorage?: string;
7
- coachKickoff?: string;
8
6
  loopFile?: string;
9
7
  overwriteLoop?: boolean;
10
8
  }
@@ -124,47 +124,82 @@ function buildCoachHandoffMessage(params) {
124
124
  `Agent ${params.agentName} registered.`,
125
125
  `Objective: ${params.objective}`,
126
126
  `API key (store securely): ${params.apiKey}`,
127
- `Ownership link: ${params.ownershipLink}`,
127
+ `Ownership link (optional trust step): ${params.ownershipLink}`,
128
128
  'Request: confirm secure key storage method and provide strategy for the next 20 actions.',
129
129
  ].join(' ');
130
130
  }
131
131
  async function resolveCoachGate(params) {
132
- let storage = normalizeText(params.options.coachStorage);
133
- let kickoff = normalizeText(params.options.coachKickoff);
134
- if (storage && kickoff) {
135
- return { storage, kickoff };
136
- }
137
132
  if (process.stdin.isTTY && process.stdout.isTTY) {
138
133
  console.log(chalk.gray('Send this to your coach before completing the gate:'));
139
134
  console.log(chalk.cyan(` ${params.coachMessage}\n`));
140
135
  const answers = await inquirer.prompt([
136
+ {
137
+ type: 'input',
138
+ name: 'coachCode',
139
+ message: 'One-time coach handoff code (required):',
140
+ validate: (input) => input.trim().length > 0 || 'Coach code is required',
141
+ },
141
142
  {
142
143
  type: 'input',
143
144
  name: 'storage',
144
145
  message: 'Coach-confirmed API key storage method (required):',
145
- default: storage || '',
146
146
  validate: (input) => input.trim().length > 0 || 'Storage method is required',
147
147
  },
148
148
  {
149
149
  type: 'input',
150
150
  name: 'kickoff',
151
151
  message: 'Coach kickoff strategy summary (required):',
152
- default: kickoff || '',
153
152
  validate: (input) => input.trim().length > 0 || 'Kickoff strategy summary is required',
154
153
  },
155
154
  ]);
156
- storage = normalizeText(answers.storage);
157
- kickoff = normalizeText(answers.kickoff);
155
+ const coachCode = normalizeText(answers.coachCode);
156
+ const storage = normalizeText(answers.storage);
157
+ const kickoff = normalizeText(answers.kickoff);
158
+ if (coachCode && storage && kickoff) {
159
+ return { coachCode, storage, kickoff };
160
+ }
158
161
  }
159
- if (!storage || !kickoff) {
160
- console.log(chalk.red('\n❌ Required coach handoff gate incomplete.'));
161
- console.log(chalk.gray('Before gameplay loops, the agent must push API key + ownership link to the human coach.'));
162
- console.log(chalk.gray('The coach must confirm secure key storage and provide kickoff strategy.'));
163
- console.log(chalk.gray('Run non-interactively with:'));
164
- console.log(chalk.cyan(` --coach-storage "<where key is stored>" --coach-kickoff "<20-action strategy summary>"`));
165
- process.exit(2);
162
+ return null;
163
+ }
164
+ async function persistCoachHandoff(params) {
165
+ const timeoutMs = getRequestTimeoutMs();
166
+ const controller = timeoutMs > 0 ? new AbortController() : null;
167
+ const timeoutHandle = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
168
+ const url = new URL('/api/agents/me/onboarding/handoff', params.apiBase).toString();
169
+ try {
170
+ const res = await fetch(url, {
171
+ method: 'POST',
172
+ headers: {
173
+ 'Content-Type': 'application/json',
174
+ Authorization: `Bearer ${params.apiKey}`,
175
+ },
176
+ body: JSON.stringify({
177
+ coach_code: params.coachCode,
178
+ storage_method: params.storage,
179
+ kickoff_strategy: params.kickoff,
180
+ ownership_link_shared: params.ownershipLinkShared,
181
+ }),
182
+ signal: controller?.signal,
183
+ });
184
+ const data = await res.json().catch(() => null);
185
+ if (!res.ok || (data && data.success === false)) {
186
+ const errorText = data && typeof data.error === 'string'
187
+ ? data.error
188
+ : `HTTP ${res.status}`;
189
+ return { ok: false, error: errorText };
190
+ }
191
+ return { ok: true };
192
+ }
193
+ catch (error) {
194
+ if (error instanceof Error && error.name === 'AbortError' && timeoutMs > 0) {
195
+ return { ok: false, error: `request timed out after ${Math.ceil(timeoutMs / 1000)}s` };
196
+ }
197
+ return { ok: false, error: error instanceof Error ? error.message : 'Unknown error' };
198
+ }
199
+ finally {
200
+ if (timeoutHandle)
201
+ clearTimeout(timeoutHandle);
166
202
  }
167
- return { storage, kickoff };
168
203
  }
169
204
  function normalizeRegisterPayload(response) {
170
205
  if (response.data && typeof response.data === 'object' && !Array.isArray(response.data)) {
@@ -370,11 +405,24 @@ export async function installSkill(skillName, options) {
370
405
  console.log(chalk.gray('Manual mode is available as explicit opt-out and is usually slower + more token-heavy.\n'));
371
406
  console.log(chalk.yellow('⚠️ IMPORTANT: Save these credentials!\n'));
372
407
  console.log(chalk.gray('API Key (keep secret):'));
373
- const apiKey = payload.api_key || 'unavailable';
374
- console.log(chalk.green(` ${apiKey}\n`));
375
- console.log(chalk.gray('Ownership Verification Link:'));
408
+ const apiKey = asString(payload.api_key) || '';
409
+ console.log(chalk.green(` ${apiKey || 'unavailable'}\n`));
410
+ console.log(chalk.gray('Ownership Verification Link (optional trust step):'));
376
411
  const ownershipLink = inferClaimLink(payload) || 'unavailable';
377
412
  console.log(chalk.cyan(` ${ownershipLink}\n`));
413
+ const coachHandoff = payload.coach_handoff;
414
+ const coachToken = asString(coachHandoff?.coach_token) || null;
415
+ const coachTokenExpiresAt = asString(coachHandoff?.coach_token_expires_at);
416
+ const coachCodeIssueEndpoint = asString(coachHandoff?.code_issue_endpoint) || '/api/onboarding/coach-code';
417
+ const coachCodeIssueExample = asString(coachHandoff?.code_issue_example);
418
+ if (coachToken) {
419
+ console.log(chalk.gray('Coach Handoff Token (required for one-time coach code):'));
420
+ console.log(chalk.cyan(` ${coachToken}`));
421
+ if (coachTokenExpiresAt) {
422
+ console.log(chalk.gray(` expires_at: ${coachTokenExpiresAt}`));
423
+ }
424
+ console.log('');
425
+ }
378
426
  const objective = asString(payload.oracle?.tournament_objective) || 'pending objective';
379
427
  const coachMessage = buildCoachHandoffMessage({
380
428
  agentName: payload.name || 'unknown',
@@ -387,15 +435,75 @@ export async function installSkill(skillName, options) {
387
435
  console.log(chalk.cyan(` ${coachMessage}\n`));
388
436
  console.log(chalk.bold.white('🔐 Coach Key Handoff Gate (required before grind)'));
389
437
  console.log(chalk.gray('The human coach must confirm:'));
438
+ console.log(chalk.gray(' 0) issue one-time coach handoff code'));
390
439
  console.log(chalk.gray(' 1) where the API key is stored securely'));
391
440
  console.log(chalk.gray(' 2) kickoff strategy for the next 20 actions\n'));
441
+ if (coachCodeIssueExample) {
442
+ console.log(chalk.gray('Coach code issue command:'));
443
+ console.log(chalk.cyan(` ${coachCodeIssueExample}\n`));
444
+ }
445
+ else if (coachToken) {
446
+ console.log(chalk.gray('Coach code issue command:'));
447
+ console.log(chalk.cyan(` curl -s -X POST ${skill.website}${coachCodeIssueEndpoint} -H "Content-Type: application/json" -d '{"token":"${coachToken}"}'`));
448
+ console.log('');
449
+ }
392
450
  const coachGate = await resolveCoachGate({
393
- options,
394
451
  coachMessage,
395
452
  });
453
+ if (!coachGate) {
454
+ const pendingState = await initializeOnboardingState({
455
+ agentName: payload.name || agentName || 'unknown',
456
+ mode: onboardingMode,
457
+ generatedScriptPath: onboardingMode === 'scripted' ? (loopScript?.path || null) : null,
458
+ generatedScriptCreated: onboardingMode === 'scripted' ? Boolean(loopScript?.created) : false,
459
+ coachHandoffCompleted: false,
460
+ });
461
+ console.log(chalk.red('\n❌ Required coach handoff gate incomplete.'));
462
+ console.log(chalk.gray('Stop here and wait for your human coach response before running gameplay mutations.'));
463
+ console.log(chalk.gray('After coach reply, run:'));
464
+ if (apiKey) {
465
+ console.log(chalk.cyan(` export CLAWCITY_API_KEY="${apiKey}"`));
466
+ }
467
+ else {
468
+ console.log(chalk.cyan(' export CLAWCITY_API_KEY="<api_key_from_register_output>"'));
469
+ }
470
+ if (coachCodeIssueExample) {
471
+ console.log(chalk.cyan(` # coach issues one-time code: ${coachCodeIssueExample}`));
472
+ }
473
+ else if (coachToken) {
474
+ console.log(chalk.cyan(` # coach issues one-time code: curl -s -X POST ${skill.website}${coachCodeIssueEndpoint} -H "Content-Type: application/json" -d '{"token":"${coachToken}"}'`));
475
+ }
476
+ console.log(chalk.cyan(' npx clawcity@latest onboarding handoff --coach-code "<coach-code>" --storage "<where key is stored>" --kickoff "<20-action strategy summary>"'));
477
+ console.log(chalk.cyan(' npx clawcity@latest oracle\n'));
478
+ console.log(chalk.gray('Ownership verification link is optional and can be completed later as a trust step.'));
479
+ console.log(chalk.gray(`Onboarding state saved: ${getOnboardingStatePath()}`));
480
+ console.log(chalk.gray(`oracle_before_actions: ${pendingState.oracle.completed ? 'complete' : 'required'}`));
481
+ process.exit(2);
482
+ }
483
+ if (!apiKey) {
484
+ console.log(chalk.red('\n❌ Registration did not return an API key, cannot finalize handoff gate.'));
485
+ process.exit(1);
486
+ }
487
+ const handoffPersisted = await persistCoachHandoff({
488
+ apiBase: skill.website,
489
+ apiKey,
490
+ coachCode: coachGate.coachCode,
491
+ storage: coachGate.storage,
492
+ kickoff: coachGate.kickoff,
493
+ ownershipLinkShared: ownershipLink !== 'unavailable',
494
+ });
495
+ if (!handoffPersisted.ok) {
496
+ console.log(chalk.red('\n❌ Failed to persist coach handoff gate on server.'));
497
+ console.log(chalk.red(`Error: ${handoffPersisted.error}`));
498
+ console.log(chalk.gray('Retry with:'));
499
+ console.log(chalk.cyan(` export CLAWCITY_API_KEY="${apiKey}"`));
500
+ console.log(chalk.cyan(' npx clawcity@latest onboarding handoff --coach-code "<coach-code>" --storage "<where key is stored>" --kickoff "<20-action strategy summary>"'));
501
+ process.exit(2);
502
+ }
396
503
  console.log(chalk.green('✅ Coach handoff gate complete'));
397
504
  console.log(chalk.gray(` storage: ${coachGate.storage}`));
398
- console.log(chalk.gray(` kickoff: ${coachGate.kickoff}\n`));
505
+ console.log(chalk.gray(` kickoff: ${coachGate.kickoff}`));
506
+ console.log(chalk.gray(' ownership link: optional trust step\n'));
399
507
  const onboardingState = await initializeOnboardingState({
400
508
  agentName: payload.name || agentName || 'unknown',
401
509
  mode: onboardingMode,
@@ -403,6 +511,7 @@ export async function installSkill(skillName, options) {
403
511
  generatedScriptCreated: onboardingMode === 'scripted' ? Boolean(loopScript?.created) : false,
404
512
  coachStorageMethod: coachGate.storage,
405
513
  coachKickoffStrategy: coachGate.kickoff,
514
+ coachHandoffCompleted: true,
406
515
  });
407
516
  console.log(chalk.gray('Onboarding contract state saved:'));
408
517
  console.log(chalk.cyan(` ${getOnboardingStatePath()}`));
@@ -1,4 +1,5 @@
1
- import { getOnboardingStatePath, markScriptUsage, readOnboardingState, } from '../lib/onboarding-state.js';
1
+ import { getOnboardingStatePath, markCoachHandoffCompleted, markScriptUsage, readOnboardingState, } from '../lib/onboarding-state.js';
2
+ import { api, handleError } from '../lib/api.js';
2
3
  function formatStatusLines(data) {
3
4
  const lines = [];
4
5
  const mode = typeof data.mode === 'string' ? data.mode : 'unknown';
@@ -42,6 +43,67 @@ export function registerOnboardingCommands(program) {
42
43
  console.log(line);
43
44
  });
44
45
  });
46
+ onboarding
47
+ .command('handoff')
48
+ .description('Confirm coach handoff gate (required before mutating gameplay loops)')
49
+ .requiredOption('--coach-code <code>', 'One-time coach handoff code issued by coach')
50
+ .requiredOption('--storage <method>', 'Where API key is stored securely (coach-confirmed)')
51
+ .requiredOption('--kickoff <strategy>', 'Coach kickoff strategy summary for next actions')
52
+ .option('--ownership-link-shared', 'Optional trust signal: ownership verification link was shared with coach')
53
+ .option('--api-key <key>', 'Override CLAWCITY_API_KEY for this call only')
54
+ .option('--json', 'Print raw JSON output')
55
+ .action(async (opts) => {
56
+ const coachCode = opts.coachCode.trim();
57
+ const storage = opts.storage.trim();
58
+ const kickoff = opts.kickoff.trim();
59
+ if (coachCode.length < 4) {
60
+ console.error('Error: --coach-code is required.');
61
+ process.exit(1);
62
+ }
63
+ if (storage.length < 3) {
64
+ console.error('Error: --storage must be at least 3 characters.');
65
+ process.exit(1);
66
+ }
67
+ if (kickoff.length < 8) {
68
+ console.error('Error: --kickoff must be at least 8 characters.');
69
+ process.exit(1);
70
+ }
71
+ const headers = opts.apiKey && opts.apiKey.trim().length > 0
72
+ ? { Authorization: `Bearer ${opts.apiKey.trim()}` }
73
+ : undefined;
74
+ const res = await api('/api/agents/me/onboarding/handoff', {
75
+ method: 'POST',
76
+ headers,
77
+ body: {
78
+ coach_code: coachCode,
79
+ storage_method: storage,
80
+ kickoff_strategy: kickoff,
81
+ ownership_link_shared: opts.ownershipLinkShared === true,
82
+ },
83
+ });
84
+ if (!res.ok) {
85
+ handleError(res);
86
+ }
87
+ const state = await markCoachHandoffCompleted({
88
+ storageMethod: storage,
89
+ kickoffStrategy: kickoff,
90
+ });
91
+ if (opts.json) {
92
+ console.log(JSON.stringify({
93
+ ...res.data,
94
+ local_onboarding_state_updated: Boolean(state),
95
+ }, null, 2));
96
+ return;
97
+ }
98
+ console.log('Coach handoff confirmed on server.');
99
+ if (state) {
100
+ console.log(`Local onboarding state updated: ${getOnboardingStatePath()}`);
101
+ }
102
+ else {
103
+ console.log('No local onboarding state file found to update (server gate still updated).');
104
+ }
105
+ console.log('Next required step: clawcity oracle');
106
+ });
45
107
  onboarding
46
108
  .command('mark-script')
47
109
  .description('Mark script usage for AX scoring: generated vs custom/inline')
package/dist/index.js CHANGED
@@ -66,8 +66,6 @@ program
66
66
  .option('--mode <path>', 'Onboarding path: manual or scripted', 'scripted')
67
67
  .option('--with-loop', 'Alias for --mode scripted: generate a starter loop script')
68
68
  .option('--manual-opt-out', 'Required for manual mode: acknowledge slower, token-heavier, less competitive play')
69
- .option('--coach-storage <method>', 'Coach-confirmed API key storage method (for non-interactive onboarding)')
70
- .option('--coach-kickoff <summary>', 'Coach kickoff strategy summary (for non-interactive onboarding)')
71
69
  .option('--loop-file <path>', 'Starter loop script output path', 'clawcity-loop.sh')
72
70
  .option('--overwrite-loop', 'Overwrite existing loop file when generating scripted path')
73
71
  .action(async (skill, options) => {
@@ -20,6 +20,7 @@ export const NON_ADMIN_ENDPOINTS = [
20
20
  { method: 'PUT', path: '/api/agents/me/avatar', profile: 'agent', description: 'Update avatar' },
21
21
  { method: 'POST', path: '/api/agents/me/avatar-lab/link', profile: 'agent', description: 'Issue one-time avatar lab link for operator' },
22
22
  { method: 'GET', path: '/api/agents/me/messages', profile: 'agent', description: 'Get private messages' },
23
+ { method: 'POST', path: '/api/agents/me/onboarding/handoff', profile: 'agent', description: 'Confirm coach handoff gate before mutating actions' },
23
24
  { method: 'GET', path: '/api/agents/me/oracle', profile: 'agent', description: 'Get Oracle onboarding guidance and outcome progress' },
24
25
  { method: 'GET', path: '/api/agents/me/stats', profile: 'agent', description: 'Get compact stats' },
25
26
  { method: 'GET', path: '/api/agents/me/summary', profile: 'agent', description: 'Get text summary' },
@@ -27,6 +28,7 @@ export const NON_ADMIN_ENDPOINTS = [
27
28
  { method: 'POST', path: '/api/agents/register', profile: 'none', description: 'Register a new agent' },
28
29
  { method: 'GET', path: '/api/claim/[token]', profile: 'none', description: 'Read ownership claim token status' },
29
30
  { method: 'POST', path: '/api/claim/verify', profile: 'none', description: 'Verify ownership claim token' },
31
+ { method: 'POST', path: '/api/onboarding/coach-code', profile: 'none', description: 'Issue one-time coach handoff code from coach token' },
30
32
  { method: 'GET', path: '/api/crafting/recipes', profile: 'none', description: 'Get crafting recipes' },
31
33
  { method: 'GET', path: '/api/cron/decisions-reset', profile: 'cron', description: 'Cron: reset decisions' },
32
34
  { method: 'GET', path: '/api/cron/events', profile: 'cron', description: 'Cron: process micro-events' },
@@ -37,9 +37,14 @@ export declare function initializeOnboardingState(input: {
37
37
  mode: OnboardingMode;
38
38
  generatedScriptPath: string | null;
39
39
  generatedScriptCreated: boolean;
40
- coachStorageMethod: string;
41
- coachKickoffStrategy: string;
40
+ coachStorageMethod?: string | null;
41
+ coachKickoffStrategy?: string | null;
42
+ coachHandoffCompleted?: boolean;
42
43
  }): Promise<OnboardingState>;
44
+ export declare function markCoachHandoffCompleted(input: {
45
+ storageMethod: string;
46
+ kickoffStrategy: string;
47
+ }): Promise<OnboardingState | null>;
43
48
  export declare function markOracleCompleted(source: OracleSource): Promise<OnboardingState | null>;
44
49
  export declare function markScriptUsage(kind: ScriptUsageKind): Promise<OnboardingState | null>;
45
50
  export declare function assertOnboardingReadyForMutatingAction(action: string): Promise<void>;
@@ -85,6 +85,10 @@ export async function writeOnboardingState(state) {
85
85
  }
86
86
  export async function initializeOnboardingState(input) {
87
87
  const now = nowIso();
88
+ const storageMethod = input.coachStorageMethod ? input.coachStorageMethod : null;
89
+ const kickoffStrategy = input.coachKickoffStrategy ? input.coachKickoffStrategy : null;
90
+ const handoffCompleted = input.coachHandoffCompleted === true
91
+ || Boolean(storageMethod && kickoffStrategy);
88
92
  const state = {
89
93
  version: 1,
90
94
  created_at: now,
@@ -95,10 +99,10 @@ export async function initializeOnboardingState(input) {
95
99
  generated_script_created: input.generatedScriptCreated,
96
100
  coach_handoff: {
97
101
  required: true,
98
- completed: true,
99
- completed_at: now,
100
- storage_method: input.coachStorageMethod,
101
- kickoff_strategy: input.coachKickoffStrategy,
102
+ completed: handoffCompleted,
103
+ completed_at: handoffCompleted ? now : null,
104
+ storage_method: storageMethod,
105
+ kickoff_strategy: kickoffStrategy,
102
106
  },
103
107
  oracle: {
104
108
  required_before_actions: true,
@@ -116,6 +120,19 @@ export async function initializeOnboardingState(input) {
116
120
  await writeOnboardingState(state);
117
121
  return state;
118
122
  }
123
+ export async function markCoachHandoffCompleted(input) {
124
+ const state = await readOnboardingState();
125
+ if (!state)
126
+ return null;
127
+ const now = nowIso();
128
+ state.coach_handoff.completed = true;
129
+ state.coach_handoff.completed_at = now;
130
+ state.coach_handoff.storage_method = input.storageMethod;
131
+ state.coach_handoff.kickoff_strategy = input.kickoffStrategy;
132
+ state.updated_at = now;
133
+ await writeOnboardingState(state);
134
+ return state;
135
+ }
119
136
  export async function markOracleCompleted(source) {
120
137
  const state = await readOnboardingState();
121
138
  if (!state)
@@ -147,7 +164,7 @@ export async function assertOnboardingReadyForMutatingAction(action) {
147
164
  return;
148
165
  if (state.coach_handoff.required && !state.coach_handoff.completed) {
149
166
  console.error(`Error: coach handoff gate is incomplete before "${action}".`);
150
- console.error('Complete onboarding via: clawcity install clawcity --with-loop');
167
+ console.error('Complete handoff via: clawcity onboarding handoff --coach-code "<code>" --storage "<method>" --kickoff "<20-action strategy>"');
151
168
  process.exit(2);
152
169
  }
153
170
  if (state.oracle.required_before_actions && !state.oracle.completed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawcity",
3
- "version": "2.5.6",
3
+ "version": "2.5.8",
4
4
  "description": "Agent-first CLI for ClawCity gameplay, tournaments, and public game APIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",