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 +62 -16
- package/dist/bootstrap/helper-prompt.d.ts +1 -0
- package/dist/bootstrap/helper-prompt.js +88 -0
- package/dist/bootstrap/helper-prompt.js.map +1 -0
- package/dist/cli.js +362 -55
- package/dist/cli.js.map +1 -1
- package/dist/core/config.d.ts +74 -0
- package/dist/core/config.js +284 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/doctor.js +5 -0
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/paths.d.ts +2 -0
- package/dist/core/paths.js +2 -0
- package/dist/core/paths.js.map +1 -1
- package/dist/core/provision-options.d.ts +24 -0
- package/dist/core/provision-options.js +89 -0
- package/dist/core/provision-options.js.map +1 -0
- package/dist/core/run-state.js +1 -2
- package/dist/core/run-state.js.map +1 -1
- package/dist/core/verifiers.d.ts +8 -0
- package/dist/core/verifiers.js +110 -0
- package/dist/core/verifiers.js.map +1 -0
- package/dist/providers/agentmail.js +4 -2
- package/dist/providers/agentmail.js.map +1 -1
- package/dist/providers/bitwarden.js +2 -2
- package/dist/providers/bitwarden.js.map +1 -1
- package/dist/providers/telegram.js +4 -2
- package/dist/providers/telegram.js.map +1 -1
- package/dist/tui/bootstrap.d.ts +20 -0
- package/dist/tui/bootstrap.js +240 -0
- package/dist/tui/bootstrap.js.map +1 -0
- package/package.json +6 -1
- package/scripts/postinstall-opentui-jsx-runtime.mjs +40 -0
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# oneclaw
|
|
2
2
|
|
|
3
|
-
`oneclaw`
|
|
3
|
+
`oneclaw` creates and applies identity packs for AI systems.
|
|
4
4
|
|
|
5
|
-
Current
|
|
6
|
-
- AgentMail (API-
|
|
7
|
-
- Telegram
|
|
8
|
-
- Bitwarden (
|
|
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
|
|
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
|
-
##
|
|
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
|
-
--
|
|
30
|
-
--
|
|
31
|
-
--
|
|
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
|
|
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
|
-
##
|
|
90
|
+
## Helper prompt for another setup AI
|
|
47
91
|
|
|
48
92
|
```bash
|
|
49
|
-
oneclaw
|
|
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
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
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`);
|