replicas-cli 0.3.129 → 0.3.131

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 (2) hide show
  1. package/dist/index.cjs +754 -190
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -111,17 +111,17 @@ var require_visit = __commonJS({
111
111
  visit.BREAK = BREAK;
112
112
  visit.SKIP = SKIP;
113
113
  visit.REMOVE = REMOVE;
114
- function visit_(key, node, visitor, path8) {
115
- const ctrl = callVisitor(key, node, visitor, path8);
114
+ function visit_(key, node, visitor, path10) {
115
+ const ctrl = callVisitor(key, node, visitor, path10);
116
116
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
117
- replaceNode(key, path8, ctrl);
118
- return visit_(key, ctrl, visitor, path8);
117
+ replaceNode(key, path10, ctrl);
118
+ return visit_(key, ctrl, visitor, path10);
119
119
  }
120
120
  if (typeof ctrl !== "symbol") {
121
121
  if (identity.isCollection(node)) {
122
- path8 = Object.freeze(path8.concat(node));
122
+ path10 = Object.freeze(path10.concat(node));
123
123
  for (let i = 0; i < node.items.length; ++i) {
124
- const ci = visit_(i, node.items[i], visitor, path8);
124
+ const ci = visit_(i, node.items[i], visitor, path10);
125
125
  if (typeof ci === "number")
126
126
  i = ci - 1;
127
127
  else if (ci === BREAK)
@@ -132,13 +132,13 @@ var require_visit = __commonJS({
132
132
  }
133
133
  }
134
134
  } else if (identity.isPair(node)) {
135
- path8 = Object.freeze(path8.concat(node));
136
- const ck = visit_("key", node.key, visitor, path8);
135
+ path10 = Object.freeze(path10.concat(node));
136
+ const ck = visit_("key", node.key, visitor, path10);
137
137
  if (ck === BREAK)
138
138
  return BREAK;
139
139
  else if (ck === REMOVE)
140
140
  node.key = null;
141
- const cv = visit_("value", node.value, visitor, path8);
141
+ const cv = visit_("value", node.value, visitor, path10);
142
142
  if (cv === BREAK)
143
143
  return BREAK;
144
144
  else if (cv === REMOVE)
@@ -159,17 +159,17 @@ var require_visit = __commonJS({
159
159
  visitAsync.BREAK = BREAK;
160
160
  visitAsync.SKIP = SKIP;
161
161
  visitAsync.REMOVE = REMOVE;
162
- async function visitAsync_(key, node, visitor, path8) {
163
- const ctrl = await callVisitor(key, node, visitor, path8);
162
+ async function visitAsync_(key, node, visitor, path10) {
163
+ const ctrl = await callVisitor(key, node, visitor, path10);
164
164
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
165
- replaceNode(key, path8, ctrl);
166
- return visitAsync_(key, ctrl, visitor, path8);
165
+ replaceNode(key, path10, ctrl);
166
+ return visitAsync_(key, ctrl, visitor, path10);
167
167
  }
168
168
  if (typeof ctrl !== "symbol") {
169
169
  if (identity.isCollection(node)) {
170
- path8 = Object.freeze(path8.concat(node));
170
+ path10 = Object.freeze(path10.concat(node));
171
171
  for (let i = 0; i < node.items.length; ++i) {
172
- const ci = await visitAsync_(i, node.items[i], visitor, path8);
172
+ const ci = await visitAsync_(i, node.items[i], visitor, path10);
173
173
  if (typeof ci === "number")
174
174
  i = ci - 1;
175
175
  else if (ci === BREAK)
@@ -180,13 +180,13 @@ var require_visit = __commonJS({
180
180
  }
181
181
  }
182
182
  } else if (identity.isPair(node)) {
183
- path8 = Object.freeze(path8.concat(node));
184
- const ck = await visitAsync_("key", node.key, visitor, path8);
183
+ path10 = Object.freeze(path10.concat(node));
184
+ const ck = await visitAsync_("key", node.key, visitor, path10);
185
185
  if (ck === BREAK)
186
186
  return BREAK;
187
187
  else if (ck === REMOVE)
188
188
  node.key = null;
189
- const cv = await visitAsync_("value", node.value, visitor, path8);
189
+ const cv = await visitAsync_("value", node.value, visitor, path10);
190
190
  if (cv === BREAK)
191
191
  return BREAK;
192
192
  else if (cv === REMOVE)
@@ -213,23 +213,23 @@ var require_visit = __commonJS({
213
213
  }
214
214
  return visitor;
215
215
  }
216
- function callVisitor(key, node, visitor, path8) {
216
+ function callVisitor(key, node, visitor, path10) {
217
217
  if (typeof visitor === "function")
218
- return visitor(key, node, path8);
218
+ return visitor(key, node, path10);
219
219
  if (identity.isMap(node))
220
- return visitor.Map?.(key, node, path8);
220
+ return visitor.Map?.(key, node, path10);
221
221
  if (identity.isSeq(node))
222
- return visitor.Seq?.(key, node, path8);
222
+ return visitor.Seq?.(key, node, path10);
223
223
  if (identity.isPair(node))
224
- return visitor.Pair?.(key, node, path8);
224
+ return visitor.Pair?.(key, node, path10);
225
225
  if (identity.isScalar(node))
226
- return visitor.Scalar?.(key, node, path8);
226
+ return visitor.Scalar?.(key, node, path10);
227
227
  if (identity.isAlias(node))
228
- return visitor.Alias?.(key, node, path8);
228
+ return visitor.Alias?.(key, node, path10);
229
229
  return void 0;
230
230
  }
231
- function replaceNode(key, path8, node) {
232
- const parent = path8[path8.length - 1];
231
+ function replaceNode(key, path10, node) {
232
+ const parent = path10[path10.length - 1];
233
233
  if (identity.isCollection(parent)) {
234
234
  parent.items[key] = node;
235
235
  } else if (identity.isPair(parent)) {
@@ -839,10 +839,10 @@ var require_Collection = __commonJS({
839
839
  var createNode = require_createNode();
840
840
  var identity = require_identity();
841
841
  var Node = require_Node();
842
- function collectionFromPath(schema, path8, value) {
842
+ function collectionFromPath(schema, path10, value) {
843
843
  let v = value;
844
- for (let i = path8.length - 1; i >= 0; --i) {
845
- const k = path8[i];
844
+ for (let i = path10.length - 1; i >= 0; --i) {
845
+ const k = path10[i];
846
846
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
847
847
  const a = [];
848
848
  a[k] = v;
@@ -861,7 +861,7 @@ var require_Collection = __commonJS({
861
861
  sourceObjects: /* @__PURE__ */ new Map()
862
862
  });
863
863
  }
864
- var isEmptyPath = (path8) => path8 == null || typeof path8 === "object" && !!path8[Symbol.iterator]().next().done;
864
+ var isEmptyPath = (path10) => path10 == null || typeof path10 === "object" && !!path10[Symbol.iterator]().next().done;
865
865
  var Collection = class extends Node.NodeBase {
866
866
  constructor(type, schema) {
867
867
  super(type);
@@ -891,11 +891,11 @@ var require_Collection = __commonJS({
891
891
  * be a Pair instance or a `{ key, value }` object, which may not have a key
892
892
  * that already exists in the map.
893
893
  */
894
- addIn(path8, value) {
895
- if (isEmptyPath(path8))
894
+ addIn(path10, value) {
895
+ if (isEmptyPath(path10))
896
896
  this.add(value);
897
897
  else {
898
- const [key, ...rest] = path8;
898
+ const [key, ...rest] = path10;
899
899
  const node = this.get(key, true);
900
900
  if (identity.isCollection(node))
901
901
  node.addIn(rest, value);
@@ -909,8 +909,8 @@ var require_Collection = __commonJS({
909
909
  * Removes a value from the collection.
910
910
  * @returns `true` if the item was found and removed.
911
911
  */
912
- deleteIn(path8) {
913
- const [key, ...rest] = path8;
912
+ deleteIn(path10) {
913
+ const [key, ...rest] = path10;
914
914
  if (rest.length === 0)
915
915
  return this.delete(key);
916
916
  const node = this.get(key, true);
@@ -924,8 +924,8 @@ var require_Collection = __commonJS({
924
924
  * scalar values from their surrounding node; to disable set `keepScalar` to
925
925
  * `true` (collections are always returned intact).
926
926
  */
927
- getIn(path8, keepScalar) {
928
- const [key, ...rest] = path8;
927
+ getIn(path10, keepScalar) {
928
+ const [key, ...rest] = path10;
929
929
  const node = this.get(key, true);
930
930
  if (rest.length === 0)
931
931
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -943,8 +943,8 @@ var require_Collection = __commonJS({
943
943
  /**
944
944
  * Checks if the collection includes a value with the key `key`.
945
945
  */
946
- hasIn(path8) {
947
- const [key, ...rest] = path8;
946
+ hasIn(path10) {
947
+ const [key, ...rest] = path10;
948
948
  if (rest.length === 0)
949
949
  return this.has(key);
950
950
  const node = this.get(key, true);
@@ -954,8 +954,8 @@ var require_Collection = __commonJS({
954
954
  * Sets a value in this collection. For `!!set`, `value` needs to be a
955
955
  * boolean to add/remove the item from the set.
956
956
  */
957
- setIn(path8, value) {
958
- const [key, ...rest] = path8;
957
+ setIn(path10, value) {
958
+ const [key, ...rest] = path10;
959
959
  if (rest.length === 0) {
960
960
  this.set(key, value);
961
961
  } else {
@@ -3470,9 +3470,9 @@ var require_Document = __commonJS({
3470
3470
  this.contents.add(value);
3471
3471
  }
3472
3472
  /** Adds a value to the document. */
3473
- addIn(path8, value) {
3473
+ addIn(path10, value) {
3474
3474
  if (assertCollection(this.contents))
3475
- this.contents.addIn(path8, value);
3475
+ this.contents.addIn(path10, value);
3476
3476
  }
3477
3477
  /**
3478
3478
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -3547,14 +3547,14 @@ var require_Document = __commonJS({
3547
3547
  * Removes a value from the document.
3548
3548
  * @returns `true` if the item was found and removed.
3549
3549
  */
3550
- deleteIn(path8) {
3551
- if (Collection.isEmptyPath(path8)) {
3550
+ deleteIn(path10) {
3551
+ if (Collection.isEmptyPath(path10)) {
3552
3552
  if (this.contents == null)
3553
3553
  return false;
3554
3554
  this.contents = null;
3555
3555
  return true;
3556
3556
  }
3557
- return assertCollection(this.contents) ? this.contents.deleteIn(path8) : false;
3557
+ return assertCollection(this.contents) ? this.contents.deleteIn(path10) : false;
3558
3558
  }
3559
3559
  /**
3560
3560
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -3569,10 +3569,10 @@ var require_Document = __commonJS({
3569
3569
  * scalar values from their surrounding node; to disable set `keepScalar` to
3570
3570
  * `true` (collections are always returned intact).
3571
3571
  */
3572
- getIn(path8, keepScalar) {
3573
- if (Collection.isEmptyPath(path8))
3572
+ getIn(path10, keepScalar) {
3573
+ if (Collection.isEmptyPath(path10))
3574
3574
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
3575
- return identity.isCollection(this.contents) ? this.contents.getIn(path8, keepScalar) : void 0;
3575
+ return identity.isCollection(this.contents) ? this.contents.getIn(path10, keepScalar) : void 0;
3576
3576
  }
3577
3577
  /**
3578
3578
  * Checks if the document includes a value with the key `key`.
@@ -3583,10 +3583,10 @@ var require_Document = __commonJS({
3583
3583
  /**
3584
3584
  * Checks if the document includes a value at `path`.
3585
3585
  */
3586
- hasIn(path8) {
3587
- if (Collection.isEmptyPath(path8))
3586
+ hasIn(path10) {
3587
+ if (Collection.isEmptyPath(path10))
3588
3588
  return this.contents !== void 0;
3589
- return identity.isCollection(this.contents) ? this.contents.hasIn(path8) : false;
3589
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path10) : false;
3590
3590
  }
3591
3591
  /**
3592
3592
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -3603,13 +3603,13 @@ var require_Document = __commonJS({
3603
3603
  * Sets a value in this document. For `!!set`, `value` needs to be a
3604
3604
  * boolean to add/remove the item from the set.
3605
3605
  */
3606
- setIn(path8, value) {
3607
- if (Collection.isEmptyPath(path8)) {
3606
+ setIn(path10, value) {
3607
+ if (Collection.isEmptyPath(path10)) {
3608
3608
  this.contents = value;
3609
3609
  } else if (this.contents == null) {
3610
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path8), value);
3610
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path10), value);
3611
3611
  } else if (assertCollection(this.contents)) {
3612
- this.contents.setIn(path8, value);
3612
+ this.contents.setIn(path10, value);
3613
3613
  }
3614
3614
  }
3615
3615
  /**
@@ -5569,9 +5569,9 @@ var require_cst_visit = __commonJS({
5569
5569
  visit.BREAK = BREAK;
5570
5570
  visit.SKIP = SKIP;
5571
5571
  visit.REMOVE = REMOVE;
5572
- visit.itemAtPath = (cst, path8) => {
5572
+ visit.itemAtPath = (cst, path10) => {
5573
5573
  let item = cst;
5574
- for (const [field2, index] of path8) {
5574
+ for (const [field2, index] of path10) {
5575
5575
  const tok = item?.[field2];
5576
5576
  if (tok && "items" in tok) {
5577
5577
  item = tok.items[index];
@@ -5580,23 +5580,23 @@ var require_cst_visit = __commonJS({
5580
5580
  }
5581
5581
  return item;
5582
5582
  };
5583
- visit.parentCollection = (cst, path8) => {
5584
- const parent = visit.itemAtPath(cst, path8.slice(0, -1));
5585
- const field2 = path8[path8.length - 1][0];
5583
+ visit.parentCollection = (cst, path10) => {
5584
+ const parent = visit.itemAtPath(cst, path10.slice(0, -1));
5585
+ const field2 = path10[path10.length - 1][0];
5586
5586
  const coll = parent?.[field2];
5587
5587
  if (coll && "items" in coll)
5588
5588
  return coll;
5589
5589
  throw new Error("Parent collection not found");
5590
5590
  };
5591
- function _visit(path8, item, visitor) {
5592
- let ctrl = visitor(item, path8);
5591
+ function _visit(path10, item, visitor) {
5592
+ let ctrl = visitor(item, path10);
5593
5593
  if (typeof ctrl === "symbol")
5594
5594
  return ctrl;
5595
5595
  for (const field2 of ["key", "value"]) {
5596
5596
  const token = item[field2];
5597
5597
  if (token && "items" in token) {
5598
5598
  for (let i = 0; i < token.items.length; ++i) {
5599
- const ci = _visit(Object.freeze(path8.concat([[field2, i]])), token.items[i], visitor);
5599
+ const ci = _visit(Object.freeze(path10.concat([[field2, i]])), token.items[i], visitor);
5600
5600
  if (typeof ci === "number")
5601
5601
  i = ci - 1;
5602
5602
  else if (ci === BREAK)
@@ -5607,10 +5607,10 @@ var require_cst_visit = __commonJS({
5607
5607
  }
5608
5608
  }
5609
5609
  if (typeof ctrl === "function" && field2 === "key")
5610
- ctrl = ctrl(item, path8);
5610
+ ctrl = ctrl(item, path10);
5611
5611
  }
5612
5612
  }
5613
- return typeof ctrl === "function" ? ctrl(item, path8) : ctrl;
5613
+ return typeof ctrl === "function" ? ctrl(item, path10) : ctrl;
5614
5614
  }
5615
5615
  exports2.visit = visit;
5616
5616
  }
@@ -6912,14 +6912,14 @@ var require_parser = __commonJS({
6912
6912
  case "scalar":
6913
6913
  case "single-quoted-scalar":
6914
6914
  case "double-quoted-scalar": {
6915
- const fs9 = this.flowScalar(this.type);
6915
+ const fs11 = this.flowScalar(this.type);
6916
6916
  if (atNextItem || it.value) {
6917
- map2.items.push({ start, key: fs9, sep: [] });
6917
+ map2.items.push({ start, key: fs11, sep: [] });
6918
6918
  this.onKeyLine = true;
6919
6919
  } else if (it.sep) {
6920
- this.stack.push(fs9);
6920
+ this.stack.push(fs11);
6921
6921
  } else {
6922
- Object.assign(it, { key: fs9, sep: [] });
6922
+ Object.assign(it, { key: fs11, sep: [] });
6923
6923
  this.onKeyLine = true;
6924
6924
  }
6925
6925
  return;
@@ -7047,13 +7047,13 @@ var require_parser = __commonJS({
7047
7047
  case "scalar":
7048
7048
  case "single-quoted-scalar":
7049
7049
  case "double-quoted-scalar": {
7050
- const fs9 = this.flowScalar(this.type);
7050
+ const fs11 = this.flowScalar(this.type);
7051
7051
  if (!it || it.value)
7052
- fc.items.push({ start: [], key: fs9, sep: [] });
7052
+ fc.items.push({ start: [], key: fs11, sep: [] });
7053
7053
  else if (it.sep)
7054
- this.stack.push(fs9);
7054
+ this.stack.push(fs11);
7055
7055
  else
7056
- Object.assign(it, { key: fs9, sep: [] });
7056
+ Object.assign(it, { key: fs11, sep: [] });
7057
7057
  return;
7058
7058
  }
7059
7059
  case "flow-map-end":
@@ -7405,19 +7405,19 @@ var LEGACY_WEB_APP_PATHS = {
7405
7405
  dashboardWorkspaces: `${DASHBOARD_PATH}/workspaces`
7406
7406
  };
7407
7407
  function getWorkspaceDashboardUrl(workspaceId, options = {}) {
7408
- const { encodeWorkspaceId = true, mode, mediaId, webAppUrl = DEFAULT_WEB_APP_URL } = options;
7408
+ const { encodeWorkspaceId = true, mode, mediaId, webAppUrl: webAppUrl2 = DEFAULT_WEB_APP_URL } = options;
7409
7409
  const workspaceIdValue = encodeWorkspaceId ? encodeURIComponent(workspaceId) : workspaceId;
7410
7410
  const params = [];
7411
7411
  if (mode) params.push(`mode=${encodeURIComponent(mode)}`);
7412
7412
  if (mediaId) params.push(`media=${encodeURIComponent(mediaId)}`);
7413
7413
  const query = params.length > 0 ? `?${params.join("&")}` : "";
7414
- return `${webAppUrl}${WEB_APP_PATHS.fullscreenWorkspaces}/${workspaceIdValue}${query}`;
7414
+ return `${webAppUrl2}${WEB_APP_PATHS.fullscreenWorkspaces}/${workspaceIdValue}${query}`;
7415
7415
  }
7416
7416
  function getOrganizationMediaPath(organizationId, mediaId) {
7417
7417
  return `/organizations/${encodeURIComponent(organizationId)}/media/${encodeURIComponent(mediaId)}`;
7418
7418
  }
7419
- function getOrganizationMediaUrl(organizationId, mediaId, webAppUrl = DEFAULT_WEB_APP_URL) {
7420
- return `${webAppUrl}${getOrganizationMediaPath(organizationId, mediaId)}`;
7419
+ function getOrganizationMediaUrl(organizationId, mediaId, webAppUrl2 = DEFAULT_WEB_APP_URL) {
7420
+ return `${webAppUrl2}${getOrganizationMediaPath(organizationId, mediaId)}`;
7421
7421
  }
7422
7422
 
7423
7423
  // src/commands/login.ts
@@ -8580,10 +8580,10 @@ function mergeDefs(...defs) {
8580
8580
  function cloneDef(schema) {
8581
8581
  return mergeDefs(schema._zod.def);
8582
8582
  }
8583
- function getElementAtPath(obj, path8) {
8584
- if (!path8)
8583
+ function getElementAtPath(obj, path10) {
8584
+ if (!path10)
8585
8585
  return obj;
8586
- return path8.reduce((acc, key) => acc?.[key], obj);
8586
+ return path10.reduce((acc, key) => acc?.[key], obj);
8587
8587
  }
8588
8588
  function promiseAllObject(promisesObj) {
8589
8589
  const keys = Object.keys(promisesObj);
@@ -8992,11 +8992,11 @@ function explicitlyAborted(x, startIndex = 0) {
8992
8992
  }
8993
8993
  return false;
8994
8994
  }
8995
- function prefixIssues(path8, issues) {
8995
+ function prefixIssues(path10, issues) {
8996
8996
  return issues.map((iss) => {
8997
8997
  var _a3;
8998
8998
  (_a3 = iss).path ?? (_a3.path = []);
8999
- iss.path.unshift(path8);
8999
+ iss.path.unshift(path10);
9000
9000
  return iss;
9001
9001
  });
9002
9002
  }
@@ -9143,16 +9143,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
9143
9143
  }
9144
9144
  function formatError(error51, mapper = (issue2) => issue2.message) {
9145
9145
  const fieldErrors = { _errors: [] };
9146
- const processError = (error52, path8 = []) => {
9146
+ const processError = (error52, path10 = []) => {
9147
9147
  for (const issue2 of error52.issues) {
9148
9148
  if (issue2.code === "invalid_union" && issue2.errors.length) {
9149
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
9149
+ issue2.errors.map((issues) => processError({ issues }, [...path10, ...issue2.path]));
9150
9150
  } else if (issue2.code === "invalid_key") {
9151
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
9151
+ processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
9152
9152
  } else if (issue2.code === "invalid_element") {
9153
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
9153
+ processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
9154
9154
  } else {
9155
- const fullpath = [...path8, ...issue2.path];
9155
+ const fullpath = [...path10, ...issue2.path];
9156
9156
  if (fullpath.length === 0) {
9157
9157
  fieldErrors._errors.push(mapper(issue2));
9158
9158
  } else {
@@ -9179,17 +9179,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
9179
9179
  }
9180
9180
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
9181
9181
  const result = { errors: [] };
9182
- const processError = (error52, path8 = []) => {
9182
+ const processError = (error52, path10 = []) => {
9183
9183
  var _a3, _b;
9184
9184
  for (const issue2 of error52.issues) {
9185
9185
  if (issue2.code === "invalid_union" && issue2.errors.length) {
9186
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
9186
+ issue2.errors.map((issues) => processError({ issues }, [...path10, ...issue2.path]));
9187
9187
  } else if (issue2.code === "invalid_key") {
9188
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
9188
+ processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
9189
9189
  } else if (issue2.code === "invalid_element") {
9190
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
9190
+ processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
9191
9191
  } else {
9192
- const fullpath = [...path8, ...issue2.path];
9192
+ const fullpath = [...path10, ...issue2.path];
9193
9193
  if (fullpath.length === 0) {
9194
9194
  result.errors.push(mapper(issue2));
9195
9195
  continue;
@@ -9221,8 +9221,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
9221
9221
  }
9222
9222
  function toDotPath(_path) {
9223
9223
  const segs = [];
9224
- const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
9225
- for (const seg of path8) {
9224
+ const path10 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
9225
+ for (const seg of path10) {
9226
9226
  if (typeof seg === "number")
9227
9227
  segs.push(`[${seg}]`);
9228
9228
  else if (typeof seg === "symbol")
@@ -21914,13 +21914,13 @@ function resolveRef(ref, ctx) {
21914
21914
  if (!ref.startsWith("#")) {
21915
21915
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
21916
21916
  }
21917
- const path8 = ref.slice(1).split("/").filter(Boolean);
21918
- if (path8.length === 0) {
21917
+ const path10 = ref.slice(1).split("/").filter(Boolean);
21918
+ if (path10.length === 0) {
21919
21919
  return ctx.rootSchema;
21920
21920
  }
21921
21921
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
21922
- if (path8[0] === defsKey) {
21923
- const key = path8[1];
21922
+ if (path10[0] === defsKey) {
21923
+ const key = path10[1];
21924
21924
  if (!key || !ctx.defs[key]) {
21925
21925
  throw new Error(`Reference not found: ${ref}`);
21926
21926
  }
@@ -23287,11 +23287,11 @@ function parseShellScript(script, cwd) {
23287
23287
  parseUntil(scope, 0, false);
23288
23288
  return scope.commands;
23289
23289
  }
23290
- function resolveShellPath(dir, path8) {
23291
- if (path8.includes(SHELL_OPAQUE) || path8.startsWith("~")) return null;
23292
- if (!path8.startsWith("/") && dir === null) return null;
23290
+ function resolveShellPath(dir, path10) {
23291
+ if (path10.includes(SHELL_OPAQUE) || path10.startsWith("~")) return null;
23292
+ if (!path10.startsWith("/") && dir === null) return null;
23293
23293
  const parts = [];
23294
- for (const part of (path8.startsWith("/") ? path8 : `${dir}/${path8}`).split("/")) {
23294
+ for (const part of (path10.startsWith("/") ? path10 : `${dir}/${path10}`).split("/")) {
23295
23295
  if (part === "..") parts.pop();
23296
23296
  else if (part && part !== ".") parts.push(part);
23297
23297
  }
@@ -23645,8 +23645,8 @@ function parseSkillName(tool, input) {
23645
23645
  const skillName = parsedInput.skill ?? parsedInput.name;
23646
23646
  return typeof skillName === "string" && skillName ? skillName : null;
23647
23647
  }
23648
- function skillNameFromFilePath(path8) {
23649
- const parts = path8.split(/[\\/]+/).filter(Boolean);
23648
+ function skillNameFromFilePath(path10) {
23649
+ const parts = path10.split(/[\\/]+/).filter(Boolean);
23650
23650
  if (parts.length < 2 || parts[parts.length - 1].toLowerCase() !== "skill.md") return null;
23651
23651
  const skillName = parts[parts.length - 2];
23652
23652
  return /[*?[\]{}…$]/.test(skillName) || ["skills", ".system"].includes(skillName.toLowerCase()) ? null : skillName;
@@ -23656,19 +23656,19 @@ function skillFilePath(tool, input) {
23656
23656
  if (!READ_TOOL_NAMES.has(tool.toLowerCase())) return null;
23657
23657
  const parsedInput = typeof input === "string" ? safeJsonParse(input, null) : input;
23658
23658
  if (!isRecord(parsedInput)) return null;
23659
- const path8 = [parsedInput.path, parsedInput.filePath, parsedInput.file_path].find((value) => typeof value === "string" && value.length > 0);
23660
- return path8 && skillNameFromFilePath(path8) ? path8 : null;
23659
+ const path10 = [parsedInput.path, parsedInput.filePath, parsedInput.file_path].find((value) => typeof value === "string" && value.length > 0);
23660
+ return path10 && skillNameFromFilePath(path10) ? path10 : null;
23661
23661
  }
23662
23662
  function createCallDisplayMessage(message2) {
23663
23663
  if (message2.inputRequest) return { ...message2, type: "tool_call" };
23664
- const path8 = skillFilePath(message2.tool, message2.input);
23665
- const skillName = message2.server.toLowerCase() === "skills" ? message2.tool : path8 ? skillNameFromFilePath(path8) : parseSkillName(message2.tool, message2.input);
23664
+ const path10 = skillFilePath(message2.tool, message2.input);
23665
+ const skillName = message2.server.toLowerCase() === "skills" ? message2.tool : path10 ? skillNameFromFilePath(path10) : parseSkillName(message2.tool, message2.input);
23666
23666
  if (skillName === null) return { ...message2, type: "tool_call" };
23667
23667
  return {
23668
23668
  id: message2.id,
23669
23669
  type: "skill",
23670
23670
  skillName,
23671
- ...path8 ? { path: path8 } : {},
23671
+ ...path10 ? { path: path10 } : {},
23672
23672
  status: message2.status,
23673
23673
  timestamp: message2.timestamp
23674
23674
  };
@@ -23681,11 +23681,11 @@ function insertDisplayMessageByTimestamp(messages, message2) {
23681
23681
  }
23682
23682
 
23683
23683
  // ../shared/src/display-message/format.ts
23684
- function fileContentDiff(path8, content, action) {
23684
+ function fileContentDiff(path10, content, action) {
23685
23685
  const lines = content.replace(/\n$/, "").split("\n");
23686
23686
  return [
23687
- `--- ${action === "add" ? "/dev/null" : `a/${path8}`}`,
23688
- `+++ ${action === "add" ? `b/${path8}` : "/dev/null"}`,
23687
+ `--- ${action === "add" ? "/dev/null" : `a/${path10}`}`,
23688
+ `+++ ${action === "add" ? `b/${path10}` : "/dev/null"}`,
23689
23689
  action === "add" ? `@@ -0,0 +1,${lines.length} @@` : `@@ -1,${lines.length} +0,0 @@`,
23690
23690
  ...lines.map((line) => `${action === "add" ? "+" : "-"}${line}`)
23691
23691
  ].join("\n");
@@ -23712,7 +23712,7 @@ var LINE_COUNT_VALUE_FLAGS = /* @__PURE__ */ new Set([
23712
23712
  ]);
23713
23713
  var SCRIPT_ROOT = "/";
23714
23714
  var commandName = (argv) => argv[0].split("/").pop() ?? argv[0];
23715
- var fileWrites = ({ writes }) => writes.filter(({ path: path8 }) => path8 !== "/dev/null");
23715
+ var fileWrites = ({ writes }) => writes.filter(({ path: path10 }) => path10 !== "/dev/null");
23716
23716
  function shellCommands(command) {
23717
23717
  return parseShellScript(command, SCRIPT_ROOT).filter(({ wrapper }) => !wrapper);
23718
23718
  }
@@ -23724,8 +23724,8 @@ function skillNamesFromCommand(command) {
23724
23724
  if (!SKILL_READ_COMMANDS.has(name) || writesFiles(name, argv) || fileWrites(shellCommand).length > 0) return [];
23725
23725
  const paths = readCommandPaths(name, argv);
23726
23726
  const stdinTarget = shellCommand.stdinTarget?.replaceAll(SHELL_OPAQUE, "\u2026");
23727
- return (paths.length === 0 && stdinTarget ? [stdinTarget] : paths).flatMap((path8) => {
23728
- const resolved = path8.startsWith("~") ? path8 : resolveShellPath(shellCommand.dir, path8)?.replace(SCRIPT_ROOT, "");
23727
+ return (paths.length === 0 && stdinTarget ? [stdinTarget] : paths).flatMap((path10) => {
23728
+ const resolved = path10.startsWith("~") ? path10 : resolveShellPath(shellCommand.dir, path10)?.replace(SCRIPT_ROOT, "");
23729
23729
  const skillName = resolved && skillNameFromFilePath(resolved);
23730
23730
  return skillName ? [skillName] : [];
23731
23731
  });
@@ -25188,7 +25188,7 @@ var LINEAR_ABILITY = {
25188
25188
 
25189
25189
  // ../shared/src/default-skills/replicas-agent/abilities/media.ts
25190
25190
  var SECTION8 = `### Media
25191
- Deliver screenshots, GIFs, screen recordings, generated diagrams, audio clips, and self-contained HTML pages to the Replicas dashboard and, when applicable, inline in the destination workflow.
25191
+ Deliver screenshots, GIFs, screen recordings, generated diagrams, and audio clips to the Replicas dashboard and, when applicable, inline in the destination workflow.
25192
25192
 
25193
25193
  **Reference:** \`references/MEDIA.md\`
25194
25194
 
@@ -25196,16 +25196,15 @@ Use this when:
25196
25196
  - You are about to run \`replicas media upload\`; this reference owns the complete Replicas-first, destination-second workflow
25197
25197
  - You produce a screenshot, recording, generated image, or audio clip the user should see
25198
25198
  - You need to show motion or an interaction (an animation, a popup that moves, a flow) in a PR or message: make a short looping GIF with \`replicas media gif\`, never a grid of frames
25199
- - You produce a self-contained HTML report that should be linked from a pull request
25200
25199
  - You record video output (browser automation, screen capture) \u2014 including the recommended aspect ratio and FPS
25201
25200
  - You need to embed media in a Slack/Linear/GitHub/GitLab message AND keep a referenceable copy in the Replicas dashboard`;
25202
- var REFERENCE8 = `# Media (Screenshots, GIFs, Recordings, Audio, HTML)
25201
+ var REFERENCE8 = `# Media (Screenshots, GIFs, Recordings, Audio)
25203
25202
 
25204
- This is the single source of truth for delivering screenshots, GIFs, screen recordings, generated diagrams, audio clips, and self-contained HTML pages through Replicas and external surfaces (Slack, Linear, GitHub, GitLab).
25203
+ This is the single source of truth for delivering screenshots, GIFs, screen recordings, generated diagrams, and audio clips through Replicas and external surfaces (Slack, Linear, GitHub, GitLab). HTML pages are visuals: publish them with \`replicas visualize\` (see \`replicas visualize --help\`), not \`media upload\`.
25205
25204
 
25206
25205
  ## Always
25207
25206
 
25208
- - Every picture, GIF, recording, or HTML page you mention in your reply to the user shows up in that reply: paste its printed \`![\u2026](\u2026)\` or \`HTML page\` line on its own line. Do this even when you also posted it to a PR, Slack, Linear, or GitLab, and even when the task came from one of them.
25207
+ - Every picture, GIF, recording, or visual you mention in your reply to the user shows up in that reply: paste its printed \`![\u2026](\u2026)\` line on its own line. Do this even when you also posted it to a PR, Slack, Linear, or GitLab, and even when the task came from one of them.
25209
25208
  - Never send the user somewhere else to see it ("see the Canvas", "check the media tab"). Uploads also appear in the Canvas's media folder, so they stay there for later.
25210
25209
  - Keep the whole \`replicas media upload\` output. Don't pipe it through \`tail\`, \`grep\`, or \`head\`; you need every line it prints.
25211
25210
 
@@ -25257,8 +25256,8 @@ Make comparisons easy to see at a glance, whether it's before and after or optio
25257
25256
 
25258
25257
  Upload to Replicas in these cases \u2014 and **only** these cases:
25259
25258
 
25260
- 1. **Media you show the user.** A screenshot, screen recording, generated diagram, audio clip, or HTML page, once you decide to show it to the user or share it. Screenshots you take only to check your own work stay local. Upload before you delete the file or send it anywhere else. This applies even when you're also sending the file to Slack, Linear, GitHub, or GitLab.
25261
- 2. **Files the user explicitly asks you to upload.** If the user sends or points at a file (image, video, audio, HTML) and asks you to upload it, run \`replicas media upload\`. Otherwise leave it alone \u2014 files in the workspace the user did not ask about should not be auto-uploaded as media.
25259
+ 1. **Media you show the user.** A screenshot, screen recording, generated diagram, or audio clip, once you decide to show it to the user or share it. Screenshots you take only to check your own work stay local. Upload before you delete the file or send it anywhere else. This applies even when you're also sending the file to Slack, Linear, GitHub, or GitLab.
25260
+ 2. **Files the user explicitly asks you to upload.** If the user sends or points at a file (image, video, audio) and asks you to upload it, run \`replicas media upload\`. Otherwise leave it alone \u2014 files in the workspace the user did not ask about should not be auto-uploaded as media.
25262
25261
  3. **Anything you plan to share externally (Slack, Linear, GitHub, GitLab, etc.).** Upload to Replicas first, then follow the destination's delivery path below.
25263
25262
 
25264
25263
  If none of these apply, don't upload.
@@ -25268,7 +25267,7 @@ If none of these apply, don't upload.
25268
25267
  \`replicas media upload\` is always the first delivery action. When the current task has an external destination, every other delivery step follows only after that command succeeds. For every file:
25269
25268
 
25270
25269
  1. First, run \`replicas media upload\` with the original file. Do not begin any destination upload until the command succeeds. This creates the canonical dashboard copy and prints an embed plus a per-file dashboard link. Every image gets a public link, so its printed embed works in chat and on GitHub.
25271
- 2. Include the printed embed line in the Replicas chat reply. HTML uses its printed page link instead of an image embed.
25270
+ 2. Include the printed embed line in the Replicas chat reply.
25272
25271
  3. Identify every destination in the task and use its inline representation from the table below. Slack, Linear, and GitLab require an upload of the same original bytes; GitHub uses the image's printed public embed.
25273
25272
  4. On Slack and Linear, put the **View in Replicas** dashboard link next to the destination embed as a secondary link. GitHub and GitLab need only the embed.
25274
25273
  5. Check each destination response. If its upload or public link fails, say so explicitly and keep the dashboard link; never claim that a dashboard link is an inline embed.
@@ -25315,15 +25314,13 @@ For each media file, the CLI prints an embed line and a dashboard link:
25315
25314
 
25316
25315
  Match each "View in Replicas" line to the embed line directly above it \u2014 that's the deep link for that file.
25317
25316
 
25318
- HTML uploads print an \`HTML page\` Markdown link instead of an image embed. It previews the page inside Replicas chat only. They also print a \`Forge link (organization only)\` to use everywhere else: it opens a dedicated page, where signed-in organization members go straight to the report and signed-out members sign in first.
25319
-
25320
25317
  Video and audio stay private unless they must play without Replicas sign-in; then pass \`--access public\` (alias \`--share\`):
25321
25318
 
25322
25319
  \`\`\`bash
25323
25320
  replicas media upload demo.mp4 --name "Checkout demo" --access public
25324
25321
  \`\`\`
25325
25322
 
25326
- Public links are unguessable bearer URLs: anyone with the link can view the file until it is rotated or revoked. HTML pages only support organization access. Reuse the link printed at upload. \`replicas media share <media-id>\` issues a new link and breaks the old one everywhere it is embedded, and \`replicas media revoke <media-id>\` removes public access.
25323
+ Public links are unguessable bearer URLs: anyone with the link can view the file until it is rotated or revoked. Reuse the link printed at upload. \`replicas media share <media-id>\` issues a new link and breaks the old one everywhere it is embedded, and \`replicas media revoke <media-id>\` removes public access.
25327
25324
 
25328
25325
  Permanently delete an upload with \`replicas media delete <media-id>\`. The backend only permits deletion when the media belongs to the current workspace; media from another workspace returns not found.
25329
25326
 
@@ -25331,8 +25328,8 @@ Permanently delete an upload with \`replicas media delete <media-id>\`. The back
25331
25328
 
25332
25329
  | URL | What it is | Where it's allowed |
25333
25330
  |---|---|---|
25334
- | \`https://api.replicas.dev/v1/media/<id>\` (inside \`![\u2026](\u2026)\`) | Authenticated API redirect printed for private video, audio, and HTML \u2014 requires the Replicas chat session | **Only** in the Replicas chat reply. Nowhere else. |
25335
- | \`https://app.replicas.dev/organizations/<organization-id>/media/<media-id>\` | Organization-only page (\`Forge link\`) printed for every HTML upload, and for video or audio with \`--access organization\` | Pull requests and other places where every viewer belongs to the Replicas organization. |
25331
+ | \`https://api.replicas.dev/v1/media/<id>\` (inside \`![\u2026](\u2026)\`) | Authenticated API redirect printed for private video, audio, and visuals \u2014 requires the Replicas chat session | **Only** in the Replicas chat reply. Nowhere else. |
25332
+ | \`https://app.replicas.dev/organizations/<organization-id>/media/<media-id>\` | Organization-only page (\`Forge link\`) printed for every visual, and for video or audio with \`--access organization\` | Pull requests and other places where every viewer belongs to the Replicas organization. |
25336
25333
  | \`https://api.replicas.dev/v1/media/public/<id>/<token>\` | Public bearer URL printed for every image, and for video or audio with \`--access public\` | The Replicas chat reply and GitHub embeds. |
25337
25334
  | \`https://app.replicas.dev/workspaces/<workspace-id>?mode=media&media=<media-id>\` | Authenticated dashboard deep link that opens the media file in the workspace | Everywhere external: Slack, GitHub, GitLab, Linear, PR bodies, comments, emails. |
25338
25335
 
@@ -25350,7 +25347,7 @@ This rule has no exceptions:
25350
25347
 
25351
25348
  ### ALWAYS link a Replicas web URL \u2014 never the authenticated API URL \u2014 when referencing media externally
25352
25349
 
25353
- For an HTML page, use its printed \`Forge link\`. For other authenticated media references, use the dashboard form:
25350
+ For a visual, use its printed \`Forge link\`: it opens a dedicated page, where signed-in organization members go straight to it and signed-out members sign in first. For other authenticated media references, use the dashboard form:
25354
25351
 
25355
25352
  \`\`\`
25356
25353
  https://app.replicas.dev/workspaces/<workspace-id>?mode=media&media=<media-id>
@@ -25364,7 +25361,7 @@ Always render as a markdown / mrkdwn hyperlink labeled **View in Replicas** \u20
25364
25361
 
25365
25362
  ## In your Replicas chat reply
25366
25363
 
25367
- Include each markdown embed line **verbatim** where you want that file to render inline. The chat substitutes each one with an embedded image, video, or audio player. For HTML, use the printed \`HTML page\` link. Multiple embeds can appear in a single reply.
25364
+ Include each markdown embed line **verbatim** where you want that file to render inline. The chat substitutes each one with an embedded image, video, or audio player. Multiple embeds can appear in a single reply.
25368
25365
 
25369
25366
  Never reply with only dashboard links (for example a "Links:" list of \`?mode=media&media=\` URLs) in place of the embeds: a dashboard link renders as plain text, so the user sees nothing inline. This applies even when the task came from Slack, Linear, GitHub, or GitLab; the chat reply still gets the embeds.
25370
25367
 
@@ -25507,7 +25504,7 @@ GitHub does not play external videos inline, so show motion with a GIF (see *Mak
25507
25504
 
25508
25505
  Public links are unauthenticated bearer capabilities. Revoke with \`replicas media revoke <media-id>\` **only** when the user asks or the media should intentionally stop rendering; never as routine post-task cleanup, since revoking breaks every embed already posted on the PR.
25509
25506
 
25510
- For a self-contained HTML report, upload it and put the printed \`Forge link\` Markdown in the PR body or comment. GitHub cannot render the page inline, so use the normal link rather than image syntax. HTML pages cannot be shared publicly.
25507
+ For a self-contained HTML report, publish it with \`replicas visualize\` and put the printed \`Forge link\` Markdown in the PR body or comment. GitHub cannot render the page inline, so use the normal link rather than image syntax. Visuals cannot be shared publicly.
25511
25508
 
25512
25509
  Do **not** commit screenshots to the repo, use placeholder URLs, expose the authenticated \`/v1/media/<id>\` URL, or automate GitHub's undocumented browser upload flow.
25513
25510
 
@@ -25545,9 +25542,8 @@ Auto-detected from the filename extension:
25545
25542
  | \`png\`, \`jpg\`, \`jpeg\`, \`webp\`, \`gif\` | image |
25546
25543
  | \`mp4\`, \`webm\` | video |
25547
25544
  | \`mp3\`, \`wav\` | audio |
25548
- | \`html\`, \`htm\` | html |
25549
25545
 
25550
- For other extensions, pass \`--kind image|video|audio|html\` explicitly. The kind applies to every file in that invocation, so group files of the same kind together:
25546
+ For other extensions, pass \`--kind image|video|audio\` explicitly. The kind applies to every file in that invocation, so group files of the same kind together:
25551
25547
 
25552
25548
  \`\`\`bash
25553
25549
  replicas media upload diagram.svg --name "Architecture diagram" --kind image
@@ -25556,16 +25552,16 @@ replicas media upload chart-a.svg chart-b.svg --name "Latency chart" --name "Err
25556
25552
 
25557
25553
  ## Options
25558
25554
 
25559
- - \`--kind <image|video|audio|html>\` \u2014 override auto-detection
25555
+ - \`--kind <image|video|audio>\` \u2014 override auto-detection
25560
25556
  - \`--name <name>\`: required display name shown in the dashboard, one per file in order
25561
25557
  - \`--session-id <id>\` \u2014 associate the upload with a specific session
25562
- - \`--access organization|public\`: for video and audio, print an organization-only link or create a public bearer URL. Images are always public, and HTML always gets an organization-only link
25563
- - \`--share\`: alias for \`--access public\`; not supported for HTML
25558
+ - \`--access organization|public\`: for video and audio, print an organization-only link or create a public bearer URL. Images are always public
25559
+ - \`--share\`: alias for \`--access public\`
25564
25560
  `;
25565
25561
  var MEDIA_ABILITY = {
25566
25562
  label: "Media",
25567
25563
  description: "Deliver media to Replicas and destination workflows with inline uploads.",
25568
- bullet: "- Producing or showing the user any media \u2014 screenshots, screen recordings, generated images or diagrams, audio clips, or HTML recap pages \u2014 including in your Replicas chat reply, PR descriptions/comments, and other external platforms. Paste the printed `![\u2026](\u2026)` embeds in your chat reply, never only dashboard links, and upload the file itself to Slack, Linear, or GitLab. To show motion, turn the recording into a short GIF with `replicas media gif` instead of a grid of frames",
25564
+ bullet: "- Producing or showing the user any media \u2014 screenshots, screen recordings, generated images or diagrams, or audio clips \u2014 including in your Replicas chat reply, PR descriptions/comments, and other external platforms. Paste the printed `![\u2026](\u2026)` embeds in your chat reply, never only dashboard links, and upload the file itself to Slack, Linear, or GitLab. To show motion, turn the recording into a short GIF with `replicas media gif` instead of a grid of frames",
25569
25565
  section: SECTION8,
25570
25566
  referenceFile: { name: "MEDIA.md", content: REFERENCE8 }
25571
25567
  };
@@ -26384,6 +26380,245 @@ var SLACK_ABILITY = {
26384
26380
  referenceFile: { name: "SLACK.md", content: REFERENCE14 }
26385
26381
  };
26386
26382
 
26383
+ // ../shared/src/design-tokens.ts
26384
+ var DESIGN_COLORS = {
26385
+ dark: {
26386
+ background: "#111111",
26387
+ foreground: "#ededed",
26388
+ muted: "#1f1f1f",
26389
+ mutedForeground: "#a3a3a3",
26390
+ accent: "#2b2b2b",
26391
+ primary: "#3eeba3",
26392
+ primaryForeground: "#111111",
26393
+ border: "#2a2a2a",
26394
+ destructive: "#ef4444",
26395
+ warning: "#f59e0b",
26396
+ sidebar: "#181818",
26397
+ sidebarAccent: "#1f1f1f",
26398
+ chatCodeBackground: "#111111",
26399
+ chatCodeHeader: "#151515",
26400
+ chatCodeBorder: "#333333",
26401
+ chatCodeForeground: "#e5e7eb",
26402
+ chatCodeMuted: "#9ca3af"
26403
+ },
26404
+ light: {
26405
+ background: "#ffffff",
26406
+ foreground: "#162118",
26407
+ muted: "#f2f3f1",
26408
+ mutedForeground: "#67706a",
26409
+ accent: "#e8ece7",
26410
+ primary: "#2f9d57",
26411
+ primaryForeground: "#ffffff",
26412
+ border: "#e2e6e0",
26413
+ destructive: "#dc2626",
26414
+ warning: "#92400e",
26415
+ sidebar: "#f7f8f6",
26416
+ sidebarAccent: "#e8ece7",
26417
+ chatCodeBackground: "#f7f8f6",
26418
+ chatCodeHeader: "#eef1ed",
26419
+ chatCodeBorder: "#d9ded8",
26420
+ chatCodeForeground: "#162118",
26421
+ chatCodeMuted: "#67706a"
26422
+ }
26423
+ };
26424
+
26425
+ // ../shared/src/visuals.ts
26426
+ var VISUAL_MEASURE_WIDTHS = [320, 375, 430, 520, 640, 760, 880, 952];
26427
+ var VISUAL_COLUMN_WIDTH = 760;
26428
+ var VISUAL_PHONE_WIDTH = 390;
26429
+ var VISUAL_MIN_HEIGHT = 24;
26430
+ var VISUAL_MAX_HEIGHT = 4e3;
26431
+ var VISUAL_FONT_PATH = "/fonts/GeneralSans-Variable.woff2";
26432
+ var FONT_SANS = '"General Sans", system-ui, -apple-system, "Segoe UI", sans-serif';
26433
+ var FONT_MONO = '"Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace';
26434
+ var CHART_COLORS = {
26435
+ dark: ["#199e70", "#3987e5", "#d95926", "#9085e9", "#c98500", "#d55181"],
26436
+ light: ["#2f9d57", "#2a78d6", "#eb6834", "#4a3aa7", "#eda100", "#e87ba4"]
26437
+ };
26438
+ function visualTheme(appearance, fontUrl) {
26439
+ const colors = DESIGN_COLORS[appearance];
26440
+ return {
26441
+ appearance,
26442
+ variables: {
26443
+ "--background": colors.background,
26444
+ "--foreground": colors.foreground,
26445
+ "--muted": colors.muted,
26446
+ "--muted-foreground": colors.mutedForeground,
26447
+ "--accent": colors.accent,
26448
+ "--primary": colors.primary,
26449
+ "--primary-foreground": colors.primaryForeground,
26450
+ "--border": colors.border,
26451
+ "--ring": colors.primary,
26452
+ "--destructive": colors.destructive,
26453
+ "--warning": colors.warning,
26454
+ "--code-background": colors.chatCodeBackground,
26455
+ "--code-foreground": colors.chatCodeForeground,
26456
+ ...Object.fromEntries(CHART_COLORS[appearance].map((color, index) => [`--chart-${index + 1}`, color])),
26457
+ "--radius": "6px",
26458
+ "--font-sans": FONT_SANS,
26459
+ "--font-mono": FONT_MONO
26460
+ },
26461
+ ...fontUrl ? { fontUrl } : {}
26462
+ };
26463
+ }
26464
+ var VISUAL_PAGE_RULES = `- Write one self-contained HTML file with inline <style> and <script>; a fragment without <html> or <head> is fine. Put data inline instead of fetching it. Libraries may load from public CDNs such as cdn.jsdelivr.net. Local images written as absolute paths (src="/abs/shot.png", url(/abs/bg.webp)) are inlined when you publish.
26465
+ - The page sits directly on the chat background, as wide as your reply (about ${VISUAL_COLUMN_WIDTH}px on desktop, ${VISUAL_PHONE_WIDTH - 30}px on phones), with its left edge aligned to your text. Leave html and body transparent, use a fluid width with no outer padding, and add no outer card, border, or title banner: the page is part of your reply.
26466
+ - Let content set the height. Never use 100vh, height:100%, or min-height:100vh on html, body, or the outer element; publishing refuses a page whose height follows the frame. Give charts fixed pixel heights.
26467
+ - Color everything with the theme variables so the page follows light and dark mode live: --foreground, --muted-foreground (secondary text), --background, --muted (subtle fills), --accent, --border, --primary and --primary-foreground (Replicas green), --destructive, --warning, --code-background, --code-foreground, and --chart-1 to --chart-6 for series (use them in order and label series directly). Never hardcode a light or dark palette. A canvas or chart library that needs literal colors can read them with getComputedStyle(document.documentElement) and redraw on matchMedia("(prefers-color-scheme: dark)") changes.
26468
+ - Text is 13px General Sans, like the chat; --font-sans and --font-mono are set. Keep Replicas' flat look: separate sections with 1px var(--border) lines or var(--muted) fills, without shadows or rounded cards. Use var(--radius) only on small controls.
26469
+ - Interaction is welcome: hover details, tabs, toggles, sliders. Links open in a new browser tab.
26470
+ - Keep the top-right corner (about 110\xD740px) free of anything essential: the chat shows the visual's actions there on hover.`;
26471
+ var THEME_FRAGMENT_KEY = "replicas-theme";
26472
+ var HOST_CONTEXT_CHANGED_METHOD = "ui/notifications/host-context-changed";
26473
+ var SIZE_CHANGED_METHOD = "ui/notifications/size-changed";
26474
+ var OPEN_LINK_METHOD = "ui/open-link";
26475
+ function visualThemeFragment(theme) {
26476
+ return `#${THEME_FRAGMENT_KEY}=${encodeURIComponent(JSON.stringify(theme))}`;
26477
+ }
26478
+ function clampVisualHeight(height) {
26479
+ return Math.min(VISUAL_MAX_HEIGHT, Math.max(VISUAL_MIN_HEIGHT, Math.round(height)));
26480
+ }
26481
+ var VISUAL_CONTENT_HEIGHT_EXPRESSION = "(function(){var r=document.documentElement;return Math.ceil(r.scrollHeight>r.clientHeight?r.scrollHeight:r.getBoundingClientRect().height)})()";
26482
+ var BASE_CSS = "html{color:var(--foreground);font-family:var(--font-sans);font-size:13px;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%;scrollbar-width:none}html::-webkit-scrollbar{display:none}body{margin:0}:where(*,*::before,*::after){box-sizing:border-box}code,kbd,pre,samp{font-family:var(--font-mono)}:where(:focus-visible){outline:2px solid var(--ring);outline-offset:-2px}";
26483
+ function rootRule(theme) {
26484
+ const declarations = Object.entries(theme.variables).map(([name, value]) => `${name}:${value};`).join("");
26485
+ return `:root{color-scheme:${theme.appearance};${declarations}}`;
26486
+ }
26487
+ var THEME_STYLE_ID = "replicas-visual-theme";
26488
+ var BOOTSTRAP_SCRIPT = `
26489
+ (() => {
26490
+ const style = document.getElementById('${THEME_STYLE_ID}');
26491
+ const nativeView = window.ReactNativeWebView;
26492
+ const framed = window.parent !== window;
26493
+ const post = (message) => nativeView ? nativeView.postMessage(JSON.stringify(message)) : window.parent.postMessage(message, '*');
26494
+ const applyTheme = (theme) => {
26495
+ const variables = Object.entries(theme.variables).map(([name, value]) => name + ':' + value + ';').join('');
26496
+ const font = theme.fontUrl ? '@font-face{font-family:"General Sans";src:url("' + theme.fontUrl + '") format("woff2");font-weight:200 700;font-display:swap}' : '';
26497
+ style.textContent = ':root{color-scheme:' + theme.appearance + ';' + variables + '}' + font + ${JSON.stringify(BASE_CSS)};
26498
+ };
26499
+ try {
26500
+ const fragment = /[#&]${THEME_FRAGMENT_KEY}=([^&]*)/.exec(location.hash);
26501
+ if (fragment) {
26502
+ applyTheme(JSON.parse(decodeURIComponent(fragment[1])));
26503
+ history.replaceState(history.state, '', location.pathname + location.search);
26504
+ }
26505
+ } catch {}
26506
+ if (!nativeView) {
26507
+ document.addEventListener('click', (event) => {
26508
+ const anchor = event.isTrusted && event.composedPath().find((target) => target instanceof Element && target.matches('a[href]'));
26509
+ if (!anchor) return;
26510
+ const url = new URL(anchor.getAttribute('href'), document.baseURI);
26511
+ if (!/^https?:$/.test(url.protocol) || url.href.split('#')[0] === location.href.split('#')[0]) return;
26512
+ if (framed) {
26513
+ event.preventDefault();
26514
+ post({ jsonrpc: '2.0', method: '${OPEN_LINK_METHOD}', params: { url: url.href } });
26515
+ } else {
26516
+ anchor.setAttribute('target', '_blank');
26517
+ anchor.setAttribute('rel', 'noopener');
26518
+ }
26519
+ }, true);
26520
+ }
26521
+ if (!framed && !nativeView) return;
26522
+ let reportedHeight;
26523
+ const reportHeight = () => {
26524
+ const height = ${VISUAL_CONTENT_HEIGHT_EXPRESSION};
26525
+ if (height === reportedHeight) return;
26526
+ reportedHeight = height;
26527
+ post({ jsonrpc: '2.0', method: '${SIZE_CHANGED_METHOD}', params: { height } });
26528
+ };
26529
+ window.addEventListener('message', (event) => {
26530
+ if (event.source !== window.parent || event.data?.method !== '${HOST_CONTEXT_CHANGED_METHOD}') return;
26531
+ const { theme, styles, fontUrl } = event.data.params;
26532
+ applyTheme({ appearance: theme, variables: styles.variables, fontUrl });
26533
+ reportedHeight = undefined;
26534
+ if (document.readyState !== 'loading') reportHeight();
26535
+ });
26536
+ const resizeObserver = new ResizeObserver(reportHeight);
26537
+ resizeObserver.observe(document.documentElement);
26538
+ document.addEventListener('DOMContentLoaded', () => {
26539
+ resizeObserver.observe(document.body);
26540
+ reportHeight();
26541
+ });
26542
+ window.addEventListener('load', reportHeight);
26543
+ })();
26544
+ `.trim().replace(/\s*\n\s*/g, " ");
26545
+ function bootstrapMarkup(scan) {
26546
+ const css = `${rootRule(visualTheme("dark"))}@media (prefers-color-scheme: light){${rootRule(visualTheme("light"))}}${BASE_CSS}`;
26547
+ return [
26548
+ /<meta\s[^>]*charset/i.test(scan.slice(0, 4096)) ? "" : '<meta charset="utf-8">',
26549
+ /<meta\s[^>]*name\s*=\s*["']?viewport/i.test(scan) ? "" : '<meta name="viewport" content="width=device-width, initial-scale=1">',
26550
+ `<style id="${THEME_STYLE_ID}">${css}</style>`,
26551
+ `<script>${BOOTSTRAP_SCRIPT}</script>`
26552
+ ].join("");
26553
+ }
26554
+ var blankNonMarkup = (html) => html.replace(
26555
+ /<!--[\s\S]*?(?:-->|$)|<(script|style|textarea|title|xmp|iframe|noembed|noframes|noscript|template)\b[\s\S]*?(?:<\/\1\s*>|$)/gi,
26556
+ (match) => " ".repeat(match.length)
26557
+ );
26558
+ function injectVisualBootstrap(html) {
26559
+ const scan = blankNonMarkup(html);
26560
+ const markup = bootstrapMarkup(scan);
26561
+ const doctype = /^\s*<!doctype[^>]*>/i.exec(scan);
26562
+ const prefix = doctype ? "" : "<!doctype html>";
26563
+ const headOpen = /<head(?:\s[^>]*)?>/i.exec(scan);
26564
+ if (headOpen) {
26565
+ const at2 = headOpen.index + headOpen[0].length;
26566
+ return `${prefix}${html.slice(0, at2)}${markup}${html.slice(at2)}`;
26567
+ }
26568
+ const htmlOpen = /<html(?:\s[^>]*)?>/i.exec(scan);
26569
+ const at = htmlOpen ? htmlOpen.index + htmlOpen[0].length : doctype ? doctype[0].length : 0;
26570
+ return `${prefix}${html.slice(0, at)}<head>${markup}</head>${html.slice(at)}`;
26571
+ }
26572
+
26573
+ // ../shared/src/default-skills/replicas-agent/abilities/visuals.ts
26574
+ var SECTION15 = `### Visuals
26575
+ Show a chart, diagram, comparison, timeline, or mockup inline in your Replicas chat reply, in the chat's own font and colors.
26576
+
26577
+ **Reference:** \`references/VISUALS.md\`
26578
+
26579
+ Use this when:
26580
+ - Seeing it would explain faster than reading it: numbers over time, a flow or architecture, a before and after, options side by side
26581
+ - The user asks to see, chart, plot, compare, or visualize something`;
26582
+ var REFERENCE15 = `# Visuals
26583
+
26584
+ A visual is a self-contained HTML page shown inline in your Replicas chat reply. It sits on the chat background in the chat's font and colors, grows to fit its content, and follows the user's light or dark theme live.
26585
+
26586
+ ## When to make one
26587
+
26588
+ Make one when seeing beats reading: how parts of a system connect, how a request or data flows, numbers or options compared, a timeline, a before and after, or a UI mockup. Make one when the user asks to see, chart, compare, sketch, or visualize something. Never draw these as ASCII art or text diagrams.
26589
+
26590
+ Don't make one when:
26591
+ - A short answer or list covers it.
26592
+ - It is a plain table: write a Markdown table.
26593
+ - It is screenshots, or a before and after of them: upload them as images, which readers can open full screen and zoom. See "Before and after" in \`references/MEDIA.md\`.
26594
+ - The user wants a page, site, or component in their project: build that in the repo.
26595
+
26596
+ ## Make it
26597
+
26598
+ 1. Write the page, for example \`/tmp/visuals/<name>.html\`, following the page rules below.
26599
+ 2. Run \`replicas visualize <page.html> --title "<short title>"\`. It checks the page in a headless browser, publishes it, and prints two screenshots (dark at desktop width, light at phone width) with any console errors and layout warnings.
26600
+ 3. Open both screenshots and look. If anything is wrong, fix the page and publish again, then use the newest line. While iterating, \`replicas visualize <page.html> --check\` renders without publishing.
26601
+ 4. Paste the printed \`![\u2026](\u2026)\` line on its own line where the visual belongs in your final reply.
26602
+
26603
+ ## Page rules
26604
+
26605
+ ${VISUAL_PAGE_RULES}
26606
+
26607
+ ## In your reply
26608
+
26609
+ - The user sees the visual itself. Don't introduce it ("Here's a chart", "Here's the diagram"), say where it is, or restate its data. Add only what it doesn't say, such as the takeaway or a caveat.
26610
+ - Paste the line in your final reply, not in a progress update. To change a visual, edit the page, publish it again, and paste the new line.
26611
+ - The line only renders in Replicas chat. When you post to pull requests, Slack, Linear, or anywhere else, link the printed Forge link instead. Leave the Forge link out of your chat reply.
26612
+ `;
26613
+ var VISUALS_ABILITY = {
26614
+ label: "Visuals",
26615
+ description: "Show charts, diagrams, comparisons, and mockups inline in chat replies.",
26616
+ // The visuals_instructions system prompt block is its trigger.
26617
+ bullet: "",
26618
+ section: SECTION15,
26619
+ referenceFile: { name: "VISUALS.md", content: REFERENCE15 }
26620
+ };
26621
+
26387
26622
  // ../shared/src/default-skills/replicas-agent/registry.ts
26388
26623
  var REPLICAS_AGENT_ABILITY_REGISTRY = {
26389
26624
  replicas: REPLICAS_ABILITY,
@@ -26399,7 +26634,8 @@ var REPLICAS_AGENT_ABILITY_REGISTRY = {
26399
26634
  plugins: PLUGINS_ABILITY,
26400
26635
  previews: PREVIEWS_ABILITY,
26401
26636
  sentry: SENTRY_ABILITY,
26402
- slack: SLACK_ABILITY
26637
+ slack: SLACK_ABILITY,
26638
+ visuals: VISUALS_ABILITY
26403
26639
  };
26404
26640
  var REPLICAS_AGENT_ABILITIES = Object.keys(
26405
26641
  REPLICAS_AGENT_ABILITY_REGISTRY
@@ -26604,7 +26840,7 @@ function stripAttachmentNote(text3) {
26604
26840
  }
26605
26841
  return {
26606
26842
  content,
26607
- files: match ? [...match[1].matchAll(/^- (.+)$/gm)].map(([, path8]) => path8) : [],
26843
+ files: match ? [...match[1].matchAll(/^- (.+)$/gm)].map(([, path10]) => path10) : [],
26608
26844
  walkthroughs
26609
26845
  };
26610
26846
  }
@@ -27493,7 +27729,7 @@ function parseOpencodeEvents(events) {
27493
27729
  upsertDisplayMessage(messages, {
27494
27730
  id,
27495
27731
  type: "file_change",
27496
- changes: files.map((path8) => ({ path: path8, kind: "update" })),
27732
+ changes: files.map((path10) => ({ path: path10, kind: "update" })),
27497
27733
  status: "completed",
27498
27734
  timestamp
27499
27735
  });
@@ -28781,7 +29017,7 @@ var headlessAgentRequestSchema = external_exports.object({
28781
29017
  });
28782
29018
 
28783
29019
  // ../shared/src/cli-version.ts
28784
- var CLI_VERSION = "0.3.129";
29020
+ var CLI_VERSION = "0.3.131";
28785
29021
 
28786
29022
  // ../shared/src/version.ts
28787
29023
  function compareVersions(v1, v2) {
@@ -29506,9 +29742,6 @@ var MEDIA_KIND = {
29506
29742
  };
29507
29743
  var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO, MEDIA_KIND.HTML];
29508
29744
  var PUBLIC_MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
29509
- function isPublicMediaKind(value) {
29510
- return PUBLIC_MEDIA_KINDS.some((kind) => kind === value);
29511
- }
29512
29745
  var MAX_ENGINE_LOG_BYTES = 50 * 1024 * 1024;
29513
29746
 
29514
29747
  // ../shared/src/sse.ts
@@ -31311,8 +31544,8 @@ async function claudeAuthCommand(options = {}) {
31311
31544
  var CLIENT_ID = "1031625952748946";
31312
31545
  var AUTH_URL = "https://auth.meta.com";
31313
31546
  var API_KEY_URL = "https://api.meta.ai/muse-code/key";
31314
- async function post(path8, body) {
31315
- return fetch(`${AUTH_URL}${path8}`, {
31547
+ async function post(path10, body) {
31548
+ return fetch(`${AUTH_URL}${path10}`, {
31316
31549
  method: "POST",
31317
31550
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
31318
31551
  body
@@ -31508,7 +31741,7 @@ async function credentialsGetCommand(service, options) {
31508
31741
  });
31509
31742
  }
31510
31743
  async function credentialsSetCommand(service, options) {
31511
- const path8 = credentialsPath(service, options, true);
31744
+ const path10 = credentialsPath(service, options, true);
31512
31745
  const { org: org2, models, ...fields } = options;
31513
31746
  const secretField = service === "bedrock" ? "aws_secret_access_key" : "api_key";
31514
31747
  const body = {
@@ -31516,7 +31749,7 @@ async function credentialsSetCommand(service, options) {
31516
31749
  ...models ? { models: models.split(",").map((model2) => model2.trim()).filter(Boolean) } : {},
31517
31750
  [secretField]: await readSecret(service, secretField === "api_key" ? "API key" : "AWS secret access key")
31518
31751
  };
31519
- const response = await orgAuthenticatedFetch(path8, { method: "POST", body });
31752
+ const response = await orgAuthenticatedFetch(path10, { method: "POST", body });
31520
31753
  output(response, () => console.log(import_chalk17.default.green(`Saved ${scopeLabel(options)} ${service} credentials.`)));
31521
31754
  }
31522
31755
  async function credentialsRemoveCommand(service, options) {
@@ -32788,8 +33021,8 @@ ${url2}`));
32788
33021
  });
32789
33022
  }
32790
33023
  async function prMergeCommand(id, prUrl) {
32791
- await orgAuthenticatedFetch(`/v1/replica/${id}/pull-requests/merge`, { method: "POST", body: { prUrl } });
32792
- outputSuccess(`Merged ${prUrl}`);
33024
+ const { queued } = await orgAuthenticatedFetch(`/v1/replica/${id}/pull-requests/merge`, { method: "POST", body: { prUrl } });
33025
+ outputSuccess(`${queued ? "Added to the merge queue:" : "Merged"} ${prUrl}`);
32793
33026
  }
32794
33027
  function prUpdateCommand(action) {
32795
33028
  return async (id, prUrl) => {
@@ -32829,7 +33062,7 @@ async function canvasPutCommand(id, filename, options) {
32829
33062
 
32830
33063
  // src/commands/chat.ts
32831
33064
  var import_chalk23 = __toESM(require("chalk"));
32832
- var chatPath2 = (id, chatId, path8 = "") => `/v1/replica/${id}/chats/${chatId}${path8}`;
33065
+ var chatPath2 = (id, chatId, path10 = "") => `/v1/replica/${id}/chats/${chatId}${path10}`;
32833
33066
  function printQueue(queue) {
32834
33067
  if (queue.length === 0) {
32835
33068
  console.log("Queue is empty.");
@@ -32914,8 +33147,8 @@ async function chatQueueListCommand(id, chatId) {
32914
33147
  printQueue(queue);
32915
33148
  });
32916
33149
  }
32917
- async function mutateQueue(path8, method, body) {
32918
- output(await orgAuthenticatedFetch(path8, { method, body }), ({ queue }) => {
33150
+ async function mutateQueue(path10, method, body) {
33151
+ output(await orgAuthenticatedFetch(path10, { method, body }), ({ queue }) => {
32919
33152
  if (queue) printQueue(queue);
32920
33153
  else console.log("Done.");
32921
33154
  });
@@ -32928,7 +33161,7 @@ var chatQueueActionCommand = (action) => (id, chatId, messageId) => mutateQueue(
32928
33161
 
32929
33162
  // src/commands/plugins.ts
32930
33163
  var import_chalk24 = __toESM(require("chalk"));
32931
- var pluginsPath = async ({ env }, path8) => env ? `/v1/environments/${await resolveEnvironmentId(env)}/plugins${path8}` : `/v1/plugins${path8}`;
33164
+ var pluginsPath = async ({ env }, path10) => env ? `/v1/environments/${await resolveEnvironmentId(env)}/plugins${path10}` : `/v1/plugins${path10}`;
32932
33165
  async function pluginsListCommand(options) {
32933
33166
  output(await orgAuthenticatedFetch(await pluginsPath(options, "/connections")), ({ plugins: plugins2, environment: environment2 }) => {
32934
33167
  if (!options.all && environment2.length === 0) console.log("No plugins connected. Pass --all to list available plugins.");
@@ -33901,10 +34134,10 @@ var AgentRequestError = class extends Error {
33901
34134
  this.status = status;
33902
34135
  }
33903
34136
  };
33904
- async function agentFetch(path8, options) {
33905
- return (await agentRequest(path8, options)).json();
34137
+ async function agentFetch(path10, options) {
34138
+ return (await agentRequest(path10, options)).json();
33906
34139
  }
33907
- async function agentRequest(path8, options) {
34140
+ async function agentRequest(path10, options) {
33908
34141
  const config3 = readAgentConfig();
33909
34142
  if (!config3) {
33910
34143
  throw new Error("Agent mode config not found");
@@ -33920,7 +34153,7 @@ async function agentRequest(path8, options) {
33920
34153
  ...options?.headers || {}
33921
34154
  };
33922
34155
  const body = rawBody === void 0 ? void 0 : rawBody instanceof FormData || rawBody instanceof ReadableStream ? rawBody : JSON.stringify(rawBody);
33923
- const response = await fetch(`${MONOLITH_URL}${path8}`, {
34156
+ const response = await fetch(`${MONOLITH_URL}${path10}`, {
33924
34157
  ...options,
33925
34158
  headers,
33926
34159
  body,
@@ -33932,7 +34165,7 @@ async function agentRequest(path8, options) {
33932
34165
  }
33933
34166
  return response;
33934
34167
  }
33935
- async function engineFetch(path8, options) {
34168
+ async function engineFetch(path10, options) {
33936
34169
  const config3 = readAgentConfig();
33937
34170
  if (!config3) {
33938
34171
  throw new Error("Agent mode config not found");
@@ -33942,7 +34175,7 @@ async function engineFetch(path8, options) {
33942
34175
  "Content-Type": "application/json",
33943
34176
  ...options?.headers || {}
33944
34177
  };
33945
- const response = await fetch(`http://localhost:${ENGINE_PORT}${path8}`, {
34178
+ const response = await fetch(`http://localhost:${ENGINE_PORT}${path10}`, {
33946
34179
  ...options,
33947
34180
  headers,
33948
34181
  body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
@@ -34033,7 +34266,7 @@ var MEDIA_ACCESS_VALUES = ["organization", "public"];
34033
34266
  function publicMediaUrl(mediaId, token) {
34034
34267
  return `${MONOLITH_URL}/v1/media/public/${mediaId}/${token}`;
34035
34268
  }
34036
- var MEDIA_DELIVERY_HINT = "Next: paste each media line above (`![\u2026](\u2026)`, `[\u2026](\u2026)`, or `HTML page`) verbatim into your Replicas chat reply so it renders inline; View in Replicas links are secondary and never replace it. Slack, Linear, and GitLab also need a native upload of the file bytes; see the replicas-agent skill's references/MEDIA.md.";
34269
+ var MEDIA_DELIVERY_HINT = "Next: paste each media line above (`![\u2026](\u2026)` or `[\u2026](\u2026)`) verbatim into your Replicas chat reply so it renders inline; View in Replicas links are secondary and never replace it. Slack, Linear, and GitLab also need a native upload of the file bytes; see the replicas-agent skill's references/MEDIA.md.";
34037
34270
  var markdownLabel = (name) => name.replace(/[[\]]/g, "");
34038
34271
  function formatMediaEmbed(name, media2, publicUrl) {
34039
34272
  if (publicUrl) return `${media2.kind === MEDIA_KIND.IMAGE ? "!" : ""}[${markdownLabel(name)}](${publicUrl})`;
@@ -34069,6 +34302,13 @@ function resolveKind(ext, override) {
34069
34302
  }
34070
34303
  return inferred;
34071
34304
  }
34305
+ async function uploadMedia(file2, fileName, fields) {
34306
+ const form = new FormData();
34307
+ form.append("file", file2, fileName);
34308
+ for (const [name, value] of Object.entries(fields)) if (value !== void 0) form.append(name, value);
34309
+ const { media: media2 } = await agentFetch("/v1/engine/media", { method: "POST", body: form });
34310
+ return media2;
34311
+ }
34072
34312
  async function uploadOne(filePath, displayName, kind, options) {
34073
34313
  const absPath = import_path6.default.resolve(filePath);
34074
34314
  if (!import_fs7.default.existsSync(absPath)) {
@@ -34084,16 +34324,11 @@ async function uploadOne(filePath, displayName, kind, options) {
34084
34324
  const fileName = import_path6.default.basename(absPath);
34085
34325
  const ext = import_path6.default.extname(absPath).slice(1).toLowerCase();
34086
34326
  const contentType = mediaInfo(ext)?.contentType ?? "application/octet-stream";
34087
- const form = new FormData();
34088
- form.append("file", new Blob([import_fs7.default.readFileSync(absPath)], { type: contentType }), fileName);
34089
- form.append("kind", kind);
34090
- if (options.sessionId) form.append("session_id", options.sessionId);
34091
- form.append("display_name", displayName);
34092
- const { media: media2 } = await agentFetch("/v1/engine/media", {
34093
- method: "POST",
34094
- body: form
34327
+ return uploadMedia(new Blob([import_fs7.default.readFileSync(absPath)], { type: contentType }), fileName, {
34328
+ kind,
34329
+ display_name: displayName,
34330
+ session_id: options.sessionId || void 0
34095
34331
  });
34096
- return media2;
34097
34332
  }
34098
34333
  async function mediaUploadCommand(filePaths, options) {
34099
34334
  if (filePaths.length === 0) {
@@ -34122,8 +34357,8 @@ async function mediaUploadCommand(filePaths, options) {
34122
34357
  const ext = import_path6.default.extname(filePath).slice(1).toLowerCase();
34123
34358
  return resolveKind(ext, options.kind);
34124
34359
  });
34125
- if (publicAccess && kinds.some((kind) => !isPublicMediaKind(kind))) {
34126
- throw new Error("HTML pages only support organization access. Use --access organization.");
34360
+ if (kinds.includes(MEDIA_KIND.HTML)) {
34361
+ throw new Error('Publish HTML pages with `replicas visualize <page.html> --title "<title>"`. It shows them inline in chat, styled like the chat, and prints a Forge link for everywhere else.');
34127
34362
  }
34128
34363
  const shared = kinds.map((kind) => publicAccess || kind === MEDIA_KIND.IMAGE);
34129
34364
  const results = await Promise.allSettled(
@@ -34160,9 +34395,8 @@ async function mediaUploadCommand(filePaths, options) {
34160
34395
  name: names[i],
34161
34396
  media: media2,
34162
34397
  public_url: publicUrl,
34163
- embed: media2.kind === MEDIA_KIND.HTML ? `HTML page: [${markdownLabel(names[i])}](${MONOLITH_URL}/v1/media/${media2.id})` : !shared[i] || publicUrl ? formatMediaEmbed(names[i], media2, publicUrl) : void 0,
34164
- // The HTML page link above only works inside Replicas chat, so HTML always gets a link that works elsewhere.
34165
- organization_url: media2.kind === MEDIA_KIND.HTML || options.access === "organization" && !shared[i] ? getOrganizationMediaUrl(media2.organization_id, media2.id) : void 0,
34398
+ embed: !shared[i] || publicUrl ? formatMediaEmbed(names[i], media2, publicUrl) : void 0,
34399
+ organization_url: options.access === "organization" && !shared[i] ? getOrganizationMediaUrl(media2.organization_id, media2.id) : void 0,
34166
34400
  dashboard_url: config3?.workspace_id ? getWorkspaceDashboardUrl(config3.workspace_id, { mode: "media", mediaId: media2.id }) : void 0
34167
34401
  });
34168
34402
  } else {
@@ -34302,6 +34536,330 @@ async function mediaGifCommand(input, options) {
34302
34536
  );
34303
34537
  }
34304
34538
 
34539
+ // src/commands/visualize.ts
34540
+ var import_fs10 = __toESM(require("fs"));
34541
+ var import_http3 = __toESM(require("http"));
34542
+ var import_path9 = __toESM(require("path"));
34543
+
34544
+ // src/lib/headless-chrome.ts
34545
+ var import_child_process5 = require("child_process");
34546
+ var import_fs9 = __toESM(require("fs"));
34547
+ var import_os4 = __toESM(require("os"));
34548
+ var import_path8 = __toESM(require("path"));
34549
+ var import_stream = require("stream");
34550
+ var CHROME_NAMES = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"];
34551
+ var COMMAND_TIMEOUT_MS = 15e3;
34552
+ var CLOSE_TIMEOUT_MS = 2e3;
34553
+ function findChrome() {
34554
+ const dirs = (process.env.PATH ?? "").split(import_path8.default.delimiter).filter(Boolean);
34555
+ return dirs.flatMap((dir) => CHROME_NAMES.map((name) => import_path8.default.join(dir, name))).find((file2) => import_fs9.default.existsSync(file2)) ?? null;
34556
+ }
34557
+ function launchChrome() {
34558
+ const executable = findChrome();
34559
+ if (!executable) throw new Error(`Chrome is not installed (looked for ${CHROME_NAMES.join(", ")} on PATH)`);
34560
+ const userDataDir = import_fs9.default.mkdtempSync(import_path8.default.join(import_os4.default.tmpdir(), "replicas-chrome-"));
34561
+ const child = (0, import_child_process5.spawn)(executable, [
34562
+ "--headless=new",
34563
+ "--remote-debugging-pipe",
34564
+ "--no-first-run",
34565
+ "--no-default-browser-check",
34566
+ "--disable-gpu",
34567
+ "--disable-extensions",
34568
+ "--disable-dev-shm-usage",
34569
+ "--hide-scrollbars",
34570
+ "--mute-audio",
34571
+ `--user-data-dir=${userDataDir}`,
34572
+ ...process.getuid?.() === 0 ? ["--no-sandbox"] : [],
34573
+ "about:blank"
34574
+ ], { stdio: ["ignore", "ignore", "pipe", "pipe", "pipe"] });
34575
+ const [, , , input, outputStream] = child.stdio;
34576
+ if (!(input instanceof import_stream.Writable) || !(outputStream instanceof import_stream.Readable)) throw new Error("Chrome did not open its DevTools pipes");
34577
+ let stderr = "";
34578
+ child.stderr?.on("data", (chunk) => {
34579
+ stderr = (stderr + chunk.toString()).slice(-2e3);
34580
+ });
34581
+ let nextId = 1;
34582
+ let exitError = null;
34583
+ const pending = /* @__PURE__ */ new Map();
34584
+ const handlers = /* @__PURE__ */ new Map();
34585
+ const failAll = (error51) => {
34586
+ exitError = error51;
34587
+ for (const request of pending.values()) {
34588
+ clearTimeout(request.timer);
34589
+ request.reject(error51);
34590
+ }
34591
+ pending.clear();
34592
+ };
34593
+ child.on("error", (error51) => failAll(error51));
34594
+ input.on("error", (error51) => failAll(error51));
34595
+ child.on("exit", (code, signal) => failAll(new Error(`Chrome exited (${signal ?? code})${stderr.trim() ? `: ${stderr.trim().split("\n").pop()}` : ""}`)));
34596
+ let buffer = "";
34597
+ outputStream.setEncoding("utf8");
34598
+ outputStream.on("data", (chunk) => {
34599
+ buffer += chunk;
34600
+ let end;
34601
+ while ((end = buffer.indexOf("\0")) !== -1) {
34602
+ const message2 = JSON.parse(buffer.slice(0, end));
34603
+ buffer = buffer.slice(end + 1);
34604
+ if (!isRecord(message2)) continue;
34605
+ if (typeof message2.id === "number") {
34606
+ const request = pending.get(message2.id);
34607
+ if (!request) continue;
34608
+ pending.delete(message2.id);
34609
+ clearTimeout(request.timer);
34610
+ if (isRecord(message2.error)) request.reject(new Error(String(message2.error.message)));
34611
+ else request.resolve(isRecord(message2.result) ? message2.result : {});
34612
+ } else if (typeof message2.method === "string") {
34613
+ const sessionId = typeof message2.sessionId === "string" ? message2.sessionId : "";
34614
+ for (const handler of handlers.get(`${sessionId}:${message2.method}`) ?? []) handler.handle(isRecord(message2.params) ? message2.params : {});
34615
+ }
34616
+ }
34617
+ });
34618
+ const send = (method, params = {}, sessionId) => new Promise((resolve3, reject) => {
34619
+ if (exitError) return reject(exitError);
34620
+ const id = nextId++;
34621
+ const timer = setTimeout(() => {
34622
+ pending.delete(id);
34623
+ reject(new Error(`Chrome did not answer ${method} within ${COMMAND_TIMEOUT_MS / 1e3}s`));
34624
+ }, COMMAND_TIMEOUT_MS);
34625
+ pending.set(id, { resolve: resolve3, reject, timer });
34626
+ input.write(`${JSON.stringify({ id, method, params, ...sessionId ? { sessionId } : {} })}\0`);
34627
+ });
34628
+ return {
34629
+ async newPage() {
34630
+ const { targetId } = await send("Target.createTarget", { url: "about:blank" });
34631
+ const { sessionId } = await send("Target.attachToTarget", { targetId, flatten: true });
34632
+ const page = {
34633
+ send: (method, params) => send(method, params, sessionId),
34634
+ on: (method, handler) => {
34635
+ const key = `${sessionId}:${method}`;
34636
+ const entry = { handle: handler };
34637
+ handlers.set(key, [...handlers.get(key) ?? [], entry]);
34638
+ return () => handlers.set(key, (handlers.get(key) ?? []).filter((existing) => existing !== entry));
34639
+ },
34640
+ async evaluate(expression) {
34641
+ const { result, exceptionDetails } = await page.send("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true });
34642
+ if (exceptionDetails) throw new Error(exceptionDetails.text);
34643
+ return result.value;
34644
+ }
34645
+ };
34646
+ await Promise.all(["Page.enable", "Runtime.enable", "Log.enable"].map((method) => page.send(method)));
34647
+ return page;
34648
+ },
34649
+ // Callers exit the process right after, and Chrome's helper processes write into the profile until Chrome exits.
34650
+ async close() {
34651
+ if (child.exitCode === null && child.signalCode === null) {
34652
+ const exited = new Promise((resolve3) => child.once("exit", resolve3));
34653
+ const kill = setTimeout(() => child.kill("SIGKILL"), CLOSE_TIMEOUT_MS);
34654
+ send("Browser.close").catch(() => child.kill("SIGKILL"));
34655
+ await exited;
34656
+ clearTimeout(kill);
34657
+ }
34658
+ import_fs9.default.rmSync(userDataDir, { recursive: true, force: true });
34659
+ }
34660
+ };
34661
+ }
34662
+
34663
+ // src/commands/visualize.ts
34664
+ var MIB = 1024 * 1024;
34665
+ var MAX_IMAGE_BYTES = 10 * MIB;
34666
+ var MAX_PAGE_BYTES = 25 * MIB;
34667
+ var LOAD_TIMEOUT_MS = 1e4;
34668
+ var SCREENSHOT_DIR = "/tmp/replicas-visualize";
34669
+ var LOCAL_IMAGE = /(["'`])(\/(?!\/)[^"'`\r\n]*?\.(?:png|jpe?g|gif|webp|avif|svg))\1|url\(\s*(\/(?!\/)[^\s"'`()]*?\.(?:png|jpe?g|gif|webp|avif|svg))\s*\)/gi;
34670
+ function inlineLocalImages(html) {
34671
+ const dataUris = /* @__PURE__ */ new Map();
34672
+ const dataUri = (file2) => {
34673
+ if (!dataUris.has(file2)) {
34674
+ const stat = import_fs10.default.statSync(file2, { throwIfNoEntry: false });
34675
+ if (stat && stat.size > MAX_IMAGE_BYTES) throw new Error(`${file2} is ${(stat.size / MIB).toFixed(1)} MiB; each image must be at most ${MAX_IMAGE_BYTES / MIB} MiB.`);
34676
+ dataUris.set(file2, stat?.isFile() ? `data:${classifyCanvasFilename(file2).mimeType};base64,${import_fs10.default.readFileSync(file2).toString("base64")}` : null);
34677
+ }
34678
+ return dataUris.get(file2) ?? null;
34679
+ };
34680
+ const inlined = html.replace(LOCAL_IMAGE, (match, quote, quoted, bare) => {
34681
+ const uri = dataUri(quoted ?? bare ?? "");
34682
+ if (!uri) return match;
34683
+ return quoted ? `${quote}${uri}${quote}` : `url(${uri})`;
34684
+ });
34685
+ if (Buffer.byteLength(inlined) > MAX_PAGE_BYTES) throw new Error(`With its images inlined the page is over ${MAX_PAGE_BYTES / MIB} MiB. Use smaller images.`);
34686
+ return { html: inlined, missing: [...dataUris].filter(([, uri]) => uri === null).map(([file2]) => file2) };
34687
+ }
34688
+ var webAppUrl = () => process.env.REPLICAS_WEB_URL || DEFAULT_WEB_APP_URL;
34689
+ async function servePage(html) {
34690
+ const font = await fetch(new URL(VISUAL_FONT_PATH, webAppUrl()), { signal: AbortSignal.timeout(5e3) }).then((response) => response.ok ? response.arrayBuffer() : null).catch(() => null);
34691
+ const server = import_http3.default.createServer((request, response) => {
34692
+ if (request.url === VISUAL_FONT_PATH && font) {
34693
+ response.writeHead(200, { "Content-Type": "font/woff2", "Access-Control-Allow-Origin": "*" });
34694
+ response.end(Buffer.from(font));
34695
+ return;
34696
+ }
34697
+ response.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": "sandbox allow-scripts" });
34698
+ response.end(html);
34699
+ });
34700
+ await new Promise((resolve3) => server.listen(0, "127.0.0.1", resolve3));
34701
+ const address = server.address();
34702
+ const origin = `http://127.0.0.1:${typeof address === "object" && address ? address.port : 0}`;
34703
+ return { server, pageUrl: `${origin}/page.html`, fontUrl: font ? `${origin}${VISUAL_FONT_PATH}` : void 0 };
34704
+ }
34705
+ function waitFor(page, method, timeoutMs) {
34706
+ return new Promise((resolve3) => {
34707
+ const stop = page.on(method, () => {
34708
+ clearTimeout(timer);
34709
+ stop();
34710
+ resolve3(true);
34711
+ });
34712
+ const timer = setTimeout(() => {
34713
+ stop();
34714
+ resolve3(false);
34715
+ }, timeoutMs);
34716
+ });
34717
+ }
34718
+ async function settledHeight(page) {
34719
+ let last = -1;
34720
+ for (let attempt = 0; attempt < 20; attempt++) {
34721
+ const height = Number(await page.evaluate(
34722
+ `new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve(${VISUAL_CONTENT_HEIGHT_EXPRESSION}))))`
34723
+ ));
34724
+ if (height === last) return height;
34725
+ last = height;
34726
+ }
34727
+ return last;
34728
+ }
34729
+ async function inspect(html, source) {
34730
+ const chrome = launchChrome();
34731
+ let server;
34732
+ try {
34733
+ const served = await servePage(html);
34734
+ server = served.server;
34735
+ const { pageUrl, fontUrl } = served;
34736
+ const page = await chrome.newPage();
34737
+ const messages = [];
34738
+ page.on("Runtime.consoleAPICalled", ({ type, args }) => {
34739
+ const text3 = args.map((arg) => arg.value === void 0 ? arg.description ?? "" : typeof arg.value === "string" ? arg.value : JSON.stringify(arg.value)).join(" ");
34740
+ messages.push({ level: type === "error" || type === "assert" ? "error" : type === "warning" ? "warning" : "log", text: text3 });
34741
+ });
34742
+ page.on("Runtime.exceptionThrown", ({ exceptionDetails }) => {
34743
+ messages.push({ level: "error", text: exceptionDetails.exception?.description ?? exceptionDetails.text });
34744
+ });
34745
+ page.on("Log.entryAdded", ({ entry }) => {
34746
+ if (entry.level === "error") messages.push({ level: "error", text: `${entry.text}${entry.url ? ` (${entry.url})` : ""}` });
34747
+ });
34748
+ let loads = 0;
34749
+ const load = async (appearance, width, height) => {
34750
+ const { r, g, b } = hexToRgb(DESIGN_COLORS[appearance].background);
34751
+ await page.send("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 1, mobile: false });
34752
+ await page.send("Emulation.setEmulatedMedia", { features: [{ name: "prefers-color-scheme", value: appearance }] });
34753
+ await page.send("Emulation.setDefaultBackgroundColorOverride", { color: { r, g, b, a: 1 } });
34754
+ const loaded = waitFor(page, "Page.loadEventFired", LOAD_TIMEOUT_MS);
34755
+ await page.send("Page.navigate", { url: `${pageUrl}?load=${++loads}${visualThemeFragment(visualTheme(appearance, fontUrl))}` });
34756
+ if (!await loaded) throw new Error(`The page did not finish loading within ${LOAD_TIMEOUT_MS / 1e3}s. Check for scripts, images, or fonts that never finish downloading.`);
34757
+ await page.evaluate("document.fonts.ready.then(() => true)");
34758
+ };
34759
+ const resize = (width, height) => page.send("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 1, mobile: false });
34760
+ const screenshot = async (appearance, width) => {
34761
+ await load(appearance, width, 600);
34762
+ await resize(width, clampVisualHeight(await settledHeight(page)));
34763
+ const { data } = await page.send("Page.captureScreenshot", { format: "png" });
34764
+ import_fs10.default.mkdirSync(SCREENSHOT_DIR, { recursive: true });
34765
+ const file2 = import_path9.default.join(SCREENSHOT_DIR, `${import_path9.default.basename(source, import_path9.default.extname(source))}-${appearance}-${width}.png`);
34766
+ import_fs10.default.writeFileSync(file2, Buffer.from(data, "base64"));
34767
+ return file2;
34768
+ };
34769
+ await load("dark", VISUAL_COLUMN_WIDTH, 600);
34770
+ const heights = [];
34771
+ const overflowWidths = [];
34772
+ for (const width of VISUAL_MEASURE_WIDTHS) {
34773
+ await resize(width, 600);
34774
+ heights.push([width, await settledHeight(page)]);
34775
+ if (await page.evaluate("document.documentElement.scrollWidth > document.documentElement.clientWidth + 1") === true) overflowWidths.push(width);
34776
+ }
34777
+ await resize(VISUAL_COLUMN_WIDTH, 300);
34778
+ const short = await settledHeight(page);
34779
+ await resize(VISUAL_COLUMN_WIDTH, 1500);
34780
+ const tall = await settledHeight(page);
34781
+ const backgroundColor = await page.evaluate(
34782
+ "[document.documentElement, document.body].map((element) => element && getComputedStyle(element).backgroundColor).find((color) => color && !/^(transparent|rgba\\(.*, 0\\))$/.test(color)) ?? null"
34783
+ );
34784
+ const background = typeof backgroundColor === "string" ? backgroundColor : null;
34785
+ const screenshots = [await screenshot("dark", VISUAL_COLUMN_WIDTH), await screenshot("light", VISUAL_PHONE_WIDTH)];
34786
+ const traced = messages.map((message2) => ({ ...message2, text: message2.text.replaceAll(new RegExp(`${pageUrl}\\?load=\\d+`, "g"), source) }));
34787
+ const consoleMessages = traced.filter((message2, index) => traced.findIndex((other) => other.text === message2.text) === index);
34788
+ return { heights, viewportDependent: Math.abs(short - tall) > 1, overflowWidths, background, consoleMessages, screenshots };
34789
+ } finally {
34790
+ server?.close();
34791
+ await chrome.close();
34792
+ }
34793
+ }
34794
+ function hexToRgb(hex3) {
34795
+ const value = parseInt(hex3.slice(1), 16);
34796
+ return { r: value >> 16, g: value >> 8 & 255, b: value & 255 };
34797
+ }
34798
+ var VIEWPORT_DEPENDENT_FIX = "Its height follows the frame (100vh, height:100%, or min-height:100vh on html, body, or the outer element), so in chat it would grow without end. Let content set the height.";
34799
+ function warnings(inspection) {
34800
+ return [
34801
+ ...inspection.viewportDependent ? [VIEWPORT_DEPENDENT_FIX] : [],
34802
+ ...inspection.overflowWidths.length > 0 ? [`Content overflows horizontally at ${inspection.overflowWidths.join("px, ")}px. Let it wrap or stack at narrow widths.`] : [],
34803
+ ...inspection.heights.some(([, height]) => height > VISUAL_MAX_HEIGHT) ? [`The page is taller than ${VISUAL_MAX_HEIGHT}px at some widths, so chat cuts off the bottom. Make it shorter or split it into several visuals.`] : [],
34804
+ ...inspection.background ? [`The page paints its own background (${inspection.background}) on html or body. Leave them transparent so it sits on the chat background.`] : []
34805
+ ];
34806
+ }
34807
+ var consoleErrors = (inspection) => inspection.consoleMessages.filter((message2) => message2.level === "error").map((message2) => `Console error: ${message2.text}`);
34808
+ async function visualizeCommand(file2, options) {
34809
+ const source = import_path9.default.resolve(file2);
34810
+ if (!import_fs10.default.statSync(source, { throwIfNoEntry: false })?.isFile()) throw new Error(`File not found: ${source}`);
34811
+ const { html: inlined, missing } = inlineLocalImages(import_fs10.default.readFileSync(source, "utf8"));
34812
+ const html = injectVisualBootstrap(inlined);
34813
+ const missingImages = missing.length > 0 ? [`These local images could not be read, so they won't show: ${missing.join(", ")}. Use absolute paths to existing image files.`] : [];
34814
+ if (options.check) {
34815
+ const inspection2 = await inspect(html, source);
34816
+ output({ file: source, ...inspection2, warnings: [...missingImages, ...warnings(inspection2)] }, (result) => {
34817
+ console.log(`Checked ${source} (dark at ${VISUAL_COLUMN_WIDTH}px, light at ${VISUAL_PHONE_WIDTH}px).
34818
+ `);
34819
+ console.log("Screenshots (open both and look before publishing):");
34820
+ for (const screenshot of result.screenshots) console.log(` ${screenshot}`);
34821
+ console.log(`
34822
+ Height: ${result.heights.filter(([width]) => width === VISUAL_COLUMN_WIDTH || width === VISUAL_MEASURE_WIDTHS[0]).map(([width, height]) => `${height}px at ${width}px`).join(", ")}`);
34823
+ if (result.consoleMessages.length > 0) {
34824
+ console.log("\nConsole:");
34825
+ for (const message2 of result.consoleMessages) console.log(` ${message2.level.padEnd(7)} ${message2.text}`);
34826
+ }
34827
+ const errors = result.consoleMessages.some((message2) => message2.level === "error") ? ["The console errors above."] : [];
34828
+ if (result.warnings.length + errors.length > 0) {
34829
+ console.log("\nFix before publishing:");
34830
+ for (const warning of [...result.warnings, ...errors]) console.log(` - ${warning}`);
34831
+ }
34832
+ console.log(`
34833
+ Next: publish with \`replicas visualize ${file2} --title "<short title>"\`.`);
34834
+ });
34835
+ return;
34836
+ }
34837
+ const title2 = options.title?.trim();
34838
+ if (!title2) throw new Error('Pass --title "<short title>" to publish, or --check to preview without publishing.');
34839
+ const inspection = await inspect(html, source);
34840
+ if (inspection.viewportDependent) throw new Error(`Not published. ${VIEWPORT_DEPENDENT_FIX}`);
34841
+ const media2 = await uploadMedia(new Blob([html], { type: "text/html; charset=utf-8" }), "visual.html", {
34842
+ kind: "html",
34843
+ display_name: title2,
34844
+ visual_heights: JSON.stringify(inspection.heights.map(([width, height]) => [width, clampVisualHeight(height)]))
34845
+ });
34846
+ const embed = `![${title2.replace(/[[\]]/g, "")}](${MONOLITH_URL}/v1/media/${media2.id})`;
34847
+ const forgeUrl = getOrganizationMediaUrl(media2.organization_id, media2.id);
34848
+ const notes = [...missingImages, ...warnings(inspection), ...consoleErrors(inspection)];
34849
+ const { screenshots } = inspection;
34850
+ output({ media: media2, embed, forgeUrl, screenshots, warnings: notes }, () => {
34851
+ console.log(embed);
34852
+ console.log(`Forge link (organization only): [View ${title2}](${forgeUrl})`);
34853
+ console.log("\nScreenshots (open both; if something looks wrong, fix the page and publish again):");
34854
+ for (const screenshot of screenshots) console.log(` ${screenshot}`);
34855
+ if (notes.length > 0) {
34856
+ console.log("\nPublished, but fix these and publish again:");
34857
+ for (const note of notes) console.log(` - ${note}`);
34858
+ }
34859
+ console.log("\nNext: paste the ![\u2026](\u2026) line on its own line where the visual belongs in your final reply. The user sees the visual itself, so don't introduce or restate it; add only what it doesn't say. The Forge link is for posting outside Replicas chat (PRs, Slack, Linear); leave it out of your chat reply.");
34860
+ });
34861
+ }
34862
+
34305
34863
  // src/commands/gpu.ts
34306
34864
  var import_chalk28 = __toESM(require("chalk"));
34307
34865
  var import_node_child_process2 = require("child_process");
@@ -34449,21 +35007,21 @@ function assertTransferSize(bytes, localPath) {
34449
35007
  }
34450
35008
  }
34451
35009
  function archive(localPath) {
34452
- const path8 = (0, import_node_path2.resolve)(localPath);
34453
- if (!(0, import_node_fs2.existsSync)(path8)) throw new Error(`${path8} does not exist`);
34454
- if (!(0, import_node_fs2.statSync)(path8).isDirectory()) {
34455
- assertTransferSize((0, import_node_fs2.statSync)(path8).size, localPath);
34456
- return (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", (0, import_node_path2.dirname)(path8), (0, import_node_path2.basename)(path8)], { stdio: ["ignore", "pipe", "pipe"] });
35010
+ const path10 = (0, import_node_path2.resolve)(localPath);
35011
+ if (!(0, import_node_fs2.existsSync)(path10)) throw new Error(`${path10} does not exist`);
35012
+ if (!(0, import_node_fs2.statSync)(path10).isDirectory()) {
35013
+ assertTransferSize((0, import_node_fs2.statSync)(path10).size, localPath);
35014
+ return (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", (0, import_node_path2.dirname)(path10), (0, import_node_path2.basename)(path10)], { stdio: ["ignore", "pipe", "pipe"] });
34457
35015
  }
34458
- const listed = (0, import_node_child_process2.spawnSync)("git", ["-C", path8, "ls-files", "-co", "--exclude-standard", "-z"], { maxBuffer: 256 * 1024 * 1024 });
35016
+ const listed = (0, import_node_child_process2.spawnSync)("git", ["-C", path10, "ls-files", "-co", "--exclude-standard", "-z"], { maxBuffer: 256 * 1024 * 1024 });
34459
35017
  if (listed.status !== 0) {
34460
- const du = (0, import_node_child_process2.spawnSync)("du", ["-sk", "--exclude=.git", "--exclude=node_modules", path8]);
35018
+ const du = (0, import_node_child_process2.spawnSync)("du", ["-sk", "--exclude=.git", "--exclude=node_modules", path10]);
34461
35019
  assertTransferSize(Number(du.stdout.toString().split(" ")[0]) * 1024, localPath);
34462
- return (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", path8, "--exclude=.git", "--exclude=node_modules", "."], { stdio: ["ignore", "pipe", "pipe"] });
35020
+ return (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", path10, "--exclude=.git", "--exclude=node_modules", "."], { stdio: ["ignore", "pipe", "pipe"] });
34463
35021
  }
34464
- const files = listed.stdout.toString().split("\0").filter((file2) => file2 && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(path8, file2)));
34465
- assertTransferSize(files.reduce((total, file2) => total + (0, import_node_fs2.statSync)((0, import_node_path2.join)(path8, file2)).size, 0), localPath);
34466
- const child = (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", path8, "--null", "-T", "-"], { stdio: ["pipe", "pipe", "pipe"] });
35022
+ const files = listed.stdout.toString().split("\0").filter((file2) => file2 && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(path10, file2)));
35023
+ assertTransferSize(files.reduce((total, file2) => total + (0, import_node_fs2.statSync)((0, import_node_path2.join)(path10, file2)).size, 0), localPath);
35024
+ const child = (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", path10, "--null", "-T", "-"], { stdio: ["pipe", "pipe", "pipe"] });
34467
35025
  child.stdin.end(files.map((file2) => `${file2}\0`).join(""));
34468
35026
  return child;
34469
35027
  }
@@ -35089,12 +35647,17 @@ if (isAgentMode()) {
35089
35647
  service.command("list").description("List registered services and whether they are running").action(run(serviceListCommand));
35090
35648
  service.command("logs <name>").description("Print the last lines of a service log").option("-n, --lines <n>", "Number of lines to print (default 50)").action(run(serviceLogsCommand));
35091
35649
  media.command("gif <recording>").description("Turn a screen recording into a short looping GIF under GitHub's 5 MiB image limit, lowering fps and width until it fits").option("-o, --output <path>", "Output .gif path (default: the recording path with .gif)").option("--start <seconds>", "Where the GIF starts in the recording (default 0)").option("--duration <seconds>", "Length in seconds, 1-30 (default 10)").option("--width <px>", "Maximum width, 200-1280 (default 960)").option("--fps <n>", "Maximum frame rate, 5-20 (default 12)").option("--crop <w:h:x:y>", "Crop to the region that changes, in recording pixels, before scaling").action(run(mediaGifCommand));
35092
- media.command("upload <files...>").description("Upload one or more image, video, audio, or HTML files. Multiple files are uploaded concurrently.").option("-k, --kind <kind>", "Media kind: image, video, audio, or html (inferred from extension if omitted)").option("-s, --session-id <id>", "Session ID to associate the asset with").option(
35650
+ media.command("upload <files...>").description("Upload one or more image, video, or audio files. Multiple files are uploaded concurrently. Publish HTML pages with `replicas visualize`.").option("-k, --kind <kind>", "Media kind: image, video, or audio (inferred from extension if omitted)").option("-s, --session-id <id>", "Session ID to associate the asset with").option(
35093
35651
  "-n, --name <name>",
35094
35652
  "Required human-facing display name shown in the dashboard. Pass one --name per file, in order.",
35095
35653
  (value, previous) => [...previous, value],
35096
35654
  []
35097
- ).option("--access <access>", "Link access for video, audio, and HTML: organization (sign-in required) or public (video and audio only). Images always get a public link").option("--share", "Alias for --access public; not supported for HTML").action(run(mediaUploadCommand));
35655
+ ).option("--access <access>", "Link access for video and audio: organization (sign-in required) or public. Images always get a public link").option("--share", "Alias for --access public").action(run(mediaUploadCommand));
35656
+ program.command("visualize <file>").description("Show a self-contained HTML page (chart, diagram, comparison, mockup) inline in your Replicas chat reply, styled like the chat").option("-t, --title <title>", "Short name for the visual, used as its label (required to publish)").option("--check", "Render without publishing: screenshots in dark and light, heights, console output, and layout warnings").addHelpText("after", `
35657
+ Publishing checks the page too and prints screenshots: look at them, and if something is wrong, fix the page and publish again. Paste the printed ![\u2026](\u2026) line where the visual belongs in your reply.
35658
+
35659
+ Page rules:
35660
+ ${VISUAL_PAGE_RULES}`).action(run(visualizeCommand));
35098
35661
  media.command("share <mediaId>").description("Rotate an image's public forge URL, or create one for video or audio media").action(run(mediaShareCommand));
35099
35662
  media.command("revoke <mediaId>").description("Revoke the public forge URL for uploaded media").action(run(mediaRevokeCommand));
35100
35663
  media.command("delete <mediaId>").description("Permanently delete media uploaded by this workspace").action(run(mediaDeleteCommand));
@@ -35120,6 +35683,7 @@ if (isAgentMode()) {
35120
35683
  "preview",
35121
35684
  "service",
35122
35685
  "media",
35686
+ "visualize",
35123
35687
  "gpu",
35124
35688
  "history",
35125
35689
  "skills",