@ghostystudio/cli 0.39.0 → 0.40.0

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 (3) hide show
  1. package/README.md +5 -0
  2. package/dist/index.js +208 -71
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -83,6 +83,7 @@ ghosty conversations ls --user EMAIL [--since 30d] · conversations show <id> <c
83
83
  ghosty turns stats [--since 14d] [--all] · push log --user EMAIL [--since 2d]
84
84
  ghosty agents doctor --user EMAIL [agent] [--turns 8] · usage --images [--plan free] [--by user]
85
85
  ghosty novedades ls|create|set|test|send|rm [--title] [--body] [--image FILE] [--link] [--to] [--publish] [--at]
86
+ ghosty sites ls [--house] · sites publish <file.html|folder> [--title] [--slug S] [--site <slug|id>] · sites slug <slug|id> <new-slug> # house sites on EasyBits
86
87
  ghosty credentials ls|set|rm [provider] [--from-env VAR] # never as an argument
87
88
  ghosty docs
88
89
 
@@ -99,6 +100,10 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
99
100
 
100
101
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
101
102
 
103
+ ### Changes in 0.40.0
104
+
105
+ - `sites ls [--house]`: the sites your agents published (or, staff, Ghosty's house sites). `sites publish <file.html|folder> [--title] [--slug S] [--site <slug|id>]` (staff) publishes a house site on EasyBits — no owner, no expiry, no banner; `--site` updates one. `sites slug <slug|id> <new-slug>` (staff) changes a site's slug. Slug errors come with a code: `slug_invalid`, `slug_taken`, `slug_unsupported` (EasyBits doesn't accept a slug yet; nothing changes, exit 1).
106
+
102
107
  ### Changes in 0.39.0
103
108
 
104
109
  - `spaces transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys]` (staff): hands a workspace to a new owner in one step. Its agents go with it and spend the new owner's key, so `--copy-keys` copies the ones they lack (the dry run lists what's missing); the previous owner keeps the role you pick, and the agents' boxes are recycled so the new owner shows up everywhere.
package/dist/index.js CHANGED
@@ -95,7 +95,7 @@ var require_visit = __commonJS({
95
95
  "use strict";
96
96
  var identity = require_identity();
97
97
  var BREAK = /* @__PURE__ */ Symbol("break visit");
98
- var SKIP = /* @__PURE__ */ Symbol("skip children");
98
+ var SKIP2 = /* @__PURE__ */ Symbol("skip children");
99
99
  var REMOVE = /* @__PURE__ */ Symbol("remove node");
100
100
  function visit(node, visitor) {
101
101
  const visitor_ = initVisitor(visitor);
@@ -107,7 +107,7 @@ var require_visit = __commonJS({
107
107
  visit_(null, node, visitor_, Object.freeze([]));
108
108
  }
109
109
  visit.BREAK = BREAK;
110
- visit.SKIP = SKIP;
110
+ visit.SKIP = SKIP2;
111
111
  visit.REMOVE = REMOVE;
112
112
  function visit_(key, node, visitor, path) {
113
113
  const ctrl = callVisitor(key, node, visitor, path);
@@ -155,7 +155,7 @@ var require_visit = __commonJS({
155
155
  await visitAsync_(null, node, visitor_, Object.freeze([]));
156
156
  }
157
157
  visitAsync.BREAK = BREAK;
158
- visitAsync.SKIP = SKIP;
158
+ visitAsync.SKIP = SKIP2;
159
159
  visitAsync.REMOVE = REMOVE;
160
160
  async function visitAsync_(key, node, visitor, path) {
161
161
  const ctrl = await callVisitor(key, node, visitor, path);
@@ -3122,9 +3122,9 @@ var require_timestamp = __commonJS({
3122
3122
  const match = str.match(timestamp.test);
3123
3123
  if (!match)
3124
3124
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
3125
- const [, year, month, day, hour, minute, second] = match.map(Number);
3125
+ const [, year, month, day2, hour, minute, second] = match.map(Number);
3126
3126
  const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
3127
- let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
3127
+ let date = Date.UTC(year, month - 1, day2, hour || 0, minute || 0, second || 0, millisec);
3128
3128
  const tz = match[8];
3129
3129
  if (tz && tz !== "Z") {
3130
3130
  let d = parseSexagesimal(tz, false);
@@ -3995,10 +3995,10 @@ var require_resolve_block_map = __commonJS({
3995
3995
  let offset = bm.offset;
3996
3996
  let commentEnd = null;
3997
3997
  for (const collItem of bm.items) {
3998
- const { start, key, sep, value } = collItem;
3998
+ const { start, key, sep: sep2, value } = collItem;
3999
3999
  const keyProps = resolveProps.resolveProps(start, {
4000
4000
  indicator: "explicit-key-ind",
4001
- next: key ?? sep?.[0],
4001
+ next: key ?? sep2?.[0],
4002
4002
  offset,
4003
4003
  onError,
4004
4004
  parentIndent: bm.indent,
@@ -4012,7 +4012,7 @@ var require_resolve_block_map = __commonJS({
4012
4012
  else if ("indent" in key && key.indent !== bm.indent)
4013
4013
  onError(offset, "BAD_INDENT", startColMsg);
4014
4014
  }
4015
- if (!keyProps.anchor && !keyProps.tag && !sep) {
4015
+ if (!keyProps.anchor && !keyProps.tag && !sep2) {
4016
4016
  commentEnd = keyProps.end;
4017
4017
  if (keyProps.comment) {
4018
4018
  if (map.comment)
@@ -4036,7 +4036,7 @@ var require_resolve_block_map = __commonJS({
4036
4036
  ctx.atKey = false;
4037
4037
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
4038
4038
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
4039
- const valueProps = resolveProps.resolveProps(sep ?? [], {
4039
+ const valueProps = resolveProps.resolveProps(sep2 ?? [], {
4040
4040
  indicator: "map-value-ind",
4041
4041
  next: value,
4042
4042
  offset: keyNode.range[2],
@@ -4052,7 +4052,7 @@ var require_resolve_block_map = __commonJS({
4052
4052
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
4053
4053
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
4054
4054
  }
4055
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep, null, valueProps, onError);
4055
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep2, null, valueProps, onError);
4056
4056
  if (ctx.schema.compat)
4057
4057
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
4058
4058
  offset = valueNode.range[2];
@@ -4143,7 +4143,7 @@ var require_resolve_end = __commonJS({
4143
4143
  let comment = "";
4144
4144
  if (end) {
4145
4145
  let hasSpace = false;
4146
- let sep = "";
4146
+ let sep2 = "";
4147
4147
  for (const token of end) {
4148
4148
  const { source, type } = token;
4149
4149
  switch (type) {
@@ -4157,13 +4157,13 @@ var require_resolve_end = __commonJS({
4157
4157
  if (!comment)
4158
4158
  comment = cb;
4159
4159
  else
4160
- comment += sep + cb;
4161
- sep = "";
4160
+ comment += sep2 + cb;
4161
+ sep2 = "";
4162
4162
  break;
4163
4163
  }
4164
4164
  case "newline":
4165
4165
  if (comment)
4166
- sep += source;
4166
+ sep2 += source;
4167
4167
  hasSpace = true;
4168
4168
  break;
4169
4169
  default:
@@ -4206,18 +4206,18 @@ var require_resolve_flow_collection = __commonJS({
4206
4206
  let offset = fc.offset + fc.start.source.length;
4207
4207
  for (let i = 0; i < fc.items.length; ++i) {
4208
4208
  const collItem = fc.items[i];
4209
- const { start, key, sep, value } = collItem;
4209
+ const { start, key, sep: sep2, value } = collItem;
4210
4210
  const props = resolveProps.resolveProps(start, {
4211
4211
  flow: fcName,
4212
4212
  indicator: "explicit-key-ind",
4213
- next: key ?? sep?.[0],
4213
+ next: key ?? sep2?.[0],
4214
4214
  offset,
4215
4215
  onError,
4216
4216
  parentIndent: fc.indent,
4217
4217
  startOnNewline: false
4218
4218
  });
4219
4219
  if (!props.found) {
4220
- if (!props.anchor && !props.tag && !sep && !value) {
4220
+ if (!props.anchor && !props.tag && !sep2 && !value) {
4221
4221
  if (i === 0 && props.comma)
4222
4222
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
4223
4223
  else if (i < fc.items.length - 1)
@@ -4271,8 +4271,8 @@ var require_resolve_flow_collection = __commonJS({
4271
4271
  }
4272
4272
  }
4273
4273
  }
4274
- if (!isMap && !sep && !props.found) {
4275
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep, null, props, onError);
4274
+ if (!isMap && !sep2 && !props.found) {
4275
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep2, null, props, onError);
4276
4276
  coll.items.push(valueNode);
4277
4277
  offset = valueNode.range[2];
4278
4278
  if (isBlock(value))
@@ -4284,7 +4284,7 @@ var require_resolve_flow_collection = __commonJS({
4284
4284
  if (isBlock(key))
4285
4285
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
4286
4286
  ctx.atKey = false;
4287
- const valueProps = resolveProps.resolveProps(sep ?? [], {
4287
+ const valueProps = resolveProps.resolveProps(sep2 ?? [], {
4288
4288
  flow: fcName,
4289
4289
  indicator: "map-value-ind",
4290
4290
  next: value,
@@ -4295,8 +4295,8 @@ var require_resolve_flow_collection = __commonJS({
4295
4295
  });
4296
4296
  if (valueProps.found) {
4297
4297
  if (!isMap && !props.found && ctx.options.strict) {
4298
- if (sep)
4299
- for (const st of sep) {
4298
+ if (sep2)
4299
+ for (const st of sep2) {
4300
4300
  if (st === valueProps.found)
4301
4301
  break;
4302
4302
  if (st.type === "newline") {
@@ -4313,7 +4313,7 @@ var require_resolve_flow_collection = __commonJS({
4313
4313
  else
4314
4314
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
4315
4315
  }
4316
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) : null;
4316
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep2, null, valueProps, onError) : null;
4317
4317
  if (valueNode) {
4318
4318
  if (isBlock(value))
4319
4319
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -4493,7 +4493,7 @@ var require_resolve_block_scalar = __commonJS({
4493
4493
  chompStart = i + 1;
4494
4494
  }
4495
4495
  let value = "";
4496
- let sep = "";
4496
+ let sep2 = "";
4497
4497
  let prevMoreIndented = false;
4498
4498
  for (let i = 0; i < contentStart; ++i)
4499
4499
  value += lines[i][0].slice(trimIndent) + "\n";
@@ -4510,24 +4510,24 @@ var require_resolve_block_scalar = __commonJS({
4510
4510
  indent = "";
4511
4511
  }
4512
4512
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
4513
- value += sep + indent.slice(trimIndent) + content;
4514
- sep = "\n";
4513
+ value += sep2 + indent.slice(trimIndent) + content;
4514
+ sep2 = "\n";
4515
4515
  } else if (indent.length > trimIndent || content[0] === " ") {
4516
- if (sep === " ")
4517
- sep = "\n";
4518
- else if (!prevMoreIndented && sep === "\n")
4519
- sep = "\n\n";
4520
- value += sep + indent.slice(trimIndent) + content;
4521
- sep = "\n";
4516
+ if (sep2 === " ")
4517
+ sep2 = "\n";
4518
+ else if (!prevMoreIndented && sep2 === "\n")
4519
+ sep2 = "\n\n";
4520
+ value += sep2 + indent.slice(trimIndent) + content;
4521
+ sep2 = "\n";
4522
4522
  prevMoreIndented = true;
4523
4523
  } else if (content === "") {
4524
- if (sep === "\n")
4524
+ if (sep2 === "\n")
4525
4525
  value += "\n";
4526
4526
  else
4527
- sep = "\n";
4527
+ sep2 = "\n";
4528
4528
  } else {
4529
- value += sep + content;
4530
- sep = " ";
4529
+ value += sep2 + content;
4530
+ sep2 = " ";
4531
4531
  prevMoreIndented = false;
4532
4532
  }
4533
4533
  }
@@ -4710,25 +4710,25 @@ var require_resolve_flow_scalar = __commonJS({
4710
4710
  trimBoth = /^[ \t]+|[ \t]+$/g;
4711
4711
  }
4712
4712
  let res = match[1].replace(trimEnd, "");
4713
- let sep = " ";
4713
+ let sep2 = " ";
4714
4714
  let pos = line2.lastIndex;
4715
4715
  while (match = line2.exec(source)) {
4716
4716
  const lm = match[1].replace(trimBoth, "");
4717
4717
  if (lm === "") {
4718
- if (sep === "\n")
4719
- res += sep;
4718
+ if (sep2 === "\n")
4719
+ res += sep2;
4720
4720
  else
4721
- sep = "\n";
4721
+ sep2 = "\n";
4722
4722
  } else {
4723
- res += sep + lm;
4724
- sep = " ";
4723
+ res += sep2 + lm;
4724
+ sep2 = " ";
4725
4725
  }
4726
4726
  pos = line2.lastIndex;
4727
4727
  }
4728
4728
  const last = /[ \t]*(.*)/sy;
4729
4729
  last.lastIndex = pos;
4730
4730
  match = last.exec(source);
4731
- return res + sep + (match?.[1] ?? "");
4731
+ return res + sep2 + (match?.[1] ?? "");
4732
4732
  }
4733
4733
  function doubleQuotedValue(source, onError) {
4734
4734
  let res = "";
@@ -5538,14 +5538,14 @@ var require_cst_stringify = __commonJS({
5538
5538
  }
5539
5539
  }
5540
5540
  }
5541
- function stringifyItem({ start, key, sep, value }) {
5541
+ function stringifyItem({ start, key, sep: sep2, value }) {
5542
5542
  let res = "";
5543
5543
  for (const st of start)
5544
5544
  res += st.source;
5545
5545
  if (key)
5546
5546
  res += stringifyToken(key);
5547
- if (sep)
5548
- for (const st of sep)
5547
+ if (sep2)
5548
+ for (const st of sep2)
5549
5549
  res += st.source;
5550
5550
  if (value)
5551
5551
  res += stringifyToken(value);
@@ -5560,7 +5560,7 @@ var require_cst_visit = __commonJS({
5560
5560
  "../../node_modules/yaml/dist/parse/cst-visit.js"(exports) {
5561
5561
  "use strict";
5562
5562
  var BREAK = /* @__PURE__ */ Symbol("break visit");
5563
- var SKIP = /* @__PURE__ */ Symbol("skip children");
5563
+ var SKIP2 = /* @__PURE__ */ Symbol("skip children");
5564
5564
  var REMOVE = /* @__PURE__ */ Symbol("remove item");
5565
5565
  function visit(cst, visitor) {
5566
5566
  if ("type" in cst && cst.type === "document")
@@ -5568,7 +5568,7 @@ var require_cst_visit = __commonJS({
5568
5568
  _visit(Object.freeze([]), cst, visitor);
5569
5569
  }
5570
5570
  visit.BREAK = BREAK;
5571
- visit.SKIP = SKIP;
5571
+ visit.SKIP = SKIP2;
5572
5572
  visit.REMOVE = REMOVE;
5573
5573
  visit.itemAtPath = (cst, path) => {
5574
5574
  let item = cst;
@@ -6712,18 +6712,18 @@ var require_parser = __commonJS({
6712
6712
  if (this.type === "map-value-ind") {
6713
6713
  const prev = getPrevProps(this.peek(2));
6714
6714
  const start = getFirstKeyStartProps(prev);
6715
- let sep;
6715
+ let sep2;
6716
6716
  if (scalar.end) {
6717
- sep = scalar.end;
6718
- sep.push(this.sourceToken);
6717
+ sep2 = scalar.end;
6718
+ sep2.push(this.sourceToken);
6719
6719
  delete scalar.end;
6720
6720
  } else
6721
- sep = [this.sourceToken];
6721
+ sep2 = [this.sourceToken];
6722
6722
  const map = {
6723
6723
  type: "block-map",
6724
6724
  offset: scalar.offset,
6725
6725
  indent: scalar.indent,
6726
- items: [{ start, key: scalar, sep }]
6726
+ items: [{ start, key: scalar, sep: sep2 }]
6727
6727
  };
6728
6728
  this.onKeyLine = true;
6729
6729
  this.stack[this.stack.length - 1] = map;
@@ -6876,15 +6876,15 @@ var require_parser = __commonJS({
6876
6876
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
6877
6877
  const start2 = getFirstKeyStartProps(it.start);
6878
6878
  const key = it.key;
6879
- const sep = it.sep;
6880
- sep.push(this.sourceToken);
6879
+ const sep2 = it.sep;
6880
+ sep2.push(this.sourceToken);
6881
6881
  delete it.key;
6882
6882
  delete it.sep;
6883
6883
  this.stack.push({
6884
6884
  type: "block-map",
6885
6885
  offset: this.offset,
6886
6886
  indent: this.indent,
6887
- items: [{ start: start2, key, sep }]
6887
+ items: [{ start: start2, key, sep: sep2 }]
6888
6888
  });
6889
6889
  } else if (start.length > 0) {
6890
6890
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -7078,13 +7078,13 @@ var require_parser = __commonJS({
7078
7078
  const prev = getPrevProps(parent);
7079
7079
  const start = getFirstKeyStartProps(prev);
7080
7080
  fixFlowSeqItems(fc);
7081
- const sep = fc.end.splice(1, fc.end.length);
7082
- sep.push(this.sourceToken);
7081
+ const sep2 = fc.end.splice(1, fc.end.length);
7082
+ sep2.push(this.sourceToken);
7083
7083
  const map = {
7084
7084
  type: "block-map",
7085
7085
  offset: fc.offset,
7086
7086
  indent: fc.indent,
7087
- items: [{ start, key: fc, sep }]
7087
+ items: [{ start, key: fc, sep: sep2 }]
7088
7088
  };
7089
7089
  this.onKeyLine = true;
7090
7090
  this.stack[this.stack.length - 1] = map;
@@ -8088,6 +8088,29 @@ var SERVER_MESSAGES = [
8088
8088
  ["turnToken excede {} caracteres", "turnToken exceeds {} characters"],
8089
8089
  ["turnToken no puede llevar espacios", "turnToken can't contain whitespace"],
8090
8090
  ["turnToken requerido: los MCP de este agente usan ${turn.token}", "turnToken required: this agent's MCP servers use ${turn.token}"],
8091
+ // — Sitios de la casa (`ghosty sites`, 3-oct)
8092
+ ["los sitios de la casa: s\xF3lo el staff de Ghosty", "house sites: Ghosty staff only"],
8093
+ ["publicar sitios de la casa: s\xF3lo el staff de Ghosty", "publishing house sites: Ghosty staff only"],
8094
+ ["cambiar el slug de un sitio: s\xF3lo el staff de Ghosty", "changing a site's slug: Ghosty staff only"],
8095
+ ["scope acepta mine o house", "scope takes mine or house"],
8096
+ ["falta html o files", "missing html or files"],
8097
+ ["falta slug", "missing slug"],
8098
+ ["EasyBits todav\xEDa no permite elegir ni cambiar el slug", "EasyBits doesn't allow choosing or changing the slug yet"],
8099
+ ["el slug \xAB{}\xBB ya est\xE1 ocupado en EasyBits", "slug \xAB{}\xBB is already taken on EasyBits"],
8100
+ ["el slug \xAB{}\xBB ya est\xE1 ocupado", "slug \xAB{}\xBB is already taken"],
8101
+ ["EasyBits rechaz\xF3 el slug \xAB{}\xBB por inv\xE1lido", "EasyBits rejected slug \xAB{}\xBB as invalid"],
8102
+ ["el slug \xAB{}\xBB est\xE1 reservado", "slug \xAB{}\xBB is reserved"],
8103
+ ["el slug debe tener entre 3 y 60 caracteres", "the slug must be 3 to 60 characters"],
8104
+ ["el slug s\xF3lo admite min\xFAsculas (a-z), n\xFAmeros y guiones", "the slug only allows lowercase letters (a-z), digits and hyphens"],
8105
+ ["el slug no puede empezar ni terminar en guion", "the slug can't start or end with a hyphen"],
8106
+ ["el slug no puede llevar dos guiones seguidos", "the slug can't have two hyphens in a row"],
8107
+ ["no encontr\xE9 un sitio vivo \xAB{}\xBB; publica sin `site` para crear uno nuevo", "no live site \xAB{}\xBB; publish without `site` to create a new one"],
8108
+ ["no encontr\xE9 un sitio vivo \xAB{}\xBB", "no live site \xAB{}\xBB"],
8109
+ ["ese sitio no es de la casa (s\xF3lo se actualizan con `--site` los publicados como casa)", "that site isn't a house site (--site only updates sites published as house)"],
8110
+ ["la publicaci\xF3n de sitios no est\xE1 disponible en este momento", "site publishing isn't available right now"],
8111
+ ["no pude conectar con el servicio de sitios ({})", "couldn't reach the sites service ({})"],
8112
+ ["no se pudo crear el sitio; int\xE9ntalo en un momento", "couldn't create the site; try again in a moment"],
8113
+ ["no se pudo subir {}: {}", "couldn't upload {}: {}"],
8091
8114
  // — Infra interna (casi nunca llega al CLI, pero por si acaso)
8092
8115
  ["SESSION_SECRET ausente en producci\xF3n \u2014 las cookies quedar\xEDan firmadas con un valor p\xFAblico. Inyectarlo con ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32).", "SESSION_SECRET missing in production \u2014 cookies would be signed with a public value. Inject it with ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32)."],
8093
8116
  ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
@@ -8390,7 +8413,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8390
8413
  const req = httpsRequest(`${base}${path}`, {
8391
8414
  method: "POST",
8392
8415
  timeout: timeoutMs,
8393
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.39.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8416
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.40.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8394
8417
  }, (res) => {
8395
8418
  let buf = "";
8396
8419
  res.on("data", (c) => buf += c);
@@ -8432,7 +8455,7 @@ async function request(path, opts = {}, retried = false) {
8432
8455
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8433
8456
  headers: {
8434
8457
  authorization: `Bearer ${token}`,
8435
- "user-agent": `ghosty-cli/${"0.39.0"}`,
8458
+ "user-agent": `ghosty-cli/${"0.40.0"}`,
8436
8459
  "accept-language": lang(),
8437
8460
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8438
8461
  ...opts.headers
@@ -8514,12 +8537,12 @@ var enc = encodeURIComponent;
8514
8537
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
8515
8538
  var MIME = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif", pdf: "application/pdf", ogg: "audio/ogg", opus: "audio/ogg", mp3: "audio/mpeg", m4a: "audio/mp4", wav: "audio/wav", mp4: "video/mp4" };
8516
8539
  async function localFiles(paths) {
8517
- const { readFile: readFile14 } = await import("node:fs/promises");
8518
- const { basename: basename4 } = await import("node:path");
8540
+ const { readFile: readFile15 } = await import("node:fs/promises");
8541
+ const { basename: basename5 } = await import("node:path");
8519
8542
  return Promise.all(
8520
8543
  paths.map(async (p) => {
8521
8544
  const ext = p.toLowerCase().split(".").pop() ?? "";
8522
- return { name: basename4(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await readFile14(p)).toString("base64") };
8545
+ return { name: basename5(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await readFile15(p)).toString("base64") };
8523
8546
  })
8524
8547
  );
8525
8548
  }
@@ -9618,7 +9641,7 @@ async function call(path, v, init = {}) {
9618
9641
  "x-ghosty-ts": ts,
9619
9642
  "x-ghosty-sig": sig,
9620
9643
  "content-type": "application/json",
9621
- "user-agent": `ghosty-cli/${"0.39.0"}`
9644
+ "user-agent": `ghosty-cli/${"0.40.0"}`
9622
9645
  },
9623
9646
  body: method === "GET" ? void 0 : raw
9624
9647
  });
@@ -11272,6 +11295,16 @@ var HELPS_ES = {
11272
11295
  summary: "Staff de Ghosty: las novedades de la app Android. Cada una con su ilustraci\xF3n; mismos frenos que el admin: pru\xE9bala en tu tel\xE9fono primero, push de 9 a 21 h CDMX, 72 h entre env\xEDos a todos.",
11273
11296
  notes: ["ls dice, de una ya enviada, cu\xE1ntos entreg\xF3 y rechaz\xF3 FCM (del push log, desde el 1-oct).", "--image se publica en img.ghosty.studio (WebP 1200 px). --to vac\xEDo = todos. send sin --at la manda ya (pregunta); --at '' quita la programaci\xF3n.", "--min-version N (versionCode de Android): si la app del tester es m\xE1s vieja, el bot\xF3n dice \xABActualizar\xBB y lanza la actualizaci\xF3n de Play desde la app; '' la quita."]
11274
11297
  },
11298
+ sites: {
11299
+ summary: "Sitios est\xE1ticos publicados en EasyBits con la cuenta de casa de Ghosty. ls lista los que publicaron tus agentes; el staff publica los artefactos de Ghosty (demos, landings) como sitios de la casa \u2014 sin due\xF1o, sin caducidad, sin la franja \xABHecha con Ghosty\xBB \u2014 y cambia el slug de cualquier sitio.",
11300
+ flags: [
11301
+ ["--house", "ls (staff): los sitios de la casa en vez de los tuyos"],
11302
+ ["--title T", "publish: el nombre del sitio (default: el <title> del HTML o el nombre de la carpeta)"],
11303
+ ["--slug S", "publish: el slug de un sitio NUEVO (a-z, 0-9 y guiones, 3\u201360, no reservado)"],
11304
+ ["--site X", "publish: actualiza ese sitio de la casa (slug, id o URL) en vez de crear uno"]
11305
+ ],
11306
+ notes: ["Una carpeta necesita index.html en la ra\xEDz; se saltan ocultos y node_modules. Topes: 1 MB por archivo, 8 MB y 40 archivos por sitio.", "Sin --site, publish siempre crea un sitio nuevo (los de la casa nunca se buscan por t\xEDtulo).", "Errores de slug: slug_invalid, slug_taken, y slug_unsupported mientras EasyBits no acepte slug (exit 1, no cambia nada)."]
11307
+ },
11275
11308
  spaces: {
11276
11309
  summary: "Espacios: crea uno a tu nombre (prueba), lista los tuyos (o, staff, los de cualquiera) y operaciones del staff de Ghosty \u2014 entrar/salir del espacio de un cliente, cambiar su combo, mover \xABpagado hasta\xBB, renombrar su slug, cambiar de due\xF1o. Cada cambio queda en la bit\xE1cora del espacio.",
11277
11310
  flags: [
@@ -11871,6 +11904,19 @@ HELPS.spaces = {
11871
11904
  learn: `${REF}#espacios`
11872
11905
  };
11873
11906
  HELPS.workspaces = HELPS.spaces;
11907
+ HELPS.sites = {
11908
+ summary: "Static sites published on EasyBits with Ghosty's house account. ls lists the ones your agents published; staff publish Ghosty's own artifacts (demos, landings) as house sites \u2014 no owner, no expiry, no \xABMade with Ghosty\xBB banner \u2014 and change any site's slug.",
11909
+ usage: ["ghosty sites ls [--house]", "ghosty sites publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>]", "ghosty sites slug <slug|id> <new-slug>"],
11910
+ flags: [
11911
+ ["--house", "ls (staff): Ghosty's house sites instead of yours"],
11912
+ ["--title T", "publish: the site's name (default: the HTML <title> or the folder name)"],
11913
+ ["--slug S", "publish: the slug for a NEW site (a-z, 0-9 and hyphens, 3\u201360, not reserved)"],
11914
+ ["--site X", "publish: update that house site (slug, id or URL) instead of creating one"]
11915
+ ],
11916
+ examples: ["ghosty sites ls --house", 'ghosty sites publish ./melon.html --title "Gelatina de sand\xEDa" --slug gelatina-de-sandia', "ghosty sites publish ./landing/ --site gelatina-de-sandia", "ghosty sites slug arbol-palpitante gelatina-de-sandia"],
11917
+ notes: ["A folder needs index.html at its root; hidden files and node_modules are skipped. Limits: 1 MB per file, 8 MB and 40 files per site.", "Without --site, publish always creates a new site (house sites are never matched by title).", "Slug errors: slug_invalid, slug_taken, and slug_unsupported while EasyBits does not accept a slug yet (exit 1, nothing changes)."],
11918
+ learn: `${REF}#sitios`
11919
+ };
11874
11920
  function helpText(cmd) {
11875
11921
  const base = HELPS[cmd];
11876
11922
  if (!base) return void 0;
@@ -12476,7 +12522,7 @@ ${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s)
12476
12522
  d.noQuota += x.noQuota;
12477
12523
  days.set(x.day, d);
12478
12524
  }
12479
- rows = [...days].map(([day, x]) => ({ day, ...fmt2(x) }));
12525
+ rows = [...days].map(([day2, x]) => ({ day: day2, ...fmt2(x) }));
12480
12526
  } else rows = r.agents.map((x) => ({ agent: x.agent, ...fmt2(x) }));
12481
12527
  const cols = [...by.has("day") ? ["day"] : [], ...by.has("agent") ? ["agent"] : [], "turns", "failed", "tokens", "no balance"];
12482
12528
  const total = r.agents.reduce((n, a) => n + a.billable, 0);
@@ -12601,8 +12647,84 @@ async function demosCmd(sub, a1, rest, v) {
12601
12647
  throw usage(t(`Unknown demos subcommand: ${sub ?? "(none)"}.`, `Subcomando de demos desconocido: ${sub ?? "(ninguno)"}.`), 'ghosty demos history <agent> \xB7 ghosty demos try <slug> "message"');
12602
12648
  }
12603
12649
 
12650
+ // src/commands/sites.ts
12651
+ import { readdir as readdir5, readFile as readFile13, stat as stat2 } from "node:fs/promises";
12652
+ import { basename as basename4, join as join10, relative as relative2, sep } from "node:path";
12653
+ var TEXT_EXT = /* @__PURE__ */ new Set(["html", "htm", "css", "js", "mjs", "json", "txt", "xml", "svg", "webmanifest", "md"]);
12654
+ var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".DS_Store"]);
12655
+ async function collectSiteFiles(target) {
12656
+ const st = await stat2(target).catch(() => null);
12657
+ if (!st) throw usage(t(`Not found: ${target}`, `No existe: ${target}`));
12658
+ if (st.isFile()) {
12659
+ if (!/\.html?$/i.test(target)) throw usage(t("A single file must be .html (or pass a folder).", "Un archivo suelto debe ser .html (o pasa una carpeta)."));
12660
+ const html = await readFile13(target, "utf8");
12661
+ return { files: [{ path: "index.html", content: html, encoding: "text" }], title: titleOf(html) };
12662
+ }
12663
+ const files = [];
12664
+ const walk = async (dir) => {
12665
+ for (const e of await readdir5(dir, { withFileTypes: true })) {
12666
+ if (e.name.startsWith(".") || SKIP.has(e.name)) continue;
12667
+ const full = join10(dir, e.name);
12668
+ if (e.isDirectory()) await walk(full);
12669
+ else if (e.isFile()) {
12670
+ const path = relative2(target, full).split(sep).join("/");
12671
+ const ext = path.split(".").pop()?.toLowerCase() ?? "";
12672
+ const buf = await readFile13(full);
12673
+ files.push(TEXT_EXT.has(ext) ? { path, content: buf.toString("utf8"), encoding: "text" } : { path, content: buf.toString("base64"), encoding: "base64" });
12674
+ }
12675
+ }
12676
+ };
12677
+ await walk(target);
12678
+ const index = files.find((f) => f.path === "index.html");
12679
+ if (!index) throw usage(t(`${target} has no index.html at its root.`, `${target} no tiene index.html en la ra\xEDz.`));
12680
+ return { files: files.sort((a, b) => a.path.localeCompare(b.path)), title: titleOf(index.content) ?? basename4(target.replace(/\/+$/, "")) };
12681
+ }
12682
+ function titleOf(html) {
12683
+ const m = html.match(/<title[^>]*>([^<]*)<\/title>/i);
12684
+ const s = m?.[1]?.replace(/\s+/g, " ").trim();
12685
+ return s || null;
12686
+ }
12687
+ var kb = (n) => n ? `${Math.max(1, Math.round(n / 1024))} KB` : "";
12688
+ var day = (iso) => iso ? iso.slice(0, 10) : "";
12689
+ async function sitesCmd(sub, a1, a2, v) {
12690
+ const ex = "ghosty sites publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>] \xB7 ls [--house] \xB7 slug <slug|id> <new-slug>";
12691
+ switch (sub ?? "ls") {
12692
+ case "ls":
12693
+ case "list": {
12694
+ const r = await api(`/api/v2/sites?scope=${v.house ? "house" : "mine"}`);
12695
+ const rows = r.sites.map((s) => ({ slug: s.slug, title: (s.title ?? "").slice(0, 40), url: s.url, files: s.files ?? "", size: kb(s.bytes), published: day(s.publishedAt), expires: s.expiresAt ? day(s.expiresAt) : t("never", "nunca") }));
12696
+ return out(r, () => `${bold(v.house ? t("House sites", "Sitios de la casa") : t("Your sites", "Tus sitios"))}
12697
+ ${table(rows, ["slug", "title", "url", "files", "size", "published", "expires"])}`);
12698
+ }
12699
+ case "publish":
12700
+ case "deploy": {
12701
+ if (!a1) throw usage(t("Missing the file or folder.", "Falta el archivo o la carpeta."), ex);
12702
+ const { files, title } = await collectSiteFiles(a1);
12703
+ const body = {
12704
+ title: v.title ?? title ?? "Sitio de Ghosty",
12705
+ ...files.length === 1 && files[0].path === "index.html" && files[0].encoding === "text" ? { html: files[0].content } : { files },
12706
+ ...v.site ? { site: v.site } : {},
12707
+ ...v.slug ? { slug: v.slug } : {}
12708
+ };
12709
+ const r = await api("/api/v2/sites", { method: "POST", body });
12710
+ return out(r, () => `${lavender("\u2713")} ${r.created ? t("Published", "Publicado") : t("Updated", "Actualizado")} ${bold(r.url)}
12711
+ ${dim(t(`slug ${r.slug} \xB7 ${files.length} file(s) \xB7 update it: ghosty sites publish ${a1} --site ${r.slug}`, `slug ${r.slug} \xB7 ${files.length} archivo(s) \xB7 actual\xEDzalo: ghosty sites publish ${a1} --site ${r.slug}`))}`);
12712
+ }
12713
+ case "slug":
12714
+ case "rename": {
12715
+ if (!a1 || !a2) throw usage(t("Missing the site and the new slug.", "Faltan el sitio y el slug nuevo."), "ghosty sites slug <slug|id> <new-slug>");
12716
+ const r = await api(`/api/v2/sites/${encodeURIComponent(a1)}`, { method: "PATCH", body: { slug: a2 } });
12717
+ return out(r, () => `${lavender("\u2713")} ${r.oldSlug} \u2192 ${bold(r.slug)}
12718
+ ${r.url}
12719
+ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viejo pueden dejar de funcionar."))}`);
12720
+ }
12721
+ default:
12722
+ throw usage(t(`Unknown sites subcommand: ${sub}.`, `Subcomando de sites desconocido: ${sub}.`), ex);
12723
+ }
12724
+ }
12725
+
12604
12726
  // src/commands/support.ts
12605
- import { readFile as readFile13 } from "node:fs/promises";
12727
+ import { readFile as readFile14 } from "node:fs/promises";
12606
12728
  var enc12 = encodeURIComponent;
12607
12729
  var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
12608
12730
  async function supportThreads(email, v) {
@@ -12712,7 +12834,7 @@ async function novedadesCmd(sub, id, v) {
12712
12834
  const fields = async () => ({
12713
12835
  ...v.title !== void 0 ? { title: v.title } : {},
12714
12836
  ...v.body !== void 0 ? { body: v.body } : {},
12715
- ...v.image ? { image: (await readFile13(v.image)).toString("base64") } : {},
12837
+ ...v.image ? { image: (await readFile14(v.image)).toString("base64") } : {},
12716
12838
  ...v.link !== void 0 ? { enlace: v.link || null } : {},
12717
12839
  ...v.button !== void 0 ? { boton: v.button || null } : {},
12718
12840
  ...v.to !== void 0 ? { destinatarios: v.to || null } : {},
@@ -12904,6 +13026,11 @@ ${bold("Support")} ${dim("(Ghosty staff)")}
12904
13026
  push log --user EMAIL Pushes sent to someone and what Apple/Google answered [--since 2d]
12905
13027
  novedades ls|create|set|test|send|rm Android app announcements [--title] [--body] [--image FILE] [--link] [--to] [--min-version N] [--publish] [--at]
12906
13028
 
13029
+ ${bold("Sites")} ${dim("(static sites on EasyBits with Ghosty's house account)")}
13030
+ sites ls Sites your agents published [--house: Ghosty's own demos and landings (staff)]
13031
+ sites publish <file.html|folder> Publish a house site (staff): no owner, no expiry, no banner [--title] [--slug S] [--site <slug|id>: update that one]
13032
+ sites slug <slug|id> <new-slug> Change a site's slug (staff)
13033
+
12907
13034
  ${bold("Demos")}
12908
13035
  demos try <slug> "msg" ["msg2"\u2026] Try a public demo as a visitor (same thread across runs) [--new] [--recycle-between --agent X]
12909
13036
  demos history <id> What visitors did: turns, messages, files (Ghosty staff) [--since 2d] [--session web:\u2026] [--timeline] [--full]
@@ -13034,6 +13161,11 @@ ${bold("Soporte")} ${dim("(staff de Ghosty)")}
13034
13161
  push log --user CORREO Avisos que se le mandaron a alguien y qu\xE9 contest\xF3 Apple/Google [--since 2d]
13035
13162
  novedades ls|create|set|test|send|rm Novedades de la app Android [--title] [--body] [--image ARCHIVO] [--link] [--to] [--min-version N] [--publish] [--at]
13036
13163
 
13164
+ ${bold("Sitios")} ${dim("(sitios est\xE1ticos en EasyBits con la cuenta de casa de Ghosty)")}
13165
+ sites ls Sitios que publicaron tus agentes [--house: demos y landings de Ghosty (staff)]
13166
+ sites publish <archivo.html|carpeta> Publica un sitio de la casa (staff): sin due\xF1o, sin caducidad, sin franja [--title] [--slug S] [--site <slug|id>: actualiza \xE9se]
13167
+ sites slug <slug|id> <slug-nuevo> Cambia el slug de un sitio (staff)
13168
+
13037
13169
  ${bold("Demos")}
13038
13170
  demos try <slug> "msg" ["msg2"\u2026] Prueba un demo p\xFAblico como visitante (mismo hilo entre corridas) [--new] [--recycle-between --agent X]
13039
13171
  demos history <id> Qu\xE9 hicieron los visitantes: turnos, mensajes, archivos (staff de Ghosty) [--since 2d] [--session web:\u2026] [--timeline] [--full]
@@ -13079,7 +13211,7 @@ ${bold("Opciones globales")}
13079
13211
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
13080
13212
  ${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
13081
13213
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
13082
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "demos", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
13214
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
13083
13215
  var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
13084
13216
  function unknown(what, input, known, helpCmd) {
13085
13217
  const s = suggest(input, known);
@@ -13155,6 +13287,8 @@ async function main() {
13155
13287
  config: { type: "string" },
13156
13288
  "turn-token": { type: "string" },
13157
13289
  slug: { type: "string" },
13290
+ site: { type: "string" },
13291
+ house: { type: "boolean" },
13158
13292
  on: { type: "boolean" },
13159
13293
  off: { type: "boolean" },
13160
13294
  turns: { type: "string" },
@@ -13302,7 +13436,7 @@ async function main() {
13302
13436
  const tokens = splitTokens(pos, v);
13303
13437
  globals.token = tokens.session;
13304
13438
  globals.profile = v.profile;
13305
- if (v.version) return out({ version: "0.39.0" }, () => "0.39.0");
13439
+ if (v.version) return out({ version: "0.40.0" }, () => "0.40.0");
13306
13440
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
13307
13441
  const context = (await readConfig()).workspace;
13308
13442
  if (!explicitToken()?.startsWith("gat_")) {
@@ -13391,6 +13525,9 @@ async function main() {
13391
13525
  case "novedades":
13392
13526
  case "announcements":
13393
13527
  return novedadesCmd(sub, a1, v);
13528
+ case "sites":
13529
+ case "site":
13530
+ return sitesCmd(sub, a1, a2, v);
13394
13531
  case "board":
13395
13532
  return boardCmd(sub, a1, v, a2);
13396
13533
  case "boards":
@@ -13451,7 +13588,7 @@ async function main() {
13451
13588
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
13452
13589
  }
13453
13590
  }
13454
- var notice = updateNotice("0.39.0").catch(() => null);
13591
+ var notice = updateNotice("0.40.0").catch(() => null);
13455
13592
  main().then(
13456
13593
  async () => {
13457
13594
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Official Ghosty Studio CLI — configure, test and chat with your agents from the terminal. Built for humans and coding agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/index.js --banner:js='#!/usr/bin/env node\nimport { createRequire as __cr } from \"node:module\"; const require = __cr(import.meta.url);' --define:__VERSION__=\\\"$npm_package_version\\\"",
18
18
  "prepack": "npm run build",
19
- "test": "for t in flags prompt api rows wa-auth resolve aliases i18n help completion photos channels-retry spaces; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
19
+ "test": "for t in flags prompt api rows wa-auth resolve aliases i18n help completion photos channels-retry spaces sites; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
20
20
  },
21
21
  "keywords": [
22
22
  "ghosty",