dot-studio 0.0.1 → 0.0.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.
Files changed (148) hide show
  1. package/README.md +20 -200
  2. package/client/assets/ActFrame-BYOBkLYW.js +1 -0
  3. package/client/assets/ActFrame-C_WEt6bv.css +1 -0
  4. package/client/assets/ActInspectorPanel-C3VlS7tB.js +1 -0
  5. package/client/assets/ActInspectorPanel-CE6s6GYv.css +1 -0
  6. package/client/assets/AssistantChat-BOyW0K79.js +1 -0
  7. package/client/assets/AssistantChat-DoVmHvMJ.css +1 -0
  8. package/client/assets/CanvasTerminalFrame-BC-79q9U.css +1 -0
  9. package/client/assets/CanvasTerminalFrame-DxKbexK6.js +4 -0
  10. package/client/assets/CanvasTrackingFrame-DumxhNwg.js +1 -0
  11. package/client/assets/CanvasTrackingFrame-G4rRrfne.css +1 -0
  12. package/client/assets/CanvasWindowFrame-ziJeVfHG.js +1 -0
  13. package/client/assets/DanceBundleEditorFrame-CH8VDUMK.js +1 -0
  14. package/client/assets/DanceBundleEditorFrame-DaLqMflT.css +1 -0
  15. package/client/assets/MarkdownEditorFrame-DVecIZpZ.css +1 -0
  16. package/client/assets/MarkdownEditorFrame-Dwpgs2GX.js +2 -0
  17. package/client/assets/MarkdownRenderer-Cz8A4AgP.js +1 -0
  18. package/client/assets/PublishModal-DUlHz0fT.js +1 -0
  19. package/client/assets/TodoDock-DcVf7zQG.js +1 -0
  20. package/client/assets/WorkspaceToolbar-CXYi_sMD.js +2 -0
  21. package/client/assets/WorkspaceToolbar-CiQvVocC.css +1 -0
  22. package/client/assets/chat-message-visibility-YwJ-AQno.js +11 -0
  23. package/client/assets/dnd-vendor-CIAZE2P2.js +5 -0
  24. package/client/assets/flow-vendor-BZV40eAE.css +1 -0
  25. package/client/assets/flow-vendor-C868rU-6.js +23 -0
  26. package/client/assets/icon-vendor-I2JVIi1s.js +501 -0
  27. package/client/assets/index-BMY4hrBP.js +3 -0
  28. package/client/assets/index-C-vnj9y3.js +1 -0
  29. package/client/assets/index-C9HTqfZw.css +1 -0
  30. package/client/assets/index-CWrv6O3o.js +64 -0
  31. package/client/assets/index-DMS12-Q2.js +8 -0
  32. package/client/assets/index-Dn7t_Y7G.js +1 -0
  33. package/client/assets/index-p-wk7iGH.css +1 -0
  34. package/client/assets/markdown-vendor-BSTcku12.css +10 -0
  35. package/client/assets/markdown-vendor-DnTJ9hmR.js +35 -0
  36. package/client/assets/participant-labels-Cf3qP3GB.js +1 -0
  37. package/client/assets/queries-Dm1jEHfc.js +1 -0
  38. package/client/assets/query-vendor-_taqgrbn.js +1 -0
  39. package/client/assets/react-vendor-DzpMUNDT.js +49 -0
  40. package/client/assets/settings-utils-l7KCS3Ev.js +1 -0
  41. package/client/assets/terminal-vendor-6GBZ9nXN.css +32 -0
  42. package/client/assets/terminal-vendor-D0xRnmbI.js +112 -0
  43. package/client/index.html +13 -3
  44. package/dist/cli.js +25 -3
  45. package/dist/server/app.js +72 -0
  46. package/dist/server/index.js +2 -62
  47. package/dist/server/lib/act-session-policy.js +31 -0
  48. package/dist/server/lib/chat-session.js +101 -0
  49. package/dist/server/lib/config.js +18 -4
  50. package/dist/server/lib/dot-authoring.js +171 -102
  51. package/dist/server/lib/dot-loader.js +9 -8
  52. package/dist/server/lib/dot-login.js +8 -190
  53. package/dist/server/lib/dot-source.js +11 -0
  54. package/dist/server/lib/model-catalog.js +74 -15
  55. package/dist/server/lib/opencode-auth.js +4 -1
  56. package/dist/server/lib/opencode-errors.js +70 -38
  57. package/dist/server/lib/opencode-sidecar.js +5 -2
  58. package/dist/server/lib/project-config.js +8 -0
  59. package/dist/server/lib/runtime-tools.js +46 -8
  60. package/dist/server/lib/safe-mode.js +410 -0
  61. package/dist/server/lib/session-execution.js +81 -0
  62. package/dist/server/lib/sse.js +22 -0
  63. package/dist/server/routes/act-runtime-threads.js +156 -0
  64. package/dist/server/routes/act-runtime-tools.js +157 -0
  65. package/dist/server/routes/act-runtime.js +7 -0
  66. package/dist/server/routes/adapter.js +32 -0
  67. package/dist/server/routes/assets-collection.js +16 -0
  68. package/dist/server/routes/assets-detail.js +38 -0
  69. package/dist/server/routes/assets.js +4 -158
  70. package/dist/server/routes/chat-messages.js +104 -0
  71. package/dist/server/routes/chat-sessions.js +104 -0
  72. package/dist/server/routes/chat-stream.js +15 -0
  73. package/dist/server/routes/chat.js +6 -353
  74. package/dist/server/routes/compile.js +5 -91
  75. package/dist/server/routes/dot-assets.js +77 -0
  76. package/dist/server/routes/dot-core.js +62 -0
  77. package/dist/server/routes/dot-performer.js +80 -0
  78. package/dist/server/routes/dot.js +6 -267
  79. package/dist/server/routes/drafts-collection.js +40 -0
  80. package/dist/server/routes/drafts-dance-bundle.js +113 -0
  81. package/dist/server/routes/drafts-item.js +86 -0
  82. package/dist/server/routes/drafts.js +9 -0
  83. package/dist/server/routes/health.js +18 -33
  84. package/dist/server/routes/opencode-core.js +120 -0
  85. package/dist/server/routes/opencode-file.js +67 -0
  86. package/dist/server/routes/opencode-mcp.js +74 -0
  87. package/dist/server/routes/opencode-provider.js +41 -0
  88. package/dist/server/routes/opencode.js +8 -418
  89. package/dist/server/routes/route-errors.js +10 -0
  90. package/dist/server/routes/safe-actions.js +60 -0
  91. package/dist/server/routes/safe-summary.js +20 -0
  92. package/dist/server/routes/safe.js +7 -0
  93. package/dist/server/routes/workspaces.js +47 -0
  94. package/dist/server/services/act-runtime/act-context-builder.js +81 -0
  95. package/dist/server/services/act-runtime/act-runtime-service.js +313 -0
  96. package/dist/server/services/act-runtime/act-runtime-utils.js +10 -0
  97. package/dist/server/services/act-runtime/act-tool-projection.js +26 -0
  98. package/dist/server/services/act-runtime/act-tools.js +151 -0
  99. package/dist/server/services/act-runtime/board-persistence.js +38 -0
  100. package/dist/server/services/act-runtime/event-logger.js +73 -0
  101. package/dist/server/services/act-runtime/event-router.js +102 -0
  102. package/dist/server/services/act-runtime/mailbox.js +149 -0
  103. package/dist/server/services/act-runtime/safety-guard.js +162 -0
  104. package/dist/server/services/act-runtime/session-queue.js +114 -0
  105. package/dist/server/services/act-runtime/thread-manager.js +351 -0
  106. package/dist/server/services/act-runtime/wake-cascade.js +306 -0
  107. package/dist/server/services/act-runtime/wake-evaluator.js +43 -0
  108. package/dist/server/services/act-runtime/wake-performer-resolver.js +68 -0
  109. package/dist/server/services/act-runtime/wake-prompt-builder.js +77 -0
  110. package/dist/server/services/adapter-view-service.js +6 -0
  111. package/dist/server/services/asset-service.js +366 -0
  112. package/dist/server/services/chat-event-stream-service.js +157 -0
  113. package/dist/server/services/chat-service.js +207 -0
  114. package/dist/server/services/chat-session-service.js +203 -0
  115. package/dist/server/services/compile-service.js +4 -0
  116. package/dist/server/services/dance-bundle-service.js +222 -0
  117. package/dist/server/services/dot-add-service.js +59 -0
  118. package/dist/server/services/dot-service.js +178 -0
  119. package/dist/server/services/draft-service.js +367 -0
  120. package/dist/server/services/opencode-projection/dance-compiler.js +164 -0
  121. package/dist/server/services/opencode-projection/performer-compiler.js +195 -0
  122. package/dist/server/services/opencode-projection/preview-service.js +31 -0
  123. package/dist/server/services/opencode-projection/projection-manifest.js +98 -0
  124. package/dist/server/services/opencode-projection/stage-projection-service.js +188 -0
  125. package/dist/server/services/opencode-service.js +338 -0
  126. package/dist/server/services/safe-service.js +33 -0
  127. package/dist/server/services/studio-assistant/assistant-service.js +172 -0
  128. package/dist/server/services/studio-service.js +69 -0
  129. package/dist/server/services/workspace-service.js +224 -0
  130. package/dist/server/terminal.js +57 -11
  131. package/dist/shared/act-types.js +4 -0
  132. package/dist/shared/adapter-view.js +1 -0
  133. package/dist/shared/asset-contracts.js +1 -0
  134. package/dist/shared/assistant-actions.js +1 -0
  135. package/dist/shared/chat-contracts.js +1 -0
  136. package/dist/shared/dot-contracts.js +1 -0
  137. package/dist/shared/dot-types.js +4 -0
  138. package/dist/shared/draft-contracts.js +2 -0
  139. package/dist/shared/model-types.js +2 -0
  140. package/dist/shared/performer-mcp-portability.js +10 -0
  141. package/dist/shared/safe-mode.js +1 -0
  142. package/dist/shared/session-metadata.js +4 -3
  143. package/package.json +6 -4
  144. package/client/assets/index-C2eIILoa.css +0 -41
  145. package/client/assets/index-DUPZ_Lw5.js +0 -616
  146. package/dist/server/lib/act-runtime.js +0 -1282
  147. package/dist/server/lib/prompt.js +0 -222
  148. package/dist/server/routes/stages.js +0 -137
@@ -0,0 +1,222 @@
1
+ /**
2
+ * dance-bundle-service.ts — CRUD operations for Dance skill bundles.
3
+ *
4
+ * A Dance bundle is a directory-backed draft that stores:
5
+ * .dance-of-tal/drafts/dance/<draftId>/
6
+ * ├── draft.json (metadata)
7
+ * ├── SKILL.md (main skill content)
8
+ * ├── scripts/ (helper scripts)
9
+ * ├── references/ (reference docs)
10
+ * └── assets/ (assets/media)
11
+ *
12
+ * This module handles bundle-specific file operations.
13
+ * Generic draft CRUD (create/read/update/delete/list) lives in draft-service.ts.
14
+ */
15
+ import fs from 'fs/promises';
16
+ import path from 'path';
17
+ import { getDotDir } from '../lib/dot-source.js';
18
+ // ── Path resolution ─────────────────────────────────────
19
+ const BUNDLE_SCAFFOLD_DIRS = ['scripts', 'references', 'assets'];
20
+ export function danceBundleDir(cwd, draftId) {
21
+ return path.join(getDotDir(cwd), 'drafts', 'dance', draftId);
22
+ }
23
+ export async function isDanceBundleDraft(cwd, draftId) {
24
+ const bundlePath = path.join(danceBundleDir(cwd, draftId), 'draft.json');
25
+ try {
26
+ await fs.access(bundlePath);
27
+ return true;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ // ── Path sanitization ───────────────────────────────────
34
+ /**
35
+ * Ensures a relative path is safe and sandboxed within the bundle root.
36
+ * Rejects absolute paths, `..` traversal, null bytes, and empty paths.
37
+ */
38
+ export function sanitizeBundlePath(filePath) {
39
+ if (!filePath || typeof filePath !== 'string') {
40
+ throw new Error('File path is required.');
41
+ }
42
+ // Reject null bytes
43
+ if (filePath.includes('\0')) {
44
+ throw new Error('Invalid file path: contains null bytes.');
45
+ }
46
+ // Normalize and resolve
47
+ const normalized = path.normalize(filePath);
48
+ // Reject absolute paths
49
+ if (path.isAbsolute(normalized)) {
50
+ throw new Error('Absolute paths are not allowed.');
51
+ }
52
+ // Reject path traversal
53
+ if (normalized.startsWith('..') || normalized.includes(`${path.sep}..`)) {
54
+ throw new Error('Path traversal is not allowed.');
55
+ }
56
+ // Reject paths that try to escape via leading slash after normalize
57
+ if (normalized.startsWith(path.sep)) {
58
+ throw new Error('Invalid path.');
59
+ }
60
+ return normalized;
61
+ }
62
+ // ── Bundle scaffold ─────────────────────────────────────
63
+ /**
64
+ * Create the initial directory structure for a Dance bundle.
65
+ */
66
+ export async function scaffoldDanceBundle(cwd, draftId, skillContent) {
67
+ const bundleRoot = danceBundleDir(cwd, draftId);
68
+ await fs.mkdir(bundleRoot, { recursive: true });
69
+ // Create scaffold directories
70
+ for (const dir of BUNDLE_SCAFFOLD_DIRS) {
71
+ await fs.mkdir(path.join(bundleRoot, dir), { recursive: true });
72
+ }
73
+ // Write SKILL.md
74
+ await fs.writeFile(path.join(bundleRoot, 'SKILL.md'), skillContent, 'utf-8');
75
+ }
76
+ // ── Bundle tree ─────────────────────────────────────────
77
+ async function buildTree(dirPath, basePath) {
78
+ const entries = [];
79
+ let dirEntries;
80
+ try {
81
+ dirEntries = await fs.readdir(dirPath, { withFileTypes: true });
82
+ }
83
+ catch {
84
+ return entries;
85
+ }
86
+ // Sort: directories first, then files, alphabetical within each group
87
+ dirEntries.sort((a, b) => {
88
+ const aIsDir = a.isDirectory() ? 0 : 1;
89
+ const bIsDir = b.isDirectory() ? 0 : 1;
90
+ if (aIsDir !== bIsDir)
91
+ return aIsDir - bIsDir;
92
+ return a.name.localeCompare(b.name);
93
+ });
94
+ for (const entry of dirEntries) {
95
+ // Skip draft.json from the tree view — it's internal metadata
96
+ if (entry.name === 'draft.json' && basePath === '')
97
+ continue;
98
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
99
+ if (entry.isDirectory()) {
100
+ const children = await buildTree(path.join(dirPath, entry.name), relativePath);
101
+ entries.push({
102
+ name: entry.name,
103
+ type: 'directory',
104
+ path: relativePath,
105
+ children,
106
+ });
107
+ }
108
+ else if (entry.isFile()) {
109
+ entries.push({
110
+ name: entry.name,
111
+ type: 'file',
112
+ path: relativePath,
113
+ });
114
+ }
115
+ }
116
+ return entries;
117
+ }
118
+ export async function getDanceBundleTree(cwd, draftId) {
119
+ const bundleRoot = danceBundleDir(cwd, draftId);
120
+ return buildTree(bundleRoot, '');
121
+ }
122
+ // ── File read/write ─────────────────────────────────────
123
+ export async function readDanceBundleFile(cwd, draftId, filePath) {
124
+ const safePath = sanitizeBundlePath(filePath);
125
+ const fullPath = path.join(danceBundleDir(cwd, draftId), safePath);
126
+ try {
127
+ return await fs.readFile(fullPath, 'utf-8');
128
+ }
129
+ catch (error) {
130
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
131
+ throw new Error(`File not found: ${safePath}`);
132
+ }
133
+ throw error;
134
+ }
135
+ }
136
+ export async function writeDanceBundleFile(cwd, draftId, filePath, content) {
137
+ const safePath = sanitizeBundlePath(filePath);
138
+ const fullPath = path.join(danceBundleDir(cwd, draftId), safePath);
139
+ // Ensure parent directory exists
140
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
141
+ await fs.writeFile(fullPath, content, 'utf-8');
142
+ }
143
+ // ── File/directory creation ─────────────────────────────
144
+ export async function createDanceBundleFile(cwd, draftId, filePath, isDirectory = false) {
145
+ const safePath = sanitizeBundlePath(filePath);
146
+ const fullPath = path.join(danceBundleDir(cwd, draftId), safePath);
147
+ if (isDirectory) {
148
+ await fs.mkdir(fullPath, { recursive: true });
149
+ }
150
+ else {
151
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
152
+ // Only create if doesn't exist
153
+ try {
154
+ await fs.access(fullPath);
155
+ throw new Error(`File already exists: ${safePath}`);
156
+ }
157
+ catch (error) {
158
+ if (error instanceof Error && error.message.startsWith('File already exists'))
159
+ throw error;
160
+ await fs.writeFile(fullPath, '', 'utf-8');
161
+ }
162
+ }
163
+ }
164
+ // ── File deletion ───────────────────────────────────────
165
+ export async function deleteDanceBundleFile(cwd, draftId, filePath) {
166
+ const safePath = sanitizeBundlePath(filePath);
167
+ // Protect critical files
168
+ if (safePath === 'SKILL.md') {
169
+ throw new Error('Cannot delete SKILL.md — it is the primary skill file.');
170
+ }
171
+ const fullPath = path.join(danceBundleDir(cwd, draftId), safePath);
172
+ await fs.rm(fullPath, { recursive: true, force: true });
173
+ }
174
+ // ── Bundle content helpers ──────────────────────────────
175
+ /**
176
+ * Read the SKILL.md content from a bundle draft.
177
+ * Returns null if the file doesn't exist.
178
+ */
179
+ export async function readBundleSkillContent(cwd, draftId) {
180
+ try {
181
+ return await fs.readFile(path.join(danceBundleDir(cwd, draftId), 'SKILL.md'), 'utf-8');
182
+ }
183
+ catch {
184
+ return null;
185
+ }
186
+ }
187
+ /**
188
+ * Write the SKILL.md content to a bundle draft.
189
+ */
190
+ export async function writeBundleSkillContent(cwd, draftId, content) {
191
+ await fs.writeFile(path.join(danceBundleDir(cwd, draftId), 'SKILL.md'), content, 'utf-8');
192
+ }
193
+ /**
194
+ * Get all file paths in a bundle for projection purposes.
195
+ * Returns absolute paths.
196
+ */
197
+ export async function listBundleFilePaths(cwd, draftId) {
198
+ const bundleRoot = danceBundleDir(cwd, draftId);
199
+ const files = [];
200
+ async function walk(dir) {
201
+ let entries;
202
+ try {
203
+ entries = await fs.readdir(dir, { withFileTypes: true });
204
+ }
205
+ catch {
206
+ return;
207
+ }
208
+ for (const entry of entries) {
209
+ const fullPath = path.join(dir, entry.name);
210
+ if (entry.name === 'draft.json')
211
+ continue; // skip metadata
212
+ if (entry.isFile()) {
213
+ files.push(fullPath);
214
+ }
215
+ else if (entry.isDirectory()) {
216
+ await walk(fullPath);
217
+ }
218
+ }
219
+ }
220
+ await walk(bundleRoot);
221
+ return files;
222
+ }
@@ -0,0 +1,59 @@
1
+ // dot add service — installs Dance skills from a GitHub repo
2
+ import path from 'path';
3
+ import { parseSource, getOwnerRepo, shallowClone, discoverSkills, copySkillDir, upsertSkillLockEntry, readPluginManifest, ensureDotDir, danceAssetDir, getGlobalCwd, reportInstall, } from '../lib/dot-source.js';
4
+ import { invalidate } from '../lib/cache.js';
5
+ export async function addDanceFromGitHub(cwd, source, scope) {
6
+ const parsed = parseSource(source);
7
+ const { tempDir, cleanup } = await shallowClone({ url: parsed.url, ref: parsed.ref });
8
+ try {
9
+ const searchDir = parsed.subpath ? path.join(tempDir, parsed.subpath) : tempDir;
10
+ let skills = await discoverSkills(searchDir);
11
+ // Check plugin manifest for additional skill paths
12
+ const manifest = await readPluginManifest(tempDir);
13
+ if (manifest && manifest.skills.length > 0) {
14
+ const existingNames = new Set(skills.map((s) => s.name));
15
+ for (const entry of manifest.skills) {
16
+ if (existingNames.has(entry.name))
17
+ continue;
18
+ const skillDir = path.join(tempDir, entry.path);
19
+ const discovered = await discoverSkills(skillDir);
20
+ skills.push(...discovered.filter((s) => !existingNames.has(s.name)));
21
+ }
22
+ }
23
+ // Apply skill filter from @skill shorthand
24
+ if (parsed.skillFilter) {
25
+ skills = skills.filter((s) => s.name === parsed.skillFilter);
26
+ if (skills.length === 0) {
27
+ throw new Error(`Skill '${parsed.skillFilter}' not found in ${parsed.url}`);
28
+ }
29
+ }
30
+ if (skills.length === 0) {
31
+ throw new Error(`No SKILL.md files found in ${source}`);
32
+ }
33
+ // Install each skill — use global cwd when scope is 'global'
34
+ const targetCwd = scope === 'global' ? getGlobalCwd() : cwd;
35
+ const owner = parsed.owner;
36
+ const stage = parsed.repo;
37
+ const ownerRepo = getOwnerRepo(parsed.url);
38
+ const installed = [];
39
+ await ensureDotDir(targetCwd);
40
+ for (const skill of skills) {
41
+ const urn = `dance/@${owner}/${stage}/${skill.name}`;
42
+ const destDir = danceAssetDir(targetCwd, urn);
43
+ const srcDir = path.dirname(skill.skillMdPath);
44
+ copySkillDir(srcDir, destDir);
45
+ await upsertSkillLockEntry(targetCwd, urn, {
46
+ source: 'github',
47
+ sourceUrl: parsed.url.replace(/\.git$/, ''),
48
+ skillPath: skill.relativePath,
49
+ });
50
+ reportInstall(urn).catch(() => { });
51
+ installed.push({ urn, name: skill.name, description: skill.description });
52
+ }
53
+ invalidate('assets');
54
+ return { installed, source: parsed.url };
55
+ }
56
+ finally {
57
+ await cleanup();
58
+ }
59
+ }
@@ -0,0 +1,178 @@
1
+ import fs from 'fs/promises';
2
+ import { ensureDotDir, getDotDir, getGlobalCwd, getGlobalDotDir, initRegistry, installActWithDependencies, installAsset, installPerformerWithDeps, readAsset, reportInstall, searchRegistry, parsePerformerAsset } from '../lib/dot-source.js';
3
+ import { clearDotAuthUser, publishStudioAsset, readDotAuthUser, saveLocalStudioAsset, uninstallStudioAsset } from '../lib/dot-authoring.js';
4
+ import { startDotLogin } from '../lib/dot-login.js';
5
+ import { invalidate } from '../lib/cache.js';
6
+ import { findInstalledDependents } from './asset-service.js';
7
+ export function resolveDotCwd(cwd, scope) {
8
+ if (scope === 'global') {
9
+ return getGlobalCwd();
10
+ }
11
+ return cwd;
12
+ }
13
+ export async function getDotStatus(cwd) {
14
+ const dotDir = getDotDir(cwd);
15
+ const globalDotDir = getGlobalDotDir();
16
+ const [stageExists, globalExists] = await Promise.all([
17
+ fs.access(dotDir).then(() => true).catch(() => false),
18
+ fs.access(globalDotDir).then(() => true).catch(() => false),
19
+ ]);
20
+ return {
21
+ initialized: stageExists || globalExists,
22
+ stageInitialized: stageExists,
23
+ globalInitialized: globalExists,
24
+ dotDir,
25
+ globalDotDir,
26
+ projectDir: cwd,
27
+ };
28
+ }
29
+ export async function getDotStatusSnapshot(cwd) {
30
+ try {
31
+ return await getDotStatus(cwd);
32
+ }
33
+ catch {
34
+ return {
35
+ initialized: false,
36
+ stageInitialized: false,
37
+ globalInitialized: false,
38
+ dotDir: '',
39
+ globalDotDir: '',
40
+ projectDir: cwd,
41
+ };
42
+ }
43
+ }
44
+ export async function getDotPerformer(cwd, urn) {
45
+ const raw = await readAsset(cwd, urn);
46
+ if (!raw)
47
+ return null;
48
+ try {
49
+ return parsePerformerAsset(raw);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ export async function searchDotRegistry(query, options) {
56
+ return searchRegistry(query, {
57
+ kind: options.kind || undefined,
58
+ limit: options.limit,
59
+ });
60
+ }
61
+ /** Validates that performer URNs follow the 3-part format: kind/@author/name */
62
+ export function validateDotPerformer(performer) {
63
+ // Canonical assets are already validated by parsePerformerAsset,
64
+ // but we can add extra runtime checks if needed.
65
+ if (!performer.payload.tal && (!performer.payload.dances || performer.payload.dances.length === 0)) {
66
+ throw new Error("Invalid performer: at least one of 'tal' or 'dances' must be present.");
67
+ }
68
+ }
69
+ export async function initDotRegistry(cwd, scope) {
70
+ const targetCwd = resolveDotCwd(cwd, scope);
71
+ await initRegistry(targetCwd);
72
+ return {
73
+ ok: true,
74
+ dotDir: getDotDir(targetCwd),
75
+ scope: scope || 'stage',
76
+ };
77
+ }
78
+ export async function installDotAsset(cwd, input) {
79
+ const targetCwd = resolveDotCwd(cwd, input.scope);
80
+ await ensureDotDir(targetCwd);
81
+ if (input.urn.startsWith('performer/')) {
82
+ const result = await installPerformerWithDeps(targetCwd, input.urn, input.force);
83
+ // Report installs for non-skipped assets (best-effort)
84
+ for (const asset of result.installedAssets) {
85
+ if (!asset.skipped)
86
+ reportInstall(asset.urn).catch(() => { });
87
+ }
88
+ invalidate('assets');
89
+ return { ...result, scope: input.scope || 'stage' };
90
+ }
91
+ if (input.urn.startsWith('act/')) {
92
+ const result = await installActWithDependencies(targetCwd, input.urn, input.force);
93
+ for (const asset of result.installedAssets) {
94
+ if (!asset.skipped)
95
+ reportInstall(asset.urn).catch(() => { });
96
+ }
97
+ invalidate('assets');
98
+ return { ...result, scope: input.scope || 'stage' };
99
+ }
100
+ const result = await installAsset(targetCwd, input.urn, input.force);
101
+ if (!result.skipped)
102
+ reportInstall(input.urn).catch(() => { });
103
+ invalidate('assets');
104
+ return { ...result, scope: input.scope || 'stage' };
105
+ }
106
+ export async function getDotAuthUser() {
107
+ const auth = await readDotAuthUser();
108
+ return {
109
+ authenticated: !!auth,
110
+ username: auth?.username || null,
111
+ };
112
+ }
113
+ export async function loginToDot() {
114
+ const result = await startDotLogin();
115
+ return { ok: true, ...result };
116
+ }
117
+ export async function logoutFromDot() {
118
+ await clearDotAuthUser();
119
+ return { ok: true };
120
+ }
121
+ export async function saveDotLocalAsset(cwd, input) {
122
+ const auth = await readDotAuthUser();
123
+ const author = input.author || auth?.username;
124
+ if (!author) {
125
+ throw new Error('No author available. Sign in with `dot login` first.');
126
+ }
127
+ const saved = await saveLocalStudioAsset({
128
+ cwd,
129
+ kind: input.kind,
130
+ author,
131
+ slug: input.slug,
132
+ payload: input.payload,
133
+ });
134
+ invalidate('assets');
135
+ return { ok: true, ...saved };
136
+ }
137
+ export async function publishDotAsset(cwd, input) {
138
+ const auth = await readDotAuthUser();
139
+ if (!auth) {
140
+ const error = Object.assign(new Error('You are not logged in. Run `dot login` first.'), { status: 401 });
141
+ throw error;
142
+ }
143
+ const result = await publishStudioAsset({
144
+ cwd,
145
+ kind: input.kind,
146
+ slug: input.slug,
147
+ payload: input.payload,
148
+ tags: input.tags,
149
+ auth,
150
+ });
151
+ invalidate('assets');
152
+ return { ok: true, ...result };
153
+ }
154
+ export async function uninstallDotAsset(cwd, input) {
155
+ const deletedUrns = [];
156
+ if (input.cascade) {
157
+ const plan = await findInstalledDependents(cwd, input.urn);
158
+ // Delete dependents first (bottom-up: acts before performers)
159
+ const sortedDependents = [...plan.dependents].sort((a, b) => {
160
+ const order = { act: 0, performer: 1, dance: 2, tal: 3 };
161
+ return (order[a.kind] ?? 9) - (order[b.kind] ?? 9);
162
+ });
163
+ for (const dep of sortedDependents) {
164
+ try {
165
+ await uninstallStudioAsset(cwd, dep.urn);
166
+ deletedUrns.push(dep.urn);
167
+ }
168
+ catch { /* skip already-deleted */ }
169
+ }
170
+ }
171
+ const result = await uninstallStudioAsset(cwd, input.urn);
172
+ deletedUrns.push(input.urn);
173
+ invalidate('assets');
174
+ return { ok: true, ...result, deletedUrns };
175
+ }
176
+ export async function previewUninstallDotAsset(cwd, input) {
177
+ return findInstalledDependents(cwd, input.urn);
178
+ }