@1agh/maude 0.43.0 → 0.44.0
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/apps/studio/acp/bridge.ts +44 -10
- package/apps/studio/acp/env.ts +23 -0
- package/apps/studio/acp/login-state.ts +263 -0
- package/apps/studio/acp/plugin-bootstrap.ts +31 -25
- package/apps/studio/acp/probe.ts +152 -1
- package/apps/studio/annotations-layer.tsx +396 -46
- package/apps/studio/api.ts +395 -2
- package/apps/studio/bin/_audio-search.mjs +140 -0
- package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
- package/apps/studio/bin/_import-asset.mjs +815 -0
- package/apps/studio/bin/_import-brand.mjs +635 -0
- package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
- package/apps/studio/bin/_import-tokens.mjs +1201 -0
- package/apps/studio/bin/_transcribe.mjs +419 -0
- package/apps/studio/bin/_transcribe.test.mjs +80 -0
- package/apps/studio/bin/audio-search.sh +28 -0
- package/apps/studio/bin/generate.sh +154 -0
- package/apps/studio/bin/import-asset.sh +34 -0
- package/apps/studio/bin/import-brand.sh +29 -0
- package/apps/studio/bin/import-tokens.sh +33 -0
- package/apps/studio/bin/photo-bg-remove.sh +119 -21
- package/apps/studio/bin/transcribe.sh +39 -0
- package/apps/studio/canvas-edit.ts +197 -2
- package/apps/studio/canvas-lib.tsx +115 -6
- package/apps/studio/client/app.jsx +1954 -685
- package/apps/studio/client/generate-dialog.jsx +352 -0
- package/apps/studio/client/github.js +14 -0
- package/apps/studio/client/inspector-controls.jsx +781 -0
- package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
- package/apps/studio/client/panels/ChatPanel.jsx +15 -33
- package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
- package/apps/studio/client/panels/ReadinessList.jsx +227 -7
- package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
- package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
- package/apps/studio/client/photo-knobs.jsx +167 -172
- package/apps/studio/client/styles/3-shell-maude.css +334 -28
- package/apps/studio/client/styles/4-components.css +223 -0
- package/apps/studio/client/styles/6-acp-chat.css +86 -0
- package/apps/studio/client/tour/quick-setup-tour.js +39 -0
- package/apps/studio/commands/annotation-strokes-command.ts +13 -3
- package/apps/studio/config.schema.json +70 -0
- package/apps/studio/context-menu.tsx +11 -2
- package/apps/studio/context.ts +16 -0
- package/apps/studio/design-setup-readiness.ts +115 -0
- package/apps/studio/dist/client.bundle.js +2097 -2088
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/draw/palette.ts +34 -0
- package/apps/studio/footage/schema.test.ts +92 -0
- package/apps/studio/footage/schema.ts +179 -1
- package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
- package/apps/studio/generation/adapters/gemini.ts +542 -0
- package/apps/studio/generation/adapters/groq.test.ts +80 -0
- package/apps/studio/generation/adapters/groq.ts +247 -0
- package/apps/studio/generation/audio-library.test.ts +95 -0
- package/apps/studio/generation/audio-library.ts +156 -0
- package/apps/studio/generation/captions.test.ts +78 -0
- package/apps/studio/generation/captions.ts +132 -0
- package/apps/studio/generation/download.ts +76 -0
- package/apps/studio/generation/elevenlabs.test.ts +223 -0
- package/apps/studio/generation/gemini.test.ts +350 -0
- package/apps/studio/generation/jobs.test.ts +128 -0
- package/apps/studio/generation/jobs.ts +243 -0
- package/apps/studio/generation/keys.test.ts +75 -0
- package/apps/studio/generation/keys.ts +162 -0
- package/apps/studio/generation/prefs.test.ts +95 -0
- package/apps/studio/generation/prefs.ts +82 -0
- package/apps/studio/generation/registry.ts +88 -0
- package/apps/studio/generation/types.test.ts +123 -0
- package/apps/studio/generation/types.ts +266 -0
- package/apps/studio/generation/whisper-models.test.ts +152 -0
- package/apps/studio/generation/whisper-models.ts +257 -0
- package/apps/studio/generation/whisper-spike-results.md +61 -0
- package/apps/studio/git/service.ts +3 -1
- package/apps/studio/http.ts +867 -9
- package/apps/studio/media/intro.mp4 +0 -0
- package/apps/studio/media-commit-chain.ts +119 -0
- package/apps/studio/paths.ts +10 -0
- package/apps/studio/readiness.ts +108 -20
- package/apps/studio/scaffold-design.ts +1145 -0
- package/apps/studio/server.ts +13 -1
- package/apps/studio/test/_helpers.ts +15 -2
- package/apps/studio/test/acp-bridge.test.ts +34 -1
- package/apps/studio/test/acp-env.test.ts +30 -0
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
- package/apps/studio/test/acp-session-plugins.test.ts +72 -1
- package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
- package/apps/studio/test/annotations-layer.test.ts +174 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +37 -0
- package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
- package/apps/studio/test/csrf-write-guard.test.ts +23 -1
- package/apps/studio/test/design-setup-readiness.test.ts +155 -0
- package/apps/studio/test/element-structural-edit.test.ts +108 -0
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
- package/apps/studio/test/generate-route.test.ts +106 -0
- package/apps/studio/test/import-asset-browser.test.ts +64 -0
- package/apps/studio/test/import-asset-route.test.ts +71 -0
- package/apps/studio/test/import-asset.test.ts +399 -0
- package/apps/studio/test/import-brand-browser.test.ts +88 -0
- package/apps/studio/test/import-brand-route.test.ts +120 -0
- package/apps/studio/test/import-brand.test.ts +206 -0
- package/apps/studio/test/import-tokens.test.ts +722 -0
- package/apps/studio/test/media-commit-chain.test.ts +210 -0
- package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
- package/apps/studio/test/readiness.test.ts +13 -11
- package/apps/studio/test/scaffold-design.test.ts +122 -0
- package/apps/studio/test/tour-overlay.test.tsx +16 -0
- package/apps/studio/tool-palette.tsx +11 -6
- package/apps/studio/ui-prefs.ts +130 -0
- package/apps/studio/use-canvas-media-drop.tsx +4 -2
- package/apps/studio/use-chrome-visibility.tsx +8 -2
- package/apps/studio/whats-new.json +96 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/bin/maude.mjs +6 -6
- package/cli/commands/design.mjs +33 -0
- package/cli/lib/gitignore-block.mjs +1 -0
- package/cli/lib/pkg-root.mjs +107 -0
- package/cli/lib/pkg-root.test.mjs +79 -0
- package/cli/lib/reconstruct-toolset.test.mjs +194 -0
- package/cli/lib/update-check.mjs +8 -0
- package/package.json +12 -10
- package/plugins/design/dependencies.json +18 -0
|
Binary file
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file media-commit-chain.ts — serialized accumulator chain for
|
|
3
|
+
* concurrent async media-intake commits.
|
|
4
|
+
* @scope apps/studio/media-commit-chain.ts
|
|
5
|
+
* @purpose Fixes the Phase-23 batch-drop data-loss bug: a Finder drop of
|
|
6
|
+
* N files fires N independent async completions (image decode /
|
|
7
|
+
* asset upload), each of which used to read a `strokesRef.current`
|
|
8
|
+
* mirror and call `commitStrokes(before, after)` on its own.
|
|
9
|
+
* `strokesRef` only catches up on the NEXT render, so two
|
|
10
|
+
* completions landing before a render commit both compute
|
|
11
|
+
* `before` from the same stale snapshot — the second commit's
|
|
12
|
+
* `setStrokesState` overwrites the first's, silently dropping it.
|
|
13
|
+
*
|
|
14
|
+
* This chain fixes it by threading the PREVIOUS link's resulting
|
|
15
|
+
* array forward as the next link's `before` while links are
|
|
16
|
+
* in flight (Promise-chained, mirroring `editApplyChainRef` /
|
|
17
|
+
* `structuralWrite` in client/app.jsx). The subtle part is what
|
|
18
|
+
* happens once the queue drains: a naive "go idle, trust a fresh
|
|
19
|
+
* `getSnapshot()` again" reset raced in practice — `getSnapshot()`
|
|
20
|
+
* (`strokesRef.current`) only catches up on React's NEXT render,
|
|
21
|
+
* which can take longer than the handful of microtask ticks this
|
|
22
|
+
* chain itself needs to settle a link, so a fast-idling chain
|
|
23
|
+
* could hand the next link a `getSnapshot()` that hasn't caught
|
|
24
|
+
* up yet — reproducing the same loss one level down (confirmed
|
|
25
|
+
* empirically against a real dev server: see the Task 5 write-up).
|
|
26
|
+
* Instead, every idle read is RECONCILED against `remembered`
|
|
27
|
+
* (the last array this chain itself produced): for every id the
|
|
28
|
+
* chain itself has ever touched, `remembered`'s value WINS
|
|
29
|
+
* outright, even if the fresh snapshot also contains that id —
|
|
30
|
+
* presence alone isn't enough, since fresh can hold a STALE
|
|
31
|
+
* value for an id the chain already updated (see `reconcile()`
|
|
32
|
+
* below and DDR-165). Only ids fresh has that the chain has
|
|
33
|
+
* never touched get folded in from it, which is what still lets
|
|
34
|
+
* an unrelated synchronous edit (pen/shape/drag, made while
|
|
35
|
+
* idle) through.
|
|
36
|
+
*
|
|
37
|
+
* Framework/DOM-free by design so the no-loss guarantee is
|
|
38
|
+
* testable without React or a real image decode/upload — see
|
|
39
|
+
* test/media-commit-chain.test.ts.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export interface MediaCommitResult<T> {
|
|
43
|
+
/** The chain's new accumulator value — what the NEXT queued link builds on. */
|
|
44
|
+
after: readonly T[];
|
|
45
|
+
/**
|
|
46
|
+
* Override the `before` passed to `onCommit` (defaults to this link's
|
|
47
|
+
* accumulator `before`). Lets a caller exclude an ephemeral, never-
|
|
48
|
+
* separately-committed entry (e.g. an optimistic blob: preview) from the
|
|
49
|
+
* undo record while still folding it into the accumulator correctly.
|
|
50
|
+
*/
|
|
51
|
+
commitBefore?: readonly T[];
|
|
52
|
+
label?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MediaCommitChain<T> {
|
|
56
|
+
/**
|
|
57
|
+
* Enqueue a synchronous mutation. `mutate` receives the current
|
|
58
|
+
* accumulated `before` (the previous link's `after` while a link is still
|
|
59
|
+
* in flight, or a reconciled `getSnapshot()` read once the queue has
|
|
60
|
+
* drained) and returns a result, or `null` to skip (no-op, `before` passes
|
|
61
|
+
* through unchanged). `onCommit` fires with `(before, after, label)` in
|
|
62
|
+
* strict queue order, once per non-null mutation.
|
|
63
|
+
*/
|
|
64
|
+
enqueue(
|
|
65
|
+
mutate: (before: readonly T[]) => MediaCommitResult<T> | null,
|
|
66
|
+
onCommit: (before: readonly T[], after: readonly T[], label?: string) => void
|
|
67
|
+
): Promise<readonly T[]>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createMediaCommitChain<T>(
|
|
71
|
+
getSnapshot: () => readonly T[],
|
|
72
|
+
keyOf: (item: T) => string
|
|
73
|
+
): MediaCommitChain<T> {
|
|
74
|
+
let pending: Promise<readonly T[]> | null = null;
|
|
75
|
+
// The last array this chain itself produced. Never cleared — only ever
|
|
76
|
+
// superseded by a fresh reconciled read once every id it carries shows up
|
|
77
|
+
// in `getSnapshot()` too (see reconcile()).
|
|
78
|
+
let remembered: readonly T[] | null = null;
|
|
79
|
+
|
|
80
|
+
function reconcile(): readonly T[] {
|
|
81
|
+
const fresh = getSnapshot();
|
|
82
|
+
if (!remembered) return fresh;
|
|
83
|
+
// Prefer `remembered`'s value for any id the CHAIN itself produced —
|
|
84
|
+
// not just fold in ids fresh is missing. `fresh` (getSnapshot) can
|
|
85
|
+
// contain a STALE value for an id the chain already swapped: e.g. an
|
|
86
|
+
// image stroke's optimistic (blob:) href is set via a separate, non-
|
|
87
|
+
// chain setState, and `fresh` catches up to THAT before it catches up
|
|
88
|
+
// to the chain's OWN later commit swapping it to the real href — same
|
|
89
|
+
// id, stale value. Checking "is the id present" (the previous version
|
|
90
|
+
// of this function) said "yes, already rendered" and used fresh's
|
|
91
|
+
// stale copy, silently reverting the swap — which then gets filtered
|
|
92
|
+
// out entirely as still-ephemeral at the persistence layer. `remembered`
|
|
93
|
+
// is always at least as fresh as anything the chain has itself
|
|
94
|
+
// touched, so it wins on overlap; anything fresh has that remembered
|
|
95
|
+
// doesn't (an external, non-chain edit made while idle) still comes
|
|
96
|
+
// through via the second spread.
|
|
97
|
+
const rememberedKeys = new Set(remembered.map(keyOf));
|
|
98
|
+
const notInRemembered = fresh.filter((item) => !rememberedKeys.has(keyOf(item)));
|
|
99
|
+
return [...remembered, ...notInRemembered];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const enqueue: MediaCommitChain<T>['enqueue'] = (mutate, onCommit) => {
|
|
103
|
+
const seed = pending ?? Promise.resolve(reconcile());
|
|
104
|
+
const link: Promise<readonly T[]> = seed.then((before) => {
|
|
105
|
+
const result = mutate(before);
|
|
106
|
+
if (!result) return before;
|
|
107
|
+
onCommit(result.commitBefore ?? before, result.after, result.label);
|
|
108
|
+
remembered = result.after;
|
|
109
|
+
return result.after;
|
|
110
|
+
});
|
|
111
|
+
pending = link;
|
|
112
|
+
void link.then(() => {
|
|
113
|
+
if (pending === link) pending = null;
|
|
114
|
+
});
|
|
115
|
+
return link;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return { enqueue };
|
|
119
|
+
}
|
package/apps/studio/paths.ts
CHANGED
|
@@ -58,6 +58,16 @@ export const RUNTIME_BUNDLES_DIR: string = join(DIST_DIR, 'runtime');
|
|
|
58
58
|
*/
|
|
59
59
|
export const STICKERS_DIR: string = join(DEV_SERVER_ROOT, 'stickers');
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* `<DEV_SERVER_ROOT>/media/` — bundled Maude-product media (the intro
|
|
63
|
+
* showreel, DDR-166 Phase 1 T2). Same shape as `STICKERS_DIR`: ships with the
|
|
64
|
+
* `@1agh/maude` tarball (a subdir of `apps/studio`, already covered by the
|
|
65
|
+
* existing `files` entry), resolved from DEV_SERVER_ROOT per DDR-045 — this is
|
|
66
|
+
* MAUDE's OWN asset store, never the served project's `.design/assets/`, so
|
|
67
|
+
* every user sees the same intro regardless of which project is open.
|
|
68
|
+
*/
|
|
69
|
+
export const MEDIA_DIR: string = join(DEV_SERVER_ROOT, 'media');
|
|
70
|
+
|
|
61
71
|
/**
|
|
62
72
|
* Absolute path to a bundled plugin's loadable tree (`commands/`, `agents/`,
|
|
63
73
|
* `skills/`, `hooks/`, `.claude-plugin/plugin.json`), or `null` when this layout
|
package/apps/studio/readiness.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { existsSync, readFileSync } from 'node:fs';
|
|
15
15
|
import { homedir } from 'node:os';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
|
+
import { getClaudeAuthStatus } from './acp/login-state.ts';
|
|
17
18
|
import { isNativePluginContext } from './acp/plugin-bootstrap.ts';
|
|
18
19
|
import { resolveAdapterEntry, resolveClaudePath } from './acp/probe.ts';
|
|
19
20
|
import { resolveBrowser } from './bin/_ensure-browser.mjs';
|
|
@@ -32,6 +33,19 @@ export interface ReadinessItem {
|
|
|
32
33
|
detail: string;
|
|
33
34
|
/** Copy-paste-able fix, present only when the item is not satisfied. */
|
|
34
35
|
remediation?: string;
|
|
36
|
+
/** Verbatim shell command for a dedicated code-block + Copy affordance (DDR-166 T0c) — kept separate from the prose `remediation`. */
|
|
37
|
+
installCommand?: string;
|
|
38
|
+
/** UI action hint (DDR-166): 'install' renders a "Set up AI editing" button that runs the official installer then auto-chains to sign-in; 'signin' renders a "Sign in to Claude" button alone. */
|
|
39
|
+
action?: 'install' | 'signin';
|
|
40
|
+
/**
|
|
41
|
+
* DDR-166 Decision 3 (binding — a security-review-mandated control, not
|
|
42
|
+
* cosmetic): the resolved absolute path of the binary a Sign-in click is
|
|
43
|
+
* about to spawn, shown BEFORE the button so a pre-existing PATH-hijacked
|
|
44
|
+
* `claude` isn't invisible to the one human capable of noticing it's wrong.
|
|
45
|
+
*/
|
|
46
|
+
resolvedPath?: string;
|
|
47
|
+
/** True when `resolvedPath` is the binary Maude itself just installed (content-pinned — see probe.ts `setTrustedClaudeBin`), not something already on the user's PATH. */
|
|
48
|
+
resolvedViaMaude?: boolean;
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
export interface ReadinessReport {
|
|
@@ -164,37 +178,101 @@ export function scanPlugins(): PluginScan {
|
|
|
164
178
|
* (DDR-128 hardening) so this can't be turned into a spawn-storm from a drive-by page.
|
|
165
179
|
*/
|
|
166
180
|
export async function probeReadiness(): Promise<ReadinessReport> {
|
|
167
|
-
|
|
181
|
+
let [claude, maude, agentBrowser] = await Promise.all([
|
|
168
182
|
(async () => resolveClaudePath() ?? (await resolveOnPath('claude')))(),
|
|
169
183
|
resolveOnPath('maude'),
|
|
170
184
|
resolveOnPath('agent-browser'),
|
|
171
185
|
]);
|
|
172
186
|
|
|
187
|
+
// Deterministic E2E stub for the two claude-readiness states that are
|
|
188
|
+
// otherwise near-impossible to reproduce on a real, already-set-up dev
|
|
189
|
+
// machine (mirrors MAUDE_E2E_FAKE_GITHUB_LOGIN in oauth.rs — same problem,
|
|
190
|
+
// same shape of fix: a real state that needs a pristine/logged-out machine
|
|
191
|
+
// to occur naturally gets a narrow, explicitly-named override instead of a
|
|
192
|
+
// hand-rolled sandbox that fights the OS's own PATH resolution). Never
|
|
193
|
+
// affects real users — unset in every normal launch.
|
|
194
|
+
const e2eForce = process.env.MAUDE_E2E_FORCE_CLAUDE_STATUS;
|
|
195
|
+
// A verified, explicitly-provisioned MAUDE_CLAUDE_BIN override (T0c's
|
|
196
|
+
// install flow, once its freshness+hash check passes) must never be
|
|
197
|
+
// clobbered back to "missing"/"signed-out" by this coarser stub — mirrors
|
|
198
|
+
// the same precedence fix in probe.ts's resolveClaudePath(). Without this,
|
|
199
|
+
// the acp-cold-start scenario could never observe its OWN install actually
|
|
200
|
+
// taking effect after starting from a forced-missing state.
|
|
201
|
+
const hasVerifiedOverride =
|
|
202
|
+
!!process.env.MAUDE_CLAUDE_BIN && claude === process.env.MAUDE_CLAUDE_BIN;
|
|
203
|
+
if (e2eForce === 'missing' && !hasVerifiedOverride) claude = null;
|
|
204
|
+
else if (e2eForce === 'signed-out' && !hasVerifiedOverride) claude = claude || '/usr/bin/claude'; // present, but auth probe below is also stubbed
|
|
205
|
+
|
|
173
206
|
const items: ReadinessItem[] = [];
|
|
174
207
|
|
|
208
|
+
// DDR-166 T0d — honest 3-state instead of presence-only: not installed (an
|
|
209
|
+
// "install" action, T0c — runs the same verbatim official one-liner a user
|
|
210
|
+
// would type themselves, live-verified 2026-07-13 against
|
|
211
|
+
// code.claude.com/docs/en/quickstart.md; the copy-paste command below stays
|
|
212
|
+
// as the fallback/opt-out path) / installed-but-signed-out (a "Sign in"
|
|
213
|
+
// action, T0d) / signed-in-on-subscription.
|
|
214
|
+
const authStatus =
|
|
215
|
+
e2eForce === 'signed-out' ? { loggedIn: false } : claude ? await getClaudeAuthStatus() : null;
|
|
216
|
+
const signedIn = !!authStatus?.loggedIn;
|
|
217
|
+
const offSubscription =
|
|
218
|
+
signedIn && authStatus?.apiProvider && authStatus.apiProvider !== 'firstParty';
|
|
219
|
+
// DDR-166 Decision 5 — the settings-UI opt-out (prefs.rs `claude_auto_setup`,
|
|
220
|
+
// DEFAULT ON) reaches the dev-server as an env var set fresh at each sidecar
|
|
221
|
+
// spawn. Disabled → fall back to guide-only: no automated action offered,
|
|
222
|
+
// same posture as before T0c/T0d existed (still shows the copy-paste
|
|
223
|
+
// command below, just no button).
|
|
224
|
+
const autoSetupEnabled = process.env.MAUDE_CLAUDE_AUTOSETUP_ENABLED !== '0';
|
|
175
225
|
items.push({
|
|
176
226
|
id: 'claude',
|
|
177
227
|
label: 'Claude Code (the `claude` CLI)',
|
|
178
228
|
required: true,
|
|
179
|
-
status: claude ? 'present' : 'missing',
|
|
180
|
-
detail: claude
|
|
181
|
-
? '
|
|
182
|
-
:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
229
|
+
status: claude && signedIn ? 'present' : 'missing',
|
|
230
|
+
detail: !claude
|
|
231
|
+
? 'Not found on PATH.'
|
|
232
|
+
: signedIn
|
|
233
|
+
? offSubscription
|
|
234
|
+
? `Signed in, but not on a Claude subscription (${authStatus?.apiProvider}) — AI editing may bill metered API usage.`
|
|
235
|
+
: `Signed in${authStatus?.subscriptionType ? ` · ${authStatus.subscriptionType}` : ''} — AI editing runs on your own Pro/Max subscription.`
|
|
236
|
+
: 'Installed, but not signed in.',
|
|
237
|
+
remediation: !claude
|
|
238
|
+
? 'Install Claude Code, then sign in. AI editing runs on your own Pro/Max subscription — never API billing.'
|
|
239
|
+
: signedIn
|
|
240
|
+
? undefined
|
|
241
|
+
: 'Sign in to connect it to your Pro/Max subscription.',
|
|
242
|
+
installCommand: !claude ? 'curl -fsSL https://claude.ai/install.sh | bash' : undefined,
|
|
243
|
+
action: !autoSetupEnabled ? undefined : !claude ? 'install' : !signedIn ? 'signin' : undefined,
|
|
244
|
+
resolvedPath: claude ?? undefined,
|
|
245
|
+
resolvedViaMaude: !!claude && claude === process.env.MAUDE_CLAUDE_BIN,
|
|
186
246
|
});
|
|
187
247
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
248
|
+
// DDR-166 T0b / DDR-168 — `maude` is bundled into the packaged app (compiled
|
|
249
|
+
// via apps/desktop/scripts/build-cli-binary.mjs, shipped as `externalBin`
|
|
250
|
+
// `binaries/maude`; sidecar.rs stages it into its own narrow, single-binary
|
|
251
|
+
// PATH directory, PREPENDS that directory ahead of the rest of PATH, and sets
|
|
252
|
+
// MAUDE_BUNDLED_CLI_PATH to that exact path). Prepending makes this
|
|
253
|
+
// deterministic, not a best-effort fallback: inside the sidecar's own spawned
|
|
254
|
+
// children the bundled copy always wins PATH resolution, so `maude === bundledCliPath`
|
|
255
|
+
// is the normal case on the desktop path, not merely one possible outcome. This
|
|
256
|
+
// row disappears entirely for the end user rather than surfacing an
|
|
257
|
+
// `npm i -g` instruction for something already shipped — it's an internal
|
|
258
|
+
// plumbing detail, not a recognizable capability like the agent-browser/
|
|
259
|
+
// adapter rows below. In dev contexts where the bundling step hasn't run
|
|
260
|
+
// (a bare `bun run server.ts`, `maude design serve`), MAUDE_BUNDLED_CLI_PATH
|
|
261
|
+
// is unset and this falls through to the pre-T0b behavior unchanged.
|
|
262
|
+
const bundledCliPath = process.env.MAUDE_BUNDLED_CLI_PATH || null;
|
|
263
|
+
const maudeIsBundled = !!bundledCliPath && maude === bundledCliPath;
|
|
264
|
+
if (!maudeIsBundled) {
|
|
265
|
+
items.push({
|
|
266
|
+
id: 'maude',
|
|
267
|
+
label: 'maude CLI',
|
|
268
|
+
required: true,
|
|
269
|
+
status: maude ? 'present' : 'missing',
|
|
270
|
+
detail: maude ? 'On PATH — `/design:edit` can reach its helpers.' : 'Not found on PATH.',
|
|
271
|
+
remediation: maude
|
|
272
|
+
? undefined
|
|
273
|
+
: 'Install it: `npm i -g @1agh/maude`. `/design:edit` shells out to `maude design …`, so it must be on PATH.',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
198
276
|
|
|
199
277
|
const scan = scanPlugins();
|
|
200
278
|
// DDR-143 — on the native/desktop path the ACP chat session AUTO-LOADS the
|
|
@@ -206,8 +284,18 @@ export async function probeReadiness(): Promise<ReadinessReport> {
|
|
|
206
284
|
// `design` alone — demanding a plugin the chat doesn't use would be a false red.
|
|
207
285
|
// The web `maude design serve` path ships no bundle (native=false, dir null) →
|
|
208
286
|
// the manual marketplace remediation still shows there.
|
|
287
|
+
//
|
|
288
|
+
// DDR-168 — `designAutoloaded` no longer requires `!scan.design`: the bundled
|
|
289
|
+
// copy is injected unconditionally now (plugin-bootstrap.ts), regardless of
|
|
290
|
+
// whether a marketplace copy is ALSO installed at the user level (the
|
|
291
|
+
// double-registration risk that used to gate this is closed structurally in
|
|
292
|
+
// bridge.ts's `options.settings.enabledPlugins` override instead). So this row
|
|
293
|
+
// reads "auto-loaded" even on a machine that separately has design@maude
|
|
294
|
+
// installed — that's the whole point: the bundled, release-matched copy is
|
|
295
|
+
// what actually runs in the chat, so the UI should say so rather than imply
|
|
296
|
+
// "ready because you already had it."
|
|
209
297
|
const native = isNativePluginContext();
|
|
210
|
-
const designAutoloaded = native && DESIGN_PLUGIN_DIR !== null
|
|
298
|
+
const designAutoloaded = native && DESIGN_PLUGIN_DIR !== null;
|
|
211
299
|
const designReady = scan.design || designAutoloaded;
|
|
212
300
|
const pluginStatus: ReadinessStatus = designReady
|
|
213
301
|
? 'present'
|
|
@@ -221,7 +309,7 @@ export async function probeReadiness(): Promise<ReadinessReport> {
|
|
|
221
309
|
status: pluginStatus,
|
|
222
310
|
detail: designReady
|
|
223
311
|
? designAutoloaded
|
|
224
|
-
? '
|
|
312
|
+
? 'Bundled with this app — loads automatically in the chat session.'
|
|
225
313
|
: 'design@maude is installed.'
|
|
226
314
|
: scan.status === 'unknown'
|
|
227
315
|
? "Couldn't read Claude Code's plugin registry — check it manually."
|