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
@@ -1,11 +1,26 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- import { assetFilePath, ensureDotDir, getGlobalDotDir } from 'dance-of-tal/lib/registry';
4
- import { getPayloadTags, loadLocalAssetByUrn, parseUrn, publishSingleAsset, resolveDependencies, } from 'dance-of-tal/lib/publishing';
3
+ import { assetFilePath, danceAssetDir, ensureDotDir, getGlobalCwd } from './dot-source.js';
4
+ import { getPayloadTags, loadLocalAssetByUrn, parseDotAsset, parseUrn, publishSingleAsset, resolveDependencies, } from './dot-source.js';
5
5
  const SLUG_RE = /^[a-z0-9][a-z0-9._-]{1,98}[a-z0-9]$/;
6
+ // Re-export auth helpers so dot-service.ts can import from one place
7
+ export { readAuthUser as readDotAuthUser, clearAuthUser as clearDotAuthUser } from './dot-source.js';
6
8
  function isRecord(value) {
7
9
  return !!value && typeof value === 'object' && !Array.isArray(value);
8
10
  }
11
+ /**
12
+ * Derives the URN stage from the working directory.
13
+ * Uses the sanitized basename of the cwd (e.g. /projects/my-app → my-app).
14
+ */
15
+ function stageFromCwd(cwd) {
16
+ const base = path.basename(cwd);
17
+ // Sanitize to lowercase slug format
18
+ return base
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, '-')
21
+ .replace(/^-+|-+$/g, '')
22
+ || 'default';
23
+ }
9
24
  function sanitizeSlug(value) {
10
25
  const slug = value.trim();
11
26
  if (!SLUG_RE.test(slug)) {
@@ -29,11 +44,8 @@ function sanitizeAuthor(value) {
29
44
  }
30
45
  return author;
31
46
  }
32
- function todayIsoDate() {
33
- return new Date().toISOString().split('T')[0];
34
- }
35
- function normalizeDescription(name, value) {
36
- return typeof value === 'string' && value.trim() ? value.trim() : name;
47
+ function normalizeDescription(value) {
48
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
37
49
  }
38
50
  function ensureUrn(value, kind) {
39
51
  if (typeof value !== 'string') {
@@ -41,80 +53,79 @@ function ensureUrn(value, kind) {
41
53
  }
42
54
  const urn = value.trim();
43
55
  const parts = urn.split('/');
44
- if (parts.length !== 3 || parts[0] !== kind || !parts[1].startsWith('@') || !parts[2]) {
45
- throw new Error(`Invalid URN '${urn}'. Expected ${kind}/@<author>/<name>.`);
56
+ // Accept both 3-segment (kind/@owner/name) and 4-segment (kind/@owner/stage/name)
57
+ const validLength = parts.length === 3 || parts.length === 4;
58
+ if (!validLength || parts[0] !== kind || !parts[1].startsWith('@') || !parts[parts.length - 1]) {
59
+ throw new Error(`Invalid URN '${urn}'. Expected ${kind}/@<owner>/<name> or ${kind}/@<owner>/<stage>/<name>.`);
46
60
  }
47
61
  return urn;
48
62
  }
63
+ function finalizeAsset(asset) {
64
+ return parseDotAsset(asset);
65
+ }
49
66
  function normalizeTalPayload(author, slug, payload) {
50
- const name = typeof payload.name === 'string' && payload.name.trim() ? payload.name.trim() : slug;
51
- const content = typeof payload.content === 'string' ? payload.content : '';
52
- return {
53
- type: `tal/@${author}/${slug}`,
54
- slug,
55
- name,
56
- description: normalizeDescription(name, payload.description),
67
+ return finalizeAsset({
68
+ $schema: 'https://schemas.danceoftal.com/assets/tal.v1.json',
69
+ kind: 'tal',
70
+ urn: `tal/@${author}/${slug}`,
71
+ ...(normalizeDescription(payload.description) ? { description: normalizeDescription(payload.description) } : {}),
57
72
  tags: sanitizeTags(payload.tags),
58
- featuredScore: typeof payload.featuredScore === 'number' ? payload.featuredScore : 0,
59
- createdAt: typeof payload.createdAt === 'string' && payload.createdAt.trim() ? payload.createdAt : todayIsoDate(),
60
- content,
61
- };
73
+ payload: {
74
+ content: typeof payload.content === 'string' ? payload.content : '',
75
+ },
76
+ });
62
77
  }
63
- function normalizeDancePayload(author, slug, payload) {
64
- const name = typeof payload.name === 'string' && payload.name.trim() ? payload.name.trim() : slug;
65
- const content = typeof payload.content === 'string' ? payload.content : '';
66
- return {
67
- type: `dance/@${author}/${slug}`,
68
- slug,
69
- name,
70
- description: normalizeDescription(name, payload.description),
78
+ function normalizeDancePayload(author, stage, slug, payload) {
79
+ return finalizeAsset({
80
+ $schema: 'https://schemas.danceoftal.com/assets/dance.v1.json',
81
+ kind: 'dance',
82
+ urn: `dance/@${author}/${stage}/${slug}`,
83
+ ...(normalizeDescription(payload.description) ? { description: normalizeDescription(payload.description) } : {}),
71
84
  tags: sanitizeTags(payload.tags),
72
- content,
73
- ...(isRecord(payload.schema) ? { schema: payload.schema } : {}),
74
- ...(isRecord(payload.exemplarSet) ? { exemplarSet: payload.exemplarSet } : {}),
75
- };
85
+ payload: {
86
+ content: typeof payload.content === 'string' ? payload.content : '',
87
+ },
88
+ });
76
89
  }
77
90
  function normalizePerformerPayload(author, slug, payload) {
78
- const name = typeof payload.name === 'string' && payload.name.trim() ? payload.name.trim() : slug;
79
- const danceValue = payload.dance;
80
- const dance = typeof danceValue === 'string'
81
- ? ensureUrn(danceValue, 'dance')
82
- : Array.isArray(danceValue)
83
- ? danceValue.map((value) => ensureUrn(value, 'dance'))
84
- : undefined;
91
+ const dances = Array.isArray(payload.dances)
92
+ ? payload.dances.map((value) => ensureUrn(value, 'dance'))
93
+ : undefined;
85
94
  const tal = payload.tal !== undefined && payload.tal !== null ? ensureUrn(payload.tal, 'tal') : undefined;
86
- const act = payload.act !== undefined && payload.act !== null ? ensureUrn(payload.act, 'act') : undefined;
87
- if (!tal && (!dance || dance.length === 0)) {
88
- throw new Error("Performer assets require at least one Tal or Dance reference.");
95
+ let modelValue = undefined;
96
+ if (isRecord(payload.model)
97
+ && typeof payload.model.provider === 'string'
98
+ && typeof payload.model.modelId === 'string') {
99
+ modelValue = payload.model;
89
100
  }
90
- return {
91
- type: `performer/@${author}/${slug}`,
92
- slug,
93
- name,
94
- description: normalizeDescription(name, payload.description),
101
+ return finalizeAsset({
102
+ $schema: 'https://schemas.danceoftal.com/assets/performer.v1.json',
103
+ kind: 'performer',
104
+ urn: `performer/@${author}/${slug}`,
105
+ ...(normalizeDescription(payload.description) ? { description: normalizeDescription(payload.description) } : {}),
95
106
  tags: sanitizeTags(payload.tags),
96
- ...(tal ? { tal } : {}),
97
- ...(dance
98
- ? { dance: Array.isArray(dance) && dance.length === 1 ? dance[0] : dance }
99
- : {}),
100
- ...(act ? { act } : {}),
101
- ...(typeof payload.model === 'string' && payload.model.trim() ? { model: payload.model.trim() } : {}),
102
- ...(isRecord(payload.mcp_config) ? { mcp_config: payload.mcp_config } : {}),
103
- };
107
+ payload: {
108
+ ...(tal ? { tal } : {}),
109
+ ...(dances ? { dances } : {}),
110
+ ...(modelValue !== undefined ? { model: modelValue } : {}),
111
+ ...(typeof payload.modelVariant === 'string' && payload.modelVariant.trim() ? { modelVariant: payload.modelVariant.trim() } : {}),
112
+ ...(isRecord(payload.mcp_config) ? { mcp_config: payload.mcp_config } : {}),
113
+ },
114
+ });
104
115
  }
105
116
  function normalizeActPayload(author, slug, payload) {
106
- const name = typeof payload.name === 'string' && payload.name.trim() ? payload.name.trim() : slug;
107
- return {
108
- type: `act/@${author}/${slug}`,
109
- slug,
110
- name,
111
- description: normalizeDescription(name, payload.description),
117
+ return finalizeAsset({
118
+ $schema: 'https://schemas.danceoftal.com/assets/act.v1.json',
119
+ kind: 'act',
120
+ urn: `act/@${author}/${slug}`,
121
+ ...(normalizeDescription(payload.description) ? { description: normalizeDescription(payload.description) } : {}),
112
122
  tags: sanitizeTags(payload.tags),
113
- entryNode: typeof payload.entryNode === 'string' ? payload.entryNode : '',
114
- nodes: isRecord(payload.nodes) ? payload.nodes : {},
115
- edges: Array.isArray(payload.edges) ? payload.edges : [],
116
- ...(typeof payload.maxIterations === 'number' ? { maxIterations: payload.maxIterations } : {}),
117
- };
123
+ payload: {
124
+ ...(Array.isArray(payload.actRules) ? { actRules: payload.actRules } : {}),
125
+ participants: Array.isArray(payload.participants) ? payload.participants : [],
126
+ relations: Array.isArray(payload.relations) ? payload.relations : [],
127
+ },
128
+ });
118
129
  }
119
130
  export function normalizeStudioAssetPayload(kind, authorInput, slugInput, payloadInput) {
120
131
  if (!isRecord(payloadInput)) {
@@ -126,43 +137,52 @@ export function normalizeStudioAssetPayload(kind, authorInput, slugInput, payloa
126
137
  case 'tal':
127
138
  return normalizeTalPayload(author, slug, payloadInput);
128
139
  case 'dance':
129
- return normalizeDancePayload(author, slug, payloadInput);
140
+ // Dance URN requires a stage — caller must pass stage in slug as 'stage/name'
141
+ // or use saveLocalStudioAsset which derives stage from cwd
142
+ return normalizeDancePayload(author, 'default', slug, payloadInput);
130
143
  case 'performer':
131
144
  return normalizePerformerPayload(author, slug, payloadInput);
132
145
  case 'act':
133
146
  return normalizeActPayload(author, slug, payloadInput);
134
147
  }
135
148
  }
136
- export async function readDotAuthUser() {
137
- try {
138
- const raw = await fs.readFile(path.join(getGlobalDotDir(), 'auth.json'), 'utf-8');
139
- const parsed = JSON.parse(raw);
140
- if (!parsed?.token || !parsed?.username) {
141
- return null;
149
+ export async function saveLocalStudioAsset(options) {
150
+ const author = sanitizeAuthor(options.author);
151
+ const slug = sanitizeSlug(options.slug);
152
+ const stage = stageFromCwd(options.cwd);
153
+ // All Studio assets use 4-segment URN: kind/@author/stage/name
154
+ const urn = `${options.kind}/@${author}/${stage}/${slug}`;
155
+ await ensureDotDir(options.cwd);
156
+ if (options.kind === 'dance') {
157
+ // Dance is a bundle directory — write SKILL.md with proper frontmatter
158
+ const bundleDir = danceAssetDir(options.cwd, urn);
159
+ const skillMdPath = path.join(bundleDir, 'SKILL.md');
160
+ await fs.mkdir(bundleDir, { recursive: true });
161
+ if (!isRecord(options.payload)) {
162
+ throw new Error('Dance payload must be a JSON object.');
142
163
  }
143
- return {
144
- token: String(parsed.token),
145
- username: sanitizeAuthor(String(parsed.username)),
146
- };
147
- }
148
- catch {
149
- return null;
150
- }
151
- }
152
- export async function clearDotAuthUser() {
153
- try {
154
- await fs.unlink(path.join(getGlobalDotDir(), 'auth.json'));
155
- }
156
- catch (error) {
157
- if (error?.code !== 'ENOENT') {
158
- throw error;
164
+ const content = typeof options.payload.content === 'string' ? options.payload.content : '';
165
+ const description = typeof options.payload.description === 'string' ? options.payload.description : '';
166
+ const frontmatter = [
167
+ '---',
168
+ `name: ${JSON.stringify(slug)}`,
169
+ ...(description ? [`description: ${JSON.stringify(description)}`] : []),
170
+ '---',
171
+ ].join('\n');
172
+ const skillMdContent = `${frontmatter}\n\n${content}`;
173
+ let existed = true;
174
+ try {
175
+ await fs.access(skillMdPath);
176
+ }
177
+ catch {
178
+ existed = false;
159
179
  }
180
+ await fs.writeFile(skillMdPath, skillMdContent, 'utf-8');
181
+ return { urn, path: skillMdPath, existed, payload: options.payload };
160
182
  }
161
- }
162
- export async function saveLocalStudioAsset(options) {
163
- const normalized = normalizeStudioAssetPayload(options.kind, options.author, options.slug, options.payload);
164
- const urn = `${options.kind}/@${sanitizeAuthor(options.author)}/${sanitizeSlug(options.slug)}`;
165
- await ensureDotDir(options.cwd);
183
+ // Tal / Performer / Act — JSON file with 4-segment URN embedded
184
+ const normalized = normalizeStudioAssetPayload(options.kind, author, slug, options.payload);
185
+ normalized.urn = urn;
166
186
  const filePath = assetFilePath(options.cwd, urn);
167
187
  await fs.mkdir(path.dirname(filePath), { recursive: true });
168
188
  let existed = true;
@@ -173,12 +193,7 @@ export async function saveLocalStudioAsset(options) {
173
193
  existed = false;
174
194
  }
175
195
  await fs.writeFile(filePath, JSON.stringify(normalized, null, 2), 'utf-8');
176
- return {
177
- urn,
178
- path: filePath,
179
- existed,
180
- payload: normalized,
181
- };
196
+ return { urn, path: filePath, existed, payload: normalized };
182
197
  }
183
198
  export async function publishStudioAsset(options) {
184
199
  const slug = sanitizeSlug(options.slug);
@@ -223,7 +238,7 @@ export async function publishStudioAsset(options) {
223
238
  if (!parsed) {
224
239
  continue;
225
240
  }
226
- const published = await publishSingleAsset(parsed.kind, parsed.name, dependency.payload, sanitizeTags(getPayloadTags(dependency.payload)), options.auth.token);
241
+ const published = await publishSingleAsset(parsed.kind, parsed.stage, parsed.name, dependency.payload, sanitizeTags(getPayloadTags(dependency.payload)), options.auth.token);
227
242
  if (published) {
228
243
  dependenciesPublished.push(dependency.urn);
229
244
  }
@@ -232,8 +247,20 @@ export async function publishStudioAsset(options) {
232
247
  }
233
248
  }
234
249
  }
250
+ // Dance assets are not publishable through the Studio registry pipeline
251
+ if (options.kind === 'dance') {
252
+ throw new Error('Dance assets cannot be published via the Studio registry pipeline. Use `dot add` to register from GitHub.');
253
+ }
254
+ const publishKind = options.kind;
255
+ // publishStudioAsset receives `options.slug` which is name only; stage is embedded in URN.
256
+ // For the studio, all assets use the stage from `parseUrn` of the built URN.
257
+ // The registry API needs (kind, stage, name, ...) — stage is the slug middle segment.
258
+ // Studio draft slugs are simple (no stage prefix), so we use the username as the stage owner.
259
+ // For now stage == slug to maintain backward compat (studio always publishes to the user's root stage).
235
260
  const tags = options.tags && options.tags.length > 0 ? sanitizeTags(options.tags) : sanitizeTags(getPayloadTags(localPayload));
236
- const mainPublished = await publishSingleAsset(options.kind, slug, localPayload, tags, options.auth.token);
261
+ const publishUrn = parseUrn(`${publishKind}/@${username}/${slug}`);
262
+ const publishStage = publishUrn?.stage ?? slug;
263
+ const mainPublished = await publishSingleAsset(publishKind, publishStage, slug, localPayload, tags, options.auth.token);
237
264
  const urn = `${options.kind}/@${username}/${slug}`;
238
265
  return {
239
266
  urn,
@@ -243,3 +270,45 @@ export async function publishStudioAsset(options) {
243
270
  dependenciesExisting,
244
271
  };
245
272
  }
273
+ export async function uninstallStudioAsset(cwd, urn) {
274
+ const [kind] = urn.split('/');
275
+ if (kind === 'dance') {
276
+ // Dance is a directory bundle — remove the whole directory
277
+ const tryRemoveDir = async (dir) => {
278
+ try {
279
+ await fs.access(dir);
280
+ await fs.rm(dir, { recursive: true, force: true });
281
+ return true;
282
+ }
283
+ catch {
284
+ return false;
285
+ }
286
+ };
287
+ if (await tryRemoveDir(danceAssetDir(cwd, urn))) {
288
+ return { urn, scope: 'stage' };
289
+ }
290
+ if (await tryRemoveDir(danceAssetDir(getGlobalCwd(), urn))) {
291
+ return { urn, scope: 'global' };
292
+ }
293
+ throw new Error(`Dance asset not found: ${urn}`);
294
+ }
295
+ // Tal / Performer / Act — single JSON file
296
+ const stagePath = assetFilePath(cwd, urn);
297
+ try {
298
+ await fs.access(stagePath);
299
+ await fs.unlink(stagePath);
300
+ return { urn, scope: 'stage' };
301
+ }
302
+ catch {
303
+ // Not found in stage, try global
304
+ }
305
+ const globalPath = assetFilePath(getGlobalCwd(), urn);
306
+ try {
307
+ await fs.access(globalPath);
308
+ await fs.unlink(globalPath);
309
+ return { urn, scope: 'global' };
310
+ }
311
+ catch {
312
+ throw new Error(`Asset not found: ${urn}`);
313
+ }
314
+ }
@@ -27,17 +27,18 @@ export async function ensureDotLoaderServer(cwd) {
27
27
  const statusData = unwrapOpencodeResult(await oc.mcp.status(params)) || {};
28
28
  const existing = statusData[serverName];
29
29
  if (!existing) {
30
+ const config = {
31
+ type: 'local',
32
+ command: [resolveDotCommand()],
33
+ enabled: true,
34
+ environment: {
35
+ DANCE_OF_TAL_PROJECT_DIR: path.resolve(cwd),
36
+ },
37
+ };
30
38
  unwrapOpencodeResult(await oc.mcp.add({
31
39
  ...params,
32
40
  name: serverName,
33
- config: {
34
- type: 'local',
35
- command: [resolveDotCommand()],
36
- enabled: true,
37
- environment: {
38
- DANCE_OF_TAL_PROJECT_DIR: path.resolve(cwd),
39
- },
40
- },
41
+ config,
41
42
  }));
42
43
  }
43
44
  const refreshedStatusData = unwrapOpencodeResult(await oc.mcp.status(params)) || {};
@@ -1,190 +1,8 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import http from 'http';
4
- import crypto from 'crypto';
5
- import open from 'open';
6
- import { getGlobalDotDir } from 'dance-of-tal/lib/registry';
7
- import { readDotAuthUser } from './dot-authoring.js';
8
- const SUPABASE_URL = process.env.DOT_SUPABASE_URL || 'https://qbildcrfjencoqkngyfw.supabase.co';
9
- const SUPABASE_ANON_KEY = process.env.DOT_SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFiaWxkY3JmamVuY29xa25neWZ3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzIyNjE5MzYsImV4cCI6MjA4NzgzNzkzNn0.9aI9FU-j20w3UIG7BuVtmpAPh3qClz7xTNXjcq7ofNQ';
10
- const DOT_LOGIN_REDIRECT_URI = 'http://localhost:4242/callback';
11
- const LOGIN_SERVER_TIMEOUT_MS = 180_000;
12
- let loginState = null;
13
- function getAuthFilePath() {
14
- return path.join(getGlobalDotDir(), 'auth.json');
15
- }
16
- async function saveAuthToken(token, username) {
17
- const authFile = getAuthFilePath();
18
- await fs.mkdir(path.dirname(authFile), { recursive: true });
19
- await fs.writeFile(authFile, JSON.stringify({ token, username }, null, 2), 'utf-8');
20
- }
21
- function generateCodeVerifier() {
22
- return crypto.randomBytes(32).toString('base64url');
23
- }
24
- function generateCodeChallenge(verifier) {
25
- return crypto.createHash('sha256').update(verifier).digest('base64url');
26
- }
27
- function clearLoginState() {
28
- if (!loginState) {
29
- return;
30
- }
31
- clearTimeout(loginState.timeout);
32
- loginState.server.close();
33
- loginState = null;
34
- }
35
- async function releaseStaleLoginPort() {
36
- const controller = new AbortController();
37
- const timer = setTimeout(() => controller.abort(), 1_500);
38
- try {
39
- // Legacy dot login closes itself when /callback is hit without a code.
40
- await fetch(DOT_LOGIN_REDIRECT_URI, {
41
- method: 'GET',
42
- signal: controller.signal,
43
- }).catch(() => { });
44
- await new Promise((resolve) => setTimeout(resolve, 250));
45
- }
46
- finally {
47
- clearTimeout(timer);
48
- }
49
- }
50
- async function listenOnLoginPort(server) {
51
- const tryListen = async () => new Promise((resolve, reject) => {
52
- const onError = (error) => {
53
- server.off('listening', onListening);
54
- reject(error);
55
- };
56
- const onListening = () => {
57
- server.off('error', onError);
58
- resolve();
59
- };
60
- server.once('error', onError);
61
- server.once('listening', onListening);
62
- server.listen(4242);
63
- });
64
- try {
65
- await tryListen();
66
- }
67
- catch (error) {
68
- if (error?.code !== 'EADDRINUSE') {
69
- throw error;
70
- }
71
- await releaseStaleLoginPort();
72
- try {
73
- await tryListen();
74
- }
75
- catch (retryError) {
76
- if (retryError?.code === 'EADDRINUSE') {
77
- throw new Error('Port 4242 is already in use by another process. Finish or close the other DOT login flow, then try again.');
78
- }
79
- throw retryError;
80
- }
81
- }
82
- }
83
- export async function startDotLogin() {
84
- const auth = await readDotAuthUser();
85
- if (auth) {
86
- return {
87
- started: false,
88
- alreadyAuthenticated: true,
89
- username: auth.username,
90
- };
91
- }
92
- if (loginState) {
93
- return {
94
- started: false,
95
- alreadyRunning: true,
96
- authUrl: loginState.authUrl,
97
- browserOpened: false,
98
- };
99
- }
100
- const codeVerifier = generateCodeVerifier();
101
- const codeChallenge = generateCodeChallenge(codeVerifier);
102
- const authUrl = `${SUPABASE_URL}/auth/v1/authorize?provider=github&redirect_to=${encodeURIComponent(DOT_LOGIN_REDIRECT_URI)}&code_challenge=${codeChallenge}&code_challenge_method=s256`;
103
- const server = http.createServer(async (req, res) => {
104
- try {
105
- const url = new URL(req.url || '/', DOT_LOGIN_REDIRECT_URI);
106
- if (url.pathname !== '/callback') {
107
- res.writeHead(404).end('Not Found');
108
- return;
109
- }
110
- const code = url.searchParams.get('code');
111
- if (!code) {
112
- res.writeHead(400, { 'Content-Type': 'text/html' });
113
- res.end("<h2 style='color: red; text-align: center; font-family: sans-serif; margin-top: 50px;'>Authentication failed: No code received. You can close this window.</h2>");
114
- clearLoginState();
115
- return;
116
- }
117
- res.writeHead(200, { 'Content-Type': 'text/html' });
118
- res.write("<h2 style='font-family: sans-serif; text-align: center; margin-top: 50px;'>Completing authentication... Please wait.</h2>");
119
- try {
120
- const tokenRes = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=pkce`, {
121
- method: 'POST',
122
- headers: {
123
- 'Content-Type': 'application/json',
124
- apikey: SUPABASE_ANON_KEY,
125
- },
126
- body: JSON.stringify({
127
- auth_code: code,
128
- code_verifier: codeVerifier,
129
- }),
130
- });
131
- const data = await tokenRes.json();
132
- if (!tokenRes.ok || !data.access_token) {
133
- throw new Error(data.error_description || data.msg || 'Failed to exchange token');
134
- }
135
- const username = data.user?.user_metadata?.preferred_username || data.user?.user_metadata?.user_name;
136
- if (!username) {
137
- throw new Error('Could not determine GitHub username from token.');
138
- }
139
- await saveAuthToken(data.access_token, username);
140
- res.end(`
141
- <script>
142
- document.body.innerHTML = "<h2 style='color: green; font-family: sans-serif; text-align: center; margin-top: 50px;'>Authentication Successful! You can safely close this window.</h2>";
143
- setTimeout(() => window.close(), 3000);
144
- </script>
145
- `);
146
- }
147
- catch (error) {
148
- res.end(`
149
- <script>
150
- document.body.innerHTML = "<h2 style='color: red; font-family: sans-serif; text-align: center; margin-top: 50px;'>Authentication Failed. ${String(error?.message || 'Unknown error')}</h2>";
151
- </script>
152
- `);
153
- }
154
- finally {
155
- clearLoginState();
156
- }
157
- }
158
- catch {
159
- try {
160
- res.writeHead(500).end('Server Error');
161
- }
162
- catch {
163
- // ignore
164
- }
165
- clearLoginState();
166
- }
167
- });
168
- await listenOnLoginPort(server);
169
- loginState = {
170
- server,
171
- authUrl,
172
- timeout: setTimeout(() => {
173
- clearLoginState();
174
- }, LOGIN_SERVER_TIMEOUT_MS),
175
- };
176
- let browserOpened = true;
177
- try {
178
- await open(authUrl);
179
- }
180
- catch {
181
- browserOpened = false;
182
- }
183
- return {
184
- started: true,
185
- alreadyRunning: false,
186
- alreadyAuthenticated: false,
187
- authUrl,
188
- browserOpened,
189
- };
190
- }
1
+ /**
2
+ * Studio server — thin re-export of dot lib login.
3
+ * Auth logic lives in dance-of-tal/lib/auth; this file is kept for
4
+ * backward-compatible imports within the studio server.
5
+ */
6
+ export { startLogin, readAuthUser, saveAuthToken, clearAuthUser } from './dot-source.js';
7
+ // Backward-compatible alias used by dot-service.ts
8
+ export { startLogin as startDotLogin } from './dot-source.js';
@@ -0,0 +1,11 @@
1
+ // dot Package Re-exports — Server Only
2
+ // Functions from dot that require Node.js runtime (file I/O, HTTP, etc.)
3
+ // For dot TYPES, import from 'shared/dot-types.ts' instead.
4
+ export { assetFilePath, danceAssetDir, ensureDotDir, getAssetPayload, getDotDir, getGlobalCwd, getGlobalDotDir, initRegistry, readAsset, } from 'dance-of-tal/lib/registry';
5
+ export { getRegistryPackage, reportInstall, searchRegistry, } from 'dance-of-tal/lib/registry-api';
6
+ export { installActWithDependencies, installPerformerWithDeps, } from 'dance-of-tal/lib/dependency-resolver';
7
+ export { installAsset, } from 'dance-of-tal/lib/installer';
8
+ export { readAuthUser, saveAuthToken, clearAuthUser, startLogin, } from 'dance-of-tal/lib/auth';
9
+ export { getPayloadTags, loadLocalAssetByUrn, parseUrn, publishSingleAsset, resolveDependencies, } from 'dance-of-tal/lib/publishing';
10
+ export { parseDotAsset, parsePerformerAsset, parseActAsset, slugFromUrn, } from 'dance-of-tal/contracts';
11
+ export { parseSource, getOwnerRepo, shallowClone, discoverSkills, copySkillDir, upsertSkillLockEntry, readPluginManifest, } from 'dance-of-tal/lib/add';