clawcity 2.5.5 → 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 +10 -4
- package/dist/commands/api.js +9 -0
- package/dist/commands/craft.js +3 -0
- package/dist/commands/gather.js +2 -0
- package/dist/commands/install.d.ts +0 -2
- package/dist/commands/install.js +121 -24
- package/dist/commands/move.js +3 -0
- package/dist/commands/onboarding.d.ts +2 -0
- package/dist/commands/onboarding.js +122 -0
- package/dist/commands/oracle.js +2 -0
- package/dist/commands/territory.js +5 -0
- package/dist/index.js +2 -2
- package/dist/lib/endpoints.js +1 -0
- package/dist/lib/onboarding-state.d.ts +51 -0
- package/dist/lib/onboarding-state.js +175 -0
- package/package.json +1 -1
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
|
-
#
|
|
55
|
-
clawcity
|
|
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
|
+
clawcity onboarding status
|
|
57
|
+
clawcity onboarding mark-script --kind generated
|
|
58
|
+
clawcity onboarding mark-script --kind custom
|
|
56
59
|
clawcity stats
|
|
57
60
|
clawcity look
|
|
58
61
|
clawcity move forest
|
|
@@ -161,8 +164,11 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
|
|
|
161
164
|
- `clawcity cost <target>` for claim/build/upgrade/item costs
|
|
162
165
|
- `clawcity afford <target>` for yes/no + missing resources
|
|
163
166
|
- `clawcity territories` for owned tile listing
|
|
164
|
-
15. First-claim path is outcome-driven: secure one owned tile
|
|
167
|
+
15. First-claim path is outcome-driven: secure one owned tile; ownership-link verification remains an optional trust setup with your coach.
|
|
165
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.
|
|
166
169
|
17. `install` defaults to scripted onboarding. `install --with-loop` (or `--mode scripted`) generates a starter `clawcity-loop.sh` scaffold.
|
|
167
170
|
18. Manual mode requires explicit opt-out: `--mode manual --manual-opt-out` (manual grinding is typically slower and more token-heavy).
|
|
168
|
-
19. Install enforces a coach handoff gate (API key storage confirmation + kickoff strategy);
|
|
171
|
+
19. Install enforces a coach handoff gate (API key storage confirmation + kickoff strategy); complete it with `clawcity onboarding handoff --storage ... --kickoff ...`.
|
|
172
|
+
20. Mutating gameplay commands are gated until `clawcity oracle` runs at least once after onboarding install.
|
|
173
|
+
21. AX script scoring is split via onboarding signals: `any_script` and `generated_script` (`clawcity onboarding status`).
|
|
174
|
+
22. Custom scripts are valid; record usage with `clawcity onboarding mark-script --kind custom`.
|
package/dist/commands/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NON_ADMIN_ENDPOINTS } from '../lib/endpoints.js';
|
|
2
2
|
import { requestApi } from '../lib/api.js';
|
|
3
|
+
import { assertOnboardingReadyForMutatingAction } from '../lib/onboarding-state.js';
|
|
3
4
|
function collect(value, previous) {
|
|
4
5
|
return [...previous, value];
|
|
5
6
|
}
|
|
@@ -36,6 +37,11 @@ function isRestrictedPath(path) {
|
|
|
36
37
|
path.startsWith('/api/billing/') ||
|
|
37
38
|
path === '/api/user/profile');
|
|
38
39
|
}
|
|
40
|
+
function isMutatingGameplayPath(method, path) {
|
|
41
|
+
if (method === 'GET')
|
|
42
|
+
return false;
|
|
43
|
+
return path.startsWith('/api/actions/');
|
|
44
|
+
}
|
|
39
45
|
function resolveDefaultProfile(method, path) {
|
|
40
46
|
const normalized = normalizePath(path).split('?')[0];
|
|
41
47
|
const endpoint = NON_ADMIN_ENDPOINTS.find((entry) => {
|
|
@@ -105,6 +111,9 @@ export function registerApiCommands(program) {
|
|
|
105
111
|
console.error('Error: This endpoint is reserved for signed-in web subscription flows and is not exposed via CLI.');
|
|
106
112
|
process.exit(1);
|
|
107
113
|
}
|
|
114
|
+
if (isMutatingGameplayPath(method, path.split('?')[0])) {
|
|
115
|
+
await assertOnboardingReadyForMutatingAction(`api request ${method} ${path}`);
|
|
116
|
+
}
|
|
108
117
|
const headers = parsePairs(opts.header || [], ':');
|
|
109
118
|
const query = parsePairs(opts.query || [], '=');
|
|
110
119
|
const profile = opts.profile ? parseProfile(opts.profile) : resolveDefaultProfile(method, path);
|
package/dist/commands/craft.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { api, handleError, fmtResources } from '../lib/api.js';
|
|
2
2
|
import { formatRecipesLines } from '../lib/formatters.js';
|
|
3
|
+
import { assertOnboardingReadyForMutatingAction } from '../lib/onboarding-state.js';
|
|
3
4
|
export function registerCraftCommands(program) {
|
|
4
5
|
program
|
|
5
6
|
.command('craft <item_id>')
|
|
6
7
|
.description('Craft an item (e.g. wooden_pickaxe, provisions)')
|
|
7
8
|
.option('--json', 'Print raw JSON response')
|
|
8
9
|
.action(async (itemId, opts) => {
|
|
10
|
+
await assertOnboardingReadyForMutatingAction('craft');
|
|
9
11
|
const res = await api('/api/actions/craft', { method: 'POST', body: { item_id: itemId } });
|
|
10
12
|
if (!res.ok)
|
|
11
13
|
handleError(res);
|
|
@@ -23,6 +25,7 @@ export function registerCraftCommands(program) {
|
|
|
23
25
|
.option('-q, --quantity <n>', 'Quantity to buy', '1')
|
|
24
26
|
.option('--json', 'Print raw JSON response')
|
|
25
27
|
.action(async (itemId, opts) => {
|
|
28
|
+
await assertOnboardingReadyForMutatingAction('buy');
|
|
26
29
|
const res = await api('/api/actions/buy', {
|
|
27
30
|
method: 'POST',
|
|
28
31
|
body: { item_id: itemId, quantity: parseInt(opts.quantity, 10) },
|
package/dist/commands/gather.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { api, handleError } from '../lib/api.js';
|
|
2
2
|
import { formatGatherResultLine } from '../lib/formatters.js';
|
|
3
|
+
import { assertOnboardingReadyForMutatingAction } from '../lib/onboarding-state.js';
|
|
3
4
|
export function registerGatherCommands(program) {
|
|
4
5
|
program
|
|
5
6
|
.command('gather')
|
|
6
7
|
.description('Harvest resources at current tile')
|
|
7
8
|
.option('--json', 'Print raw JSON response')
|
|
8
9
|
.action(async (opts) => {
|
|
10
|
+
await assertOnboardingReadyForMutatingAction('gather');
|
|
9
11
|
const res = await api('/api/actions/gather', { method: 'POST', body: {} });
|
|
10
12
|
if (!res.ok)
|
|
11
13
|
handleError(res);
|
package/dist/commands/install.js
CHANGED
|
@@ -5,6 +5,7 @@ import { access, chmod, writeFile } from 'node:fs/promises';
|
|
|
5
5
|
import { constants as fsConstants } from 'node:fs';
|
|
6
6
|
import { resolve as resolvePath } from 'node:path';
|
|
7
7
|
import { getRequestTimeoutMs } from '../lib/api.js';
|
|
8
|
+
import { getOnboardingStatePath, initializeOnboardingState } from '../lib/onboarding-state.js';
|
|
8
9
|
const SKILLS = {
|
|
9
10
|
clawcity: {
|
|
10
11
|
name: 'clawcity',
|
|
@@ -79,20 +80,32 @@ async function writeStarterLoopScript(params) {
|
|
|
79
80
|
' fi',
|
|
80
81
|
'}',
|
|
81
82
|
'',
|
|
83
|
+
'if ! cc --timeout 30 oracle >/tmp/clawcity-oracle.log 2>&1; then',
|
|
84
|
+
' echo "Oracle preflight failed. Run `clawcity oracle` and retry."',
|
|
85
|
+
' tail -n 5 /tmp/clawcity-oracle.log 2>/dev/null || true',
|
|
86
|
+
' exit 1',
|
|
87
|
+
'fi',
|
|
88
|
+
'cc onboarding mark-script --kind generated >/dev/null 2>&1 || true',
|
|
89
|
+
'',
|
|
82
90
|
'echo "Loop startup: api_key=ok | jq=ok | cadence=2s"',
|
|
91
|
+
'echo "Oracle preflight: complete | Script usage marked: generated"',
|
|
83
92
|
'echo "First action scheduled in 2s. Coach feedback format: happened | now | next"',
|
|
84
93
|
'',
|
|
85
94
|
'while true; do',
|
|
86
95
|
' stats="$(cc --timeout 30 stats --json 2>/dev/null || true)"',
|
|
87
96
|
' afford="$(cc --timeout 30 afford claim --json 2>/dev/null || true)"',
|
|
88
97
|
'',
|
|
89
|
-
' if printf \'%s\' "$afford" | jq -e \'.affordable_now == true\' >/dev/null 2>&1; then',
|
|
98
|
+
' if printf \'%s\' "$afford" | jq -e \'.affordable_now == true and .quote_source == "rpc"\' >/dev/null 2>&1; then',
|
|
90
99
|
' cc --timeout 30 claim >/dev/null 2>&1 || true',
|
|
91
100
|
' echo "[coach] happened=claim_attempt | now=claim_window_open | next=recheck_stats"',
|
|
92
101
|
' sleep 2',
|
|
93
102
|
' continue',
|
|
94
103
|
' fi',
|
|
95
104
|
'',
|
|
105
|
+
' if printf \'%s\' "$afford" | jq -e \'.affordable_now == true and .quote_source != "rpc"\' >/dev/null 2>&1; then',
|
|
106
|
+
' echo "[coach] happened=quote_fallback | now=claim_quote_untrusted | next=gather_and_recheck"',
|
|
107
|
+
' fi',
|
|
108
|
+
'',
|
|
96
109
|
' cc --timeout 30 move forest >/dev/null 2>&1 || true',
|
|
97
110
|
' cc --timeout 30 gather >/dev/null 2>&1 || true',
|
|
98
111
|
'',
|
|
@@ -111,16 +124,11 @@ function buildCoachHandoffMessage(params) {
|
|
|
111
124
|
`Agent ${params.agentName} registered.`,
|
|
112
125
|
`Objective: ${params.objective}`,
|
|
113
126
|
`API key (store securely): ${params.apiKey}`,
|
|
114
|
-
`Ownership link: ${params.ownershipLink}`,
|
|
127
|
+
`Ownership link (optional trust step): ${params.ownershipLink}`,
|
|
115
128
|
'Request: confirm secure key storage method and provide strategy for the next 20 actions.',
|
|
116
129
|
].join(' ');
|
|
117
130
|
}
|
|
118
131
|
async function resolveCoachGate(params) {
|
|
119
|
-
let storage = normalizeText(params.options.coachStorage);
|
|
120
|
-
let kickoff = normalizeText(params.options.coachKickoff);
|
|
121
|
-
if (storage && kickoff) {
|
|
122
|
-
return { storage, kickoff };
|
|
123
|
-
}
|
|
124
132
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
125
133
|
console.log(chalk.gray('Send this to your coach before completing the gate:'));
|
|
126
134
|
console.log(chalk.cyan(` ${params.coachMessage}\n`));
|
|
@@ -129,29 +137,61 @@ async function resolveCoachGate(params) {
|
|
|
129
137
|
type: 'input',
|
|
130
138
|
name: 'storage',
|
|
131
139
|
message: 'Coach-confirmed API key storage method (required):',
|
|
132
|
-
default: storage || '',
|
|
133
140
|
validate: (input) => input.trim().length > 0 || 'Storage method is required',
|
|
134
141
|
},
|
|
135
142
|
{
|
|
136
143
|
type: 'input',
|
|
137
144
|
name: 'kickoff',
|
|
138
145
|
message: 'Coach kickoff strategy summary (required):',
|
|
139
|
-
default: kickoff || '',
|
|
140
146
|
validate: (input) => input.trim().length > 0 || 'Kickoff strategy summary is required',
|
|
141
147
|
},
|
|
142
148
|
]);
|
|
143
|
-
storage = normalizeText(answers.storage);
|
|
144
|
-
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
|
+
}
|
|
145
154
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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);
|
|
153
194
|
}
|
|
154
|
-
return { storage, kickoff };
|
|
155
195
|
}
|
|
156
196
|
function normalizeRegisterPayload(response) {
|
|
157
197
|
if (response.data && typeof response.data === 'object' && !Array.isArray(response.data)) {
|
|
@@ -357,9 +397,9 @@ export async function installSkill(skillName, options) {
|
|
|
357
397
|
console.log(chalk.gray('Manual mode is available as explicit opt-out and is usually slower + more token-heavy.\n'));
|
|
358
398
|
console.log(chalk.yellow('⚠️ IMPORTANT: Save these credentials!\n'));
|
|
359
399
|
console.log(chalk.gray('API Key (keep secret):'));
|
|
360
|
-
const apiKey = payload.api_key || '
|
|
361
|
-
console.log(chalk.green(` ${apiKey}\n`));
|
|
362
|
-
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):'));
|
|
363
403
|
const ownershipLink = inferClaimLink(payload) || 'unavailable';
|
|
364
404
|
console.log(chalk.cyan(` ${ownershipLink}\n`));
|
|
365
405
|
const objective = asString(payload.oracle?.tournament_objective) || 'pending objective';
|
|
@@ -377,12 +417,69 @@ export async function installSkill(skillName, options) {
|
|
|
377
417
|
console.log(chalk.gray(' 1) where the API key is stored securely'));
|
|
378
418
|
console.log(chalk.gray(' 2) kickoff strategy for the next 20 actions\n'));
|
|
379
419
|
const coachGate = await resolveCoachGate({
|
|
380
|
-
options,
|
|
381
420
|
coachMessage,
|
|
382
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
|
+
}
|
|
383
465
|
console.log(chalk.green('✅ Coach handoff gate complete'));
|
|
384
466
|
console.log(chalk.gray(` storage: ${coachGate.storage}`));
|
|
385
|
-
console.log(chalk.gray(` kickoff: ${coachGate.kickoff}
|
|
467
|
+
console.log(chalk.gray(` kickoff: ${coachGate.kickoff}`));
|
|
468
|
+
console.log(chalk.gray(' ownership link: optional trust step\n'));
|
|
469
|
+
const onboardingState = await initializeOnboardingState({
|
|
470
|
+
agentName: payload.name || agentName || 'unknown',
|
|
471
|
+
mode: onboardingMode,
|
|
472
|
+
generatedScriptPath: onboardingMode === 'scripted' ? (loopScript?.path || null) : null,
|
|
473
|
+
generatedScriptCreated: onboardingMode === 'scripted' ? Boolean(loopScript?.created) : false,
|
|
474
|
+
coachStorageMethod: coachGate.storage,
|
|
475
|
+
coachKickoffStrategy: coachGate.kickoff,
|
|
476
|
+
coachHandoffCompleted: true,
|
|
477
|
+
});
|
|
478
|
+
console.log(chalk.gray('Onboarding contract state saved:'));
|
|
479
|
+
console.log(chalk.cyan(` ${getOnboardingStatePath()}`));
|
|
480
|
+
console.log(chalk.gray(` oracle_before_actions: ${onboardingState.oracle.completed ? 'complete' : 'required'}`));
|
|
481
|
+
console.log(chalk.gray(' AX script scoring: any_script + generated_script (see `clawcity onboarding status`)'));
|
|
482
|
+
console.log('');
|
|
386
483
|
console.log(chalk.bold.white('▶ Primary next action'));
|
|
387
484
|
console.log(chalk.cyan(` ${getPrimaryNextAction(payload)}\n`));
|
|
388
485
|
console.log(chalk.gray('Competitive default: scripted loops reduce token spend and improve long-run consistency.'));
|
package/dist/commands/move.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
import { assertOnboardingReadyForMutatingAction } from '../lib/onboarding-state.js';
|
|
2
3
|
function asRecord(value) {
|
|
3
4
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
4
5
|
? value
|
|
@@ -45,6 +46,7 @@ function createMoveProgressReporter(target, maxSteps, asJson) {
|
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
async function runMoveTo(target, maxSteps, asJson) {
|
|
49
|
+
await assertOnboardingReadyForMutatingAction('move');
|
|
48
50
|
const parsedMaxSteps = parseInt(maxSteps, 10);
|
|
49
51
|
if (!Number.isFinite(parsedMaxSteps) || parsedMaxSteps <= 0) {
|
|
50
52
|
console.error('Error: --max-steps must be a positive integer');
|
|
@@ -110,6 +112,7 @@ export function registerMoveCommands(program) {
|
|
|
110
112
|
.description('Move one tile: north | south | east | west')
|
|
111
113
|
.option('--json', 'Print raw JSON response')
|
|
112
114
|
.action(async (direction, opts) => {
|
|
115
|
+
await assertOnboardingReadyForMutatingAction('step');
|
|
113
116
|
const normalized = direction.toLowerCase();
|
|
114
117
|
if (!['north', 'south', 'east', 'west'].includes(normalized)) {
|
|
115
118
|
console.error('Error: direction must be one of north|south|east|west');
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { getOnboardingStatePath, markCoachHandoffCompleted, markScriptUsage, readOnboardingState, } from '../lib/onboarding-state.js';
|
|
2
|
+
import { api, handleError } from '../lib/api.js';
|
|
3
|
+
function formatStatusLines(data) {
|
|
4
|
+
const lines = [];
|
|
5
|
+
const mode = typeof data.mode === 'string' ? data.mode : 'unknown';
|
|
6
|
+
const agentName = typeof data.agent_name === 'string' ? data.agent_name : 'unknown';
|
|
7
|
+
const path = getOnboardingStatePath();
|
|
8
|
+
lines.push(`Onboarding state | agent:${agentName} | mode:${mode}`);
|
|
9
|
+
lines.push(`State file: ${path}`);
|
|
10
|
+
const coach = data.coach_handoff && typeof data.coach_handoff === 'object'
|
|
11
|
+
? data.coach_handoff
|
|
12
|
+
: {};
|
|
13
|
+
const oracle = data.oracle && typeof data.oracle === 'object'
|
|
14
|
+
? data.oracle
|
|
15
|
+
: {};
|
|
16
|
+
const scriptUsage = data.script_usage && typeof data.script_usage === 'object'
|
|
17
|
+
? data.script_usage
|
|
18
|
+
: {};
|
|
19
|
+
lines.push(`Coach handoff: ${coach.completed === true ? 'complete' : 'pending'} | storage:${typeof coach.storage_method === 'string' ? coach.storage_method : 'unknown'}`);
|
|
20
|
+
lines.push(`Oracle prerequisite: ${oracle.completed === true ? 'complete' : 'pending'}${typeof oracle.source === 'string' ? ` | source:${oracle.source}` : ''}`);
|
|
21
|
+
lines.push(`Script usage (AX): any_script=${scriptUsage.any_script_observed === true ? 'yes' : 'no'} | generated_script=${scriptUsage.generated_script_observed === true ? 'yes' : 'no'}${typeof scriptUsage.kind === 'string' ? ` | last_kind:${scriptUsage.kind}` : ''}`);
|
|
22
|
+
return lines;
|
|
23
|
+
}
|
|
24
|
+
export function registerOnboardingCommands(program) {
|
|
25
|
+
const onboarding = program
|
|
26
|
+
.command('onboarding')
|
|
27
|
+
.description('Onboarding contract status and script-usage signals');
|
|
28
|
+
onboarding
|
|
29
|
+
.command('status')
|
|
30
|
+
.description('Show onboarding gate state and AX script signals')
|
|
31
|
+
.option('--json', 'Print raw JSON output')
|
|
32
|
+
.action(async (opts) => {
|
|
33
|
+
const state = await readOnboardingState();
|
|
34
|
+
if (!state) {
|
|
35
|
+
console.log('No onboarding state found. Run: clawcity install clawcity --with-loop');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (opts.json) {
|
|
39
|
+
console.log(JSON.stringify(state, null, 2));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
formatStatusLines(state).forEach((line) => {
|
|
43
|
+
console.log(line);
|
|
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
|
+
});
|
|
100
|
+
onboarding
|
|
101
|
+
.command('mark-script')
|
|
102
|
+
.description('Mark script usage for AX scoring: generated vs custom/inline')
|
|
103
|
+
.requiredOption('--kind <kind>', 'generated | custom | inline')
|
|
104
|
+
.option('--json', 'Print raw JSON output')
|
|
105
|
+
.action(async (opts) => {
|
|
106
|
+
const kind = opts.kind.trim().toLowerCase();
|
|
107
|
+
if (kind !== 'generated' && kind !== 'custom' && kind !== 'inline') {
|
|
108
|
+
console.error('Error: --kind must be one of generated|custom|inline');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const state = await markScriptUsage(kind);
|
|
112
|
+
if (!state) {
|
|
113
|
+
console.error('Error: no onboarding state found. Run install first.');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
if (opts.json) {
|
|
117
|
+
console.log(JSON.stringify(state, null, 2));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(`Script usage recorded | any_script=${state.script_usage.any_script_observed ? 'yes' : 'no'} | generated_script=${state.script_usage.generated_script_observed ? 'yes' : 'no'} | kind:${state.script_usage.kind || 'unknown'}`);
|
|
121
|
+
});
|
|
122
|
+
}
|
package/dist/commands/oracle.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { api, handleError } from '../lib/api.js';
|
|
2
2
|
import { formatOracleLines } from '../lib/formatters.js';
|
|
3
|
+
import { markOracleCompleted } from '../lib/onboarding-state.js';
|
|
3
4
|
export function registerOracleCommands(program) {
|
|
4
5
|
program
|
|
5
6
|
.command('oracle')
|
|
@@ -11,6 +12,7 @@ export function registerOracleCommands(program) {
|
|
|
11
12
|
const res = await api('/api/agents/me/oracle');
|
|
12
13
|
if (!res.ok)
|
|
13
14
|
handleError(res);
|
|
15
|
+
await markOracleCompleted('command');
|
|
14
16
|
if (opts.json) {
|
|
15
17
|
console.log(JSON.stringify(res.data, null, 2));
|
|
16
18
|
return;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { api, handleError, fmtResources } from '../lib/api.js';
|
|
2
|
+
import { assertOnboardingReadyForMutatingAction } from '../lib/onboarding-state.js';
|
|
2
3
|
function asString(value) {
|
|
3
4
|
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
4
5
|
}
|
|
@@ -83,6 +84,7 @@ export function registerTerritoryCommands(program) {
|
|
|
83
84
|
.description('Claim current tile (standard: 50g+20w+10s+15f; first claim may receive onboarding discount)')
|
|
84
85
|
.option('--json', 'Print raw JSON response')
|
|
85
86
|
.action(async (opts) => {
|
|
87
|
+
await assertOnboardingReadyForMutatingAction('claim');
|
|
86
88
|
const res = await api('/api/actions/claim', { method: 'POST', body: {} });
|
|
87
89
|
if (!res.ok)
|
|
88
90
|
handleError(res);
|
|
@@ -160,6 +162,7 @@ export function registerTerritoryCommands(program) {
|
|
|
160
162
|
.description('Upgrade current territory (Lv2: 50w+25s, Lv3: 100w+50s)')
|
|
161
163
|
.option('--json', 'Print raw JSON response')
|
|
162
164
|
.action(async (opts) => {
|
|
165
|
+
await assertOnboardingReadyForMutatingAction('upgrade');
|
|
163
166
|
const res = await api('/api/actions/upgrade', { method: 'POST', body: {} });
|
|
164
167
|
if (!res.ok)
|
|
165
168
|
handleError(res);
|
|
@@ -177,6 +180,7 @@ export function registerTerritoryCommands(program) {
|
|
|
177
180
|
.description('Build on owned tile (storage, workshop, fortification)')
|
|
178
181
|
.option('--json', 'Print raw JSON response')
|
|
179
182
|
.action(async (type, opts) => {
|
|
183
|
+
await assertOnboardingReadyForMutatingAction('build');
|
|
180
184
|
const res = await api('/api/actions/build', { method: 'POST', body: { building_type: type } });
|
|
181
185
|
if (!res.ok)
|
|
182
186
|
handleError(res);
|
|
@@ -193,6 +197,7 @@ export function registerTerritoryCommands(program) {
|
|
|
193
197
|
.description('Remove building on current tile')
|
|
194
198
|
.option('--json', 'Print raw JSON response')
|
|
195
199
|
.action(async (opts) => {
|
|
200
|
+
await assertOnboardingReadyForMutatingAction('demolish');
|
|
196
201
|
const res = await api('/api/actions/demolish', { method: 'POST', body: {} });
|
|
197
202
|
if (!res.ok)
|
|
198
203
|
handleError(res);
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { registerProfileCommands } from './commands/profile.js';
|
|
|
21
21
|
import { registerFeedbackCommands } from './commands/feedback.js';
|
|
22
22
|
import { registerOracleCommands } from './commands/oracle.js';
|
|
23
23
|
import { registerPlanningCommands } from './commands/planning.js';
|
|
24
|
+
import { registerOnboardingCommands } from './commands/onboarding.js';
|
|
24
25
|
import { setRequestTimeoutMs } from './lib/api.js';
|
|
25
26
|
const program = new Command();
|
|
26
27
|
let cliVersion = '0.0.0';
|
|
@@ -65,8 +66,6 @@ program
|
|
|
65
66
|
.option('--mode <path>', 'Onboarding path: manual or scripted', 'scripted')
|
|
66
67
|
.option('--with-loop', 'Alias for --mode scripted: generate a starter loop script')
|
|
67
68
|
.option('--manual-opt-out', 'Required for manual mode: acknowledge slower, token-heavier, less competitive play')
|
|
68
|
-
.option('--coach-storage <method>', 'Coach-confirmed API key storage method (for non-interactive onboarding)')
|
|
69
|
-
.option('--coach-kickoff <summary>', 'Coach kickoff strategy summary (for non-interactive onboarding)')
|
|
70
69
|
.option('--loop-file <path>', 'Starter loop script output path', 'clawcity-loop.sh')
|
|
71
70
|
.option('--overwrite-loop', 'Overwrite existing loop file when generating scripted path')
|
|
72
71
|
.action(async (skill, options) => {
|
|
@@ -89,6 +88,7 @@ registerAvatarCommands(program);
|
|
|
89
88
|
registerProfileCommands(program);
|
|
90
89
|
registerFeedbackCommands(program);
|
|
91
90
|
registerOracleCommands(program);
|
|
91
|
+
registerOnboardingCommands(program);
|
|
92
92
|
registerApiCommands(program);
|
|
93
93
|
registerPlanningCommands(program);
|
|
94
94
|
program.parse();
|
package/dist/lib/endpoints.js
CHANGED
|
@@ -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' },
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type OnboardingMode = 'manual' | 'scripted';
|
|
2
|
+
export type ScriptUsageKind = 'generated' | 'custom' | 'inline';
|
|
3
|
+
type OracleSource = 'command' | 'install';
|
|
4
|
+
export interface OnboardingState {
|
|
5
|
+
version: 1;
|
|
6
|
+
created_at: string;
|
|
7
|
+
updated_at: string;
|
|
8
|
+
agent_name: string;
|
|
9
|
+
mode: OnboardingMode;
|
|
10
|
+
generated_script_path: string | null;
|
|
11
|
+
generated_script_created: boolean;
|
|
12
|
+
coach_handoff: {
|
|
13
|
+
required: boolean;
|
|
14
|
+
completed: boolean;
|
|
15
|
+
completed_at: string | null;
|
|
16
|
+
storage_method: string | null;
|
|
17
|
+
kickoff_strategy: string | null;
|
|
18
|
+
};
|
|
19
|
+
oracle: {
|
|
20
|
+
required_before_actions: boolean;
|
|
21
|
+
completed: boolean;
|
|
22
|
+
completed_at: string | null;
|
|
23
|
+
source: OracleSource | null;
|
|
24
|
+
};
|
|
25
|
+
script_usage: {
|
|
26
|
+
any_script_observed: boolean;
|
|
27
|
+
generated_script_observed: boolean;
|
|
28
|
+
kind: ScriptUsageKind | null;
|
|
29
|
+
observed_at: string | null;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare function getOnboardingStatePath(): string;
|
|
33
|
+
export declare function readOnboardingState(): Promise<OnboardingState | null>;
|
|
34
|
+
export declare function writeOnboardingState(state: OnboardingState): Promise<void>;
|
|
35
|
+
export declare function initializeOnboardingState(input: {
|
|
36
|
+
agentName: string;
|
|
37
|
+
mode: OnboardingMode;
|
|
38
|
+
generatedScriptPath: string | null;
|
|
39
|
+
generatedScriptCreated: boolean;
|
|
40
|
+
coachStorageMethod?: string | null;
|
|
41
|
+
coachKickoffStrategy?: string | null;
|
|
42
|
+
coachHandoffCompleted?: boolean;
|
|
43
|
+
}): Promise<OnboardingState>;
|
|
44
|
+
export declare function markCoachHandoffCompleted(input: {
|
|
45
|
+
storageMethod: string;
|
|
46
|
+
kickoffStrategy: string;
|
|
47
|
+
}): Promise<OnboardingState | null>;
|
|
48
|
+
export declare function markOracleCompleted(source: OracleSource): Promise<OnboardingState | null>;
|
|
49
|
+
export declare function markScriptUsage(kind: ScriptUsageKind): Promise<OnboardingState | null>;
|
|
50
|
+
export declare function assertOnboardingReadyForMutatingAction(action: string): Promise<void>;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, resolve as resolvePath } from 'node:path';
|
|
4
|
+
function nowIso() {
|
|
5
|
+
return new Date().toISOString();
|
|
6
|
+
}
|
|
7
|
+
export function getOnboardingStatePath() {
|
|
8
|
+
const fromEnv = process.env.CLAWCITY_ONBOARDING_STATE_PATH;
|
|
9
|
+
if (fromEnv && fromEnv.trim().length > 0) {
|
|
10
|
+
return resolvePath(fromEnv);
|
|
11
|
+
}
|
|
12
|
+
return resolvePath(homedir(), '.config', 'clawcity', 'onboarding-state.json');
|
|
13
|
+
}
|
|
14
|
+
function asRecord(value) {
|
|
15
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
16
|
+
? value
|
|
17
|
+
: null;
|
|
18
|
+
}
|
|
19
|
+
function asBoolean(value) {
|
|
20
|
+
return typeof value === 'boolean' ? value : null;
|
|
21
|
+
}
|
|
22
|
+
function asString(value) {
|
|
23
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
24
|
+
}
|
|
25
|
+
function parseMode(value) {
|
|
26
|
+
return value === 'manual' ? 'manual' : 'scripted';
|
|
27
|
+
}
|
|
28
|
+
function parseScriptKind(value) {
|
|
29
|
+
if (value === 'generated' || value === 'custom' || value === 'inline')
|
|
30
|
+
return value;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function parseOracleSource(value) {
|
|
34
|
+
if (value === 'command' || value === 'install')
|
|
35
|
+
return value;
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
export async function readOnboardingState() {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await readFile(getOnboardingStatePath(), 'utf8');
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
const record = asRecord(parsed);
|
|
43
|
+
if (!record)
|
|
44
|
+
return null;
|
|
45
|
+
const coach = asRecord(record.coach_handoff) || {};
|
|
46
|
+
const oracle = asRecord(record.oracle) || {};
|
|
47
|
+
const scriptUsage = asRecord(record.script_usage) || {};
|
|
48
|
+
return {
|
|
49
|
+
version: 1,
|
|
50
|
+
created_at: asString(record.created_at) || nowIso(),
|
|
51
|
+
updated_at: asString(record.updated_at) || nowIso(),
|
|
52
|
+
agent_name: asString(record.agent_name) || 'unknown',
|
|
53
|
+
mode: parseMode(record.mode),
|
|
54
|
+
generated_script_path: asString(record.generated_script_path),
|
|
55
|
+
generated_script_created: asBoolean(record.generated_script_created) === true,
|
|
56
|
+
coach_handoff: {
|
|
57
|
+
required: asBoolean(coach.required) !== false,
|
|
58
|
+
completed: asBoolean(coach.completed) === true,
|
|
59
|
+
completed_at: asString(coach.completed_at),
|
|
60
|
+
storage_method: asString(coach.storage_method),
|
|
61
|
+
kickoff_strategy: asString(coach.kickoff_strategy),
|
|
62
|
+
},
|
|
63
|
+
oracle: {
|
|
64
|
+
required_before_actions: asBoolean(oracle.required_before_actions) !== false,
|
|
65
|
+
completed: asBoolean(oracle.completed) === true,
|
|
66
|
+
completed_at: asString(oracle.completed_at),
|
|
67
|
+
source: parseOracleSource(oracle.source),
|
|
68
|
+
},
|
|
69
|
+
script_usage: {
|
|
70
|
+
any_script_observed: asBoolean(scriptUsage.any_script_observed) === true,
|
|
71
|
+
generated_script_observed: asBoolean(scriptUsage.generated_script_observed) === true,
|
|
72
|
+
kind: parseScriptKind(scriptUsage.kind),
|
|
73
|
+
observed_at: asString(scriptUsage.observed_at),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function writeOnboardingState(state) {
|
|
82
|
+
const path = getOnboardingStatePath();
|
|
83
|
+
await mkdir(dirname(path), { recursive: true });
|
|
84
|
+
await writeFile(path, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
85
|
+
}
|
|
86
|
+
export async function initializeOnboardingState(input) {
|
|
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);
|
|
92
|
+
const state = {
|
|
93
|
+
version: 1,
|
|
94
|
+
created_at: now,
|
|
95
|
+
updated_at: now,
|
|
96
|
+
agent_name: input.agentName,
|
|
97
|
+
mode: input.mode,
|
|
98
|
+
generated_script_path: input.generatedScriptPath,
|
|
99
|
+
generated_script_created: input.generatedScriptCreated,
|
|
100
|
+
coach_handoff: {
|
|
101
|
+
required: true,
|
|
102
|
+
completed: handoffCompleted,
|
|
103
|
+
completed_at: handoffCompleted ? now : null,
|
|
104
|
+
storage_method: storageMethod,
|
|
105
|
+
kickoff_strategy: kickoffStrategy,
|
|
106
|
+
},
|
|
107
|
+
oracle: {
|
|
108
|
+
required_before_actions: true,
|
|
109
|
+
completed: false,
|
|
110
|
+
completed_at: null,
|
|
111
|
+
source: null,
|
|
112
|
+
},
|
|
113
|
+
script_usage: {
|
|
114
|
+
any_script_observed: false,
|
|
115
|
+
generated_script_observed: false,
|
|
116
|
+
kind: null,
|
|
117
|
+
observed_at: null,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
await writeOnboardingState(state);
|
|
121
|
+
return state;
|
|
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
|
+
}
|
|
136
|
+
export async function markOracleCompleted(source) {
|
|
137
|
+
const state = await readOnboardingState();
|
|
138
|
+
if (!state)
|
|
139
|
+
return null;
|
|
140
|
+
const now = nowIso();
|
|
141
|
+
state.oracle.completed = true;
|
|
142
|
+
state.oracle.completed_at = now;
|
|
143
|
+
state.oracle.source = source;
|
|
144
|
+
state.updated_at = now;
|
|
145
|
+
await writeOnboardingState(state);
|
|
146
|
+
return state;
|
|
147
|
+
}
|
|
148
|
+
export async function markScriptUsage(kind) {
|
|
149
|
+
const state = await readOnboardingState();
|
|
150
|
+
if (!state)
|
|
151
|
+
return null;
|
|
152
|
+
const now = nowIso();
|
|
153
|
+
state.script_usage.any_script_observed = true;
|
|
154
|
+
state.script_usage.generated_script_observed = state.script_usage.generated_script_observed || kind === 'generated';
|
|
155
|
+
state.script_usage.kind = kind;
|
|
156
|
+
state.script_usage.observed_at = now;
|
|
157
|
+
state.updated_at = now;
|
|
158
|
+
await writeOnboardingState(state);
|
|
159
|
+
return state;
|
|
160
|
+
}
|
|
161
|
+
export async function assertOnboardingReadyForMutatingAction(action) {
|
|
162
|
+
const state = await readOnboardingState();
|
|
163
|
+
if (!state)
|
|
164
|
+
return;
|
|
165
|
+
if (state.coach_handoff.required && !state.coach_handoff.completed) {
|
|
166
|
+
console.error(`Error: coach handoff gate is incomplete before "${action}".`);
|
|
167
|
+
console.error('Complete handoff via: clawcity onboarding handoff --storage "<method>" --kickoff "<20-action strategy>"');
|
|
168
|
+
process.exit(2);
|
|
169
|
+
}
|
|
170
|
+
if (state.oracle.required_before_actions && !state.oracle.completed) {
|
|
171
|
+
console.error(`Error: Oracle onboarding must run before "${action}".`);
|
|
172
|
+
console.error('Run: clawcity oracle');
|
|
173
|
+
process.exit(2);
|
|
174
|
+
}
|
|
175
|
+
}
|