@ghostystudio/cli 0.39.0 → 0.41.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 +10 -0
  2. package/dist/index.js +254 -74
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -30,6 +30,7 @@ ghosty apply <file|dir> [--agent X] [--create] [--prune] [--dry-run] # make an
30
30
  ghosty agents set <id> [--name] [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,…] [--connect ID,…] [--disconnect ID,…] [--toolsets +crm]
31
31
  ghosty agents set <id> --prompt-base <base-id> --prompt-overlay overlay.json [--dry-run] # prompt = another agent's + edits, kept in sync
32
32
  ghosty agents share|unshare <id> <email> [--role editor|admin] # share without handing it over · agents shares <id> lists them
33
+ ghosty agents teams ls|on|off <id> [workspace] [--handle x] # which Teams workspaces it answers in, and its @handle there
33
34
  ghosty agents rm <id> [--from-workspace] [--yes] # --from-workspace: turn it off in Teams first
34
35
  ghosty agents set <id> [--tool wa:tool=off,…] [--bag NAME|none] [--avatar URL|FILE]
35
36
  ghosty agents restart <id>
@@ -83,6 +84,7 @@ ghosty conversations ls --user EMAIL [--since 30d] · conversations show <id> <c
83
84
  ghosty turns stats [--since 14d] [--all] · push log --user EMAIL [--since 2d]
84
85
  ghosty agents doctor --user EMAIL [agent] [--turns 8] · usage --images [--plan free] [--by user]
85
86
  ghosty novedades ls|create|set|test|send|rm [--title] [--body] [--image FILE] [--link] [--to] [--publish] [--at]
87
+ 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
88
  ghosty credentials ls|set|rm [provider] [--from-env VAR] # never as an argument
87
89
  ghosty docs
88
90
 
@@ -99,6 +101,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
99
101
 
100
102
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
101
103
 
104
+ ### Changes in 0.41.0
105
+
106
+ - `agents teams ls <id>`: the Teams workspaces you administer, with the `@handle` it answers with in each (or the suggested one). `agents teams on <id> <workspace> [--handle x]` turns it on there, or changes its handle if it was already on; `agents teams off <id> <workspace>` turns it off (the agent stays). Same rules as the agent page (Channels → Ghosty Teams): `@ghosty` is reserved, a taken handle is rejected.
107
+
108
+ ### Changes in 0.40.0
109
+
110
+ - `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).
111
+
102
112
  ### Changes in 0.39.0
103
113
 
104
114
  - `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;
@@ -7458,9 +7458,18 @@ var SERVER_MESSAGES = [
7458
7458
  ["s\xF3lo el due\xF1o da acceso a sus conectores", "only the owner grants access to their connectors"],
7459
7459
  ["s\xF3lo el due\xF1o del agente maneja su demo", "only the agent's owner manages its demo"],
7460
7460
  ["s\xF3lo el due\xF1o puede borrar el agente", "only the owner can delete the agent"],
7461
- ["Ese agente est\xE1 activo en {}. Qu\xEDtalo de ah\xED (Ajustes \u2192 Agentes) antes de borrarlo.", "That agent is active in {}. Remove it from there (Settings \u2192 Agents) before deleting it."],
7462
7461
  ["Ese espacio no es tuyo.", "That workspace isn't yours."],
7463
7462
  ["@{} ya est\xE1 ocupado por otro agente en este espacio: dale un handle propio", "@{} is already taken by another agent in this workspace: give it its own handle"],
7463
+ // — Activar en equipos de Teams (`agents teams`)
7464
+ ["@{} ya est\xE1 ocupado en {}", "@{} is already taken in {}"],
7465
+ ["@{} est\xE1 reservado", "@{} is reserved"],
7466
+ ["los @handle {} a {}5 ya est\xE1n ocupados en {}", "handles @{} to @{}5 are already taken in {}"],
7467
+ ["no administras el equipo {}", "you don't administer the workspace {}"],
7468
+ ["no pude leer el equipo {}; int\xE9ntalo en un momento", "couldn't read the workspace {}; try again in a moment"],
7469
+ ["s\xF3lo el due\xF1o del agente lo activa en sus equipos", "only the agent's owner turns it on in their workspaces"],
7470
+ ["s\xF3lo el due\xF1o del agente o staff lo activan en Teams", "only the agent's owner or staff turn it on in Teams"],
7471
+ ["falta workspace (el slug del equipo)", "missing workspace (the workspace slug)"],
7472
+ ["Ese agente est\xE1 activo en {}. Ap\xE1galo ah\xED (Canales \u2192 Ghosty Teams) antes de borrarlo.", "That agent is active in {}. Turn it off there (Channels \u2192 Ghosty Teams) before deleting it."],
7464
7473
  ["sin cupo para otro agente ahora", "no room for another agent right now"],
7465
7474
  ["Tu plan incluye 1 agente.", "Your plan includes 1 agent."],
7466
7475
  ["Tu plan incluye {} agentes.", "Your plan includes {} agents."],
@@ -8088,6 +8097,29 @@ var SERVER_MESSAGES = [
8088
8097
  ["turnToken excede {} caracteres", "turnToken exceeds {} characters"],
8089
8098
  ["turnToken no puede llevar espacios", "turnToken can't contain whitespace"],
8090
8099
  ["turnToken requerido: los MCP de este agente usan ${turn.token}", "turnToken required: this agent's MCP servers use ${turn.token}"],
8100
+ // — Sitios de la casa (`ghosty sites`, 3-oct)
8101
+ ["los sitios de la casa: s\xF3lo el staff de Ghosty", "house sites: Ghosty staff only"],
8102
+ ["publicar sitios de la casa: s\xF3lo el staff de Ghosty", "publishing house sites: Ghosty staff only"],
8103
+ ["cambiar el slug de un sitio: s\xF3lo el staff de Ghosty", "changing a site's slug: Ghosty staff only"],
8104
+ ["scope acepta mine o house", "scope takes mine or house"],
8105
+ ["falta html o files", "missing html or files"],
8106
+ ["falta slug", "missing slug"],
8107
+ ["EasyBits todav\xEDa no permite elegir ni cambiar el slug", "EasyBits doesn't allow choosing or changing the slug yet"],
8108
+ ["el slug \xAB{}\xBB ya est\xE1 ocupado en EasyBits", "slug \xAB{}\xBB is already taken on EasyBits"],
8109
+ ["el slug \xAB{}\xBB ya est\xE1 ocupado", "slug \xAB{}\xBB is already taken"],
8110
+ ["EasyBits rechaz\xF3 el slug \xAB{}\xBB por inv\xE1lido", "EasyBits rejected slug \xAB{}\xBB as invalid"],
8111
+ ["el slug \xAB{}\xBB est\xE1 reservado", "slug \xAB{}\xBB is reserved"],
8112
+ ["el slug debe tener entre 3 y 60 caracteres", "the slug must be 3 to 60 characters"],
8113
+ ["el slug s\xF3lo admite min\xFAsculas (a-z), n\xFAmeros y guiones", "the slug only allows lowercase letters (a-z), digits and hyphens"],
8114
+ ["el slug no puede empezar ni terminar en guion", "the slug can't start or end with a hyphen"],
8115
+ ["el slug no puede llevar dos guiones seguidos", "the slug can't have two hyphens in a row"],
8116
+ ["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"],
8117
+ ["no encontr\xE9 un sitio vivo \xAB{}\xBB", "no live site \xAB{}\xBB"],
8118
+ ["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)"],
8119
+ ["la publicaci\xF3n de sitios no est\xE1 disponible en este momento", "site publishing isn't available right now"],
8120
+ ["no pude conectar con el servicio de sitios ({})", "couldn't reach the sites service ({})"],
8121
+ ["no se pudo crear el sitio; int\xE9ntalo en un momento", "couldn't create the site; try again in a moment"],
8122
+ ["no se pudo subir {}: {}", "couldn't upload {}: {}"],
8091
8123
  // — Infra interna (casi nunca llega al CLI, pero por si acaso)
8092
8124
  ["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
8125
  ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
@@ -8390,7 +8422,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8390
8422
  const req = httpsRequest(`${base}${path}`, {
8391
8423
  method: "POST",
8392
8424
  timeout: timeoutMs,
8393
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.39.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8425
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.41.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8394
8426
  }, (res) => {
8395
8427
  let buf = "";
8396
8428
  res.on("data", (c) => buf += c);
@@ -8432,7 +8464,7 @@ async function request(path, opts = {}, retried = false) {
8432
8464
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8433
8465
  headers: {
8434
8466
  authorization: `Bearer ${token}`,
8435
- "user-agent": `ghosty-cli/${"0.39.0"}`,
8467
+ "user-agent": `ghosty-cli/${"0.41.0"}`,
8436
8468
  "accept-language": lang(),
8437
8469
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8438
8470
  ...opts.headers
@@ -8514,12 +8546,12 @@ var enc = encodeURIComponent;
8514
8546
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
8515
8547
  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
8548
  async function localFiles(paths) {
8517
- const { readFile: readFile14 } = await import("node:fs/promises");
8518
- const { basename: basename4 } = await import("node:path");
8549
+ const { readFile: readFile15 } = await import("node:fs/promises");
8550
+ const { basename: basename5 } = await import("node:path");
8519
8551
  return Promise.all(
8520
8552
  paths.map(async (p) => {
8521
8553
  const ext = p.toLowerCase().split(".").pop() ?? "";
8522
- return { name: basename4(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await readFile14(p)).toString("base64") };
8554
+ return { name: basename5(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await readFile15(p)).toString("base64") };
8523
8555
  })
8524
8556
  );
8525
8557
  }
@@ -8877,6 +8909,27 @@ ${rows.length ? table(rows, ["email", "role", "accepted"]) : dim(t("Not shared w
8877
8909
  const r = await api(agentPath(id, "/shares"), { method: "POST", body: { email, ...v.role ? { role: v.role } : {} } });
8878
8910
  out(r, () => `${lavender("\u2713")} ${r.already ? t(`Already shared with ${r.email}`, `Ya estaba compartido con ${r.email}`) : t(`Shared with ${r.email}; they got an email`, `Compartido con ${r.email}; le lleg\xF3 un correo`)} ${dim(`(${r.role})`)}.`);
8879
8911
  }
8912
+ async function agentsTeams(sub, id, ws, v) {
8913
+ const ex = "ghosty agents teams ls <agent-id> \xB7 on <agent-id> <workspace> [--handle x] \xB7 off <agent-id> <workspace>";
8914
+ if (sub !== "ls" && sub !== "on" && sub !== "off") throw usage(t("Use ls, on or off.", "Usa ls, on u off."), ex);
8915
+ id = need(id, "agent id", ex);
8916
+ if (sub === "ls") {
8917
+ const r2 = await api(agentPath(id, "/teams"));
8918
+ const rows = r2.teams.map((x) => ({
8919
+ workspace: x.slug,
8920
+ handle: x.active ? `@${x.handle}` : dim(`(@${x.suggested})`),
8921
+ state: !x.confirmed ? t("unconfirmed", "sin confirmar") : x.active ? t("on", "prendido") : t("off", "apagado")
8922
+ }));
8923
+ return out(r2, () => rows.length ? table(rows, ["workspace", "handle", "state"]) : dim(t("You don't administer any Teams workspace yet.", "A\xFAn no administras ning\xFAn equipo de Teams.")));
8924
+ }
8925
+ const workspace = need(ws ?? v.workspace, "workspace", ex);
8926
+ if (sub === "off") {
8927
+ const r2 = await api(agentPath(id, `/teams?workspace=${encodeURIComponent(workspace)}`), { method: "DELETE", body: { workspace } });
8928
+ return out(r2, () => `${lavender("\u2713")} ${r2.wasActive ? t(`It no longer answers in ${workspace}.`, `Ya no contesta en ${workspace}.`) : t(`It wasn't active in ${workspace}.`, `No estaba activo en ${workspace}.`)}`);
8929
+ }
8930
+ const r = await api(agentPath(id, "/teams"), { method: "POST", body: { workspace, ...v.handle ? { handle: v.handle } : {} } });
8931
+ out(r, () => `${lavender("\u2713")} ${r.changed ? t(`Answers in ${workspace} as @${r.handle}.`, `Contesta en ${workspace} como @${r.handle}.`) : t(`Already answering in ${workspace} as @${r.handle}.`, `Ya contestaba en ${workspace} como @${r.handle}.`)}`);
8932
+ }
8880
8933
  async function agentsRestart(id) {
8881
8934
  id = need(id, "agent id", "ghosty agents restart <agent-id>");
8882
8935
  out(await api(agentPath(id, "/restart"), { method: "POST", body: {} }), () => `${lavender("\u2713")} ${t("Restarting \u2014 the next turn uses the new config.", "Reiniciando: el siguiente turno usa la config nueva.")}`);
@@ -9618,7 +9671,7 @@ async function call(path, v, init = {}) {
9618
9671
  "x-ghosty-ts": ts,
9619
9672
  "x-ghosty-sig": sig,
9620
9673
  "content-type": "application/json",
9621
- "user-agent": `ghosty-cli/${"0.39.0"}`
9674
+ "user-agent": `ghosty-cli/${"0.41.0"}`
9622
9675
  },
9623
9676
  body: method === "GET" ? void 0 : raw
9624
9677
  });
@@ -10926,7 +10979,10 @@ var HELPS_ES = {
10926
10979
  ["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
10927
10980
  ["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
10928
10981
  ["# staff: their agents, latest turns, plan and boxes", "# staff: sus agentes, \xFAltimos turnos, plan y cajas"],
10929
- ["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"]
10982
+ ["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"],
10983
+ ["# Teams workspaces you administer: where it answers and with which @handle", "# los equipos de Teams que administras: d\xF3nde contesta y con qu\xE9 @handle"],
10984
+ ["# turn it on there (or change its @handle)", "# lo prende ah\xED (o le cambia el @handle)"],
10985
+ ["# turn it off there; the agent stays", "# lo apaga ah\xED; el agente sigue"]
10930
10986
  ],
10931
10987
  flags: [
10932
10988
  ["--fields a,b", "get: regresa s\xF3lo estos campos"],
@@ -10953,6 +11009,7 @@ var HELPS_ES = {
10953
11009
  ["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
10954
11010
  ["--note TEXT", "docs replace/restore: por qu\xE9 cambi\xF3 (sale en docs history)"],
10955
11011
  ["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
11012
+ ["--handle NAME", "teams on: su @handle en ese equipo (default: sacado de su nombre, con sufijo si est\xE1 ocupado; @ghosty est\xE1 reservado). Sobre un equipo donde ya est\xE1, le cambia el handle"],
10956
11013
  ["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
10957
11014
  ["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
10958
11015
  ["--expires DATE|no", 'demo: caducidad fija en la hora de tu m\xE1quina ("2026-10-15T20:00"); no = nunca'],
@@ -11272,6 +11329,16 @@ var HELPS_ES = {
11272
11329
  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
11330
  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
11331
  },
11332
+ sites: {
11333
+ 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.",
11334
+ flags: [
11335
+ ["--house", "ls (staff): los sitios de la casa en vez de los tuyos"],
11336
+ ["--title T", "publish: el nombre del sitio (default: el <title> del HTML o el nombre de la carpeta)"],
11337
+ ["--slug S", "publish: el slug de un sitio NUEVO (a-z, 0-9 y guiones, 3\u201360, no reservado)"],
11338
+ ["--site X", "publish: actualiza ese sitio de la casa (slug, id o URL) en vez de crear uno"]
11339
+ ],
11340
+ 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)."]
11341
+ },
11275
11342
  spaces: {
11276
11343
  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
11344
  flags: [
@@ -11316,6 +11383,9 @@ var HELPS = {
11316
11383
  "ghosty agents share <agent-id> <email> [--role editor|admin]",
11317
11384
  "ghosty agents unshare <agent-id> <email> [--yes]",
11318
11385
  "ghosty agents shares <agent-id>",
11386
+ "ghosty agents teams ls <agent-id> # Teams workspaces you administer: where it answers and with which @handle",
11387
+ "ghosty agents teams on <agent-id> <workspace> [--handle NAME] # turn it on there (or change its @handle)",
11388
+ "ghosty agents teams off <agent-id> <workspace> # turn it off there; the agent stays",
11319
11389
  "ghosty agents restart <agent-id>",
11320
11390
  "ghosty agents box <agent-id> [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]",
11321
11391
  "ghosty agents demo enable|disable <agent-id>",
@@ -11347,6 +11417,7 @@ var HELPS = {
11347
11417
  ["--grant read|write|no", "docs add: access for this agent (default read; no = only upload to the account)"],
11348
11418
  ["--note TEXT", "docs replace/restore: why it changed (shown in docs history)"],
11349
11419
  ["--role R", "share: editor (configures and uses it) or admin (also shares it); again on a shared email changes the role"],
11420
+ ["--handle NAME", "teams on: its @handle in that workspace (default: from its name, with a suffix if taken; @ghosty is reserved). On an active workspace it changes the handle"],
11350
11421
  ["--yes", "rm: skip the confirmation (required without a terminal)"],
11351
11422
  ["--slug NAME", "demo: link name; a 6-char code is appended (/p/NAME-CODE)"],
11352
11423
  ["--expires DATE|no", `demo: fixed expiry in your machine's time ("2026-10-15T20:00"); no = never`],
@@ -11383,6 +11454,7 @@ var HELPS = {
11383
11454
  "ghosty agents create --name Pia --engine claude --workspace ventas --channels teams=off",
11384
11455
  "ghosty agents set <id> --toolsets +crm",
11385
11456
  "ghosty agents share <id> ana@empresa.com --role admin",
11457
+ "ghosty agents teams on <id> abogados --handle licenciado",
11386
11458
  "ghosty agents get <id> --prompt-out PROMPT.md",
11387
11459
  "ghosty agents get <id> --fields toolsets,connectors # what it really has: effective toolsets, connectors connected + granted",
11388
11460
  "ghosty agents create --name tania-mkt --like <pia-id> --board new --no-notify --dry-run",
@@ -11871,6 +11943,19 @@ HELPS.spaces = {
11871
11943
  learn: `${REF}#espacios`
11872
11944
  };
11873
11945
  HELPS.workspaces = HELPS.spaces;
11946
+ HELPS.sites = {
11947
+ 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.",
11948
+ 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>"],
11949
+ flags: [
11950
+ ["--house", "ls (staff): Ghosty's house sites instead of yours"],
11951
+ ["--title T", "publish: the site's name (default: the HTML <title> or the folder name)"],
11952
+ ["--slug S", "publish: the slug for a NEW site (a-z, 0-9 and hyphens, 3\u201360, not reserved)"],
11953
+ ["--site X", "publish: update that house site (slug, id or URL) instead of creating one"]
11954
+ ],
11955
+ 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"],
11956
+ 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)."],
11957
+ learn: `${REF}#sitios`
11958
+ };
11874
11959
  function helpText(cmd) {
11875
11960
  const base = HELPS[cmd];
11876
11961
  if (!base) return void 0;
@@ -12476,7 +12561,7 @@ ${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s)
12476
12561
  d.noQuota += x.noQuota;
12477
12562
  days.set(x.day, d);
12478
12563
  }
12479
- rows = [...days].map(([day, x]) => ({ day, ...fmt2(x) }));
12564
+ rows = [...days].map(([day2, x]) => ({ day: day2, ...fmt2(x) }));
12480
12565
  } else rows = r.agents.map((x) => ({ agent: x.agent, ...fmt2(x) }));
12481
12566
  const cols = [...by.has("day") ? ["day"] : [], ...by.has("agent") ? ["agent"] : [], "turns", "failed", "tokens", "no balance"];
12482
12567
  const total = r.agents.reduce((n, a) => n + a.billable, 0);
@@ -12601,8 +12686,84 @@ async function demosCmd(sub, a1, rest, v) {
12601
12686
  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
12687
  }
12603
12688
 
12689
+ // src/commands/sites.ts
12690
+ import { readdir as readdir5, readFile as readFile13, stat as stat2 } from "node:fs/promises";
12691
+ import { basename as basename4, join as join10, relative as relative2, sep } from "node:path";
12692
+ var TEXT_EXT = /* @__PURE__ */ new Set(["html", "htm", "css", "js", "mjs", "json", "txt", "xml", "svg", "webmanifest", "md"]);
12693
+ var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".DS_Store"]);
12694
+ async function collectSiteFiles(target) {
12695
+ const st = await stat2(target).catch(() => null);
12696
+ if (!st) throw usage(t(`Not found: ${target}`, `No existe: ${target}`));
12697
+ if (st.isFile()) {
12698
+ 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)."));
12699
+ const html = await readFile13(target, "utf8");
12700
+ return { files: [{ path: "index.html", content: html, encoding: "text" }], title: titleOf(html) };
12701
+ }
12702
+ const files = [];
12703
+ const walk = async (dir) => {
12704
+ for (const e of await readdir5(dir, { withFileTypes: true })) {
12705
+ if (e.name.startsWith(".") || SKIP.has(e.name)) continue;
12706
+ const full = join10(dir, e.name);
12707
+ if (e.isDirectory()) await walk(full);
12708
+ else if (e.isFile()) {
12709
+ const path = relative2(target, full).split(sep).join("/");
12710
+ const ext = path.split(".").pop()?.toLowerCase() ?? "";
12711
+ const buf = await readFile13(full);
12712
+ files.push(TEXT_EXT.has(ext) ? { path, content: buf.toString("utf8"), encoding: "text" } : { path, content: buf.toString("base64"), encoding: "base64" });
12713
+ }
12714
+ }
12715
+ };
12716
+ await walk(target);
12717
+ const index = files.find((f) => f.path === "index.html");
12718
+ if (!index) throw usage(t(`${target} has no index.html at its root.`, `${target} no tiene index.html en la ra\xEDz.`));
12719
+ return { files: files.sort((a, b) => a.path.localeCompare(b.path)), title: titleOf(index.content) ?? basename4(target.replace(/\/+$/, "")) };
12720
+ }
12721
+ function titleOf(html) {
12722
+ const m = html.match(/<title[^>]*>([^<]*)<\/title>/i);
12723
+ const s = m?.[1]?.replace(/\s+/g, " ").trim();
12724
+ return s || null;
12725
+ }
12726
+ var kb = (n) => n ? `${Math.max(1, Math.round(n / 1024))} KB` : "";
12727
+ var day = (iso) => iso ? iso.slice(0, 10) : "";
12728
+ async function sitesCmd(sub, a1, a2, v) {
12729
+ const ex = "ghosty sites publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>] \xB7 ls [--house] \xB7 slug <slug|id> <new-slug>";
12730
+ switch (sub ?? "ls") {
12731
+ case "ls":
12732
+ case "list": {
12733
+ const r = await api(`/api/v2/sites?scope=${v.house ? "house" : "mine"}`);
12734
+ 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") }));
12735
+ return out(r, () => `${bold(v.house ? t("House sites", "Sitios de la casa") : t("Your sites", "Tus sitios"))}
12736
+ ${table(rows, ["slug", "title", "url", "files", "size", "published", "expires"])}`);
12737
+ }
12738
+ case "publish":
12739
+ case "deploy": {
12740
+ if (!a1) throw usage(t("Missing the file or folder.", "Falta el archivo o la carpeta."), ex);
12741
+ const { files, title } = await collectSiteFiles(a1);
12742
+ const body = {
12743
+ title: v.title ?? title ?? "Sitio de Ghosty",
12744
+ ...files.length === 1 && files[0].path === "index.html" && files[0].encoding === "text" ? { html: files[0].content } : { files },
12745
+ ...v.site ? { site: v.site } : {},
12746
+ ...v.slug ? { slug: v.slug } : {}
12747
+ };
12748
+ const r = await api("/api/v2/sites", { method: "POST", body });
12749
+ return out(r, () => `${lavender("\u2713")} ${r.created ? t("Published", "Publicado") : t("Updated", "Actualizado")} ${bold(r.url)}
12750
+ ${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}`))}`);
12751
+ }
12752
+ case "slug":
12753
+ case "rename": {
12754
+ 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>");
12755
+ const r = await api(`/api/v2/sites/${encodeURIComponent(a1)}`, { method: "PATCH", body: { slug: a2 } });
12756
+ return out(r, () => `${lavender("\u2713")} ${r.oldSlug} \u2192 ${bold(r.slug)}
12757
+ ${r.url}
12758
+ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viejo pueden dejar de funcionar."))}`);
12759
+ }
12760
+ default:
12761
+ throw usage(t(`Unknown sites subcommand: ${sub}.`, `Subcomando de sites desconocido: ${sub}.`), ex);
12762
+ }
12763
+ }
12764
+
12604
12765
  // src/commands/support.ts
12605
- import { readFile as readFile13 } from "node:fs/promises";
12766
+ import { readFile as readFile14 } from "node:fs/promises";
12606
12767
  var enc12 = encodeURIComponent;
12607
12768
  var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
12608
12769
  async function supportThreads(email, v) {
@@ -12712,7 +12873,7 @@ async function novedadesCmd(sub, id, v) {
12712
12873
  const fields = async () => ({
12713
12874
  ...v.title !== void 0 ? { title: v.title } : {},
12714
12875
  ...v.body !== void 0 ? { body: v.body } : {},
12715
- ...v.image ? { image: (await readFile13(v.image)).toString("base64") } : {},
12876
+ ...v.image ? { image: (await readFile14(v.image)).toString("base64") } : {},
12716
12877
  ...v.link !== void 0 ? { enlace: v.link || null } : {},
12717
12878
  ...v.button !== void 0 ? { boton: v.button || null } : {},
12718
12879
  ...v.to !== void 0 ? { destinatarios: v.to || null } : {},
@@ -12868,6 +13029,7 @@ ${bold("Agents")}
12868
13029
  agents docs replace <id> <doc> New version (keeps name and access) --url URL | --file PATH [--note]
12869
13030
  agents docs history|restore <id> <doc> [n] Its versions; restore n as a new version
12870
13031
  agents share|unshare <id> <email> Share it without handing it over [--role editor|admin] \xB7 agents shares <id> lists them
13032
+ agents teams ls|on|off <id> [ws] Which Teams workspaces it answers in, and its @handle there [--handle x]
12871
13033
  agents rm <id> Delete an agent (owner only; asks you to type its name, or --yes) [--from-workspace: turn it off in Teams first]
12872
13034
  agents restart <id> Restart an ACP agent (needsRestart) so it loads new skills
12873
13035
  agents box <id> The machine serving it: is it on the new image? [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle [--reenv] [--force]]
@@ -12904,6 +13066,11 @@ ${bold("Support")} ${dim("(Ghosty staff)")}
12904
13066
  push log --user EMAIL Pushes sent to someone and what Apple/Google answered [--since 2d]
12905
13067
  novedades ls|create|set|test|send|rm Android app announcements [--title] [--body] [--image FILE] [--link] [--to] [--min-version N] [--publish] [--at]
12906
13068
 
13069
+ ${bold("Sites")} ${dim("(static sites on EasyBits with Ghosty's house account)")}
13070
+ sites ls Sites your agents published [--house: Ghosty's own demos and landings (staff)]
13071
+ 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]
13072
+ sites slug <slug|id> <new-slug> Change a site's slug (staff)
13073
+
12907
13074
  ${bold("Demos")}
12908
13075
  demos try <slug> "msg" ["msg2"\u2026] Try a public demo as a visitor (same thread across runs) [--new] [--recycle-between --agent X]
12909
13076
  demos history <id> What visitors did: turns, messages, files (Ghosty staff) [--since 2d] [--session web:\u2026] [--timeline] [--full]
@@ -12998,6 +13165,7 @@ ${bold("Agentes")}
12998
13165
  agents docs replace <id> <doc> Versi\xF3n nueva (conserva nombre y accesos) --url URL | --file RUTA [--note]
12999
13166
  agents docs history|restore <id> <doc> [n] Sus versiones; restaura la n como versi\xF3n nueva
13000
13167
  agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
13168
+ agents teams ls|on|off <id> [equipo] En qu\xE9 equipos de Teams contesta y con qu\xE9 @handle [--handle x]
13001
13169
  agents rm <id> Borra un agente (s\xF3lo el due\xF1o; te pide escribir su nombre, o --yes) [--from-workspace: antes lo desactiva en Teams]
13002
13170
  agents restart <id> Reinicia un agente ACP (needsRestart) para que cargue skills nuevas
13003
13171
  agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle [--reenv] [--force]]
@@ -13034,6 +13202,11 @@ ${bold("Soporte")} ${dim("(staff de Ghosty)")}
13034
13202
  push log --user CORREO Avisos que se le mandaron a alguien y qu\xE9 contest\xF3 Apple/Google [--since 2d]
13035
13203
  novedades ls|create|set|test|send|rm Novedades de la app Android [--title] [--body] [--image ARCHIVO] [--link] [--to] [--min-version N] [--publish] [--at]
13036
13204
 
13205
+ ${bold("Sitios")} ${dim("(sitios est\xE1ticos en EasyBits con la cuenta de casa de Ghosty)")}
13206
+ sites ls Sitios que publicaron tus agentes [--house: demos y landings de Ghosty (staff)]
13207
+ 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]
13208
+ sites slug <slug|id> <slug-nuevo> Cambia el slug de un sitio (staff)
13209
+
13037
13210
  ${bold("Demos")}
13038
13211
  demos try <slug> "msg" ["msg2"\u2026] Prueba un demo p\xFAblico como visitante (mismo hilo entre corridas) [--new] [--recycle-between --agent X]
13039
13212
  demos history <id> Qu\xE9 hicieron los visitantes: turnos, mensajes, archivos (staff de Ghosty) [--since 2d] [--session web:\u2026] [--timeline] [--full]
@@ -13079,8 +13252,8 @@ ${bold("Opciones globales")}
13079
13252
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
13080
13253
  ${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
13254
  ${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"];
13083
- var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
13255
+ 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"];
13256
+ var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "box"];
13084
13257
  function unknown(what, input, known, helpCmd) {
13085
13258
  const s = suggest(input, known);
13086
13259
  return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`), s ? t(`Did you mean "${s}"? Run: ${helpCmd}`, `\xBFQuisiste decir "${s}"? Corre: ${helpCmd}`) : t(`Run: ${helpCmd}`, `Corre: ${helpCmd}`));
@@ -13155,6 +13328,8 @@ async function main() {
13155
13328
  config: { type: "string" },
13156
13329
  "turn-token": { type: "string" },
13157
13330
  slug: { type: "string" },
13331
+ site: { type: "string" },
13332
+ house: { type: "boolean" },
13158
13333
  on: { type: "boolean" },
13159
13334
  off: { type: "boolean" },
13160
13335
  turns: { type: "string" },
@@ -13230,6 +13405,7 @@ async function main() {
13230
13405
  before: { type: "string" },
13231
13406
  recycle: { type: "boolean" },
13232
13407
  role: { type: "string" },
13408
+ handle: { type: "string" },
13233
13409
  wake: { type: "string" },
13234
13410
  text: { type: "string" },
13235
13411
  sender: { type: "string" },
@@ -13302,7 +13478,7 @@ async function main() {
13302
13478
  const tokens = splitTokens(pos, v);
13303
13479
  globals.token = tokens.session;
13304
13480
  globals.profile = v.profile;
13305
- if (v.version) return out({ version: "0.39.0" }, () => "0.39.0");
13481
+ if (v.version) return out({ version: "0.41.0" }, () => "0.41.0");
13306
13482
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
13307
13483
  const context = (await readConfig()).workspace;
13308
13484
  if (!explicitToken()?.startsWith("gat_")) {
@@ -13361,6 +13537,7 @@ async function main() {
13361
13537
  }
13362
13538
  if (sub === "get") return agentsGet(a1, v.fields, v["prompt-out"]);
13363
13539
  if (sub === "share" || sub === "unshare" || sub === "shares") return agentsShare(sub, a1, a2, v);
13540
+ if (sub === "teams") return agentsTeams(a1, a2, pos[4], v);
13364
13541
  if (sub === "set") return agentsSet(a1, v);
13365
13542
  if (sub === "rm" || sub === "delete") return agentsRm(a1, v);
13366
13543
  if (sub === "doctor" && v.user) return userDoctor(v.user, a1, v);
@@ -13391,6 +13568,9 @@ async function main() {
13391
13568
  case "novedades":
13392
13569
  case "announcements":
13393
13570
  return novedadesCmd(sub, a1, v);
13571
+ case "sites":
13572
+ case "site":
13573
+ return sitesCmd(sub, a1, a2, v);
13394
13574
  case "board":
13395
13575
  return boardCmd(sub, a1, v, a2);
13396
13576
  case "boards":
@@ -13451,7 +13631,7 @@ async function main() {
13451
13631
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
13452
13632
  }
13453
13633
  }
13454
- var notice = updateNotice("0.39.0").catch(() => null);
13634
+ var notice = updateNotice("0.41.0").catch(() => null);
13455
13635
  main().then(
13456
13636
  async () => {
13457
13637
  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.41.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",