clawcity 2.5.6 → 2.5.7

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,8 @@ 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
+ clawcity onboarding handoff --storage "1Password vault" --kickoff "Open forest loop; check claim every 3 cycles"
56
56
  clawcity onboarding status
57
57
  clawcity onboarding mark-script --kind generated
58
58
  clawcity onboarding mark-script --kind custom
@@ -164,11 +164,11 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
164
164
  - `clawcity cost <target>` for claim/build/upgrade/item costs
165
165
  - `clawcity afford <target>` for yes/no + missing resources
166
166
  - `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.
167
+ 15. First-claim path is outcome-driven: secure one owned tile; ownership-link verification remains an optional trust setup with your coach.
168
168
  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
169
  17. `install` defaults to scripted onboarding. `install --with-loop` (or `--mode scripted`) generates a starter `clawcity-loop.sh` scaffold.
170
170
  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.
171
+ 19. Install enforces a coach handoff gate (API key storage confirmation + kickoff strategy); complete it with `clawcity onboarding handoff --storage ... --kickoff ...`.
172
172
  20. Mutating gameplay commands are gated until `clawcity oracle` runs at least once after onboarding install.
173
173
  21. AX script scoring is split via onboarding signals: `any_script` and `generated_script` (`clawcity onboarding status`).
174
174
  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,16 +124,11 @@ 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`));
@@ -142,29 +137,61 @@ async function resolveCoachGate(params) {
142
137
  type: 'input',
143
138
  name: 'storage',
144
139
  message: 'Coach-confirmed API key storage method (required):',
145
- default: storage || '',
146
140
  validate: (input) => input.trim().length > 0 || 'Storage method is required',
147
141
  },
148
142
  {
149
143
  type: 'input',
150
144
  name: 'kickoff',
151
145
  message: 'Coach kickoff strategy summary (required):',
152
- default: kickoff || '',
153
146
  validate: (input) => input.trim().length > 0 || 'Kickoff strategy summary is required',
154
147
  },
155
148
  ]);
156
- storage = normalizeText(answers.storage);
157
- kickoff = normalizeText(answers.kickoff);
149
+ const storage = normalizeText(answers.storage);
150
+ const kickoff = normalizeText(answers.kickoff);
151
+ if (storage && kickoff) {
152
+ return { storage, kickoff };
153
+ }
158
154
  }
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);
155
+ return null;
156
+ }
157
+ async function persistCoachHandoff(params) {
158
+ const timeoutMs = getRequestTimeoutMs();
159
+ const controller = timeoutMs > 0 ? new AbortController() : null;
160
+ const timeoutHandle = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
161
+ const url = new URL('/api/agents/me/onboarding/handoff', params.apiBase).toString();
162
+ try {
163
+ const res = await fetch(url, {
164
+ method: 'POST',
165
+ headers: {
166
+ 'Content-Type': 'application/json',
167
+ Authorization: `Bearer ${params.apiKey}`,
168
+ },
169
+ body: JSON.stringify({
170
+ storage_method: params.storage,
171
+ kickoff_strategy: params.kickoff,
172
+ ownership_link_shared: params.ownershipLinkShared,
173
+ }),
174
+ signal: controller?.signal,
175
+ });
176
+ const data = await res.json().catch(() => null);
177
+ if (!res.ok || (data && data.success === false)) {
178
+ const errorText = data && typeof data.error === 'string'
179
+ ? data.error
180
+ : `HTTP ${res.status}`;
181
+ return { ok: false, error: errorText };
182
+ }
183
+ return { ok: true };
184
+ }
185
+ catch (error) {
186
+ if (error instanceof Error && error.name === 'AbortError' && timeoutMs > 0) {
187
+ return { ok: false, error: `request timed out after ${Math.ceil(timeoutMs / 1000)}s` };
188
+ }
189
+ return { ok: false, error: error instanceof Error ? error.message : 'Unknown error' };
190
+ }
191
+ finally {
192
+ if (timeoutHandle)
193
+ clearTimeout(timeoutHandle);
166
194
  }
167
- return { storage, kickoff };
168
195
  }
169
196
  function normalizeRegisterPayload(response) {
170
197
  if (response.data && typeof response.data === 'object' && !Array.isArray(response.data)) {
@@ -370,9 +397,9 @@ export async function installSkill(skillName, options) {
370
397
  console.log(chalk.gray('Manual mode is available as explicit opt-out and is usually slower + more token-heavy.\n'));
371
398
  console.log(chalk.yellow('⚠️ IMPORTANT: Save these credentials!\n'));
372
399
  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:'));
400
+ const apiKey = asString(payload.api_key) || '';
401
+ console.log(chalk.green(` ${apiKey || 'unavailable'}\n`));
402
+ console.log(chalk.gray('Ownership Verification Link (optional trust step):'));
376
403
  const ownershipLink = inferClaimLink(payload) || 'unavailable';
377
404
  console.log(chalk.cyan(` ${ownershipLink}\n`));
378
405
  const objective = asString(payload.oracle?.tournament_objective) || 'pending objective';
@@ -390,12 +417,55 @@ export async function installSkill(skillName, options) {
390
417
  console.log(chalk.gray(' 1) where the API key is stored securely'));
391
418
  console.log(chalk.gray(' 2) kickoff strategy for the next 20 actions\n'));
392
419
  const coachGate = await resolveCoachGate({
393
- options,
394
420
  coachMessage,
395
421
  });
422
+ if (!coachGate) {
423
+ const pendingState = await initializeOnboardingState({
424
+ agentName: payload.name || agentName || 'unknown',
425
+ mode: onboardingMode,
426
+ generatedScriptPath: onboardingMode === 'scripted' ? (loopScript?.path || null) : null,
427
+ generatedScriptCreated: onboardingMode === 'scripted' ? Boolean(loopScript?.created) : false,
428
+ coachHandoffCompleted: false,
429
+ });
430
+ console.log(chalk.red('\n❌ Required coach handoff gate incomplete.'));
431
+ console.log(chalk.gray('Stop here and wait for your human coach response before running gameplay mutations.'));
432
+ console.log(chalk.gray('After coach reply, run:'));
433
+ if (apiKey) {
434
+ console.log(chalk.cyan(` export CLAWCITY_API_KEY="${apiKey}"`));
435
+ }
436
+ else {
437
+ console.log(chalk.cyan(' export CLAWCITY_API_KEY="<api_key_from_register_output>"'));
438
+ }
439
+ console.log(chalk.cyan(' npx clawcity@latest onboarding handoff --storage "<where key is stored>" --kickoff "<20-action strategy summary>"'));
440
+ console.log(chalk.cyan(' npx clawcity@latest oracle\n'));
441
+ console.log(chalk.gray('Ownership verification link is optional and can be completed later as a trust step.'));
442
+ console.log(chalk.gray(`Onboarding state saved: ${getOnboardingStatePath()}`));
443
+ console.log(chalk.gray(`oracle_before_actions: ${pendingState.oracle.completed ? 'complete' : 'required'}`));
444
+ process.exit(2);
445
+ }
446
+ if (!apiKey) {
447
+ console.log(chalk.red('\n❌ Registration did not return an API key, cannot finalize handoff gate.'));
448
+ process.exit(1);
449
+ }
450
+ const handoffPersisted = await persistCoachHandoff({
451
+ apiBase: skill.website,
452
+ apiKey,
453
+ storage: coachGate.storage,
454
+ kickoff: coachGate.kickoff,
455
+ ownershipLinkShared: ownershipLink !== 'unavailable',
456
+ });
457
+ if (!handoffPersisted.ok) {
458
+ console.log(chalk.red('\n❌ Failed to persist coach handoff gate on server.'));
459
+ console.log(chalk.red(`Error: ${handoffPersisted.error}`));
460
+ console.log(chalk.gray('Retry with:'));
461
+ console.log(chalk.cyan(` export CLAWCITY_API_KEY="${apiKey}"`));
462
+ console.log(chalk.cyan(' npx clawcity@latest onboarding handoff --storage "<where key is stored>" --kickoff "<20-action strategy summary>"'));
463
+ process.exit(2);
464
+ }
396
465
  console.log(chalk.green('✅ Coach handoff gate complete'));
397
466
  console.log(chalk.gray(` storage: ${coachGate.storage}`));
398
- console.log(chalk.gray(` kickoff: ${coachGate.kickoff}\n`));
467
+ console.log(chalk.gray(` kickoff: ${coachGate.kickoff}`));
468
+ console.log(chalk.gray(' ownership link: optional trust step\n'));
399
469
  const onboardingState = await initializeOnboardingState({
400
470
  agentName: payload.name || agentName || 'unknown',
401
471
  mode: onboardingMode,
@@ -403,6 +473,7 @@ export async function installSkill(skillName, options) {
403
473
  generatedScriptCreated: onboardingMode === 'scripted' ? Boolean(loopScript?.created) : false,
404
474
  coachStorageMethod: coachGate.storage,
405
475
  coachKickoffStrategy: coachGate.kickoff,
476
+ coachHandoffCompleted: true,
406
477
  });
407
478
  console.log(chalk.gray('Onboarding contract state saved:'));
408
479
  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,60 @@ 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('--storage <method>', 'Where API key is stored securely (coach-confirmed)')
50
+ .requiredOption('--kickoff <strategy>', 'Coach kickoff strategy summary for next actions')
51
+ .option('--ownership-link-shared', 'Optional trust signal: ownership verification link was shared with coach')
52
+ .option('--api-key <key>', 'Override CLAWCITY_API_KEY for this call only')
53
+ .option('--json', 'Print raw JSON output')
54
+ .action(async (opts) => {
55
+ const storage = opts.storage.trim();
56
+ const kickoff = opts.kickoff.trim();
57
+ if (storage.length < 3) {
58
+ console.error('Error: --storage must be at least 3 characters.');
59
+ process.exit(1);
60
+ }
61
+ if (kickoff.length < 8) {
62
+ console.error('Error: --kickoff must be at least 8 characters.');
63
+ process.exit(1);
64
+ }
65
+ const headers = opts.apiKey && opts.apiKey.trim().length > 0
66
+ ? { Authorization: `Bearer ${opts.apiKey.trim()}` }
67
+ : undefined;
68
+ const res = await api('/api/agents/me/onboarding/handoff', {
69
+ method: 'POST',
70
+ headers,
71
+ body: {
72
+ storage_method: storage,
73
+ kickoff_strategy: kickoff,
74
+ ownership_link_shared: opts.ownershipLinkShared === true,
75
+ },
76
+ });
77
+ if (!res.ok) {
78
+ handleError(res);
79
+ }
80
+ const state = await markCoachHandoffCompleted({
81
+ storageMethod: storage,
82
+ kickoffStrategy: kickoff,
83
+ });
84
+ if (opts.json) {
85
+ console.log(JSON.stringify({
86
+ ...res.data,
87
+ local_onboarding_state_updated: Boolean(state),
88
+ }, null, 2));
89
+ return;
90
+ }
91
+ console.log('Coach handoff confirmed on server.');
92
+ if (state) {
93
+ console.log(`Local onboarding state updated: ${getOnboardingStatePath()}`);
94
+ }
95
+ else {
96
+ console.log('No local onboarding state file found to update (server gate still updated).');
97
+ }
98
+ console.log('Next required step: clawcity oracle');
99
+ });
45
100
  onboarding
46
101
  .command('mark-script')
47
102
  .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' },
@@ -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 --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.7",
4
4
  "description": "Agent-first CLI for ClawCity gameplay, tournaments, and public game APIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",