@jstn-sdk/ma 0.1.11 → 0.1.13
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/.codex/agents/Architect.toml +0 -2
- package/.codex/agents/Auditor.toml +0 -2
- package/.codex/agents/Builder.toml +0 -2
- package/.codex/agents/Flow.toml +0 -2
- package/.codex/agents/Sage.toml +0 -2
- package/.codex/agents/Vibe.toml +0 -2
- package/.codex/hooks.json +14 -13
- package/.codex/prompts/enforcement.md +4 -0
- package/.codex/prompts/skill-contract.md +4 -0
- package/COVERAGE.md +211 -0
- package/DEMO.md +265 -0
- package/README.md +287 -37
- package/bin/ma.js +232 -71
- package/data/clone-data.ledger.json +41 -0
- package/data/clone-data.proof.json +50 -0
- package/data/clone-data.rvf +37 -0
- package/docs/README.md +7 -1
- package/docs/getting-started.md +108 -37
- package/docs/installed-sdk.md +23 -0
- package/docs/mcp-setup.md +65 -1
- package/docs/onboarding.md +18 -2
- package/docs/prompt-guidance-contract.md +17 -0
- package/docs/prompt-guidance-fragments/active-autonomy-core.md +7 -0
- package/docs/qa/release-issue-gates-0.1.13.json +644 -0
- package/docs/qa/release-readiness-0.1.13.md +116 -0
- package/docs/qa/release-readiness-0.1.5.md +1 -1
- package/docs/reference/native-engineering-patterns.md +35 -0
- package/docs/reference/native-security-playbooks.md +27 -0
- package/docs/reference/native-source-selection.md +27 -0
- package/docs/reference/native-style-and-deslop.md +20 -0
- package/docs/release-spec.md +37 -10
- package/docs/skills-publishing.md +25 -1
- package/docs/skills.md +37 -12
- package/index.js +222 -2
- package/mcp/collections.json +23 -6
- package/mcp/local/code-intel.js +113 -0
- package/mcp/local/memory.js +46 -0
- package/mcp/local/playbooks.js +168 -0
- package/mcp/local/state.js +71 -0
- package/mcp/local/team-run.js +113 -0
- package/mcp/local/trace.js +60 -0
- package/mcp/local-capabilities.json +56 -0
- package/mcp/native-playbooks.json +117 -0
- package/mcp/servers.json +34 -0
- package/package.json +8 -2
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.codex-plugin/plugin.json +4 -4
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +34 -1
- package/plugins/meta-architect/obsidian/main.js +401 -0
- package/plugins/meta-architect/obsidian/manifest.json +9 -0
- package/plugins/meta-architect/obsidian/styles.css +7 -0
- package/plugins/meta-architect/skills/align/SKILL.md +24 -0
- package/plugins/meta-architect/skills/align/agents/openai.yaml +4 -0
- package/plugins/meta-architect/skills/align/references/shared-language.md +24 -0
- package/plugins/meta-architect/skills/arch/SKILL.md +2 -0
- package/plugins/meta-architect/skills/build/SKILL.md +3 -0
- package/plugins/meta-architect/skills/cleanup/SKILL.md +23 -0
- package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +4 -0
- package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +18 -0
- package/plugins/meta-architect/skills/diagnose/SKILL.md +24 -0
- package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +4 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +2 -0
- package/plugins/meta-architect/skills/maestro/SKILL.md +36 -3
- package/plugins/meta-architect/skills/maestro/agents/openai.yaml +2 -2
- package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +44 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +5 -1
- package/plugins/meta-architect/skills/sage/references/source-selection.md +36 -0
- package/plugins/meta-architect/skills/tdd/SKILL.md +24 -0
- package/plugins/meta-architect/skills/tdd/agents/openai.yaml +4 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +5 -1
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +30 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +2 -0
- package/scripts/active-autonomy-hook.mjs +96 -0
- package/scripts/build-linux-packages.mjs +328 -0
- package/scripts/doctor.js +36 -4
- package/scripts/install.sh +28 -0
- package/scripts/linux-package-lib.mjs +40 -0
- package/scripts/linux-package-smoke.mjs +103 -0
- package/scripts/ralph/prompt.md +35 -0
- package/scripts/release-sync.js +13 -6
- package/scripts/release-verify.js +166 -1
- package/skills/align/SKILL.md +24 -0
- package/skills/align/agents/openai.yaml +4 -0
- package/skills/align/references/shared-language.md +24 -0
- package/skills/arch/SKILL.md +2 -0
- package/skills/build/SKILL.md +3 -0
- package/skills/cleanup/SKILL.md +23 -0
- package/skills/cleanup/agents/openai.yaml +4 -0
- package/skills/cleanup/references/style-and-deslop.md +18 -0
- package/skills/diagnose/SKILL.md +24 -0
- package/skills/diagnose/agents/openai.yaml +4 -0
- package/skills/flow/SKILL.md +2 -0
- package/skills/index.json +21 -6
- package/skills/maestro/SKILL.md +36 -3
- package/skills/maestro/agents/openai.yaml +2 -2
- package/skills/maestro/references/native-ingest-map.md +44 -0
- package/skills/sage/SKILL.md +5 -1
- package/skills/sage/references/source-selection.md +36 -0
- package/skills/tdd/SKILL.md +24 -0
- package/skills/tdd/agents/openai.yaml +4 -0
- package/skills/vet/SKILL.md +5 -1
- package/skills/vet/references/security-playbooks.md +30 -0
- package/skills/vibe/SKILL.md +2 -0
- package/src/bootstrap.js +141 -24
- package/src/build-gate.js +2 -2
- package/src/decision-log.js +12 -9
- package/src/launcher.js +4 -0
- package/src/mcp-config.js +130 -8
- package/src/mcp-live-client.js +289 -3
- package/src/paths.js +37 -1
- package/src/policy.js +1 -1
- package/src/release-issue-gates.js +190 -0
- package/src/release-state.js +30 -1
- package/src/runtime/active-autonomy-core.js +208 -0
- package/src/runtime/alignment-sentinel.js +165 -0
- package/src/runtime/architect-review.js +88 -0
- package/src/runtime/build-readiness.js +62 -0
- package/src/runtime/code-graph-rehearse.js +113 -0
- package/src/runtime/context-economy-core.js +276 -0
- package/src/runtime/continuity-notes.js +79 -0
- package/src/runtime/core-source-ingest.js +379 -0
- package/src/runtime/detached-provider.js +74 -0
- package/src/runtime/environment-awareness-core.js +460 -0
- package/src/runtime/exposure-catalog.js +276 -0
- package/src/runtime/guidance-stack.js +42 -0
- package/src/runtime/helper-orchestration-core.js +307 -0
- package/src/runtime/learning-loop-core.js +238 -0
- package/src/runtime/maestro-events.js +18 -0
- package/src/runtime/maestro-manager.js +605 -0
- package/src/runtime/maestro-state.js +125 -0
- package/src/runtime/mcp-policy.js +192 -0
- package/src/runtime/obsidian-integration-core.js +851 -0
- package/src/runtime/obsidian-plugin-bridge.js +739 -0
- package/src/runtime/orchestrator.js +158 -0
- package/src/runtime/prompt-strategy-core.js +230 -0
- package/src/runtime/quorum-review.js +90 -0
- package/src/runtime/ralph-execution-core.js +217 -0
- package/src/runtime/redaction-gateway.js +174 -0
- package/src/runtime/runtime-state.js +1223 -0
- package/src/runtime/semantic-recording-core.js +147 -0
- package/src/runtime/signal-hooks.js +67 -0
- package/src/runtime/skills-registry-export.js +670 -0
- package/src/runtime/startup-path.js +14 -0
- package/src/runtime/universal-plugin-broker-core.js +575 -0
- package/src/runtime/workspace-intelligence-runtime.js +674 -0
- package/src/runtime/workspace-virtualizer.js +102 -0
- package/src/runtime/workspaces.js +25 -0
- package/src/runtime-artifacts.js +407 -84
- package/src/skill-installer.js +53 -5
- package/src/skills.js +1454 -78
- package/src/state-sync.js +51 -8
- package/docs/qa/release-readiness-0.1.11.md +0 -79
- package/plugins/meta-architect/skills/meta-architect/SKILL.md +0 -32
- package/plugins/meta-architect/skills/meta-architect/agents/openai.yaml +0 -4
- package/skills/meta-architect/SKILL.md +0 -32
- package/skills/meta-architect/agents/openai.yaml +0 -4
- /package/plugins/meta-architect/skills/{meta-architect → maestro}/references/core-release-rules.md +0 -0
- /package/skills/{meta-architect → maestro}/references/core-release-rules.md +0 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
export const obsidianPluginId = "meta-architect";
|
|
7
|
+
export const obsidianPluginName = "Meta-Architect";
|
|
8
|
+
export const obsidianPluginSchemaVersion = "0.1.0";
|
|
9
|
+
export const obsidianPluginQueuePath = "Meta-Architect/Plugin Requests/request-queue.json";
|
|
10
|
+
export const obsidianPluginActiveContextPath =
|
|
11
|
+
"Meta-Architect/Plugin Context/Active Note Context.md";
|
|
12
|
+
export const obsidianPluginAttachmentDir = "Meta-Architect/Attachments";
|
|
13
|
+
|
|
14
|
+
const forbiddenMaTargets = [".ma/release.json", ".ma/decisions.json", ".ma/plans/", ".ma/specs/"];
|
|
15
|
+
|
|
16
|
+
const pluginCapabilityNames = [
|
|
17
|
+
"in_app_plugin_surface",
|
|
18
|
+
"active_note_selection_context",
|
|
19
|
+
"metadata_cache_deep_graph",
|
|
20
|
+
"frontmatter_authority_layer",
|
|
21
|
+
"link_safe_rename_move",
|
|
22
|
+
"protocol_handler",
|
|
23
|
+
"event_watchers",
|
|
24
|
+
"canvas_visual_workspace_context",
|
|
25
|
+
"attachments_binary_resources",
|
|
26
|
+
"plugin_request_queue",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export function createDefaultObsidianPluginBridgeManifest() {
|
|
30
|
+
return {
|
|
31
|
+
schemaVersion: obsidianPluginSchemaVersion,
|
|
32
|
+
plugin_id: obsidianPluginId,
|
|
33
|
+
plugin_name: obsidianPluginName,
|
|
34
|
+
records_as: "vault_context",
|
|
35
|
+
build_evidence: false,
|
|
36
|
+
semantic_role: "brain_context",
|
|
37
|
+
canonical_queue_path: obsidianPluginQueuePath,
|
|
38
|
+
active_context_note_path: obsidianPluginActiveContextPath,
|
|
39
|
+
attachment_directory: obsidianPluginAttachmentDir,
|
|
40
|
+
capabilities: pluginCapabilityNames.map((capability) => ({
|
|
41
|
+
capability,
|
|
42
|
+
enabled_by_default: true,
|
|
43
|
+
records_as: "vault_context",
|
|
44
|
+
build_evidence: false,
|
|
45
|
+
})),
|
|
46
|
+
authority_boundary: {
|
|
47
|
+
authoritative_changes_return_through: "$maestro_or_owning_lane",
|
|
48
|
+
must_not_mutate: [...forbiddenMaTargets],
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function installObsidianPlugin({
|
|
54
|
+
vaultPath,
|
|
55
|
+
pluginId = obsidianPluginId,
|
|
56
|
+
packageRoot = getPackageRoot(),
|
|
57
|
+
enable = true,
|
|
58
|
+
} = {}) {
|
|
59
|
+
if (!vaultPath || typeof vaultPath !== "string") {
|
|
60
|
+
throw new Error("Obsidian plugin install requires vaultPath");
|
|
61
|
+
}
|
|
62
|
+
const resolvedVaultPath = path.resolve(vaultPath);
|
|
63
|
+
const vaultStat = await fs.stat(resolvedVaultPath);
|
|
64
|
+
if (!vaultStat.isDirectory()) {
|
|
65
|
+
throw new Error(`Obsidian vault path is not a directory: ${resolvedVaultPath}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const sourceDir = path.join(packageRoot, "plugins", "meta-architect", "obsidian");
|
|
69
|
+
const targetDir = path.join(resolvedVaultPath, ".obsidian", "plugins", pluginId);
|
|
70
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
71
|
+
await fs.cp(sourceDir, targetDir, { recursive: true, force: true });
|
|
72
|
+
|
|
73
|
+
const manifest = JSON.parse(await fs.readFile(path.join(targetDir, "manifest.json"), "utf8"));
|
|
74
|
+
if (manifest.id !== pluginId) {
|
|
75
|
+
throw new Error(`Installed Obsidian plugin manifest id mismatch: ${manifest.id}`);
|
|
76
|
+
}
|
|
77
|
+
const enabledPlugins = enable
|
|
78
|
+
? await enableObsidianCommunityPlugin({ vaultPath: resolvedVaultPath, pluginId })
|
|
79
|
+
: null;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
record_type: "obsidian_plugin_install",
|
|
83
|
+
records_as: "vault_context",
|
|
84
|
+
build_evidence: false,
|
|
85
|
+
enabled: enable,
|
|
86
|
+
community_plugins: enabledPlugins,
|
|
87
|
+
plugin_id: pluginId,
|
|
88
|
+
vault_path: resolvedVaultPath,
|
|
89
|
+
plugin_path: targetDir,
|
|
90
|
+
installed_files: await listPluginFiles(targetDir),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function registerMetaArchitectObsidianPluginRuntime({
|
|
95
|
+
plugin,
|
|
96
|
+
app = plugin?.app,
|
|
97
|
+
Notice,
|
|
98
|
+
Modal,
|
|
99
|
+
PluginSettingTab,
|
|
100
|
+
Setting,
|
|
101
|
+
MarkdownView,
|
|
102
|
+
settings = {},
|
|
103
|
+
} = {}) {
|
|
104
|
+
if (!plugin || !app) {
|
|
105
|
+
throw new Error("Obsidian plugin runtime registration requires plugin and app");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const statusBar = plugin.addStatusBarItem?.();
|
|
109
|
+
statusBar?.setText?.("MA: vault context");
|
|
110
|
+
|
|
111
|
+
plugin.addRibbonIcon?.("network", "Send current note/selection to Meta-Architect", async () => {
|
|
112
|
+
await captureActiveNoteToVault({ app, Notice, MarkdownView, settings });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
plugin.addCommand?.({
|
|
116
|
+
id: "ma-capture-active-note",
|
|
117
|
+
name: "Send current note/selection to MA",
|
|
118
|
+
callback: async () => {
|
|
119
|
+
await captureActiveNoteToVault({ app, Notice, MarkdownView, settings });
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
plugin.addCommand?.({
|
|
124
|
+
id: "ma-drain-request-queue",
|
|
125
|
+
name: "Drain MA request queue",
|
|
126
|
+
callback: async () => {
|
|
127
|
+
const result = await drainQueueFromVault({ app, MarkdownView, settings });
|
|
128
|
+
if (Notice) {
|
|
129
|
+
new Notice(
|
|
130
|
+
`MA queue drained: ${result.processed.length} processed, ${result.refused.length} refused`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
plugin.addCommand?.({
|
|
137
|
+
id: "ma-open-map",
|
|
138
|
+
name: "Open MA map of content",
|
|
139
|
+
callback: async () => {
|
|
140
|
+
const file = app.vault?.getAbstractFileByPath?.("Meta-Architect/Map of Content.md");
|
|
141
|
+
if (file) await app.workspace?.getLeaf?.(true)?.openFile?.(file);
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (PluginSettingTab && Setting && app) {
|
|
146
|
+
plugin.addSettingTab?.(createMetaArchitectSettingTab(app, plugin, PluginSettingTab, Setting));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const protocol = registerObsidianProtocolHandlers({ plugin, app, MarkdownView, settings });
|
|
150
|
+
const watchers = registerObsidianEventWatchers({
|
|
151
|
+
plugin,
|
|
152
|
+
app,
|
|
153
|
+
onEvent: async () => {
|
|
154
|
+
await drainQueueFromVault({ app, MarkdownView, settings }).catch(() => {});
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
record_type: "obsidian_plugin_runtime_registration",
|
|
160
|
+
records_as: "vault_context",
|
|
161
|
+
build_evidence: false,
|
|
162
|
+
ribbon: true,
|
|
163
|
+
status_bar: Boolean(statusBar),
|
|
164
|
+
commands: ["ma-capture-active-note", "ma-drain-request-queue", "ma-open-map"],
|
|
165
|
+
settings_tab: Boolean(PluginSettingTab && Setting),
|
|
166
|
+
protocol,
|
|
167
|
+
watchers,
|
|
168
|
+
modal_available: Boolean(Modal),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function createObsidianSelectionContext({ app, MarkdownView } = {}) {
|
|
173
|
+
const file = app?.workspace?.getActiveFile?.() ?? null;
|
|
174
|
+
const view = app?.workspace?.getActiveViewOfType?.(MarkdownView) ?? null;
|
|
175
|
+
const leaf = app?.workspace?.activeLeaf ?? null;
|
|
176
|
+
const editor = view?.editor ?? leaf?.view?.editor ?? null;
|
|
177
|
+
const selection = safeCall(() => editor?.getSelection?.()) ?? "";
|
|
178
|
+
const cursor = safeCall(() => editor?.getCursor?.()) ?? null;
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
record_type: "obsidian_active_note_context",
|
|
182
|
+
records_as: "vault_context",
|
|
183
|
+
build_evidence: false,
|
|
184
|
+
captured_at: new Date().toISOString(),
|
|
185
|
+
active_note_path: file?.path ?? null,
|
|
186
|
+
active_note_basename: file?.basename ?? basenameWithoutExtension(file?.path),
|
|
187
|
+
selected_text: selection,
|
|
188
|
+
selected_text_char_count: selection.length,
|
|
189
|
+
cursor,
|
|
190
|
+
pane_state: safeCall(() => leaf?.getViewState?.()) ?? null,
|
|
191
|
+
view_type: view?.getViewType?.() ?? leaf?.view?.getViewType?.() ?? null,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function createObsidianMetadataGraph({ app, files } = {}) {
|
|
196
|
+
const metadataCache = app?.metadataCache;
|
|
197
|
+
const vault = app?.vault;
|
|
198
|
+
const candidatePaths = files?.map((file) => file.path ?? file).filter(Boolean);
|
|
199
|
+
const cachedPaths = candidatePaths ?? metadataCache?.getCachedFiles?.() ?? [];
|
|
200
|
+
const nodes = [];
|
|
201
|
+
|
|
202
|
+
for (const filePath of cachedPaths) {
|
|
203
|
+
const file = typeof filePath === "string" ? vault?.getAbstractFileByPath?.(filePath) : filePath;
|
|
204
|
+
const resolvedPath = file?.path ?? filePath;
|
|
205
|
+
if (!resolvedPath || typeof resolvedPath !== "string" || !resolvedPath.endsWith(".md")) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const cache = metadataCache?.getFileCache?.(file ?? { path: resolvedPath }) ?? {};
|
|
209
|
+
nodes.push({
|
|
210
|
+
path: resolvedPath,
|
|
211
|
+
records_as: "vault_context",
|
|
212
|
+
build_evidence: false,
|
|
213
|
+
frontmatter: cache.frontmatter ?? {},
|
|
214
|
+
headings: cache.headings ?? [],
|
|
215
|
+
links: cache.links ?? [],
|
|
216
|
+
embeds: cache.embeds ?? [],
|
|
217
|
+
tags: cache.tags ?? [],
|
|
218
|
+
blocks: cache.blocks ?? {},
|
|
219
|
+
sections: cache.sections ?? [],
|
|
220
|
+
resolved_links: metadataCache?.resolvedLinks?.[resolvedPath] ?? {},
|
|
221
|
+
unresolved_links: metadataCache?.unresolvedLinks?.[resolvedPath] ?? {},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
record_type: "obsidian_metadata_cache_graph",
|
|
227
|
+
records_as: "vault_context",
|
|
228
|
+
build_evidence: false,
|
|
229
|
+
indexed_at: new Date().toISOString(),
|
|
230
|
+
note_count: nodes.length,
|
|
231
|
+
nodes,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function applyObsidianFrontmatterAuthority({
|
|
236
|
+
app,
|
|
237
|
+
file,
|
|
238
|
+
project = "Meta-Architect",
|
|
239
|
+
capabilities = pluginCapabilityNames,
|
|
240
|
+
sourceWorkspace = null,
|
|
241
|
+
} = {}) {
|
|
242
|
+
if (!app?.fileManager?.processFrontMatter) {
|
|
243
|
+
throw new Error("Obsidian FileManager.processFrontMatter is required");
|
|
244
|
+
}
|
|
245
|
+
if (!file?.path) {
|
|
246
|
+
throw new Error("Obsidian frontmatter authority requires a file");
|
|
247
|
+
}
|
|
248
|
+
await app.fileManager.processFrontMatter(file, (frontmatter) => {
|
|
249
|
+
frontmatter.ma_records_as = "vault_context";
|
|
250
|
+
frontmatter.ma_project = project;
|
|
251
|
+
frontmatter.ma_capabilities = [...new Set(capabilities)];
|
|
252
|
+
if (sourceWorkspace) frontmatter.ma_source_workspace = sourceWorkspace;
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
record_type: "obsidian_frontmatter_authority",
|
|
256
|
+
records_as: "vault_context",
|
|
257
|
+
build_evidence: false,
|
|
258
|
+
note_path: file.path,
|
|
259
|
+
ma_project: project,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function renameObsidianFileSafely({ app, file, newPath } = {}) {
|
|
264
|
+
assertSafeVaultPath(newPath);
|
|
265
|
+
if (!app?.fileManager?.renameFile) {
|
|
266
|
+
throw new Error("Obsidian FileManager.renameFile is required for link-safe rename/move");
|
|
267
|
+
}
|
|
268
|
+
if (!file?.path) {
|
|
269
|
+
throw new Error("Obsidian link-safe rename requires a file");
|
|
270
|
+
}
|
|
271
|
+
await app.fileManager.renameFile(file, newPath);
|
|
272
|
+
return {
|
|
273
|
+
record_type: "obsidian_link_safe_rename",
|
|
274
|
+
records_as: "vault_context",
|
|
275
|
+
build_evidence: false,
|
|
276
|
+
from: file.path,
|
|
277
|
+
to: newPath,
|
|
278
|
+
method: "FileManager.renameFile",
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function generateObsidianMarkdownLink({ app, file, sourcePath = "", subpath, alias } = {}) {
|
|
283
|
+
if (!app?.fileManager?.generateMarkdownLink) {
|
|
284
|
+
throw new Error("Obsidian FileManager.generateMarkdownLink is required");
|
|
285
|
+
}
|
|
286
|
+
const link = app.fileManager.generateMarkdownLink(file, sourcePath, subpath, alias);
|
|
287
|
+
return {
|
|
288
|
+
record_type: "obsidian_generated_markdown_link",
|
|
289
|
+
records_as: "vault_context",
|
|
290
|
+
build_evidence: false,
|
|
291
|
+
link,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function registerObsidianProtocolHandlers({
|
|
296
|
+
plugin,
|
|
297
|
+
app,
|
|
298
|
+
MarkdownView,
|
|
299
|
+
settings = {},
|
|
300
|
+
} = {}) {
|
|
301
|
+
if (!plugin?.registerObsidianProtocolHandler) {
|
|
302
|
+
return { registered: false, handler: "ma", reason: "protocol_handler_unavailable" };
|
|
303
|
+
}
|
|
304
|
+
plugin.registerObsidianProtocolHandler("ma", async (params = {}) => {
|
|
305
|
+
if (params.action === "queue") {
|
|
306
|
+
await writeQueueRequestToVault({ app, request: params });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (params.action === "capture" || params.open === "active") {
|
|
310
|
+
await captureActiveNoteToVault({ app, MarkdownView, settings });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const target = params.path ?? "Meta-Architect/Map of Content.md";
|
|
314
|
+
const file = app?.vault?.getAbstractFileByPath?.(target);
|
|
315
|
+
if (file) await app?.workspace?.getLeaf?.(true)?.openFile?.(file);
|
|
316
|
+
});
|
|
317
|
+
return { registered: true, handler: "ma", supports: ["open", "queue", "capture"] };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function registerObsidianEventWatchers({ plugin, app, onEvent = () => {} } = {}) {
|
|
321
|
+
const registrations = [];
|
|
322
|
+
const register = (source, eventName, eventRef) => {
|
|
323
|
+
if (!eventRef) return;
|
|
324
|
+
plugin?.registerEvent?.(eventRef);
|
|
325
|
+
registrations.push({ source, event: eventName });
|
|
326
|
+
};
|
|
327
|
+
const vault = app?.vault;
|
|
328
|
+
const metadataCache = app?.metadataCache;
|
|
329
|
+
const workspace = app?.workspace;
|
|
330
|
+
|
|
331
|
+
for (const eventName of ["create", "modify", "delete", "rename"]) {
|
|
332
|
+
register(
|
|
333
|
+
"vault",
|
|
334
|
+
eventName,
|
|
335
|
+
vault?.on?.(eventName, (...args) => onEvent({ source: "vault", eventName, args })),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
for (const eventName of ["resolved", "changed", "deleted"]) {
|
|
339
|
+
register(
|
|
340
|
+
"metadataCache",
|
|
341
|
+
eventName,
|
|
342
|
+
metadataCache?.on?.(eventName, (...args) =>
|
|
343
|
+
onEvent({ source: "metadataCache", eventName, args }),
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
for (const eventName of ["active-leaf-change", "layout-change"]) {
|
|
348
|
+
register(
|
|
349
|
+
"workspace",
|
|
350
|
+
eventName,
|
|
351
|
+
workspace?.on?.(eventName, (...args) => onEvent({ source: "workspace", eventName, args })),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
record_type: "obsidian_event_watchers",
|
|
357
|
+
records_as: "vault_context",
|
|
358
|
+
build_evidence: false,
|
|
359
|
+
registered_count: registrations.length,
|
|
360
|
+
registrations,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function extractObsidianCanvasContext({ app, files } = {}) {
|
|
365
|
+
const vault = app?.vault;
|
|
366
|
+
const candidatePaths =
|
|
367
|
+
files?.map((file) => file.path ?? file).filter(Boolean) ??
|
|
368
|
+
app?.metadataCache?.getCachedFiles?.()?.filter((filePath) => filePath.endsWith(".canvas")) ??
|
|
369
|
+
[];
|
|
370
|
+
const canvases = [];
|
|
371
|
+
|
|
372
|
+
for (const filePath of candidatePaths) {
|
|
373
|
+
if (!filePath.endsWith(".canvas")) continue;
|
|
374
|
+
const file = vault?.getAbstractFileByPath?.(filePath) ?? { path: filePath };
|
|
375
|
+
const raw = await vault?.read?.(file);
|
|
376
|
+
if (typeof raw !== "string") continue;
|
|
377
|
+
const parsed = JSON.parse(raw);
|
|
378
|
+
canvases.push({
|
|
379
|
+
path: filePath,
|
|
380
|
+
records_as: "vault_context",
|
|
381
|
+
build_evidence: false,
|
|
382
|
+
nodes: parsed.nodes ?? [],
|
|
383
|
+
edges: parsed.edges ?? [],
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
record_type: "obsidian_canvas_context",
|
|
389
|
+
records_as: "vault_context",
|
|
390
|
+
build_evidence: false,
|
|
391
|
+
canvas_count: canvases.length,
|
|
392
|
+
canvases,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export async function writeObsidianAttachment({
|
|
397
|
+
app,
|
|
398
|
+
attachmentPath,
|
|
399
|
+
data,
|
|
400
|
+
contentType = "application/octet-stream",
|
|
401
|
+
} = {}) {
|
|
402
|
+
assertSafeVaultPath(attachmentPath);
|
|
403
|
+
if (!attachmentPath) {
|
|
404
|
+
throw new Error("Obsidian attachment write requires attachmentPath");
|
|
405
|
+
}
|
|
406
|
+
const bytes = toUint8Array(data);
|
|
407
|
+
const parent = attachmentPath.includes("/")
|
|
408
|
+
? attachmentPath.slice(0, attachmentPath.lastIndexOf("/"))
|
|
409
|
+
: "";
|
|
410
|
+
if (parent) await ensureVaultFolder(app, parent);
|
|
411
|
+
|
|
412
|
+
if (app?.vault?.createBinary) {
|
|
413
|
+
await app.vault.createBinary(
|
|
414
|
+
attachmentPath,
|
|
415
|
+
bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength),
|
|
416
|
+
);
|
|
417
|
+
} else if (app?.vault?.adapter?.writeBinary) {
|
|
418
|
+
await app.vault.adapter.writeBinary(attachmentPath, bytes);
|
|
419
|
+
} else {
|
|
420
|
+
throw new Error("Obsidian Vault.createBinary or DataAdapter.writeBinary is required");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
record_type: "obsidian_attachment_write",
|
|
425
|
+
records_as: "vault_context",
|
|
426
|
+
build_evidence: false,
|
|
427
|
+
attachment_path: attachmentPath,
|
|
428
|
+
content_type: contentType,
|
|
429
|
+
byte_length: bytes.byteLength,
|
|
430
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export async function drainObsidianPluginRequestQueue({
|
|
435
|
+
app,
|
|
436
|
+
queue = [],
|
|
437
|
+
MarkdownView,
|
|
438
|
+
settings = {},
|
|
439
|
+
} = {}) {
|
|
440
|
+
if (!Array.isArray(queue)) {
|
|
441
|
+
throw new Error("Obsidian plugin request queue must be an array");
|
|
442
|
+
}
|
|
443
|
+
const processed = [];
|
|
444
|
+
const refused = [];
|
|
445
|
+
|
|
446
|
+
for (const request of queue) {
|
|
447
|
+
try {
|
|
448
|
+
if (!request || typeof request !== "object") {
|
|
449
|
+
throw new Error("invalid_request");
|
|
450
|
+
}
|
|
451
|
+
const target = request.note_path ?? request.path ?? "";
|
|
452
|
+
if (target) assertSafeVaultPath(target);
|
|
453
|
+
switch (request.action) {
|
|
454
|
+
case "capture_active_note":
|
|
455
|
+
processed.push({
|
|
456
|
+
request_id: request.id ?? null,
|
|
457
|
+
action: request.action,
|
|
458
|
+
result: await captureActiveNoteToVault({ app, MarkdownView, settings }),
|
|
459
|
+
});
|
|
460
|
+
break;
|
|
461
|
+
case "create_note":
|
|
462
|
+
processed.push({
|
|
463
|
+
request_id: request.id ?? null,
|
|
464
|
+
action: request.action,
|
|
465
|
+
result: await createVaultContextNote({
|
|
466
|
+
app,
|
|
467
|
+
notePath: target,
|
|
468
|
+
content: request.content,
|
|
469
|
+
}),
|
|
470
|
+
});
|
|
471
|
+
break;
|
|
472
|
+
case "index_vault":
|
|
473
|
+
processed.push({
|
|
474
|
+
request_id: request.id ?? null,
|
|
475
|
+
action: request.action,
|
|
476
|
+
result: createObsidianMetadataGraph({ app }),
|
|
477
|
+
});
|
|
478
|
+
break;
|
|
479
|
+
case "rename_note":
|
|
480
|
+
processed.push({
|
|
481
|
+
request_id: request.id ?? null,
|
|
482
|
+
action: request.action,
|
|
483
|
+
result: await renameObsidianFileSafely({
|
|
484
|
+
app,
|
|
485
|
+
file: app?.vault?.getAbstractFileByPath?.(request.from),
|
|
486
|
+
newPath: request.to,
|
|
487
|
+
}),
|
|
488
|
+
});
|
|
489
|
+
break;
|
|
490
|
+
case "write_attachment":
|
|
491
|
+
processed.push({
|
|
492
|
+
request_id: request.id ?? null,
|
|
493
|
+
action: request.action,
|
|
494
|
+
result: await writeObsidianAttachment({
|
|
495
|
+
app,
|
|
496
|
+
attachmentPath: target,
|
|
497
|
+
data: request.data ?? "",
|
|
498
|
+
contentType: request.content_type,
|
|
499
|
+
}),
|
|
500
|
+
});
|
|
501
|
+
break;
|
|
502
|
+
default:
|
|
503
|
+
throw new Error(`unsupported_action:${request.action}`);
|
|
504
|
+
}
|
|
505
|
+
} catch (error) {
|
|
506
|
+
refused.push({
|
|
507
|
+
request_id: request?.id ?? null,
|
|
508
|
+
action: request?.action ?? null,
|
|
509
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
510
|
+
records_as: "vault_context",
|
|
511
|
+
build_evidence: false,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
record_type: "obsidian_plugin_queue_drain",
|
|
518
|
+
records_as: "vault_context",
|
|
519
|
+
build_evidence: false,
|
|
520
|
+
drained_at: new Date().toISOString(),
|
|
521
|
+
processed,
|
|
522
|
+
refused,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function captureActiveNoteToVault({ app, Notice, MarkdownView, settings = {} } = {}) {
|
|
527
|
+
const context = createObsidianSelectionContext({ app, MarkdownView });
|
|
528
|
+
const activeFile = app?.workspace?.getActiveFile?.();
|
|
529
|
+
if (activeFile) {
|
|
530
|
+
await applyObsidianFrontmatterAuthority({
|
|
531
|
+
app,
|
|
532
|
+
file: activeFile,
|
|
533
|
+
project: settings.projectName ?? "Meta-Architect",
|
|
534
|
+
sourceWorkspace: settings.sourceWorkspace ?? null,
|
|
535
|
+
}).catch(() => {});
|
|
536
|
+
}
|
|
537
|
+
const content = renderActiveContextNote(context);
|
|
538
|
+
const result = await createVaultContextNote({
|
|
539
|
+
app,
|
|
540
|
+
notePath: settings.activeContextPath ?? obsidianPluginActiveContextPath,
|
|
541
|
+
content,
|
|
542
|
+
overwrite: true,
|
|
543
|
+
});
|
|
544
|
+
if (Notice) {
|
|
545
|
+
new Notice("MA captured current Obsidian context");
|
|
546
|
+
}
|
|
547
|
+
return { ...result, active_context: context };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function drainQueueFromVault({ app, MarkdownView, settings = {} }) {
|
|
551
|
+
const queuePath = settings.queuePath ?? obsidianPluginQueuePath;
|
|
552
|
+
const file = app?.vault?.getAbstractFileByPath?.(queuePath);
|
|
553
|
+
if (!file) {
|
|
554
|
+
return drainObsidianPluginRequestQueue({ app, queue: [], MarkdownView, settings });
|
|
555
|
+
}
|
|
556
|
+
const raw = await app.vault.read(file);
|
|
557
|
+
const queue = JSON.parse(raw);
|
|
558
|
+
const result = await drainObsidianPluginRequestQueue({ app, queue, MarkdownView, settings });
|
|
559
|
+
await app.vault.modify(file, "[]\n");
|
|
560
|
+
return result;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async function writeQueueRequestToVault({ app, request }) {
|
|
564
|
+
const queuePath = obsidianPluginQueuePath;
|
|
565
|
+
await ensureVaultFolder(app, path.dirname(queuePath));
|
|
566
|
+
const file = app?.vault?.getAbstractFileByPath?.(queuePath);
|
|
567
|
+
const existing = file ? JSON.parse(await app.vault.read(file)) : [];
|
|
568
|
+
existing.push({
|
|
569
|
+
...request,
|
|
570
|
+
id: request.id ?? `ma-${Date.now()}`,
|
|
571
|
+
queued_at: new Date().toISOString(),
|
|
572
|
+
records_as: "vault_context",
|
|
573
|
+
});
|
|
574
|
+
const content = `${JSON.stringify(existing, null, 2)}\n`;
|
|
575
|
+
if (file) await app.vault.modify(file, content);
|
|
576
|
+
else await app.vault.create(queuePath, content);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async function createVaultContextNote({ app, notePath, content, overwrite = true }) {
|
|
580
|
+
assertSafeVaultPath(notePath);
|
|
581
|
+
if (typeof content !== "string" || content.trim().length === 0) {
|
|
582
|
+
throw new Error("vault_context note content required");
|
|
583
|
+
}
|
|
584
|
+
await ensureVaultFolder(app, path.dirname(notePath));
|
|
585
|
+
const normalizedContent = ensureVaultContextFrontmatter(content);
|
|
586
|
+
const existing = app?.vault?.getAbstractFileByPath?.(notePath);
|
|
587
|
+
if (existing && overwrite) {
|
|
588
|
+
await app.vault.modify(existing, normalizedContent);
|
|
589
|
+
} else if (existing) {
|
|
590
|
+
throw new Error(`note_exists:${notePath}`);
|
|
591
|
+
} else {
|
|
592
|
+
await app.vault.create(notePath, normalizedContent);
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
record_type: "obsidian_vault_context_note_write",
|
|
596
|
+
records_as: "vault_context",
|
|
597
|
+
build_evidence: false,
|
|
598
|
+
note_path: notePath,
|
|
599
|
+
char_count: normalizedContent.length,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function ensureVaultFolder(app, folderPath) {
|
|
604
|
+
if (!folderPath || folderPath === ".") return;
|
|
605
|
+
const segments = folderPath.split("/").filter(Boolean);
|
|
606
|
+
let current = "";
|
|
607
|
+
for (const segment of segments) {
|
|
608
|
+
current = current ? `${current}/${segment}` : segment;
|
|
609
|
+
if (!app?.vault?.getAbstractFileByPath?.(current)) {
|
|
610
|
+
await app?.vault?.createFolder?.(current).catch(() => {});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function renderActiveContextNote(context) {
|
|
616
|
+
return ensureVaultContextFrontmatter(`# Active Note Context
|
|
617
|
+
|
|
618
|
+
- Active note: ${context.active_note_path ? `[[${context.active_note_path.replace(/\.md$/i, "")}]]` : "none"}
|
|
619
|
+
- View type: ${context.view_type ?? "unknown"}
|
|
620
|
+
- Cursor: ${JSON.stringify(context.cursor)}
|
|
621
|
+
- Selection chars: ${context.selected_text_char_count}
|
|
622
|
+
|
|
623
|
+
## Selected Text
|
|
624
|
+
|
|
625
|
+
${context.selected_text || "_No active selection captured._"}
|
|
626
|
+
|
|
627
|
+
## Pane State
|
|
628
|
+
|
|
629
|
+
\`\`\`json
|
|
630
|
+
${JSON.stringify(context.pane_state ?? {}, null, 2)}
|
|
631
|
+
\`\`\`
|
|
632
|
+
`);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function ensureVaultContextFrontmatter(content) {
|
|
636
|
+
if (content.trimStart().startsWith("---")) {
|
|
637
|
+
return content.replace(
|
|
638
|
+
/^---\n/,
|
|
639
|
+
"---\nma_records_as: vault_context\nma_project: Meta-Architect\n",
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
return `---
|
|
643
|
+
ma_records_as: vault_context
|
|
644
|
+
ma_project: Meta-Architect
|
|
645
|
+
ma_capabilities:
|
|
646
|
+
- obsidian_integration_core
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
${content}`;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function assertSafeVaultPath(targetPath) {
|
|
653
|
+
if (!targetPath || typeof targetPath !== "string") {
|
|
654
|
+
throw new Error("vault_path_required");
|
|
655
|
+
}
|
|
656
|
+
const normalized = targetPath.replaceAll("\\", "/").replace(/^\/+/, "");
|
|
657
|
+
if (normalized.includes("../") || normalized === ".." || normalized.startsWith("..")) {
|
|
658
|
+
throw new Error("path_traversal_forbidden");
|
|
659
|
+
}
|
|
660
|
+
for (const forbidden of forbiddenMaTargets) {
|
|
661
|
+
if (normalized === forbidden || normalized.startsWith(forbidden)) {
|
|
662
|
+
throw new Error(`forbidden_authoritative_mutation:${forbidden}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function toUint8Array(data) {
|
|
668
|
+
if (data instanceof Uint8Array) return data;
|
|
669
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
670
|
+
if (ArrayBuffer.isView(data)) {
|
|
671
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
672
|
+
}
|
|
673
|
+
return new TextEncoder().encode(String(data ?? ""));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function safeCall(fn) {
|
|
677
|
+
try {
|
|
678
|
+
return fn();
|
|
679
|
+
} catch {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function basenameWithoutExtension(filePath) {
|
|
685
|
+
if (!filePath) return null;
|
|
686
|
+
return path.basename(filePath, path.extname(filePath));
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function getPackageRoot() {
|
|
690
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async function listPluginFiles(root) {
|
|
694
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
695
|
+
const files = [];
|
|
696
|
+
for (const entry of entries) {
|
|
697
|
+
const absolutePath = path.join(root, entry.name);
|
|
698
|
+
if (entry.isDirectory()) {
|
|
699
|
+
const nested = await listPluginFiles(absolutePath);
|
|
700
|
+
files.push(...nested.map((file) => `${entry.name}/${file}`));
|
|
701
|
+
} else {
|
|
702
|
+
files.push(entry.name);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return files.sort();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async function enableObsidianCommunityPlugin({ vaultPath, pluginId }) {
|
|
709
|
+
const configPath = path.join(vaultPath, ".obsidian", "community-plugins.json");
|
|
710
|
+
let enabled = [];
|
|
711
|
+
try {
|
|
712
|
+
const parsed = JSON.parse(await fs.readFile(configPath, "utf8"));
|
|
713
|
+
if (Array.isArray(parsed)) enabled = parsed.filter((entry) => typeof entry === "string");
|
|
714
|
+
} catch {}
|
|
715
|
+
if (!enabled.includes(pluginId)) {
|
|
716
|
+
enabled.push(pluginId);
|
|
717
|
+
}
|
|
718
|
+
await fs.writeFile(configPath, `${JSON.stringify(enabled, null, 2)}\n`, "utf8");
|
|
719
|
+
return enabled;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function createMetaArchitectSettingTab(app, plugin, PluginSettingTab, Setting) {
|
|
723
|
+
const instance = new PluginSettingTab(app, plugin);
|
|
724
|
+
instance.display = () => {
|
|
725
|
+
const { containerEl } = instance;
|
|
726
|
+
containerEl.empty?.();
|
|
727
|
+
containerEl.createEl?.("h2", { text: "Meta-Architect" });
|
|
728
|
+
new Setting(containerEl)
|
|
729
|
+
.setName("Vault context mode")
|
|
730
|
+
.setDesc("Obsidian notes are recorded as vault_context, never build_evidence.")
|
|
731
|
+
.addText?.((text) =>
|
|
732
|
+
text
|
|
733
|
+
.setPlaceholder("Meta-Architect")
|
|
734
|
+
.setValue?.("Meta-Architect")
|
|
735
|
+
.onChange?.(async () => {}),
|
|
736
|
+
);
|
|
737
|
+
};
|
|
738
|
+
return instance;
|
|
739
|
+
}
|