wtt-connect 0.2.43 → 0.2.45

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wtt-connect",
3
- "version": "0.2.43",
3
+ "version": "0.2.45",
4
4
  "private": false,
5
5
  "description": "WTT-native connector daemon for Codex, Claude Code, Cursor, Gemini, ACP, and other coding agent surfaces.",
6
6
  "type": "module",
package/src/main.js CHANGED
@@ -97,6 +97,9 @@ function parseArgs(args) {
97
97
  else if (a === '--preview-name') out.previewName = args[++i];
98
98
  else if (a === '--preview-token') out.previewToken = args[++i];
99
99
  else if (a === '--keep-last') out.keepLast = Number(args[++i]);
100
+ else if (a === '--snapshot-dir') out.snapshotDir = args[++i];
101
+ else if (a === '--snapshot-entry') out.snapshotEntry = args[++i];
102
+ else if (a === '--no-snapshot') out.noSnapshot = true;
100
103
  else if (a === '--timeout') out.timeout = Number(args[++i]) * 1000;
101
104
  else if (a === '--sender-agent-id') out.senderAgentId = args[++i];
102
105
  else if (a === '--sender-token') out.senderToken = args[++i];
@@ -134,8 +137,8 @@ Commands:
134
137
  upload-artifact --dir <path> Upload an OpenDesign/artifact directory to WTT
135
138
  opendesign-upload --dir <path>
136
139
  Alias for upload-artifact
137
- preview-port --port <port> [--topic-id <id>]
138
- Create a Cloud Sandbox port preview URL through sandbox outbox
140
+ preview-port --port <port> [--topic-id <id>] [--snapshot-dir <dir>]
141
+ Create a Cloud Sandbox preview URL and optional persistent snapshot
139
142
  cleanup-previews Stop preview servers previously registered by this agent
140
143
  help Show this help
141
144
  `);
@@ -146,9 +149,10 @@ async function previewPort(config, argv) {
146
149
  if (!Number.isInteger(port) || port < 1024 || port > 65535 || port === 3000) {
147
150
  throw new Error('preview-port requires --port <1024-65535>, excluding 3000');
148
151
  }
152
+ const keepLast = previewKeepLast(config, argv);
149
153
  const cleanup = cleanupOldPreviews(config, {
150
154
  currentPort: port,
151
- keepLast: previewKeepLast(config, argv),
155
+ keepLast: Math.max(0, keepLast - 1),
152
156
  });
153
157
  const preview = await createSandboxPreviewFromOutbox(config, port, {
154
158
  name: String(argv.previewName || argv.title || '').trim(),
@@ -158,7 +162,17 @@ async function previewPort(config, argv) {
158
162
  const url = preview.preview_url || preview.url;
159
163
  if (!url) throw new Error('Cloud Sandbox preview API returned no URL');
160
164
  const title = String(argv.title || argv.previewName || preview.name || 'Cloud Sandbox Preview').trim();
161
- const markdown = preview.markdown || `[preview_url:${title}](${url})`;
165
+ const snapshot = argv.noSnapshot ? null : await createPreviewSnapshot(config, argv, { title });
166
+ const snapshotUrl = snapshot?.snapshot_url || '';
167
+ const markdown = JSON.stringify({
168
+ type: 'cloud_sandbox_preview',
169
+ title,
170
+ preview_url: url,
171
+ url,
172
+ ...(snapshotUrl ? { snapshot_url: snapshotUrl } : {}),
173
+ ...(snapshot?.artifact_url ? { artifact_url: snapshot.artifact_url } : {}),
174
+ ...(snapshot?.artifact_id ? { artifact_id: snapshot.artifact_id } : {}),
175
+ });
162
176
  const topicId = String(argv.topicId || argv.sourceId || '').trim();
163
177
  let published = null;
164
178
  if (topicId) {
@@ -169,6 +183,8 @@ async function previewPort(config, argv) {
169
183
  type: 'cloud_sandbox_preview',
170
184
  port,
171
185
  preview_url: url,
186
+ ...(snapshotUrl ? { snapshot_url: snapshotUrl } : {}),
187
+ ...(snapshot?.artifact_id ? { artifact_id: snapshot.artifact_id } : {}),
172
188
  preview_cleanup: cleanup,
173
189
  });
174
190
  } finally {
@@ -176,7 +192,60 @@ async function previewPort(config, argv) {
176
192
  }
177
193
  }
178
194
  recordPreview(config, { port, url, name: preview.name || title });
179
- console.log(JSON.stringify({ ok: true, port, url, preview_url: url, markdown, cleanup, published }, null, 2));
195
+ console.log(JSON.stringify({ ok: true, port, url, preview_url: url, snapshot, markdown, cleanup, published }, null, 2));
196
+ }
197
+
198
+ async function createPreviewSnapshot(config, argv, { title = 'Cloud Sandbox Preview' } = {}) {
199
+ const dir = String(argv.snapshotDir || '').trim();
200
+ if (!dir) return null;
201
+ const resolved = resolveSnapshotDir(dir, config);
202
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
203
+ throw new Error(`snapshot directory not found: ${resolved}`);
204
+ }
205
+ const store = new DurableStore(config.storeFile).load();
206
+ const artifacts = new ArtifactManager(config, store);
207
+ const entry = String(argv.snapshotEntry || 'index.html').trim();
208
+ const artifact = await artifacts.uploadDirectory(resolved, {
209
+ source: 'cloud_sandbox_preview',
210
+ sourceId: String(argv.topicId || argv.sourceId || ''),
211
+ title,
212
+ entry,
213
+ type: 'cloud_sandbox_preview_snapshot',
214
+ metadata: {
215
+ uploaded_by: 'wtt-connect',
216
+ command: 'preview-port',
217
+ live_preview_port: Number(argv.port || argv._?.[0] || 0),
218
+ },
219
+ });
220
+ const previewUrl = absoluteWttUrl(config.wttBaseUrl, artifact.preview_url || artifact.url || '');
221
+ return {
222
+ artifact_id: artifact.artifact_id || artifact.id || '',
223
+ artifact_url: absoluteWttUrl(config.wttBaseUrl, artifact.url || ''),
224
+ snapshot_url: previewUrl,
225
+ entry,
226
+ dir: resolved,
227
+ };
228
+ }
229
+
230
+ function resolveSnapshotDir(dir, config) {
231
+ const expanded = dir.startsWith('~/') ? path.join(process.env.HOME || '', dir.slice(2)) : dir;
232
+ const base = path.resolve(config.workDir || process.cwd());
233
+ const resolved = path.resolve(path.isAbsolute(expanded) ? expanded : path.join(base, expanded));
234
+ const roots = [
235
+ config.workDir,
236
+ config.artifactDir,
237
+ config.persistentOutputDir,
238
+ ].filter(Boolean).map((root) => path.resolve(root));
239
+ if (!roots.some((root) => isPathInside(resolved, root))) {
240
+ throw new Error(`snapshot-dir refused path outside WTT workspace/state/persistent roots: ${resolved}`);
241
+ }
242
+ return resolved;
243
+ }
244
+
245
+ function absoluteWttUrl(base, maybeRelative) {
246
+ if (!maybeRelative) return '';
247
+ if (/^https?:\/\//i.test(maybeRelative)) return maybeRelative;
248
+ return `${String(base || '').replace(/\/$/, '')}${String(maybeRelative).startsWith('/') ? '' : '/'}${maybeRelative}`;
180
249
  }
181
250
 
182
251
  async function cleanupPreviewsCommand(config, argv) {
package/src/runner.js CHANGED
@@ -943,11 +943,11 @@ function renderCloudSandboxStorageInstruction(config, topicId = '') {
943
943
  '- Before publishing a preview URL, verify the server is actually listening and serving content with `curl -fsS http://127.0.0.1:<port>/ >/dev/null`.',
944
944
  '- If local curl fails, fix or restart the web server first. Never publish a preview URL for a dead port.',
945
945
  '- `wtt-connect preview-port` automatically keeps the most recent preview servers for this agent and stops older registered previews beyond the retention limit. Default retention is 3 live previews; use `--keep-last <n>` only when the user asks.',
946
- '- For historical results that should remain available after preview cleanup, also save static HTML, screenshots, reports, or build artifacts under the persistent R2 output directory and publish them as files/artifacts. Live preview URLs are for recent interactive previews, not permanent archival storage.',
946
+ '- For static pages, animations, charts, dashboards, and HTML demos, pass the generated static directory to `wtt-connect preview-port --snapshot-dir <dir> --snapshot-entry index.html` so WTT stores a persistent snapshot fallback. Live preview URLs are for recent interactive previews; snapshots/artifacts preserve history after cleanup.',
947
947
  '- If you start a web server in the sandbox and the user should preview it, call the Cloudflare Sandbox outbound Worker directly with curl and include the returned `preview_url` in your reply as `[preview_url:Short Title](<preview_url>)`.',
948
948
  '- Preview ports must be 1024-65535 and cannot be 3000. For Vite/Next-style dev servers prefer 5173, 4173, or 8080.',
949
949
  `- Preview curl rule: curl -sS -X POST "\${WTT_SANDBOX_OUTBOX_URL:-http://wtt.preview}/preview-port" -H 'content-type: application/json' -d '{"agent_id":"'\${WTT_AGENT_ID:-cloud-agent}'","port":<port>}'`,
950
- `- Prefer automatic WTT publishing when topic id is available: wtt-connect preview-port --port <port>${topicId ? ` --topic-id ${topicId}` : ' --topic-id <topic_id>'} --title "Short Title". This publishes a cloud_sandbox_preview card and is a convenience wrapper around the same sandbox outbound Worker.`,
950
+ `- Prefer automatic WTT publishing when topic id is available: wtt-connect preview-port --port <port>${topicId ? ` --topic-id ${topicId}` : ' --topic-id <topic_id>'} --title "Short Title" --snapshot-dir <static_dir> --snapshot-entry index.html. This publishes a cloud_sandbox_preview card with live and snapshot URLs.`,
951
951
  );
952
952
  return lines.join('\n');
953
953
  }