oneclaw 0.1.0 → 0.1.2

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
@@ -1,13 +1,13 @@
1
1
  # oneclaw
2
2
 
3
- `oneclaw` provisions and exports AI identity packs.
3
+ `oneclaw` creates and applies identity packs for AI systems.
4
4
 
5
- Current first-class provisioning providers:
6
- - AgentMail (API-based inbox creation)
7
- - Telegram Bot (BotFather token + API verification)
8
- - Bitwarden (browser/account checkpoint + CLI verification + vault seeding)
5
+ Current provider workflows:
6
+ - AgentMail (API-first inbox creation)
7
+ - Telegram (BotFather token + `getMe` verification)
8
+ - Bitwarden (signup checkpoint + CLI login verification + vault item seeding)
9
9
 
10
- Current export/apply targets:
10
+ Current targets:
11
11
  - `owpenbot`
12
12
  - `openclaw`
13
13
  - `nanoclaw`
@@ -19,36 +19,82 @@ npm install
19
19
  npm run build
20
20
  ```
21
21
 
22
- ## Provision
22
+ ## Default mode: bootstrap TUI
23
+
24
+ Running `oneclaw` with no command opens the OpenTUI bootstrap screen by default.
25
+
26
+ You can also invoke it explicitly:
27
+
28
+ ```bash
29
+ oneclaw bootstrap --profile default --pack founder
30
+ ```
31
+
32
+ Use `--no-tui` for plain prompt mode.
33
+ If OpenTUI runtime bindings are unavailable, oneclaw falls back to plain prompt mode automatically.
34
+ If you see `.scm` runtime extension errors, run oneclaw with Bun (`bunx oneclaw`) or use `--no-tui`.
35
+
36
+ ### Demo autopilot mode
37
+
38
+ Set `ONECLAW_DEMO=1` to run the full bootstrap flow in auto-fill mode.
39
+
40
+ ```bash
41
+ ONECLAW_DEMO=1 oneclaw
42
+ ```
43
+
44
+ In demo mode, oneclaw animates all fields as if typed, toggles bootstrap flags, and auto-saves into profile `demo` by default.
45
+
46
+ ## First-class config API
47
+
48
+ ```bash
49
+ printf '%s' "$AGENTMAIL_API_KEY" | oneclaw config set agentmail.api_key --profile default --secret --stdin
50
+ printf '%s' "$TELEGRAM_BOT_TOKEN" | oneclaw config set telegram.bot_token --profile default --secret --stdin
51
+ oneclaw config set bitwarden.email "founder@example.com" --profile default
52
+ printf '%s' "$BITWARDEN_PASSWORD" | oneclaw config set bitwarden.password --profile default --secret --stdin
53
+ oneclaw config set bitwarden.signup_done true --profile default
54
+
55
+ oneclaw config check --providers agentmail,telegram,bitwarden --profile default --verify --json
56
+ ```
57
+
58
+ Other config commands:
59
+
60
+ ```bash
61
+ oneclaw config get agentmail.api_key --profile default
62
+ oneclaw config list --profile default
63
+ oneclaw config unset telegram.bot_token --profile default
64
+ ```
65
+
66
+ ## Provision from config state
23
67
 
24
68
  ```bash
25
69
  oneclaw provision \
26
70
  --pack founder \
27
71
  --providers agentmail,telegram,bitwarden \
28
72
  --targets owpenbot,openclaw,nanoclaw \
29
- --agentmail-api-key "$AGENTMAIL_API_KEY" \
30
- --telegram-bot-token "$TELEGRAM_BOT_TOKEN" \
31
- --bitwarden-email "founder@example.com" \
32
- --bitwarden-password "$BITWARDEN_MASTER_PASSWORD" \
33
- --bitwarden-skip-signup
73
+ --profile default \
74
+ --non-interactive \
75
+ --json
34
76
  ```
35
77
 
36
- If a provider needs manual action, provisioning pauses with a clear checkpoint and resume command.
78
+ Flags override stored config values. If a provider still needs human action, oneclaw returns a blocked step with a resume command.
37
79
 
38
- ## Export
80
+ ## Export / apply
39
81
 
40
82
  ```bash
41
83
  oneclaw export --pack founder --target owpenbot --out ./owpenbot.identity.json
42
84
  oneclaw export --pack founder --target openclaw --out ./openclaw.identity.json
43
85
  oneclaw export --pack founder --target nanoclaw --out ./nanoclaw.identity.env
86
+
87
+ oneclaw apply --pack founder --target owpenbot --path ~/.openwork/owpenbot/owpenbot.json
44
88
  ```
45
89
 
46
- ## Apply
90
+ ## Helper prompt for another setup AI
47
91
 
48
92
  ```bash
49
- oneclaw apply --pack founder --target owpenbot --path ~/.openwork/owpenbot/owpenbot.json
93
+ oneclaw bootstrap-prompt
50
94
  ```
51
95
 
96
+ This prints the setup-helper prompt you can hand to another AI to collect credentials and persist config state correctly.
97
+
52
98
  ## Validate and doctor
53
99
 
54
100
  ```bash
@@ -0,0 +1 @@
1
+ export declare function helperPrompt(): string;
@@ -0,0 +1,88 @@
1
+ export function helperPrompt() {
2
+ return `You are a Setup Helper AI. You are NOT oneclaw. You guide a human through credential bootstrap for oneclaw.
3
+
4
+ Goal:
5
+ Populate oneclaw config so this command succeeds in non-interactive mode:
6
+ oneclaw provision --pack <PACK_ID> --providers agentmail,telegram,bitwarden --targets owpenbot,openclaw,nanoclaw --profile <PROFILE> --non-interactive --json
7
+
8
+ Rules:
9
+ 1) Persist credentials immediately with oneclaw config commands.
10
+ 2) Secrets must be written with --stdin --secret.
11
+ 3) Verify each provider before moving on.
12
+ 4) If blocked by CAPTCHA/login/manual gate, pause and provide one explicit human action + resume command.
13
+ 5) Never print full secrets in logs; use masked previews.
14
+
15
+ Default values:
16
+ - PACK_ID=founder
17
+ - PROFILE=default
18
+
19
+ Required keys:
20
+ - agentmail.api_key (secret)
21
+ - telegram.bot_token (secret)
22
+ - bitwarden.email (plain)
23
+ - bitwarden.password (secret)
24
+ - bitwarden.signup_done (plain bool)
25
+
26
+ Execution steps:
27
+
28
+ A) Preflight
29
+ - Run: oneclaw config check --providers agentmail,telegram,bitwarden --profile "$PROFILE" --json
30
+
31
+ B) AgentMail
32
+ - Collect AGENTMAIL_API_KEY.
33
+ - Persist:
34
+ printf '%s' "$AGENTMAIL_API_KEY" | oneclaw config set agentmail.api_key --profile "$PROFILE" --secret --stdin
35
+ - Verify:
36
+ curl -fsS https://api.agentmail.to/v0/inboxes -H "Authorization: Bearer $AGENTMAIL_API_KEY" >/dev/null
37
+
38
+ C) Telegram
39
+ - Collect TELEGRAM_BOT_TOKEN from @BotFather.
40
+ - Persist:
41
+ printf '%s' "$TELEGRAM_BOT_TOKEN" | oneclaw config set telegram.bot_token --profile "$PROFILE" --secret --stdin
42
+ - Verify:
43
+ curl -fsS "https://api.telegram.org/bot\${TELEGRAM_BOT_TOKEN}/getMe" | jq -e '.ok == true and .result.username != null' >/dev/null
44
+
45
+ D) Bitwarden
46
+ - Collect BITWARDEN_EMAIL and BITWARDEN_PASSWORD.
47
+ - Persist:
48
+ oneclaw config set bitwarden.email "$BITWARDEN_EMAIL" --profile "$PROFILE"
49
+ printf '%s' "$BITWARDEN_PASSWORD" | oneclaw config set bitwarden.password --profile "$PROFILE" --secret --stdin
50
+ oneclaw config set bitwarden.signup_done true --profile "$PROFILE"
51
+ - Verify with bw CLI when available:
52
+ bw login "$BITWARDEN_EMAIL" "$BITWARDEN_PASSWORD" --raw >/dev/null
53
+
54
+ E) Final checks
55
+ - Run: oneclaw config check --providers agentmail,telegram,bitwarden --profile "$PROFILE" --json
56
+ - Run:
57
+ oneclaw provision --pack "$PACK_ID" --providers agentmail,telegram,bitwarden --targets owpenbot,openclaw,nanoclaw --profile "$PROFILE" --non-interactive --json
58
+
59
+ Expected final JSON contract:
60
+ {
61
+ "status": "ready" | "blocked",
62
+ "pack_id": "<PACK_ID>",
63
+ "profile": "<PROFILE>",
64
+ "verified": {
65
+ "agentmail": true|false,
66
+ "telegram": true|false,
67
+ "bitwarden": true|false
68
+ },
69
+ "next_command": "<exact provision command>",
70
+ "masked": {
71
+ "agentmail.api_key": "sk_****",
72
+ "telegram.bot_token": "****",
73
+ "bitwarden.email": "u***@d***.com"
74
+ },
75
+ "blocker": {
76
+ "step": "<step id>",
77
+ "reason": "<why blocked>",
78
+ "human_action": "<single action>",
79
+ "resume_command": "<exact resume command>"
80
+ }
81
+ }
82
+
83
+ Notes:
84
+ - Telegram bot creation has no official programmatic API; BotFather token retrieval is a manual checkpoint.
85
+ - AgentMail account bootstrap may require UI once; inbox provisioning is API-first after key setup.
86
+ `;
87
+ }
88
+ //# sourceMappingURL=helper-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper-prompt.js","sourceRoot":"","sources":["../../src/bootstrap/helper-prompt.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY;IAC1B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFR,CAAC;AACF,CAAC"}
package/dist/cli.js CHANGED
@@ -4,11 +4,15 @@ import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { Command } from "commander";
6
6
  import { applyPack, exportPackToTarget, writeExport } from "./adapters/index.js";
7
+ import { helperPrompt } from "./bootstrap/helper-prompt.js";
7
8
  import { runDoctor } from "./core/doctor.js";
9
+ import { checkConfig, getConfigValue, getRequiredConfigKeys, listConfig, setConfigValue, unsetConfigValue, } from "./core/config.js";
8
10
  import { appendProvisioningRun, ensurePack, loadPack, savePack } from "./core/packs.js";
11
+ import { resolveProvisionOptions } from "./core/provision-options.js";
9
12
  import { clearRunState, createRunState, loadRunState, saveRunState } from "./core/run-state.js";
10
13
  import { isTargetId, validatePack } from "./core/schema.js";
11
14
  import { TARGETS } from "./core/types.js";
15
+ import { verifyAgentmailApiKey, verifyBitwardenCredentials, verifyTelegramBotToken, } from "./core/verifiers.js";
12
16
  import { getWorkflow, isProviderId, PROVIDERS } from "./providers/index.js";
13
17
  import { runProviderWorkflow } from "./core/workflow.js";
14
18
  function parseCsv(input) {
@@ -38,6 +42,16 @@ function parseProviders(raw) {
38
42
  }
39
43
  return list;
40
44
  }
45
+ function isDemoModeEnabled() {
46
+ const raw = process.env.ONECLAW_DEMO?.trim().toLowerCase();
47
+ if (!raw)
48
+ return false;
49
+ return ["1", "true", "yes", "on"].includes(raw);
50
+ }
51
+ function profileFrom(opts, fallback = "default") {
52
+ const raw = typeof opts.profile === "string" ? opts.profile.trim() : "";
53
+ return raw || fallback;
54
+ }
41
55
  async function askInteractive(prompt) {
42
56
  if (!process.stdin.isTTY || !process.stdout.isTTY)
43
57
  return undefined;
@@ -51,6 +65,14 @@ async function askInteractive(prompt) {
51
65
  rl.close();
52
66
  }
53
67
  }
68
+ async function readStdinTrimmed() {
69
+ const chunks = [];
70
+ for await (const chunk of process.stdin) {
71
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
72
+ }
73
+ const text = Buffer.concat(chunks).toString("utf8").trim();
74
+ return text || undefined;
75
+ }
54
76
  function asLog(run) {
55
77
  const steps = Object.entries(run.providers)
56
78
  .flatMap(([provider, state]) => state.steps.map((step) => ({
@@ -86,17 +108,319 @@ function printValue(value, asJson) {
86
108
  }
87
109
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
88
110
  }
111
+ async function runProvision(input) {
112
+ const resolved = resolveProvisionOptions({
113
+ profile: input.profile,
114
+ options: input.options,
115
+ useConfig: input.useConfig,
116
+ });
117
+ let pack = ensurePack(input.packId, input.targets);
118
+ let run = loadRunState(input.packId) || createRunState(input.packId);
119
+ for (const provider of input.providers) {
120
+ const workflow = getWorkflow(provider);
121
+ const result = await runProviderWorkflow({
122
+ workflow,
123
+ run,
124
+ pack,
125
+ options: {
126
+ ...resolved,
127
+ },
128
+ nonInteractive: input.nonInteractive,
129
+ ask: askInteractive,
130
+ log: (message) => {
131
+ if (!input.asJson)
132
+ process.stderr.write(`${message}\n`);
133
+ },
134
+ });
135
+ saveRunState(run);
136
+ pack = savePack(pack);
137
+ if (result.status === "blocked") {
138
+ return {
139
+ status: "blocked",
140
+ provider,
141
+ step: result.blockedStepId,
142
+ reason: result.blockedReason,
143
+ resumeCommand: `oneclaw provision --pack ${input.packId} --providers ${input.providers.join(",")}`,
144
+ };
145
+ }
146
+ }
147
+ const runLog = asLog(run);
148
+ pack = appendProvisioningRun(pack, runLog);
149
+ clearRunState(input.packId);
150
+ return {
151
+ status: "ok",
152
+ packId: input.packId,
153
+ providers: input.providers,
154
+ targets: pack.targets,
155
+ accounts: Object.keys(pack.accounts),
156
+ };
157
+ }
158
+ function initialDraft(profile, runProvisionByDefault) {
159
+ const agentmail = getConfigValue({ profile, key: "agentmail.api_key", reveal: true })?.value || "";
160
+ const telegram = getConfigValue({ profile, key: "telegram.bot_token", reveal: true })?.value || "";
161
+ const bwEmail = getConfigValue({ profile, key: "bitwarden.email", reveal: true })?.value || "";
162
+ const bwPassword = getConfigValue({ profile, key: "bitwarden.password", reveal: true })?.value || "";
163
+ const signup = (getConfigValue({ profile, key: "bitwarden.signup_done", reveal: true })?.value || "").toLowerCase();
164
+ return {
165
+ agentmailApiKey: agentmail,
166
+ telegramBotToken: telegram,
167
+ bitwardenEmail: bwEmail,
168
+ bitwardenPassword: bwPassword,
169
+ bitwardenSignupDone: ["1", "true", "yes", "on"].includes(signup),
170
+ runProvision: runProvisionByDefault,
171
+ };
172
+ }
173
+ async function collectBootstrapDraft(input) {
174
+ const initial = initialDraft(input.profile, input.runProvisionByDefault);
175
+ if (input.demoMode && (!input.useTui || !process.stdin.isTTY || !process.stdout.isTTY)) {
176
+ return {
177
+ cancelled: false,
178
+ draft: {
179
+ agentmailApiKey: "am_demo_sk_live_8f3m2q1z4",
180
+ telegramBotToken: "7312459901:AAH_demo_token_x8c4",
181
+ bitwardenEmail: "founder+demo@oneclaw.dev",
182
+ bitwardenPassword: "Oneclaw!Demo!2026",
183
+ bitwardenSignupDone: true,
184
+ runProvision: true,
185
+ },
186
+ };
187
+ }
188
+ if (input.useTui && process.stdin.isTTY && process.stdout.isTTY) {
189
+ try {
190
+ const tui = await import("./tui/bootstrap.js");
191
+ return tui.startBootstrapTui({
192
+ profile: input.profile,
193
+ packId: input.packId,
194
+ initial,
195
+ demo: input.demoMode,
196
+ });
197
+ }
198
+ catch (error) {
199
+ const detail = error instanceof Error ? error.message : String(error);
200
+ const hint = detail.includes("ERR_UNKNOWN_FILE_EXTENSION") || detail.includes(".scm")
201
+ ? " (OpenTUI runtime assets require Bun in this environment)"
202
+ : "";
203
+ process.stderr.write(`OpenTUI unavailable, falling back to prompt mode: ${detail}${hint}\n`);
204
+ }
205
+ }
206
+ const ask = async (label, fallback) => {
207
+ const answer = await askInteractive(`${label}${fallback ? ` [${fallback}]` : ""}: `);
208
+ return answer || fallback;
209
+ };
210
+ const signupDefault = initial.bitwardenSignupDone ? "true" : "false";
211
+ const runDefault = initial.runProvision ? "true" : "false";
212
+ return {
213
+ cancelled: false,
214
+ draft: {
215
+ agentmailApiKey: await ask("AgentMail API key", initial.agentmailApiKey),
216
+ telegramBotToken: await ask("Telegram bot token", initial.telegramBotToken),
217
+ bitwardenEmail: await ask("Bitwarden email", initial.bitwardenEmail),
218
+ bitwardenPassword: await ask("Bitwarden password", initial.bitwardenPassword),
219
+ bitwardenSignupDone: ["1", "true", "yes", "on"].includes((await ask("Bitwarden signup done (true/false)", signupDefault)).toLowerCase()),
220
+ runProvision: ["1", "true", "yes", "on"].includes((await ask("Run provision after save (true/false)", runDefault)).toLowerCase()),
221
+ },
222
+ };
223
+ }
224
+ function persistBootstrapDraft(profile, draft) {
225
+ setConfigValue({ profile, key: "agentmail.api_key", value: draft.agentmailApiKey, secret: true });
226
+ setConfigValue({ profile, key: "telegram.bot_token", value: draft.telegramBotToken, secret: true });
227
+ setConfigValue({ profile, key: "bitwarden.email", value: draft.bitwardenEmail });
228
+ setConfigValue({ profile, key: "bitwarden.password", value: draft.bitwardenPassword, secret: true });
229
+ setConfigValue({ profile, key: "bitwarden.signup_done", value: draft.bitwardenSignupDone ? "true" : "false" });
230
+ }
231
+ async function verifyFromConfig(profile) {
232
+ const agentmailKey = getConfigValue({ profile, key: "agentmail.api_key", reveal: true })?.value || "";
233
+ const telegramToken = getConfigValue({ profile, key: "telegram.bot_token", reveal: true })?.value || "";
234
+ const bitwardenEmail = getConfigValue({ profile, key: "bitwarden.email", reveal: true })?.value || "";
235
+ const bitwardenPassword = getConfigValue({ profile, key: "bitwarden.password", reveal: true })?.value || "";
236
+ const checks = {
237
+ agentmail: agentmailKey
238
+ ? await verifyAgentmailApiKey(agentmailKey)
239
+ : { ok: false, detail: "Missing agentmail.api_key" },
240
+ telegram: telegramToken
241
+ ? await verifyTelegramBotToken(telegramToken)
242
+ : { ok: false, detail: "Missing telegram.bot_token" },
243
+ bitwarden: bitwardenEmail && bitwardenPassword
244
+ ? verifyBitwardenCredentials(bitwardenEmail, bitwardenPassword)
245
+ : { ok: false, detail: "Missing bitwarden.email or bitwarden.password" },
246
+ };
247
+ return checks;
248
+ }
89
249
  const program = new Command();
90
250
  program
91
251
  .name("oneclaw")
92
252
  .description("Identity pack CLI for AI assistants")
93
- .version("0.1.0");
253
+ .version("0.1.0")
254
+ .showHelpAfterError();
255
+ program
256
+ .command("bootstrap")
257
+ .description("Collect bootstrap credentials and persist config state")
258
+ .option("--profile <profile>", "Config profile", "default")
259
+ .option("--pack <packId>", "Default pack for follow-up provision", "founder")
260
+ .option("--run-provision", "Run provision after saving state")
261
+ .option("--providers <providers>", `Comma-separated providers (${PROVIDERS.join(",")})`, "agentmail,telegram,bitwarden")
262
+ .option("--targets <targets>", `Comma-separated targets (${TARGETS.join(",")})`, TARGETS.join(","))
263
+ .option("--no-tui", "Disable OpenTUI mode")
264
+ .option("--json", "Output json")
265
+ .action(async (opts) => {
266
+ const demoMode = isDemoModeEnabled();
267
+ const requestedProfile = profileFrom(opts);
268
+ const profile = demoMode && requestedProfile === "default" ? "demo" : requestedProfile;
269
+ const packId = (typeof opts.pack === "string" && opts.pack.trim()) || "founder";
270
+ const providers = parseProviders(opts.providers);
271
+ const targets = parseTargets(opts.targets);
272
+ const asJson = Boolean(opts.json);
273
+ const runProvisionAfterSave = Boolean(opts.runProvision);
274
+ const collected = await collectBootstrapDraft({
275
+ profile,
276
+ packId,
277
+ useTui: opts.tui !== false,
278
+ runProvisionByDefault: runProvisionAfterSave,
279
+ demoMode,
280
+ });
281
+ if (collected.cancelled) {
282
+ printValue({ status: "cancelled", profile }, asJson);
283
+ return;
284
+ }
285
+ persistBootstrapDraft(profile, collected.draft);
286
+ const check = checkConfig({ profile, providers, reveal: false });
287
+ const verification = await verifyFromConfig(profile);
288
+ const verified = {
289
+ agentmail: verification.agentmail.ok,
290
+ telegram: verification.telegram.ok,
291
+ bitwarden: verification.bitwarden.ok,
292
+ };
293
+ let provisionResult = undefined;
294
+ const shouldRunProvision = collected.draft.runProvision || runProvisionAfterSave;
295
+ if (shouldRunProvision && check.ready && Object.values(verified).every(Boolean)) {
296
+ provisionResult = await runProvision({
297
+ packId,
298
+ providers,
299
+ targets,
300
+ options: {
301
+ profile,
302
+ },
303
+ profile,
304
+ useConfig: true,
305
+ nonInteractive: true,
306
+ asJson,
307
+ });
308
+ }
309
+ printValue({
310
+ status: "ok",
311
+ profile,
312
+ packId,
313
+ required: getRequiredConfigKeys(providers).map((item) => item.key),
314
+ demoMode,
315
+ check,
316
+ verification,
317
+ provision: provisionResult,
318
+ }, asJson);
319
+ });
320
+ program
321
+ .command("bootstrap-prompt")
322
+ .description("Print helper prompt for another setup AI")
323
+ .option("--json", "Output json")
324
+ .action((opts) => {
325
+ const prompt = helperPrompt();
326
+ printValue(opts.json ? { prompt } : prompt, Boolean(opts.json));
327
+ });
328
+ const configCommand = program.command("config").description("Manage persisted oneclaw config state");
329
+ configCommand
330
+ .command("set")
331
+ .description("Set a config key")
332
+ .argument("<key>")
333
+ .argument("[value]")
334
+ .option("--profile <profile>", "Config profile", "default")
335
+ .option("--secret", "Store as secret")
336
+ .option("--stdin", "Read value from stdin")
337
+ .option("--json", "Output json")
338
+ .action(async (key, value, opts) => {
339
+ const profile = profileFrom(opts);
340
+ const fromStdin = Boolean(opts.stdin) ? await readStdinTrimmed() : undefined;
341
+ const fromArg = value?.trim();
342
+ const fromPrompt = fromStdin || fromArg ? undefined : await askInteractive(`${key}: `);
343
+ const finalValue = fromStdin || fromArg || fromPrompt;
344
+ if (!finalValue) {
345
+ throw new Error(`No value provided for ${key}. Use [value], --stdin, or interactive input.`);
346
+ }
347
+ const result = setConfigValue({
348
+ profile,
349
+ key,
350
+ value: finalValue,
351
+ secret: Boolean(opts.secret),
352
+ });
353
+ printValue(result, Boolean(opts.json));
354
+ });
355
+ configCommand
356
+ .command("get")
357
+ .description("Get a config key")
358
+ .argument("<key>")
359
+ .option("--profile <profile>", "Config profile", "default")
360
+ .option("--reveal", "Reveal secret values")
361
+ .option("--json", "Output json")
362
+ .action((key, opts) => {
363
+ const profile = profileFrom(opts);
364
+ const result = getConfigValue({ profile, key, reveal: Boolean(opts.reveal) });
365
+ if (!result) {
366
+ printValue({ profile, key, found: false }, Boolean(opts.json));
367
+ process.exitCode = 1;
368
+ return;
369
+ }
370
+ printValue({ ...result, found: true }, Boolean(opts.json));
371
+ });
372
+ configCommand
373
+ .command("list")
374
+ .description("List stored config keys")
375
+ .option("--profile <profile>", "Config profile", "default")
376
+ .option("--reveal", "Reveal secret values")
377
+ .option("--json", "Output json")
378
+ .action((opts) => {
379
+ const profile = profileFrom(opts);
380
+ const result = listConfig({ profile, reveal: Boolean(opts.reveal) });
381
+ printValue(result, Boolean(opts.json));
382
+ });
383
+ configCommand
384
+ .command("unset")
385
+ .description("Remove a config key")
386
+ .argument("<key>")
387
+ .option("--profile <profile>", "Config profile", "default")
388
+ .option("--json", "Output json")
389
+ .action((key, opts) => {
390
+ const profile = profileFrom(opts);
391
+ const result = unsetConfigValue({ profile, key });
392
+ printValue(result, Boolean(opts.json));
393
+ });
394
+ configCommand
395
+ .command("check")
396
+ .description("Check required config for providers")
397
+ .option("--providers <providers>", `Comma-separated providers (${PROVIDERS.join(",")})`, "agentmail,telegram,bitwarden")
398
+ .option("--profile <profile>", "Config profile", "default")
399
+ .option("--verify", "Run provider verification checks")
400
+ .option("--json", "Output json")
401
+ .action(async (opts) => {
402
+ const profile = profileFrom(opts);
403
+ const providers = parseProviders(opts.providers);
404
+ const check = checkConfig({ profile, providers, reveal: false });
405
+ if (!opts.verify) {
406
+ printValue(check, Boolean(opts.json));
407
+ return;
408
+ }
409
+ const verification = await verifyFromConfig(profile);
410
+ printValue({
411
+ ...check,
412
+ verification,
413
+ verified: Object.fromEntries(Object.entries(verification).map(([provider, result]) => [provider, result.ok])),
414
+ }, Boolean(opts.json));
415
+ });
94
416
  program
95
417
  .command("provision")
96
418
  .description("Provision identities and update the pack")
97
419
  .requiredOption("--pack <packId>", "Identity pack id")
98
420
  .option("--providers <providers>", `Comma-separated providers (${PROVIDERS.join(",")})`)
99
421
  .option("--targets <targets>", `Comma-separated targets (${TARGETS.join(",")})`)
422
+ .option("--profile <profile>", "Config profile", "default")
423
+ .option("--no-config", "Disable config profile lookup")
100
424
  .option("--non-interactive", "Fail instead of prompting")
101
425
  .option("--json", "Output json")
102
426
  .option("--agentmail-api-key <value>")
@@ -118,64 +442,23 @@ program
118
442
  const packId = String(opts.pack);
119
443
  const providers = parseProviders(opts.providers);
120
444
  const targets = parseTargets(opts.targets);
445
+ const profile = profileFrom(opts);
121
446
  const nonInteractive = Boolean(opts.nonInteractive);
122
447
  const asJson = Boolean(opts.json);
123
- let pack = ensurePack(packId, targets);
124
- let run = loadRunState(packId) || createRunState(packId);
125
- for (const provider of providers) {
126
- const workflow = getWorkflow(provider);
127
- const result = await runProviderWorkflow({
128
- workflow,
129
- run,
130
- pack,
131
- options: {
132
- agentmailApiKey: opts.agentmailApiKey,
133
- agentmailUsername: opts.agentmailUsername,
134
- agentmailDomain: opts.agentmailDomain,
135
- agentmailDisplayName: opts.agentmailDisplayName,
136
- agentmailInboxId: opts.agentmailInboxId,
137
- agentmailWebhookSecret: opts.agentmailWebhookSecret,
138
- forceAgentmailCreate: opts.forceAgentmailCreate,
139
- telegramBotToken: opts.telegramBotToken,
140
- telegramBotUsername: opts.telegramBotUsername,
141
- telegramIdentityId: opts.telegramIdentityId,
142
- bitwardenEmail: opts.bitwardenEmail,
143
- bitwardenPassword: opts.bitwardenPassword,
144
- bitwardenVault: opts.bitwardenVault,
145
- bitwardenSkipSignup: opts.bitwardenSkipSignup,
146
- bitwardenAlreadyCreated: opts.bitwardenAlreadyCreated,
147
- },
148
- nonInteractive,
149
- ask: askInteractive,
150
- log: (message) => {
151
- if (!asJson)
152
- process.stderr.write(`${message}\n`);
153
- },
154
- });
155
- saveRunState(run);
156
- pack = savePack(pack);
157
- if (result.status === "blocked") {
158
- printValue({
159
- status: "blocked",
160
- provider,
161
- step: result.blockedStepId,
162
- reason: result.blockedReason,
163
- resumeCommand: `oneclaw provision --pack ${packId} --providers ${providers.join(",")}`,
164
- }, asJson);
165
- process.exitCode = 2;
166
- return;
167
- }
168
- }
169
- const runLog = asLog(run);
170
- pack = appendProvisioningRun(pack, runLog);
171
- clearRunState(packId);
172
- printValue({
173
- status: "ok",
448
+ const result = await runProvision({
174
449
  packId,
175
450
  providers,
176
- targets: pack.targets,
177
- accounts: Object.keys(pack.accounts),
178
- }, asJson);
451
+ targets,
452
+ options: opts,
453
+ profile,
454
+ useConfig: opts.config !== false,
455
+ nonInteractive,
456
+ asJson,
457
+ });
458
+ if (result.status === "blocked") {
459
+ process.exitCode = 2;
460
+ }
461
+ printValue(result, asJson);
179
462
  });
180
463
  program
181
464
  .command("export")
@@ -260,6 +543,30 @@ program
260
543
  const pack = loadPack(String(opts.pack));
261
544
  process.stdout.write(exportPackToTarget(pack, target));
262
545
  });
546
+ program.action(async () => {
547
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
548
+ program.outputHelp();
549
+ return;
550
+ }
551
+ const demoMode = isDemoModeEnabled();
552
+ const profile = demoMode ? "demo" : "default";
553
+ const result = await collectBootstrapDraft({
554
+ profile,
555
+ packId: "founder",
556
+ useTui: true,
557
+ runProvisionByDefault: false,
558
+ demoMode,
559
+ });
560
+ if (result.cancelled)
561
+ return;
562
+ persistBootstrapDraft(profile, result.draft);
563
+ printValue({
564
+ status: "ok",
565
+ profile,
566
+ demoMode,
567
+ message: "Bootstrap values saved. Run oneclaw config check --verify --json next.",
568
+ }, false);
569
+ });
263
570
  program.parseAsync(process.argv).catch((error) => {
264
571
  const message = error instanceof Error ? error.message : String(error);
265
572
  process.stderr.write(`${message}\n`);