@chrysb/alphaclaw 0.4.6-beta.7 → 0.4.6-beta.9

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.
Files changed (33) hide show
  1. package/bin/alphaclaw.js +2 -32
  2. package/lib/public/css/theme.css +19 -0
  3. package/lib/public/js/app.js +1 -1
  4. package/lib/public/js/components/doctor/helpers.js +71 -5
  5. package/lib/public/js/components/doctor/index.js +89 -28
  6. package/lib/public/js/components/envars.js +0 -1
  7. package/lib/public/js/components/onboarding/welcome-config.js +39 -17
  8. package/lib/public/js/components/onboarding/welcome-form-step.js +142 -47
  9. package/lib/public/js/components/onboarding/welcome-import-step.js +306 -0
  10. package/lib/public/js/components/onboarding/welcome-placeholder-review-step.js +99 -0
  11. package/lib/public/js/components/onboarding/welcome-secret-review-step.js +191 -0
  12. package/lib/public/js/components/segmented-control.js +7 -1
  13. package/lib/public/js/components/welcome/index.js +112 -0
  14. package/lib/public/js/components/welcome/use-welcome.js +561 -0
  15. package/lib/public/js/lib/api.js +221 -161
  16. package/lib/server/commands.js +1 -0
  17. package/lib/server/constants.js +0 -1
  18. package/lib/server/doctor/bootstrap-context.js +191 -0
  19. package/lib/server/doctor/prompt.js +20 -4
  20. package/lib/server/doctor/service.js +18 -4
  21. package/lib/server/gateway.js +15 -40
  22. package/lib/server/onboarding/github.js +120 -19
  23. package/lib/server/onboarding/import/import-applier.js +321 -0
  24. package/lib/server/onboarding/import/import-config.js +69 -0
  25. package/lib/server/onboarding/import/import-scanner.js +469 -0
  26. package/lib/server/onboarding/import/import-temp.js +63 -0
  27. package/lib/server/onboarding/import/secret-detector.js +289 -0
  28. package/lib/server/onboarding/index.js +256 -29
  29. package/lib/server/onboarding/workspace.js +38 -6
  30. package/lib/server/routes/onboarding.js +281 -12
  31. package/lib/server.js +12 -3
  32. package/package.json +1 -1
  33. package/lib/public/js/components/welcome.js +0 -318
@@ -0,0 +1,191 @@
1
+ import { h } from "https://esm.sh/preact";
2
+ import { useState, useCallback } from "https://esm.sh/preact/hooks";
3
+ import htm from "https://esm.sh/htm";
4
+ import { ActionButton } from "../action-button.js";
5
+ import { LoadingSpinner } from "../loading-spinner.js";
6
+
7
+ const html = htm.bind(h);
8
+
9
+ const SecretRow = ({ secret, selected, onToggle, envVarName, onEnvVarChange }) =>
10
+ html`
11
+ <div
12
+ class="border border-border rounded-lg p-3 space-y-2 ${selected
13
+ ? "bg-cyan-900/10 border-cyan-800/40"
14
+ : ""}"
15
+ >
16
+ <div class="flex items-start gap-2">
17
+ <input
18
+ type="checkbox"
19
+ checked=${selected}
20
+ onChange=${onToggle}
21
+ class="mt-0.5 rounded"
22
+ />
23
+ <div class="flex-1 min-w-0">
24
+ <div class="flex items-center gap-2 flex-wrap">
25
+ <span class="text-xs font-mono text-gray-300 truncate"
26
+ >${secret.maskedValue}</span
27
+ >
28
+ ${secret.confidence === "high"
29
+ ? html`<span
30
+ class="text-xs px-1.5 py-0.5 rounded-full bg-red-900/40 text-red-300"
31
+ >high confidence</span
32
+ >`
33
+ : html`<span
34
+ class="text-xs px-1.5 py-0.5 rounded-full bg-yellow-900/40 text-yellow-300"
35
+ >possible</span
36
+ >`}
37
+ </div>
38
+ <div class="text-xs text-gray-500 mt-1">
39
+ Found in${" "}
40
+ <span class="font-mono">${secret.file || "config"}</span>
41
+ ${secret.configPath
42
+ ? html` at <span class="font-mono">${secret.configPath}</span>`
43
+ : null}
44
+ </div>
45
+ ${secret.duplicateIn &&
46
+ html`
47
+ <div class="text-xs text-yellow-400 mt-1">
48
+ Also found in${" "}<span class="font-mono"
49
+ >${secret.duplicateIn}</span
50
+ >
51
+ </div>
52
+ `}
53
+ </div>
54
+ </div>
55
+ ${selected &&
56
+ html`
57
+ <div class="pl-6">
58
+ <label class="text-xs text-gray-500">Extract as env var:</label>
59
+ <input
60
+ type="text"
61
+ value=${envVarName}
62
+ onInput=${(e) => onEnvVarChange(e.target.value)}
63
+ class="w-full mt-1 bg-black/30 border border-border rounded-lg px-3 py-1.5 text-xs text-gray-200 outline-none focus:border-gray-500 font-mono"
64
+ />
65
+ </div>
66
+ `}
67
+ </div>
68
+ `;
69
+
70
+ export const WelcomeSecretReviewStep = ({
71
+ secrets = [],
72
+ onApprove,
73
+ onBack,
74
+ loading,
75
+ error,
76
+ }) => {
77
+ const [selections, setSelections] = useState(() => {
78
+ const initial = {};
79
+ for (const secret of secrets) {
80
+ initial[secret.configPath] = {
81
+ selected: secret.confidence === "high",
82
+ envVarName: secret.suggestedEnvVar || "",
83
+ };
84
+ }
85
+ return initial;
86
+ });
87
+
88
+ const toggleSecret = useCallback(
89
+ (configPath) => {
90
+ setSelections((prev) => ({
91
+ ...prev,
92
+ [configPath]: {
93
+ ...prev[configPath],
94
+ selected: !prev[configPath]?.selected,
95
+ },
96
+ }));
97
+ },
98
+ [],
99
+ );
100
+
101
+ const updateEnvVarName = useCallback(
102
+ (configPath, name) => {
103
+ setSelections((prev) => ({
104
+ ...prev,
105
+ [configPath]: {
106
+ ...prev[configPath],
107
+ envVarName: name,
108
+ },
109
+ }));
110
+ },
111
+ [],
112
+ );
113
+
114
+ const selectedCount = Object.values(selections).filter(
115
+ (s) => s.selected,
116
+ ).length;
117
+
118
+ const handleExtract = () => {
119
+ const approved = secrets
120
+ .filter((s) => selections[s.configPath]?.selected)
121
+ .map((s) => ({
122
+ ...s,
123
+ suggestedEnvVar:
124
+ selections[s.configPath]?.envVarName || s.suggestedEnvVar,
125
+ }));
126
+ onApprove(approved);
127
+ };
128
+
129
+ if (loading) {
130
+ return html`
131
+ <div class="flex flex-col items-center justify-center py-8 gap-3">
132
+ <${LoadingSpinner} />
133
+ <p class="text-sm text-gray-400">Applying import...</p>
134
+ </div>
135
+ `;
136
+ }
137
+
138
+ return html`
139
+ <div class="space-y-3">
140
+ <div>
141
+ <h2 class="text-sm font-medium text-gray-200">Review Secrets</h2>
142
+ <p class="text-xs text-gray-500">
143
+ Select secrets to extract into environment variables. Inline values in
144
+ config will be replaced with ${"`"}${"${"}ENV_VAR_NAME${"}"}${"`"} references.
145
+ </p>
146
+ </div>
147
+
148
+ ${error &&
149
+ html`
150
+ <div
151
+ class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
152
+ >
153
+ ${error}
154
+ </div>
155
+ `}
156
+
157
+ <div class="space-y-2 max-h-80 overflow-y-auto">
158
+ ${secrets.map(
159
+ (secret) => html`
160
+ <${SecretRow}
161
+ key=${secret.configPath}
162
+ secret=${secret}
163
+ selected=${selections[secret.configPath]?.selected || false}
164
+ envVarName=${selections[secret.configPath]?.envVarName || ""}
165
+ onToggle=${() => toggleSecret(secret.configPath)}
166
+ onEnvVarChange=${(name) =>
167
+ updateEnvVarName(secret.configPath, name)}
168
+ />
169
+ `,
170
+ )}
171
+ </div>
172
+
173
+ <div class="grid grid-cols-2 gap-2 pt-1">
174
+ <${ActionButton}
175
+ onClick=${onBack}
176
+ tone="secondary"
177
+ idleLabel="Back"
178
+ className="w-full"
179
+ />
180
+ <${ActionButton}
181
+ onClick=${handleExtract}
182
+ tone="primary"
183
+ idleLabel=${selectedCount > 0
184
+ ? `Extract ${selectedCount} Secret${selectedCount === 1 ? "" : "s"}`
185
+ : "Skip All"}
186
+ className="w-full"
187
+ />
188
+ </div>
189
+ </div>
190
+ `;
191
+ };
@@ -11,14 +11,20 @@ const html = htm.bind(h);
11
11
  * @param {*} props.value Currently selected value.
12
12
  * @param {Function} props.onChange Called with the new value on click.
13
13
  * @param {string} [props.className] Extra classes on the wrapper.
14
+ * @param {"sm"|"lg"} [props.size] Visual size variant.
15
+ * @param {boolean} [props.fullWidth] Stretch wrapper and options to 100%.
14
16
  */
15
17
  export const SegmentedControl = ({
16
18
  options = [],
17
19
  value,
18
20
  onChange = () => {},
19
21
  className = "",
22
+ size = "sm",
23
+ fullWidth = false,
20
24
  }) => html`
21
- <div class=${`ac-segmented-control ${className}`}>
25
+ <div
26
+ class=${`ac-segmented-control ${size === "lg" ? "ac-segmented-control-lg" : ""} ${fullWidth ? "ac-segmented-control-full" : ""} ${className}`.trim()}
27
+ >
22
28
  ${options.map(
23
29
  (option) => html`
24
30
  <button
@@ -0,0 +1,112 @@
1
+ import { h } from "https://esm.sh/preact";
2
+ import htm from "https://esm.sh/htm";
3
+ import { kWelcomeGroups } from "../onboarding/welcome-config.js";
4
+ import { WelcomeImportStep } from "../onboarding/welcome-import-step.js";
5
+ import { WelcomePlaceholderReviewStep } from "../onboarding/welcome-placeholder-review-step.js";
6
+ import { WelcomeSecretReviewStep } from "../onboarding/welcome-secret-review-step.js";
7
+ import { WelcomeHeader } from "../onboarding/welcome-header.js";
8
+ import { WelcomeSetupStep } from "../onboarding/welcome-setup-step.js";
9
+ import { WelcomeFormStep } from "../onboarding/welcome-form-step.js";
10
+ import { WelcomePairingStep } from "../onboarding/welcome-pairing-step.js";
11
+ import { useWelcome } from "./use-welcome.js";
12
+
13
+ const html = htm.bind(h);
14
+
15
+ export const Welcome = ({ onComplete }) => {
16
+ const { state, actions } = useWelcome({ onComplete });
17
+
18
+ return html`
19
+ <div class="max-w-lg w-full space-y-5">
20
+ <${WelcomeHeader}
21
+ groups=${kWelcomeGroups}
22
+ step=${state.step}
23
+ isSetupStep=${state.isSetupStep}
24
+ isPairingStep=${state.isPairingStep}
25
+ stepNumber=${state.stepNumber}
26
+ activeStepLabel=${state.activeStepLabel}
27
+ />
28
+
29
+ <div class="bg-surface border border-border rounded-xl p-4 space-y-3">
30
+ ${state.isImportStep
31
+ ? html`<${WelcomeImportStep}
32
+ scanResult=${state.importScanResult}
33
+ scanning=${state.importScanning}
34
+ error=${state.importError}
35
+ onApprove=${actions.handleImportApprove}
36
+ onShowSecretReview=${actions.handleShowSecretReview}
37
+ onBack=${actions.handleImportBack}
38
+ />`
39
+ : state.isSecretReviewStep
40
+ ? html`<${WelcomeSecretReviewStep}
41
+ secrets=${state.importScanResult?.secrets || []}
42
+ onApprove=${actions.handleImportApprove}
43
+ onBack=${actions.handleSecretReviewBack}
44
+ loading=${state.importScanning}
45
+ error=${state.importError}
46
+ />`
47
+ : state.isPlaceholderReviewStep
48
+ ? html`<${WelcomePlaceholderReviewStep}
49
+ placeholderReview=${state.placeholderReview}
50
+ vals=${state.vals}
51
+ setValue=${actions.setValue}
52
+ onContinue=${actions.handlePlaceholderReviewContinue}
53
+ />`
54
+ : state.isSetupStep
55
+ ? html`<${WelcomeSetupStep}
56
+ error=${state.setupError}
57
+ loading=${state.loading}
58
+ onRetry=${actions.handleSubmit}
59
+ onBack=${actions.goBackFromSetupError}
60
+ />`
61
+ : state.isPairingStep
62
+ ? html`<${WelcomePairingStep}
63
+ channel=${state.selectedPairingChannel}
64
+ pairings=${state.pairingRequestsPoll.data || []}
65
+ channels=${state.pairingChannels}
66
+ loading=${!state.pairingStatusPoll.data}
67
+ error=${state.pairingError}
68
+ onApprove=${actions.handlePairingApprove}
69
+ onReject=${actions.handlePairingReject}
70
+ canFinish=${state.pairingComplete || state.canFinishPairing}
71
+ onContinue=${actions.finishOnboarding}
72
+ />`
73
+ : html`
74
+ <${WelcomeFormStep}
75
+ activeGroup=${state.activeGroup}
76
+ vals=${state.vals}
77
+ hasAi=${state.hasAi}
78
+ setValue=${actions.setValue}
79
+ modelOptions=${state.modelOptions}
80
+ modelsLoading=${state.modelsLoading}
81
+ modelsError=${state.modelsError}
82
+ canToggleFullCatalog=${state.canToggleFullCatalog}
83
+ showAllModels=${state.showAllModels}
84
+ setShowAllModels=${actions.setShowAllModels}
85
+ selectedProvider=${state.selectedProvider}
86
+ codexLoading=${state.codexLoading}
87
+ codexStatus=${state.codexStatus}
88
+ startCodexAuth=${actions.startCodexAuth}
89
+ handleCodexDisconnect=${actions.handleCodexDisconnect}
90
+ codexAuthStarted=${state.codexAuthStarted}
91
+ codexAuthWaiting=${state.codexAuthWaiting}
92
+ codexManualInput=${state.codexManualInput}
93
+ setCodexManualInput=${actions.setCodexManualInput}
94
+ completeCodexAuth=${actions.completeCodexAuth}
95
+ codexExchanging=${state.codexExchanging}
96
+ visibleAiFieldKeys=${state.visibleAiFieldKeys}
97
+ error=${state.formError}
98
+ step=${state.step}
99
+ totalGroups=${kWelcomeGroups.length}
100
+ currentGroupValid=${state.currentGroupValid}
101
+ goBack=${actions.goBack}
102
+ goNext=${actions.goNext}
103
+ loading=${state.loading}
104
+ githubStepLoading=${state.githubStepLoading}
105
+ allValid=${state.allValid}
106
+ handleSubmit=${actions.handleSubmit}
107
+ />
108
+ `}
109
+ </div>
110
+ </div>
111
+ `;
112
+ };