@telepath-computer/television 0.1.98 → 0.1.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -33973,11 +33973,20 @@ var ArtifactClient = class {
33973
33973
  */
33974
33974
  create(input) {
33975
33975
  const body = {
33976
- type: input.type,
33976
+ kind: input.kind,
33977
33977
  title: input.title
33978
33978
  };
33979
- if (input.externalFilePath !== void 0) body.externalFilePath = input.externalFilePath;
33980
- if (input.externalURL !== void 0) body.externalURL = input.externalURL;
33979
+ switch (input.kind) {
33980
+ case "markdown":
33981
+ body.externalFilePath = input.externalFilePath;
33982
+ break;
33983
+ case "url":
33984
+ body.externalURL = input.externalURL;
33985
+ break;
33986
+ case "web-bundle":
33987
+ if (input.skipPending !== void 0) body.skipPending = input.skipPending;
33988
+ break;
33989
+ }
33981
33990
  if (input.screenID !== void 0) body.screenID = input.screenID;
33982
33991
  return this.#http.requestJSON("POST", "/artifacts", body);
33983
33992
  }
@@ -34116,10 +34125,6 @@ var TelevisionClient = class {
34116
34125
  }
34117
34126
  };
34118
34127
 
34119
- // ../shared/src/constants.ts
34120
- var MS_PER_SECOND = 1e3;
34121
- var SECONDS_PER_MINUTE = 60;
34122
-
34123
34128
  // ../server/src/server.ts
34124
34129
  var import_express2 = __toESM(require_express2(), 1);
34125
34130
  var import_node_http = __toESM(require("node:http"), 1);
@@ -47946,16 +47951,17 @@ function date4(params) {
47946
47951
  // ../../node_modules/zod/v4/classic/external.js
47947
47952
  config(en_default());
47948
47953
 
47954
+ // ../server/src/artifact-paths.ts
47955
+ var import_node_path3 = __toESM(require("node:path"), 1);
47956
+
47949
47957
  // ../artifact/src/model.ts
47950
- var ARTIFACT_TYPES = ["text/markdown", "text/html"];
47958
+ var ARTIFACT_KINDS = ["markdown", "web-bundle", "url"];
47951
47959
  var ARTIFACT_STATUSES = ["committed", "pending"];
47952
- var ARTIFACT_TYPE_TO_EXTENSION = {
47953
- "text/markdown": "md",
47954
- "text/html": "html"
47960
+ var ARTIFACT_KIND_TO_EXTENSION = {
47961
+ markdown: "md",
47962
+ "web-bundle": "html",
47963
+ url: "html"
47955
47964
  };
47956
- function isArtifactType(value) {
47957
- return ARTIFACT_TYPES.includes(value);
47958
- }
47959
47965
  function isExternalArtifactURL(value) {
47960
47966
  let parsed;
47961
47967
  try {
@@ -47967,7 +47973,7 @@ function isExternalArtifactURL(value) {
47967
47973
  }
47968
47974
  var ArtifactSchema = external_exports.object({
47969
47975
  id: external_exports.string().min(1),
47970
- type: external_exports.enum(ARTIFACT_TYPES),
47976
+ kind: external_exports.enum(ARTIFACT_KINDS),
47971
47977
  title: external_exports.string(),
47972
47978
  status: external_exports.enum(ARTIFACT_STATUSES),
47973
47979
  externalFilePath: external_exports.string().optional(),
@@ -47980,41 +47986,92 @@ var ArtifactSchema = external_exports.object({
47980
47986
  path: ["externalURL"]
47981
47987
  });
47982
47988
  }
47983
- if (value.externalURL !== void 0) {
47984
- if (!isExternalArtifactURL(value.externalURL)) {
47985
- ctx.addIssue({
47986
- code: external_exports.ZodIssueCode.custom,
47987
- message: "externalURL must be an http(s) URL",
47988
- path: ["externalURL"]
47989
- });
47990
- }
47991
- if (value.status !== "committed") {
47992
- ctx.addIssue({
47993
- code: external_exports.ZodIssueCode.custom,
47994
- message: "URL-backed artifacts must have status committed",
47995
- path: ["status"]
47996
- });
47997
- }
47989
+ if (value.kind !== "web-bundle" && value.status !== "committed") {
47990
+ ctx.addIssue({
47991
+ code: external_exports.ZodIssueCode.custom,
47992
+ message: "only web-bundle artifacts can be pending",
47993
+ path: ["status"]
47994
+ });
47995
+ }
47996
+ switch (value.kind) {
47997
+ case "markdown":
47998
+ if (value.externalFilePath === void 0) {
47999
+ ctx.addIssue({
48000
+ code: external_exports.ZodIssueCode.custom,
48001
+ message: "markdown artifacts require externalFilePath",
48002
+ path: ["externalFilePath"]
48003
+ });
48004
+ }
48005
+ if (value.externalURL !== void 0) {
48006
+ ctx.addIssue({
48007
+ code: external_exports.ZodIssueCode.custom,
48008
+ message: "markdown artifacts cannot set externalURL",
48009
+ path: ["externalURL"]
48010
+ });
48011
+ }
48012
+ break;
48013
+ case "web-bundle":
48014
+ if (value.externalFilePath !== void 0) {
48015
+ ctx.addIssue({
48016
+ code: external_exports.ZodIssueCode.custom,
48017
+ message: "web-bundle artifacts cannot set externalFilePath",
48018
+ path: ["externalFilePath"]
48019
+ });
48020
+ }
48021
+ if (value.externalURL !== void 0) {
48022
+ ctx.addIssue({
48023
+ code: external_exports.ZodIssueCode.custom,
48024
+ message: "web-bundle artifacts cannot set externalURL",
48025
+ path: ["externalURL"]
48026
+ });
48027
+ }
48028
+ break;
48029
+ case "url":
48030
+ if (value.externalURL === void 0) {
48031
+ ctx.addIssue({
48032
+ code: external_exports.ZodIssueCode.custom,
48033
+ message: "url artifacts require externalURL",
48034
+ path: ["externalURL"]
48035
+ });
48036
+ break;
48037
+ }
48038
+ if (value.externalFilePath !== void 0) {
48039
+ ctx.addIssue({
48040
+ code: external_exports.ZodIssueCode.custom,
48041
+ message: "url artifacts cannot set externalFilePath",
48042
+ path: ["externalFilePath"]
48043
+ });
48044
+ }
48045
+ if (!isExternalArtifactURL(value.externalURL)) {
48046
+ ctx.addIssue({
48047
+ code: external_exports.ZodIssueCode.custom,
48048
+ message: "externalURL must be an http(s) URL",
48049
+ path: ["externalURL"]
48050
+ });
48051
+ }
48052
+ break;
47998
48053
  }
47999
48054
  });
48000
48055
  function createArtifact(input) {
48001
- return {
48056
+ const artifact = ArtifactSchema.parse({
48002
48057
  id: input.id ?? ulid(),
48003
- type: input.type,
48058
+ kind: input.kind,
48004
48059
  title: input.title,
48005
48060
  status: input.status ?? "committed",
48006
48061
  externalFilePath: input.externalFilePath,
48007
48062
  externalURL: input.externalURL
48008
- };
48063
+ });
48064
+ return Object.fromEntries(
48065
+ Object.entries(artifact).filter(([, value]) => value !== void 0)
48066
+ );
48009
48067
  }
48010
48068
  var ViewManifestSchema = external_exports.object({
48011
48069
  name: external_exports.string().min(1),
48012
48070
  description: external_exports.string(),
48013
- accepts: external_exports.array(external_exports.string().min(1)).min(1)
48071
+ accepts: external_exports.array(external_exports.enum(ARTIFACT_KINDS)).min(1)
48014
48072
  });
48015
48073
 
48016
48074
  // ../server/src/artifact-paths.ts
48017
- var import_node_path3 = __toESM(require("node:path"), 1);
48018
48075
  function getArtifactLiveMetadataPath(storagePath, artifactID) {
48019
48076
  return import_node_path3.default.join(storagePath, "artifacts", `${artifactID}.json`);
48020
48077
  }
@@ -48046,8 +48103,8 @@ function getArtifactBundlePath(storagePath, artifactID, state) {
48046
48103
  function getArtifactPublicDirPath(storagePath, artifactID, state) {
48047
48104
  return import_node_path3.default.join(getArtifactBundlePath(storagePath, artifactID, state), "public");
48048
48105
  }
48049
- function getArtifactEntryFilePath(storagePath, artifactID, type, state) {
48050
- const extension2 = ARTIFACT_TYPE_TO_EXTENSION[type];
48106
+ function getArtifactEntryFilePath(storagePath, artifactID, kind, state) {
48107
+ const extension2 = ARTIFACT_KIND_TO_EXTENSION[kind];
48051
48108
  return import_node_path3.default.join(getArtifactPublicDirPath(storagePath, artifactID, state), `index.${extension2}`);
48052
48109
  }
48053
48110
 
@@ -48148,22 +48205,31 @@ var createScreenSchema = external_exports.object({
48148
48205
  name: external_exports.string(),
48149
48206
  id: external_exports.string().optional()
48150
48207
  });
48151
- var createArtifactBaseFields = {
48152
- type: external_exports.enum(ARTIFACT_TYPES),
48208
+ var createWebBundleArtifactSchema = external_exports.object({
48209
+ kind: external_exports.literal("web-bundle"),
48153
48210
  title: external_exports.string(),
48154
- externalFilePath: external_exports.string().optional(),
48155
- externalURL: external_exports.string().optional()
48156
- };
48157
- var externalSourceMutexRefine = (value) => !(value.externalFilePath !== void 0 && value.externalURL !== void 0);
48158
- var externalSourceMutexMessage = {
48159
- message: "externalFilePath and externalURL are mutually exclusive",
48160
- path: ["externalURL"]
48161
- };
48162
- var createArtifactSchema = external_exports.object(createArtifactBaseFields).strict().refine(externalSourceMutexRefine, externalSourceMutexMessage);
48163
- var createArtifactBodySchema = external_exports.object({
48164
- ...createArtifactBaseFields,
48165
- screenID: external_exports.string().optional()
48166
- }).strict().refine(externalSourceMutexRefine, externalSourceMutexMessage);
48211
+ skipPending: external_exports.boolean().optional()
48212
+ }).strict();
48213
+ var createMarkdownArtifactSchema = external_exports.object({
48214
+ kind: external_exports.literal("markdown"),
48215
+ title: external_exports.string(),
48216
+ externalFilePath: external_exports.string()
48217
+ }).strict();
48218
+ var createURLArtifactSchema = external_exports.object({
48219
+ kind: external_exports.literal("url"),
48220
+ title: external_exports.string(),
48221
+ externalURL: external_exports.string()
48222
+ }).strict();
48223
+ var createArtifactSchema = external_exports.discriminatedUnion("kind", [
48224
+ createWebBundleArtifactSchema,
48225
+ createMarkdownArtifactSchema,
48226
+ createURLArtifactSchema
48227
+ ]);
48228
+ var createArtifactBodySchema = external_exports.discriminatedUnion("kind", [
48229
+ createWebBundleArtifactSchema.extend({ screenID: external_exports.string().optional() }).strict(),
48230
+ createMarkdownArtifactSchema.extend({ screenID: external_exports.string().optional() }).strict(),
48231
+ createURLArtifactSchema.extend({ screenID: external_exports.string().optional() }).strict()
48232
+ ]);
48167
48233
  var patchArtifactSchema = external_exports.object({
48168
48234
  title: external_exports.string().optional()
48169
48235
  }).refine((value) => value.title !== void 0, { message: "title is required" });
@@ -48314,16 +48380,12 @@ function registerRoutes(app, store, options) {
48314
48380
  }
48315
48381
  try {
48316
48382
  const artifact = store.createArtifact({
48317
- type: parsed.data.type,
48318
- title: parsed.data.title,
48319
48383
  screenID: req.params.id,
48320
- externalFilePath: parsed.data.externalFilePath,
48321
- externalURL: parsed.data.externalURL
48384
+ ...parsed.data
48322
48385
  });
48323
- const externallyBacked = artifact.externalFilePath !== void 0 || artifact.externalURL !== void 0;
48324
48386
  res.status(HTTP_CREATED).json({
48325
48387
  artifact,
48326
- ...externallyBacked ? {} : { pendingPath: getArtifactPendingBundlePath(store.storagePath, artifact.id) }
48388
+ ...artifact.status === "pending" ? { pendingPath: getArtifactPendingBundlePath(store.storagePath, artifact.id) } : {}
48327
48389
  });
48328
48390
  } catch (error48) {
48329
48391
  handleStoreError(res, error48, "Failed to create artifact");
@@ -48372,14 +48434,10 @@ function registerRoutes(app, store, options) {
48372
48434
  return;
48373
48435
  }
48374
48436
  try {
48375
- const artifact = store.createArtifact({
48376
- type: parsed.data.type,
48377
- title: parsed.data.title,
48378
- externalFilePath: parsed.data.externalFilePath,
48379
- externalURL: parsed.data.externalURL
48380
- });
48437
+ const { screenID: _screenID, ...createInput } = parsed.data;
48438
+ const artifact = store.createArtifact(createInput);
48381
48439
  const body = { artifact };
48382
- if (artifact.externalFilePath === void 0 && artifact.externalURL === void 0) {
48440
+ if (artifact.status === "pending") {
48383
48441
  body.pendingPath = getArtifactPendingBundlePath(store.storagePath, artifact.id);
48384
48442
  }
48385
48443
  if (parsed.data.screenID !== void 0) {
@@ -48473,12 +48531,16 @@ function registerRoutes(app, store, options) {
48473
48531
  sendError(res, HTTP_NOT_FOUND, `Artifact not found: ${req.params.id}`);
48474
48532
  return;
48475
48533
  }
48476
- if (artifact.externalURL) {
48477
- res.redirect(HTTP_FOUND, artifact.externalURL);
48534
+ if (artifact.kind === "url") {
48535
+ const externalURL = artifact.externalURL;
48536
+ if (!externalURL) {
48537
+ throw new Error(`URL artifact ${artifact.id} is missing externalURL`);
48538
+ }
48539
+ res.redirect(HTTP_FOUND, externalURL);
48478
48540
  return;
48479
48541
  }
48480
48542
  const content = store.readArtifactContent(req.params.id);
48481
- res.setHeader("Content-Type", contentTypeForArtifactType(artifact.type));
48543
+ res.setHeader("Content-Type", contentTypeForArtifactKind(artifact.kind));
48482
48544
  res.send(content);
48483
48545
  } catch (error48) {
48484
48546
  handleStoreError(res, error48, "Failed to read artifact content");
@@ -48519,7 +48581,7 @@ function registerRoutes(app, store, options) {
48519
48581
  });
48520
48582
  app.put("/artifacts/:id/content", ...auth, import_express.default.text({ type: "*/*", limit: "10mb" }), (req, res) => {
48521
48583
  const artifact = store.getArtifact(req.params.id);
48522
- if (artifact?.externalURL) {
48584
+ if (artifact?.kind === "url") {
48523
48585
  sendError(res, HTTP_METHOD_NOT_ALLOWED, "URL-backed artifacts have no server-writable content");
48524
48586
  return;
48525
48587
  }
@@ -48603,13 +48665,8 @@ function registerRoutes(app, store, options) {
48603
48665
  }
48604
48666
  );
48605
48667
  }
48606
- function contentTypeForArtifactType(type) {
48607
- switch (type) {
48608
- case "text/markdown":
48609
- return "text/markdown; charset=utf-8";
48610
- case "text/html":
48611
- return "text/html; charset=utf-8";
48612
- }
48668
+ function contentTypeForArtifactKind(kind) {
48669
+ return kind === "markdown" ? "text/markdown; charset=utf-8" : "text/html; charset=utf-8";
48613
48670
  }
48614
48671
 
48615
48672
  // ../../node_modules/ws/wrapper.mjs
@@ -49255,19 +49312,6 @@ var defaultWatchContentFile = (filePath, onChange) => {
49255
49312
  var JSON_INDENT_SPACES = 2;
49256
49313
  var CONTENT_WATCH_DEBOUNCE_MS = 100;
49257
49314
  var JSON_FILE_SUFFIX = ".json";
49258
- var ACTIVITY_LOG_HEADING = "## Activity Log";
49259
- var ACTIVITY_LOG_MAX_AGE_MINUTES = 30;
49260
- var ACTIVITY_LOG_MAX_AGE_MS = ACTIVITY_LOG_MAX_AGE_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
49261
- var REQUIRED_ARTIFACT_HEADINGS = [
49262
- "## User intent",
49263
- "## Purpose",
49264
- "## Data shape",
49265
- "## Data sources",
49266
- "## Rendering",
49267
- "## Update workflow",
49268
- "## Non-goals"
49269
- ];
49270
- var TIMESTAMP_PATTERN = /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\b/g;
49271
49315
  var ServerStore = class extends EventTarget {
49272
49316
  storagePath;
49273
49317
  authToken;
@@ -49353,21 +49397,19 @@ var ServerStore = class extends EventTarget {
49353
49397
  if (!artifact) {
49354
49398
  return void 0;
49355
49399
  }
49356
- if (artifact.externalURL) {
49357
- return void 0;
49358
- }
49359
- if (artifact.externalFilePath) {
49360
- return artifact.externalFilePath;
49361
- }
49362
- const publicDir = this.getArtifactPublicDir(id);
49363
- if (!publicDir) {
49364
- return void 0;
49400
+ switch (artifact.kind) {
49401
+ case "url":
49402
+ return void 0;
49403
+ case "markdown":
49404
+ return artifact.externalFilePath;
49405
+ case "web-bundle":
49406
+ return getArtifactEntryFilePath(this.storagePath, id, artifact.kind, "committed");
49365
49407
  }
49366
- return getArtifactEntryFilePath(this.storagePath, id, artifact.type, "committed");
49408
+ return assertNever2(artifact.kind, "artifact kind");
49367
49409
  }
49368
49410
  getArtifactPublicDir(id) {
49369
49411
  const artifact = this._artifacts.get(id);
49370
- if (!artifact || artifact.externalFilePath || artifact.externalURL) {
49412
+ if (!artifact || artifact.kind !== "web-bundle") {
49371
49413
  return void 0;
49372
49414
  }
49373
49415
  return getArtifactPublicDirPath(this.storagePath, id, "committed");
@@ -49399,7 +49441,7 @@ var ServerStore = class extends EventTarget {
49399
49441
  */
49400
49442
  writeArtifactContent(id, content) {
49401
49443
  const artifact = this._artifacts.get(id);
49402
- if (artifact?.externalURL) {
49444
+ if (artifact?.kind === "url") {
49403
49445
  throw new InvalidRequestError(
49404
49446
  `Artifact ${id} is URL-backed; its content is not server-writable`
49405
49447
  );
@@ -49408,7 +49450,7 @@ var ServerStore = class extends EventTarget {
49408
49450
  if (!contentPath) {
49409
49451
  throw new NotFoundError(`Artifact not found: ${id}`, { entityType: "artifact", entityID: id });
49410
49452
  }
49411
- if (artifact && !artifact.externalFilePath && artifact.status === "pending" && !this.hasCommittedBundle(id)) {
49453
+ if (artifact?.kind === "web-bundle" && artifact.status === "pending" && !this.hasCommittedBundle(id)) {
49412
49454
  throw new InvalidRequestError(
49413
49455
  `Artifact ${id} has no committed content yet. Use commit or abandon after writing the pending bundle`
49414
49456
  );
@@ -49438,20 +49480,10 @@ var ServerStore = class extends EventTarget {
49438
49480
  );
49439
49481
  }
49440
49482
  const screen = input.screenID !== void 0 ? this.requireScreen(input.screenID) : null;
49441
- const externallyBacked = input.externalFilePath !== void 0 || input.externalURL !== void 0;
49442
- const artifact = createArtifact({
49443
- type: input.type,
49444
- title: input.title,
49445
- status: externallyBacked ? "committed" : "pending",
49446
- externalFilePath: input.externalFilePath ? this.normalizeExternalArtifactPath(input.externalFilePath, input.type) : void 0,
49447
- externalURL: input.externalURL ? this.normalizeExternalArtifactURL(input.externalURL) : void 0
49448
- });
49449
- if (!externallyBacked) {
49450
- this.scaffoldPendingBundle(artifact.id, artifact.type);
49451
- }
49483
+ const artifact = this.createKindArtifact(input);
49452
49484
  this.writeArtifact(artifact);
49453
49485
  this._artifacts.set(artifact.id, artifact);
49454
- if (artifact.status === "committed" && artifact.externalURL === void 0) {
49486
+ if (artifact.status === "committed" && artifact.kind !== "url") {
49455
49487
  this.startWatchingArtifactContent(artifact.id);
49456
49488
  }
49457
49489
  this.dispatchEvent(new ArtifactCreatedEvent("artifact-created", { artifact }));
@@ -49486,15 +49518,17 @@ var ServerStore = class extends EventTarget {
49486
49518
  if (!artifact) {
49487
49519
  throw new NotFoundError(`Artifact not found: ${artifactID}`, { entityType: "artifact", entityID: artifactID });
49488
49520
  }
49489
- if (artifact.externalFilePath || artifact.externalURL) {
49490
- throw new InvalidRequestError(`Only internal artifacts can enter pending edit: ${artifactID}`);
49521
+ if (artifact.kind !== "web-bundle") {
49522
+ throw new InvalidRequestError(`Only web-bundle artifacts can enter pending edit: ${artifactID}`);
49491
49523
  }
49492
49524
  if (artifact.status === "pending") {
49493
49525
  throw new InvalidRequestError(`Artifact ${artifactID} already has pending work. Use commit or abandon before editing again`);
49494
49526
  }
49527
+ const pendingPath = getArtifactPendingBundlePath(this.storagePath, artifactID);
49528
+ (0, import_node_fs5.rmSync)(pendingPath, { recursive: true, force: true });
49495
49529
  (0, import_node_fs5.cpSync)(
49496
49530
  getArtifactCommittedBundlePath(this.storagePath, artifactID),
49497
- getArtifactPendingBundlePath(this.storagePath, artifactID),
49531
+ pendingPath,
49498
49532
  { recursive: true }
49499
49533
  );
49500
49534
  artifact.status = "pending";
@@ -49507,21 +49541,17 @@ var ServerStore = class extends EventTarget {
49507
49541
  if (!artifact) {
49508
49542
  throw new NotFoundError(`Artifact not found: ${artifactID}`, { entityType: "artifact", entityID: artifactID });
49509
49543
  }
49510
- const issues = this.validatePendingArtifact(artifact);
49511
- if (issues.length > 0) {
49512
- throw new InvalidRequestError(
49513
- [
49514
- `Commit failed for artifact ${artifactID}:`,
49515
- ...issues.map((issue2) => `- ${issue2}`)
49516
- ].join("\n")
49517
- );
49544
+ if (artifact.kind !== "web-bundle") {
49545
+ throw new InvalidRequestError(`Only web-bundle artifacts can commit pending work: ${artifactID}`);
49546
+ }
49547
+ if (artifact.status !== "pending") {
49548
+ throw new InvalidRequestError(`Artifact ${artifactID} has no pending work to commit`);
49518
49549
  }
49519
49550
  const pendingPath = getArtifactPendingBundlePath(this.storagePath, artifactID);
49520
49551
  const committedPath = getArtifactCommittedBundlePath(this.storagePath, artifactID);
49521
49552
  this.stopWatchingArtifactContent(artifactID);
49522
49553
  (0, import_node_fs5.rmSync)(committedPath, { recursive: true, force: true });
49523
- (0, import_node_fs5.cpSync)(pendingPath, committedPath, { recursive: true });
49524
- (0, import_node_fs5.rmSync)(pendingPath, { recursive: true, force: true });
49554
+ (0, import_node_fs5.renameSync)(pendingPath, committedPath);
49525
49555
  artifact.status = "committed";
49526
49556
  this.writeArtifact(artifact);
49527
49557
  this._artifacts.set(artifactID, artifact);
@@ -49535,8 +49565,8 @@ var ServerStore = class extends EventTarget {
49535
49565
  if (!artifact) {
49536
49566
  throw new NotFoundError(`Artifact not found: ${artifactID}`, { entityType: "artifact", entityID: artifactID });
49537
49567
  }
49538
- if (artifact.externalFilePath || artifact.externalURL) {
49539
- throw new InvalidRequestError(`Only internal artifacts can abandon pending work: ${artifactID}`);
49568
+ if (artifact.kind !== "web-bundle") {
49569
+ throw new InvalidRequestError(`Only web-bundle artifacts can abandon pending work: ${artifactID}`);
49540
49570
  }
49541
49571
  if (artifact.status !== "pending") {
49542
49572
  throw new InvalidRequestError(`Artifact ${artifactID} has no pending work to abandon`);
@@ -49854,11 +49884,17 @@ var ServerStore = class extends EventTarget {
49854
49884
  }
49855
49885
  startContentWatchersForLoadedArtifacts() {
49856
49886
  for (const [artifactID, artifact] of this._artifacts.entries()) {
49857
- if (artifact.externalURL) {
49858
- continue;
49859
- }
49860
- if (artifact.status === "committed" || !artifact.externalFilePath && this.hasCommittedBundle(artifactID)) {
49861
- this.startWatchingArtifactContent(artifactID);
49887
+ switch (artifact.kind) {
49888
+ case "url":
49889
+ continue;
49890
+ case "markdown":
49891
+ this.startWatchingArtifactContent(artifactID);
49892
+ continue;
49893
+ case "web-bundle":
49894
+ if (artifact.status === "committed" || this.hasCommittedBundle(artifactID)) {
49895
+ this.startWatchingArtifactContent(artifactID);
49896
+ }
49897
+ continue;
49862
49898
  }
49863
49899
  }
49864
49900
  }
@@ -49990,6 +50026,65 @@ var ServerStore = class extends EventTarget {
49990
50026
  hasCommittedBundle(artifactID) {
49991
50027
  return (0, import_node_fs5.existsSync)(getArtifactCommittedBundlePath(this.storagePath, artifactID));
49992
50028
  }
50029
+ createKindArtifact(input) {
50030
+ const kind = input.kind;
50031
+ switch (kind) {
50032
+ case "markdown": {
50033
+ if (input.externalFilePath === void 0) {
50034
+ throw new InvalidRequestError("markdown artifacts require externalFilePath");
50035
+ }
50036
+ if (input.externalURL !== void 0) {
50037
+ throw new InvalidRequestError("markdown artifacts cannot set externalURL");
50038
+ }
50039
+ if (input.skipPending !== void 0) {
50040
+ throw new InvalidRequestError("skipPending is only valid for web-bundle artifacts");
50041
+ }
50042
+ return createArtifact({
50043
+ kind,
50044
+ title: input.title,
50045
+ status: "committed",
50046
+ externalFilePath: this.normalizeExternalArtifactPath(input.externalFilePath, kind)
50047
+ });
50048
+ }
50049
+ case "url": {
50050
+ if (input.externalURL === void 0) {
50051
+ throw new InvalidRequestError("url artifacts require externalURL");
50052
+ }
50053
+ if (input.externalFilePath !== void 0) {
50054
+ throw new InvalidRequestError("url artifacts cannot set externalFilePath");
50055
+ }
50056
+ if (input.skipPending !== void 0) {
50057
+ throw new InvalidRequestError("skipPending is only valid for web-bundle artifacts");
50058
+ }
50059
+ return createArtifact({
50060
+ kind,
50061
+ title: input.title,
50062
+ status: "committed",
50063
+ externalURL: this.normalizeExternalArtifactURL(input.externalURL)
50064
+ });
50065
+ }
50066
+ case "web-bundle": {
50067
+ if (input.externalFilePath !== void 0) {
50068
+ throw new InvalidRequestError("web-bundle artifacts cannot set externalFilePath");
50069
+ }
50070
+ if (input.externalURL !== void 0) {
50071
+ throw new InvalidRequestError("web-bundle artifacts cannot set externalURL");
50072
+ }
50073
+ const artifact = createArtifact({
50074
+ kind,
50075
+ title: input.title,
50076
+ status: input.skipPending ? "committed" : "pending"
50077
+ });
50078
+ if (artifact.status === "pending") {
50079
+ this.scaffoldPendingBundle(artifact.id);
50080
+ } else {
50081
+ this.scaffoldCommittedBundle(artifact.id);
50082
+ }
50083
+ return artifact;
50084
+ }
50085
+ }
50086
+ return assertNever2(kind, "artifact kind");
50087
+ }
49993
50088
  createDefaultScreen() {
49994
50089
  this.createScreen({ name: "Default" });
49995
50090
  }
@@ -50017,17 +50112,18 @@ var ServerStore = class extends EventTarget {
50017
50112
  throw new NotFoundError(`Artifact not found: ${artifactID}`, { entityType: "artifact", entityID: artifactID });
50018
50113
  }
50019
50114
  const affectedScreenIDs = this.collectReferencingScreenIDs(artifactID);
50020
- if (!artifact.externalFilePath && !artifact.externalURL) {
50115
+ if (artifact.kind === "web-bundle") {
50021
50116
  (0, import_node_fs5.rmSync)(getArtifactPendingBundlePath(this.storagePath, artifactID), { recursive: true, force: true });
50022
50117
  }
50023
50118
  this.stopWatchingArtifactContent(artifactID);
50024
- if (!artifact.externalFilePath && !artifact.externalURL && artifact.status === "pending" && !this.hasCommittedBundle(artifactID)) {
50119
+ if (artifact.kind === "web-bundle" && artifact.status === "pending" && !this.hasCommittedBundle(artifactID)) {
50025
50120
  this._artifacts.delete(artifactID);
50026
50121
  (0, import_node_fs5.rmSync)(getArtifactLiveMetadataPath(this.storagePath, artifactID), { force: true });
50027
50122
  this.stripArtifactFromScreens(artifactID, affectedScreenIDs);
50028
50123
  this.emitArtifactRemovedEvents(artifactID, affectedScreenIDs);
50029
50124
  return {
50030
50125
  outcome: "discarded",
50126
+ kind: "web-bundle",
50031
50127
  artifactID
50032
50128
  };
50033
50129
  }
@@ -50063,34 +50159,49 @@ var ServerStore = class extends EventTarget {
50063
50159
  const metadataPath = getArtifactTrashMetadataPath(this.storagePath, artifact.id);
50064
50160
  (0, import_node_fs5.mkdirSync)(import_node_path9.default.dirname(metadataPath), { recursive: true });
50065
50161
  (0, import_node_fs5.writeFileSync)(metadataPath, JSON.stringify(artifact, null, JSON_INDENT_SPACES));
50066
- if (artifact.externalURL) {
50067
- return {
50068
- outcome: "trashed",
50069
- artifactID: artifact.id,
50070
- metadataPath,
50071
- externalURL: artifact.externalURL
50072
- };
50073
- }
50074
- if (artifact.externalFilePath) {
50075
- return {
50076
- outcome: "trashed",
50077
- artifactID: artifact.id,
50078
- metadataPath,
50079
- externalFilePath: artifact.externalFilePath
50080
- };
50081
- }
50082
- const committedBundlePath = getArtifactCommittedBundlePath(this.storagePath, artifact.id);
50083
- const bundlePath = getArtifactTrashBundlePath(this.storagePath, artifact.id);
50084
- (0, import_node_fs5.rmSync)(bundlePath, { recursive: true, force: true });
50085
- if ((0, import_node_fs5.existsSync)(committedBundlePath)) {
50086
- (0, import_node_fs5.renameSync)(committedBundlePath, bundlePath);
50162
+ switch (artifact.kind) {
50163
+ case "url": {
50164
+ const externalURL = artifact.externalURL;
50165
+ if (!externalURL) {
50166
+ throw new Error(`URL artifact ${artifact.id} is missing externalURL`);
50167
+ }
50168
+ return {
50169
+ outcome: "trashed",
50170
+ kind: "url",
50171
+ artifactID: artifact.id,
50172
+ metadataPath,
50173
+ externalURL
50174
+ };
50175
+ }
50176
+ case "markdown": {
50177
+ const externalFilePath = artifact.externalFilePath;
50178
+ if (!externalFilePath) {
50179
+ throw new Error(`Markdown artifact ${artifact.id} is missing externalFilePath`);
50180
+ }
50181
+ return {
50182
+ outcome: "trashed",
50183
+ kind: "markdown",
50184
+ artifactID: artifact.id,
50185
+ metadataPath,
50186
+ externalFilePath
50187
+ };
50188
+ }
50189
+ case "web-bundle": {
50190
+ const committedBundlePath = getArtifactCommittedBundlePath(this.storagePath, artifact.id);
50191
+ const bundlePath = getArtifactTrashBundlePath(this.storagePath, artifact.id);
50192
+ (0, import_node_fs5.rmSync)(bundlePath, { recursive: true, force: true });
50193
+ if ((0, import_node_fs5.existsSync)(committedBundlePath)) {
50194
+ (0, import_node_fs5.renameSync)(committedBundlePath, bundlePath);
50195
+ }
50196
+ return {
50197
+ outcome: "trashed",
50198
+ kind: "web-bundle",
50199
+ artifactID: artifact.id,
50200
+ metadataPath,
50201
+ bundlePath
50202
+ };
50203
+ }
50087
50204
  }
50088
- return {
50089
- outcome: "trashed",
50090
- artifactID: artifact.id,
50091
- metadataPath,
50092
- bundlePath
50093
- };
50094
50205
  }
50095
50206
  trashScreen(screen) {
50096
50207
  const metadataPath = getTrashedScreenMetadataPath(this.storagePath, screen.id);
@@ -50258,82 +50369,14 @@ Review docs/storage.md for the current schema, and either update the file to mat
50258
50369
  writeArtifact(artifact) {
50259
50370
  (0, import_node_fs5.writeFileSync)(getArtifactLiveMetadataPath(this.storagePath, artifact.id), JSON.stringify(artifact, null, JSON_INDENT_SPACES));
50260
50371
  }
50261
- scaffoldPendingBundle(artifactID, type) {
50262
- const pendingPath = getArtifactPendingBundlePath(this.storagePath, artifactID);
50372
+ scaffoldPendingBundle(artifactID) {
50263
50373
  const publicDir = getArtifactPublicDirPath(this.storagePath, artifactID, "pending");
50264
50374
  (0, import_node_fs5.mkdirSync)(publicDir, { recursive: true });
50265
- (0, import_node_fs5.writeFileSync)(import_node_path9.default.join(pendingPath, "artifact.md"), "");
50266
- (0, import_node_fs5.writeFileSync)(import_node_path9.default.join(pendingPath, "memory.md"), "");
50267
- (0, import_node_fs5.writeFileSync)(import_node_path9.default.join(pendingPath, "data.json"), "{}");
50268
- (0, import_node_fs5.writeFileSync)(getArtifactEntryFilePath(this.storagePath, artifactID, type, "pending"), "");
50269
50375
  }
50270
- validatePendingArtifact(artifact) {
50271
- const issues = [];
50272
- const artifactID = artifact.id;
50273
- if (artifact.externalFilePath) {
50274
- issues.push(`artifacts/${artifactID}.json is external; only internal artifacts can have pending content to commit`);
50275
- }
50276
- if (artifact.status !== "pending") {
50277
- issues.push(`artifacts/${artifactID}.json must have status "pending" to commit pending content`);
50278
- }
50279
- const pendingPath = getArtifactPendingBundlePath(this.storagePath, artifactID);
50280
- this.validateArtifactDoc(import_node_path9.default.join(pendingPath, "artifact.md"), issues);
50281
- this.validateDataJson(import_node_path9.default.join(pendingPath, "data.json"), issues);
50282
- this.validateMemory(import_node_path9.default.join(pendingPath, "memory.md"), issues);
50283
- this.validateEntryFile(getArtifactEntryFilePath(this.storagePath, artifactID, artifact.type, "pending"), issues);
50284
- return issues;
50285
- }
50286
- validateArtifactDoc(filePath, issues) {
50287
- const text = this.readTextFileForValidation(filePath, issues);
50288
- if (text === null) return;
50289
- if (text.length === 0) {
50290
- issues.push(`${filePath} must be non-empty`);
50291
- }
50292
- for (const heading of REQUIRED_ARTIFACT_HEADINGS) {
50293
- if (!text.includes(heading)) {
50294
- issues.push(`${filePath} must contain heading ${heading}`);
50295
- }
50296
- }
50297
- }
50298
- validateDataJson(filePath, issues) {
50299
- const text = this.readTextFileForValidation(filePath, issues);
50300
- if (text === null) return;
50301
- try {
50302
- JSON.parse(text);
50303
- } catch (error48) {
50304
- issues.push(`${filePath} must contain valid JSON (${error48 instanceof Error ? error48.message : "parse failed"})`);
50305
- }
50306
- }
50307
- validateMemory(filePath, issues) {
50308
- const text = this.readTextFileForValidation(filePath, issues);
50309
- if (text === null) return;
50310
- if (!text.includes(ACTIVITY_LOG_HEADING)) {
50311
- issues.push(`${filePath} must contain ${ACTIVITY_LOG_HEADING}`);
50312
- }
50313
- const now = Date.now();
50314
- const timestamps = text.match(TIMESTAMP_PATTERN) ?? [];
50315
- const hasFreshTimestamp = timestamps.some((timestamp) => {
50316
- const parsed = Date.parse(timestamp);
50317
- return Number.isFinite(parsed) && parsed <= now && now - parsed <= ACTIVITY_LOG_MAX_AGE_MS;
50318
- });
50319
- if (!hasFreshTimestamp) {
50320
- issues.push(`${filePath} must contain a UTC activity timestamp from the last 30 minutes`);
50321
- }
50322
- }
50323
- validateEntryFile(filePath, issues) {
50324
- const text = this.readTextFileForValidation(filePath, issues);
50325
- if (text === null) return;
50326
- if (text.length === 0) {
50327
- issues.push(`${filePath} must be non-empty`);
50328
- }
50329
- }
50330
- readTextFileForValidation(filePath, issues) {
50331
- try {
50332
- return (0, import_node_fs5.readFileSync)(filePath, "utf8");
50333
- } catch {
50334
- issues.push(`${filePath} must exist`);
50335
- return null;
50336
- }
50376
+ scaffoldCommittedBundle(artifactID) {
50377
+ const publicDir = getArtifactPublicDirPath(this.storagePath, artifactID, "committed");
50378
+ (0, import_node_fs5.mkdirSync)(publicDir, { recursive: true });
50379
+ (0, import_node_fs5.writeFileSync)(getArtifactEntryFilePath(this.storagePath, artifactID, "web-bundle", "committed"), "");
50337
50380
  }
50338
50381
  get screensDir() {
50339
50382
  return import_node_path9.default.join(this.storagePath, "screens");
@@ -50346,7 +50389,7 @@ Review docs/storage.md for the current schema, and either update the file to mat
50346
50389
  }
50347
50390
  return rawURL;
50348
50391
  }
50349
- normalizeExternalArtifactPath(sourcePath, type) {
50392
+ normalizeExternalArtifactPath(sourcePath, kind) {
50350
50393
  if (!import_node_path9.default.isAbsolute(sourcePath)) {
50351
50394
  throw new InvalidRequestError("externalFilePath must be absolute");
50352
50395
  }
@@ -50358,9 +50401,9 @@ Review docs/storage.md for the current schema, and either update the file to mat
50358
50401
  } catch {
50359
50402
  throw new InvalidRequestError(`externalFilePath does not exist or is not readable: ${sourcePath}`);
50360
50403
  }
50361
- const expectedExtension = type === "text/html" ? ".html" : ".md";
50404
+ const expectedExtension = `.${ARTIFACT_KIND_TO_EXTENSION[kind]}`;
50362
50405
  if (import_node_path9.default.extname(sourcePath).toLowerCase() !== expectedExtension) {
50363
- throw new InvalidRequestError(`externalFilePath extension does not match artifact type ${type}: ${sourcePath}`);
50406
+ throw new InvalidRequestError(`externalFilePath extension does not match artifact kind ${kind}: ${sourcePath}`);
50364
50407
  }
50365
50408
  return sourcePath;
50366
50409
  }
@@ -50371,6 +50414,9 @@ Review docs/storage.md for the current schema, and either update the file to mat
50371
50414
  return import_node_path9.default.join(this.storagePath, "token");
50372
50415
  }
50373
50416
  };
50417
+ function assertNever2(value, context) {
50418
+ throw new Error(`Unexpected ${context}: ${JSON.stringify(value)}`);
50419
+ }
50374
50420
  function findCardIDForArtifact(layout, artifactID) {
50375
50421
  for (const node of layout) {
50376
50422
  const id = findCardIDInNode(node, artifactID);
@@ -50400,7 +50446,7 @@ function findCardIDInNode(node, artifactID) {
50400
50446
  var import_meta = {};
50401
50447
  var DAEMON_NAME = "com.television.server";
50402
50448
  var MAX_PORT = 65535;
50403
- var HELP_POINTER = "Television guidance is split across bundled skills. For screens, lifecycle work, and the `tv` CLI, read the `television` skill. For artifact authoring, use `tv skills install` or `tv skills show`, then let the matching `artifact-*` skill guide the output.";
50449
+ var HELP_POINTER = "Television ships bundled skills. The main skill is `television` \u2014 read it for screens, lifecycle, the `tv` CLI, and artifact workflow. Additional `television-*` skills cover specialized artifact types and theming. Install all bundled skills with `tv skills install <path>` (e.g. ~/.openclaw/skills) or `tv skills install -i`.";
50404
50450
  var CLIDirectiveError = class extends Error {
50405
50451
  name = "CLIDirectiveError";
50406
50452
  };
@@ -50430,8 +50476,8 @@ function resolveVercelSkillsInstallerBin() {
50430
50476
  return localRequire.resolve("skills/bin/cli.mjs");
50431
50477
  }
50432
50478
  function readCLIVersion() {
50433
- if ("0.1.98".length > 0) {
50434
- return "0.1.98";
50479
+ if ("0.1.99".length > 0) {
50480
+ return "0.1.99";
50435
50481
  }
50436
50482
  const devPackageJsonPath = import_node_path10.default.join(getDevPackageDir(), "package.json");
50437
50483
  if (!(0, import_node_fs6.existsSync)(devPackageJsonPath)) {
@@ -50441,81 +50487,30 @@ function readCLIVersion() {
50441
50487
  }
50442
50488
  var FRONTMATTER_DELIMITER = "---\n";
50443
50489
  var FRONTMATTER_DELIMITER_LENGTH = FRONTMATTER_DELIMITER.length;
50444
- function splitFrontmatter(text) {
50445
- if (!text.startsWith(FRONTMATTER_DELIMITER)) {
50446
- return { frontmatter: {}, body: text };
50447
- }
50448
- const closing = text.indexOf("\n---", FRONTMATTER_DELIMITER_LENGTH);
50449
- if (closing === -1) {
50450
- return { frontmatter: {}, body: text };
50451
- }
50452
- const block = text.slice(FRONTMATTER_DELIMITER_LENGTH, closing);
50453
- const after = text.slice(closing + FRONTMATTER_DELIMITER_LENGTH);
50454
- const body = after.startsWith("\n") ? after.slice(1) : after;
50455
- const frontmatter = {};
50456
- for (const rawLine of block.split("\n")) {
50457
- const line = rawLine.trim();
50458
- if (line === "" || line.startsWith("#")) continue;
50459
- const colon = line.indexOf(":");
50460
- if (colon === -1) continue;
50461
- const key = line.slice(0, colon).trim();
50462
- let value = line.slice(colon + 1).trim();
50463
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
50464
- value = value.slice(1, -1);
50465
- }
50466
- frontmatter[key] = value;
50467
- }
50468
- return { frontmatter, body };
50469
- }
50470
- function readBundledSkill(skillRoot) {
50471
- const skillMarkdownPath = import_node_path10.default.join(skillRoot, "SKILL.md");
50472
- if (!(0, import_node_fs6.existsSync)(skillMarkdownPath)) {
50473
- throw new Error(`Bundled skill is missing SKILL.md at ${skillMarkdownPath}.`);
50474
- }
50475
- const markdown = (0, import_node_fs6.readFileSync)(skillMarkdownPath, "utf8");
50476
- const { frontmatter } = splitFrontmatter(markdown);
50477
- const name = frontmatter.name?.trim();
50478
- const description = frontmatter.description?.trim();
50479
- if (!name || !description) {
50480
- throw new Error(`Bundled skill has invalid frontmatter at ${skillMarkdownPath}.`);
50481
- }
50482
- return { name, description, markdown, root: skillRoot };
50483
- }
50484
- function listBundledSkills(collectionRoot) {
50485
- return (0, import_node_fs6.readdirSync)(collectionRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readBundledSkill(import_node_path10.default.join(collectionRoot, entry.name))).sort((a, b) => a.name.localeCompare(b.name));
50486
- }
50487
- function resolveBundledSkill(collectionRoot, skillName) {
50488
- const skillRoot = import_node_path10.default.join(collectionRoot, skillName);
50489
- if (!(0, import_node_fs6.existsSync)(skillRoot)) {
50490
- throw createDirectiveError(`tv skills show could not find bundled skill \`${skillName}\`.`);
50491
- }
50492
- return readBundledSkill(skillRoot);
50493
- }
50494
50490
  function buildAgentHelpNote() {
50495
50491
  return [
50496
50492
  "",
50497
50493
  "Agent workflow note:",
50498
- " Television guidance is split across bundled skills.",
50499
- " For screens, lifecycle work, and the `tv` CLI, read `television`.",
50500
- " For artifact authoring, install or inspect the bundled skills with `tv skills install` or `tv skills show`.",
50501
- " Then use the matching `artifact-*` skill, such as `artifact-calendar` or `artifact-table`."
50494
+ " The main skill is `television` \u2014 read it for screens, lifecycle, the `tv` CLI, and artifact workflow.",
50495
+ " Additional `television-*` skills cover specialized artifact types and theming.",
50496
+ " Install all bundled skills: `tv skills install <path>` (e.g. ~/.openclaw/skills) or `tv skills install -i`."
50502
50497
  ].join("\n");
50503
50498
  }
50504
50499
  function buildArtifactWorkflowHelpNote(includeHTMLNote) {
50505
50500
  const lines = [
50506
50501
  "",
50507
50502
  "Agent note:",
50508
- " For Television lifecycle work, read `television`."
50503
+ " The main skill is `television` \u2014 read it for screens, lifecycle, and artifact workflow."
50509
50504
  ];
50510
50505
  if (includeHTMLNote) {
50511
- lines.push(" For artifact authoring, install or inspect the bundled skills with `tv skills install` or `tv skills show`.");
50512
- lines.push(" Then use the matching `artifact-*` skill, such as `artifact-calendar` or `artifact-table`.");
50506
+ lines.push(" Additional `television-*` skills cover specialized artifact types and theming.");
50507
+ lines.push(" Install all bundled skills: `tv skills install <path>` (e.g. ~/.openclaw/skills) or `tv skills install -i`.");
50513
50508
  }
50514
50509
  return lines.join("\n");
50515
50510
  }
50516
50511
  function commandNameFromArgv(argv) {
50517
- const commandName = argv.find((value) => !value.startsWith("-"));
50518
- return commandName ?? "tv";
50512
+ const parts = argv.filter((value) => !value.startsWith("-"));
50513
+ return parts.length > 0 ? parts.join(" ") : "tv";
50519
50514
  }
50520
50515
  function formatCLIError(error48) {
50521
50516
  if (error48 instanceof CLIDirectiveError) {
@@ -50576,6 +50571,22 @@ function resolveBundledSkillsRoot() {
50576
50571
  const devPath = import_node_path10.default.resolve(getDevPackageDir(), "../skills/dist");
50577
50572
  return (0, import_node_fs6.existsSync)(devPath) ? devPath : void 0;
50578
50573
  }
50574
+ function copyBundledSkillsToDestination(bundledSkillsRoot, destinationRoot) {
50575
+ if ((0, import_node_fs6.existsSync)(destinationRoot) && !(0, import_node_fs6.statSync)(destinationRoot).isDirectory()) {
50576
+ throw new Error(`Skills destination is not a directory: ${destinationRoot}`);
50577
+ }
50578
+ (0, import_node_fs6.mkdirSync)(destinationRoot, { recursive: true });
50579
+ const copied = [];
50580
+ const entries = (0, import_node_fs6.readdirSync)(bundledSkillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
50581
+ for (const entry of entries) {
50582
+ const sourcePath = import_node_path10.default.join(bundledSkillsRoot, entry.name);
50583
+ const destinationPath = import_node_path10.default.join(destinationRoot, entry.name);
50584
+ (0, import_node_fs6.rmSync)(destinationPath, { recursive: true, force: true });
50585
+ (0, import_node_fs6.cpSync)(sourcePath, destinationPath, { recursive: true });
50586
+ copied.push({ name: entry.name, sourcePath, destinationPath });
50587
+ }
50588
+ return copied;
50589
+ }
50579
50590
  function isInTempDirectory(inputPath) {
50580
50591
  try {
50581
50592
  const realTmp = (0, import_node_fs6.realpathSync)(import_node_os4.default.tmpdir());
@@ -50586,14 +50597,14 @@ function isInTempDirectory(inputPath) {
50586
50597
  return false;
50587
50598
  }
50588
50599
  }
50589
- function validateExternalArtifactPath(inputPath) {
50600
+ function validateExternalArtifactPath(commandName, inputPath) {
50590
50601
  if (!import_node_path10.default.isAbsolute(inputPath)) {
50591
- throw createDirectiveError("tv create-external-artifact requires --path to be an absolute path.");
50602
+ throw createDirectiveError(`tv ${commandName} requires --path to be an absolute path.`);
50592
50603
  }
50593
50604
  try {
50594
50605
  (0, import_node_fs6.accessSync)(inputPath, import_node_fs6.constants.R_OK);
50595
50606
  } catch {
50596
- throw createDirectiveError(`tv create-external-artifact could not read --path ${inputPath}.`);
50607
+ throw createDirectiveError(`tv ${commandName} could not read --path ${inputPath}.`);
50597
50608
  }
50598
50609
  return inputPath;
50599
50610
  }
@@ -50608,58 +50619,73 @@ function resolveFocusDirective(argv, commandName, focusFlag) {
50608
50619
  return shouldFocus;
50609
50620
  }
50610
50621
  function formatArtifactRemovalResult(result) {
50611
- if (result.outcome === "unlinked") {
50612
- return [` ${result.artifactID}: unlinked (still referenced elsewhere)`];
50613
- }
50614
- if (result.outcome === "discarded") {
50615
- return [` ${result.artifactID}: discarded (pending-create, no committed content)`];
50616
- }
50617
- if ("bundlePath" in result) {
50618
- return [
50619
- ` ${result.artifactID}: trashed`,
50620
- ` metadata: ${result.metadataPath}`,
50621
- ` bundle: ${result.bundlePath}`
50622
- ];
50623
- }
50624
- if ("externalURL" in result) {
50625
- return [
50626
- ` ${result.artifactID}: trashed`,
50627
- ` metadata: ${result.metadataPath}`,
50628
- ` external URL ${result.externalURL} not touched.`
50629
- ];
50622
+ switch (result.outcome) {
50623
+ case "unlinked":
50624
+ return [` ${result.artifactID}: unlinked (still referenced elsewhere)`];
50625
+ case "discarded":
50626
+ return [` ${result.artifactID}: discarded (pending-create web-bundle, no committed content)`];
50627
+ case "trashed": {
50628
+ const kind = result.kind;
50629
+ switch (kind) {
50630
+ case "web-bundle":
50631
+ return [
50632
+ ` ${result.artifactID}: trashed`,
50633
+ ` metadata: ${result.metadataPath}`,
50634
+ ` bundle: ${result.bundlePath}`
50635
+ ];
50636
+ case "url":
50637
+ return [
50638
+ ` ${result.artifactID}: trashed`,
50639
+ ` metadata: ${result.metadataPath}`,
50640
+ ` external URL ${result.externalURL} not touched.`
50641
+ ];
50642
+ case "markdown":
50643
+ return [
50644
+ ` ${result.artifactID}: trashed`,
50645
+ ` metadata: ${result.metadataPath}`,
50646
+ ` external file ${result.externalFilePath} not touched.`
50647
+ ];
50648
+ default:
50649
+ return assertNever3(kind, "artifact removal result kind");
50650
+ }
50651
+ }
50630
50652
  }
50631
- return [
50632
- ` ${result.artifactID}: trashed`,
50633
- ` metadata: ${result.metadataPath}`,
50634
- ` external file ${result.externalFilePath} not touched.`
50635
- ];
50653
+ return assertNever3(result, "artifact removal result");
50636
50654
  }
50637
50655
  function formatDeleteArtifactResult(result) {
50638
- if (result.outcome === "discarded") {
50639
- return [
50640
- `Pending-create artifact ${result.artifactID} discarded.`,
50641
- "No trash paths were written because no committed artifact existed yet."
50642
- ];
50643
- }
50644
- if ("bundlePath" in result) {
50645
- return [
50646
- `Internal artifact ${result.artifactID} moved to trash:`,
50647
- ` metadata: ${result.metadataPath}`,
50648
- ` bundle: ${result.bundlePath}`
50649
- ];
50650
- }
50651
- if ("externalURL" in result) {
50652
- return [
50653
- `URL artifact ${result.artifactID} moved to trash:`,
50654
- ` metadata: ${result.metadataPath}`,
50655
- ` external URL ${result.externalURL} not touched.`
50656
- ];
50656
+ switch (result.outcome) {
50657
+ case "discarded":
50658
+ return [
50659
+ `Pending-create web-bundle artifact ${result.artifactID} discarded.`,
50660
+ "No trash paths were written because no committed artifact existed yet."
50661
+ ];
50662
+ case "trashed": {
50663
+ const kind = result.kind;
50664
+ switch (kind) {
50665
+ case "web-bundle":
50666
+ return [
50667
+ `Web-bundle artifact ${result.artifactID} moved to trash:`,
50668
+ ` metadata: ${result.metadataPath}`,
50669
+ ` bundle: ${result.bundlePath}`
50670
+ ];
50671
+ case "url":
50672
+ return [
50673
+ `URL artifact ${result.artifactID} moved to trash:`,
50674
+ ` metadata: ${result.metadataPath}`,
50675
+ ` external URL ${result.externalURL} not touched.`
50676
+ ];
50677
+ case "markdown":
50678
+ return [
50679
+ `Markdown artifact ${result.artifactID} moved to trash:`,
50680
+ ` metadata: ${result.metadataPath}`,
50681
+ ` external file ${result.externalFilePath} not touched.`
50682
+ ];
50683
+ default:
50684
+ return assertNever3(kind, "delete artifact result kind");
50685
+ }
50686
+ }
50657
50687
  }
50658
- return [
50659
- `External artifact ${result.artifactID} moved to trash:`,
50660
- ` metadata: ${result.metadataPath}`,
50661
- ` external file ${result.externalFilePath} not touched.`
50662
- ];
50688
+ return assertNever3(result, "delete artifact result");
50663
50689
  }
50664
50690
  function formatScreenRemovalResult(result) {
50665
50691
  const header = [
@@ -50676,6 +50702,9 @@ function formatScreenRemovalResult(result) {
50676
50702
  }
50677
50703
  return header;
50678
50704
  }
50705
+ function assertNever3(value, context) {
50706
+ throw new Error(`Unexpected ${context}: ${JSON.stringify(value)}`);
50707
+ }
50679
50708
  function formatCommanderError(argv, error48) {
50680
50709
  const commandName = commandNameFromArgv(argv);
50681
50710
  const optionMatch = /option '([^']+)'/.exec(error48.message);
@@ -50728,7 +50757,8 @@ function createEnvironment(environment) {
50728
50757
  port: options.port,
50729
50758
  staticDir: options.staticDir,
50730
50759
  canonicalDir: options.canonicalDir,
50731
- public: options.public
50760
+ public: options.public,
50761
+ acpProfile: resolveACPAgentProfile(process.env)
50732
50762
  })),
50733
50763
  createDaemon: environment.createDaemon ?? ((options = {}) => {
50734
50764
  const args = [process.argv[1], "serve"];
@@ -50819,93 +50849,86 @@ function createProgram(env, argv = []) {
50819
50849
  });
50820
50850
  });
50821
50851
  });
50822
- program2.command("create-internal-artifact").description(
50823
- "Stage a pending internal artifact bundle that Television owns and renders. Internal artifacts use a staged lifecycle: this command creates the pending bundle on disk, you edit its files (artifact.md, data.json, memory.md, public/index.{md,html}), and you finish with `tv commit-pending-artifact` or discard with `tv abandon-pending-artifact`. The artifact has immediate membership on --screen so the user can see in-progress state. Pick --focus-artifact when the user should be taken to it now, or --no-focus to work in the background."
50824
- ).requiredOption("--screen <id>", "Target screen ID").requiredOption("--type <type>", `Artifact type (${ARTIFACT_TYPES.join(" or ")})`).requiredOption("--title <title>", "Artifact title").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50825
- const shouldFocus = resolveFocusDirective(argv, "create-internal-artifact", "--focus-artifact");
50826
- if (!isArtifactType(opts.type)) {
50827
- throw createDirectiveError(
50828
- `tv create-internal-artifact requires --type to be one of: ${ARTIFACT_TYPES.join(", ")}.`
50829
- );
50830
- }
50852
+ program2.command("create-web-bundle-artifact").description(
50853
+ "Create a Television-managed web bundle artifact. Pending is the default: Television creates a staging bundle on disk, you edit files under it, and you finish with `tv commit-artifact` or `tv abandon-artifact`. Pass --skip-pending to create the committed bundle immediately instead."
50854
+ ).requiredOption("--screen <id>", "Target screen ID").requiredOption("--title <title>", "Artifact title").option("--skip-pending", "Create the committed bundle immediately instead of staging pending work").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50855
+ const shouldFocus = resolveFocusDirective(argv, "create-web-bundle-artifact", "--focus-artifact");
50831
50856
  const client = createAuthenticatedClient(opts.server);
50832
50857
  const result = await client.artifacts.create({
50858
+ kind: "web-bundle",
50833
50859
  title: opts.title,
50834
- type: opts.type,
50860
+ skipPending: opts.skipPending,
50835
50861
  screenID: opts.screen
50836
50862
  });
50837
50863
  if (shouldFocus) {
50838
50864
  await client.display.focus({ artifactID: result.artifact.id, screenID: opts.screen });
50839
50865
  }
50840
- if (!("pendingPath" in result)) {
50841
- throw new Error("Server did not return pendingPath for internal artifact creation");
50866
+ if ("pendingPath" in result) {
50867
+ writeLine(env.stdout, `Pending web bundle artifact ${result.artifact.id} created.`);
50868
+ writeLine(env.stdout, `Edit files in ${result.pendingPath}, then commit with:`);
50869
+ writeLine(env.stdout, ` tv commit-artifact --id ${result.artifact.id}`);
50870
+ return;
50842
50871
  }
50843
- writeLine(env.stdout, `Pending internal artifact ${result.artifact.id} created.`);
50844
- writeLine(env.stdout, `Edit files in ${result.pendingPath}, then commit with:`);
50845
- writeLine(env.stdout, ` tv commit-pending-artifact --id ${result.artifact.id}`);
50872
+ writeLine(env.stdout, `Web bundle artifact ${result.artifact.id} created.`);
50873
+ writeLine(env.stdout, "The committed bundle is ready for direct editing on disk.");
50846
50874
  });
50847
- program2.command("edit-internal-artifact").description(
50848
- "Stage a pending edit against an existing internal artifact. The bundle becomes editable on disk; the live committed version stays in place until you finish. Read artifact.md, data.json, and memory.md before changing anything so you preserve user intent and prior decisions. Finish with `tv commit-pending-artifact`, or discard with `tv abandon-pending-artifact`."
50875
+ program2.command("edit-artifact").description(
50876
+ "Stage a pending edit against an existing committed web bundle artifact. The committed version stays live until you finish. Finish with `tv commit-artifact`, or discard with `tv abandon-artifact`."
50849
50877
  ).requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50850
50878
  const client = createAuthenticatedClient(opts.server);
50851
50879
  const result = await client.artifacts.edit({ artifactID: opts.id });
50852
50880
  writeLine(env.stdout, `Pending edit for artifact ${result.artifact.id} staged.`);
50853
50881
  writeLine(env.stdout, `Edit files in ${result.pendingPath}, then commit with:`);
50854
- writeLine(env.stdout, ` tv commit-pending-artifact --id ${result.artifact.id}`);
50882
+ writeLine(env.stdout, ` tv commit-artifact --id ${result.artifact.id}`);
50855
50883
  });
50856
- program2.command("commit-pending-artifact").description(
50857
- "Validate the pending bundle and, if valid, commit it as the live artifact. Validation enforces required structure: artifact.md must contain all required headings (## User intent, ## Purpose, ## Data shape, ## Data sources, ## Rendering, ## Update workflow, ## Non-goals); data.json must be valid JSON; memory.md must contain ## Activity Log and a fresh UTC timestamp (YYYY-MM-DDTHH:MM:SSZ); public/index.{md,html} must be non-empty and match the artifact type. The CLI prints a directive when validation fails \u2014 fix the bundle and retry."
50884
+ program2.command("commit-artifact").description(
50885
+ "Commit a pending web bundle artifact, promoting the pending bundle to the live committed artifact."
50858
50886
  ).requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50859
50887
  const client = createAuthenticatedClient(opts.server);
50860
50888
  const { artifact } = await client.artifacts.commitPending({ artifactID: opts.id });
50861
50889
  writeLine(env.stdout, `Artifact ${artifact.id} committed.`);
50862
50890
  });
50863
- program2.command("abandon-pending-artifact").description(
50864
- "Discard pending work without committing. For a pending create, the artifact never becomes live and the bundle is removed. For a pending edit, the live committed version is kept unchanged. Use this when the staged work should not ship."
50891
+ program2.command("abandon-artifact").description(
50892
+ "Discard pending web bundle work without committing. For a pending create, the artifact never becomes live. For a pending edit, the committed version stays unchanged."
50865
50893
  ).requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(false)).action(async (opts) => {
50866
50894
  const client = createAuthenticatedClient(opts.server);
50867
50895
  await client.artifacts.abandonPending({ artifactID: opts.id });
50868
50896
  writeLine(env.stdout, `Pending operation on artifact ${opts.id} abandoned.`);
50869
50897
  });
50870
- program2.command("create-external-artifact").description(
50871
- "Create an artifact that points at an existing file on disk. Television displays the file and watches it for changes, but does not own it: there is no bundle, no pending lifecycle, and `tv delete-artifact` only forgets the pointer \u2014 it never deletes the underlying file. --path must be absolute, the file must already exist and be readable, and its extension must match --type. Use this when the file already exists outside Television; use create-internal-artifact when Television should own a maintainable bundle."
50872
- ).requiredOption("--screen <id>", "Target screen ID").requiredOption("--type <type>", `Artifact type (${ARTIFACT_TYPES.join(" or ")})`).requiredOption("--title <title>", "Artifact title").requiredOption("--path <path>", "Absolute path to an existing content file").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50873
- const shouldFocus = resolveFocusDirective(argv, "create-external-artifact", "--focus-artifact");
50874
- if (!isArtifactType(opts.type)) {
50875
- throw createDirectiveError(
50876
- `tv create-external-artifact requires --type to be one of: ${ARTIFACT_TYPES.join(", ")}.`
50877
- );
50878
- }
50879
- const externalPath = validateExternalArtifactPath(opts.path);
50898
+ program2.command("create-markdown-artifact").description(
50899
+ "Create a committed markdown artifact that points at an existing markdown file on disk. Television displays the file and watches it for changes, but does not own it."
50900
+ ).requiredOption("--screen <id>", "Target screen ID").requiredOption("--title <title>", "Artifact title").requiredOption("--path <path>", "Absolute path to an existing markdown file").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50901
+ const shouldFocus = resolveFocusDirective(argv, "create-markdown-artifact", "--focus-artifact");
50902
+ const externalPath = validateExternalArtifactPath("create-markdown-artifact", opts.path);
50880
50903
  if (isInTempDirectory(externalPath)) {
50881
50904
  writeLine(
50882
50905
  env.stderr,
50883
50906
  `Warning: artifact path is in a temp directory (${externalPath}).
50884
- If you wish to display temporary content to the user, use an internal artifact instead.`
50907
+ If you wish to display temporary content to the user, use a web bundle artifact instead.`
50885
50908
  );
50886
50909
  }
50887
50910
  const client = createAuthenticatedClient(opts.server);
50888
50911
  const { artifact } = await client.artifacts.create({
50912
+ kind: "markdown",
50889
50913
  externalFilePath: externalPath,
50890
50914
  title: opts.title,
50891
- type: opts.type,
50892
50915
  screenID: opts.screen
50893
50916
  });
50894
50917
  if (shouldFocus) {
50895
50918
  await client.display.focus({ artifactID: artifact.id, screenID: opts.screen });
50896
50919
  }
50897
- writeLine(env.stdout, `External artifact ${artifact.id} created.`);
50920
+ writeLine(env.stdout, `Markdown artifact ${artifact.id} created.`);
50898
50921
  writeLine(env.stdout, `Television will display content from ${externalPath} and watch it for changes.`);
50899
50922
  });
50900
50923
  program2.command("create-url-artifact").description(
50901
- "Create an artifact that embeds a live http(s) page. Type is always text/html \u2014 do not pass --type. URL artifacts commit immediately and have no pending lifecycle. The page renders in a sandboxed <webview> in the desktop app and an <iframe> in the browser; some sites block embedding via X-Frame-Options or CSP, in which case the page may show as blank. Use this for live dashboards, docs, or third-party pages; use create-internal-artifact when the content should be Television-owned and durable."
50924
+ "Create an artifact that embeds a live http(s) page. URL artifacts commit immediately and have no pending lifecycle. The page renders in a sandboxed <webview> in the desktop app and an <iframe> in the browser; some sites block embedding via X-Frame-Options or CSP, in which case the page may show as blank."
50902
50925
  ).requiredOption("--screen <id>", "Target screen ID").requiredOption("--title <title>", "Artifact title").requiredOption("--url <url>", "http(s) URL of the page to embed").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50903
50926
  const shouldFocus = resolveFocusDirective(argv, "create-url-artifact", "--focus-artifact");
50904
50927
  const client = createAuthenticatedClient(opts.server);
50905
50928
  const { artifact } = await client.artifacts.create({
50929
+ kind: "url",
50906
50930
  externalURL: opts.url,
50907
50931
  title: opts.title,
50908
- type: "text/html",
50909
50932
  screenID: opts.screen
50910
50933
  });
50911
50934
  if (shouldFocus) {
@@ -50918,7 +50941,7 @@ If you wish to display temporary content to the user, use an internal artifact i
50918
50941
  writeJSON(env.stdout, { storagePath: getTelevisionStoragePath() });
50919
50942
  });
50920
50943
  program2.command("update-artifact").description(
50921
- "Update artifact metadata in place. Currently only --title is changeable; ID and type are immutable. This does not modify bundle content \u2014 for content changes on internal artifacts, use `tv edit-internal-artifact` and the pending-edit lifecycle."
50944
+ "Update artifact metadata in place. Currently only --title is changeable; ID and kind/type are immutable. This does not modify bundle content \u2014 for content changes on committed web bundle artifacts, use `tv edit-artifact` and the pending-edit lifecycle."
50922
50945
  ).requiredOption("--id <id>", "Artifact ID").requiredOption("--title <title>", "New title").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50923
50946
  const client = createAuthenticatedClient(opts.server);
50924
50947
  await client.artifacts.update({ artifactID: opts.id, title: opts.title });
@@ -50933,14 +50956,8 @@ If you wish to display temporary content to the user, use an internal artifact i
50933
50956
  writeLine(env.stdout, `Artifact ${result.artifactID} detached from screen ${result.screenID}.`);
50934
50957
  };
50935
50958
  program2.command("detach-artifact").description(
50936
- "Remove the artifact's card from one screen's layout. The artifact itself is never touched: its metadata, its bundle (if internal), and its pointer (if external/url) all stay intact. Detaching the last screen reference does NOT delete the artifact \u2014 it just leaves it unplaced. Use `tv delete-artifact` when the user wants global removal, and `tv list-artifacts --unplaced` to find unplaced artifacts."
50959
+ "Remove the artifact's card from one screen's layout. The artifact itself is never touched: its metadata, its web-bundle bytes, or its markdown/URL pointer all stay intact. Detaching the last screen reference does NOT delete the artifact \u2014 it just leaves it unplaced. Use `tv delete-artifact` when the user wants global removal, and `tv list-artifacts --unplaced` to find unplaced artifacts."
50937
50960
  ).requiredOption("--id <id>", "Artifact ID").requiredOption("--screen <id>", "Screen ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(detachAction);
50938
- program2.command("remove-artifact", { hidden: true }).description("Deprecated alias for detach-artifact").requiredOption("--id <id>", "Artifact ID").requiredOption("--screen <id>", "Screen ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50939
- env.stderr.write(
50940
- "Deprecated: 'tv remove-artifact' has been renamed to 'tv detach-artifact'. Note: detach no longer trashes the artifact even on the last reference; use 'tv delete-artifact' to remove an artifact globally.\n"
50941
- );
50942
- await detachAction(opts);
50943
- });
50944
50961
  program2.command("attach-artifact").description(
50945
50962
  "Attach an existing artifact to a screen by appending a default-sized card to the right end of the screen's strip. Idempotent: attaching an artifact that is already on this screen is a no-op (existing card position is preserved). An artifact may belong to multiple screens simultaneously; attach does not move it, it adds another reference. Pick --focus-artifact when the user should be taken to the new card now, or --no-focus to attach in the background."
50946
50963
  ).requiredOption("--id <id>", "Artifact ID").requiredOption("--screen <id>", "Screen ID").option("--focus-artifact", "Focus the artifact after attaching it").option("--no-focus", "Attach the artifact in the background without changing focus").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
@@ -50959,7 +50976,7 @@ If you wish to display temporary content to the user, use an internal artifact i
50959
50976
  );
50960
50977
  });
50961
50978
  program2.command("delete-artifact").description(
50962
- "Globally remove an artifact. Detaches it from every screen it appears on, then: trashes the bundle (internal), forgets the pointer (external \u2014 the underlying file is untouched), drops the URL reference (URL), or discards pending-create work. This is the only path to global removal \u2014 `tv detach-artifact` only removes one screen-level card and leaves the artifact otherwise intact. There is no restore-from-trash workflow; treat delete as terminal."
50979
+ "Globally remove an artifact. Detaches it from every screen it appears on, then: trashes the bundle (web-bundle), forgets the markdown file pointer without touching the file, forgets the URL pointer without touching the remote page, or discards pending-create work. This is the only path to global removal \u2014 `tv detach-artifact` only removes one screen-level card and leaves the artifact otherwise intact. There is no restore-from-trash workflow; treat delete as terminal."
50963
50980
  ).requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50964
50981
  const client = createAuthenticatedClient(opts.server);
50965
50982
  const result = await client.artifacts.delete({ artifactID: opts.id });
@@ -51005,10 +51022,10 @@ If you wish to display temporary content to the user, use an internal artifact i
51005
51022
  writeJSON(env.stdout, await client.screens.list());
51006
51023
  });
51007
51024
  program2.command("get-screen").description(
51008
- "Get a screen and its artifacts as JSON. Includes each artifact's kind (internal/external/url) and status (pending/committed) so you can plan follow-up actions without a second call. With no --id, auto-selects when exactly one screen exists; otherwise --id is required."
51009
- ).option("--id <id>", "Screen ID (auto-selects if only one exists)").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
51025
+ "Get a screen and its artifacts as JSON. Includes each artifact's kind (markdown/web-bundle/url) and status (pending/committed) so you can plan follow-up actions without a second call. With no --screen, auto-selects when exactly one screen exists; otherwise --screen is required."
51026
+ ).option("--screen <id>", "Screen ID (auto-selects if only one exists)").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
51010
51027
  const client = createAuthenticatedClient(opts.server);
51011
- writeJSON(env.stdout, await client.screens.get({ screenID: opts.id }));
51028
+ writeJSON(env.stdout, await client.screens.get({ screenID: opts.screen }));
51012
51029
  });
51013
51030
  program2.command("focus-status").description(
51014
51031
  "Print display state as JSON: the active (persistently focused) screen ID and the active theme name."
@@ -51050,45 +51067,38 @@ If you wish to display temporary content to the user, use an internal artifact i
51050
51067
  const skillsCommand = program2.command("skills").description("Manage the bundled Television skills").addHelpText("after", [
51051
51068
  "",
51052
51069
  "This command group manages the bundled Television skill collection.",
51053
- "`tv skills install` installs or reinstalls every bundled Television skill globally through the `skills` package.",
51054
- "`tv skills show` lists each bundled skill or prints one SKILL.md directly, which is useful for agents that need the content without running the interactive installer."
51070
+ "`tv skills install <path>` copies the bundled Television skills into the destination agent skills folder.",
51071
+ "Examples: ~/.openclaw/skills, ~/.hermes/skills, ~/.agents/skills.",
51072
+ "`tv skills install -i` runs the external skills installer against Television's bundled skills root."
51055
51073
  ].join("\n"));
51056
- skillsCommand.command("path").description("Print the bundled Television skills collection root").action(() => {
51074
+ skillsCommand.command("install").description("Copy bundled Television skills into an agent harness skills folder, or run the external installer against the bundled skills root").argument("[path]", "Destination agent skills folder (for example: ~/.openclaw/skills, ~/.hermes/skills, ~/.agents/skills)").option("-i, --interactive", "Run the external skills installer against the bundled Television skills root").allowUnknownOption(true).action(async function(inputPath, opts) {
51057
51075
  const bundledTelevisionSkillsCollectionRoot = env.resolveBundledSkillsRoot();
51058
- if (!bundledTelevisionSkillsCollectionRoot) {
51059
- throw new Error("Could not resolve the bundled Television skills root.");
51076
+ if (opts.interactive) {
51077
+ if (inputPath) {
51078
+ throw createDirectiveError("tv skills install -i does not take a destination path.");
51079
+ }
51080
+ if (!bundledTelevisionSkillsCollectionRoot) {
51081
+ throw new Error("Could not resolve the bundled Television skills root.");
51082
+ }
51083
+ const args = ["add", bundledTelevisionSkillsCollectionRoot];
51084
+ await env.runSkillsInstaller(args);
51085
+ return;
51060
51086
  }
51061
- writeLine(env.stdout, bundledTelevisionSkillsCollectionRoot);
51062
- });
51063
- skillsCommand.command("install").description("Install or reinstall all bundled Television skills globally").allowUnknownOption(true).action(async function() {
51064
- const bundledTelevisionSkillsCollectionRoot = env.resolveBundledSkillsRoot();
51065
- if (!bundledTelevisionSkillsCollectionRoot) {
51066
- throw new Error("Could not resolve the bundled Television skills root.");
51087
+ if (!inputPath) {
51088
+ throw createDirectiveError(
51089
+ "tv skills install requires a destination agent skills folder, or -i for interactive."
51090
+ );
51067
51091
  }
51068
- await env.runSkillsInstaller(["add", bundledTelevisionSkillsCollectionRoot, "--global"]);
51069
- });
51070
- skillsCommand.command("show").description("List bundled Television skills or print one SKILL.md by name").argument("[skill]", "Bundled skill name").action((skillName) => {
51071
- const bundledTelevisionSkillsCollectionRoot = env.resolveBundledSkillsRoot();
51072
51092
  if (!bundledTelevisionSkillsCollectionRoot) {
51073
51093
  throw new Error("Could not resolve the bundled Television skills root.");
51074
51094
  }
51075
- if (skillName === void 0) {
51076
- const bundledSkills = listBundledSkills(bundledTelevisionSkillsCollectionRoot);
51077
- bundledSkills.forEach((skill2, index) => {
51078
- writeLine(env.stdout, `${skill2.name} \u2014 ${skill2.description}`);
51079
- writeLine(env.stdout, ` ${skill2.root}`);
51080
- if (index < bundledSkills.length - 1) {
51081
- writeLine(env.stdout, "");
51082
- }
51083
- });
51084
- return;
51085
- }
51086
- const skill = resolveBundledSkill(bundledTelevisionSkillsCollectionRoot, skillName);
51087
- writeLine(env.stdout, `Path: ${skill.root}`);
51088
- writeLine(env.stdout, "---");
51089
- env.stdout.write(skill.markdown);
51090
- if (!skill.markdown.endsWith("\n")) {
51091
- writeLine(env.stdout, "");
51095
+ const destinationRoot = import_node_path10.default.resolve(inputPath);
51096
+ const copied = copyBundledSkillsToDestination(bundledTelevisionSkillsCollectionRoot, destinationRoot);
51097
+ writeLine(env.stdout, `Copied ${copied.length} bundled Television skill(s):`);
51098
+ for (const skill of copied) {
51099
+ writeLine(env.stdout, `- ${skill.name}`);
51100
+ writeLine(env.stdout, ` from: ${skill.sourcePath}`);
51101
+ writeLine(env.stdout, ` to: ${skill.destinationPath}`);
51092
51102
  }
51093
51103
  });
51094
51104
  program2.command("stop").description("Stop the Television system service").action(async () => {