@telepath-computer/television 0.1.80 → 0.1.82

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
@@ -50093,7 +50093,7 @@ function findCardIDInNode(node, artifactID) {
50093
50093
  var import_meta = {};
50094
50094
  var DAEMON_NAME = "com.television.server";
50095
50095
  var MAX_PORT = 65535;
50096
- var HELP_POINTER = "Load/read the installed `television` skill. Start with `what-to-read.md`. If the skill is not installed, you can use `tv skills install` (interactive, not agent-friendly) or `tv skills show` (agent-friendly, prints bundled skill files directly). If this is artifact lifecycle work, read `artifact-workflow.md`. If this is HTML artifact work, also read `html-house-style.md` and the relevant `artifact-types/*.md` companion doc.";
50096
+ var HELP_POINTER = "Load/read the installed `television` skill. Start with `SKILL.md` for the mental model and the artifact-vs-chat decision. If the skill is not installed, you can use `tv skills install` (interactive, not agent-friendly) or `tv skills show` (agent-friendly, prints bundled skill files directly). For CLI commands, focus, and screen placement, read `cli-capabilities.md`. For artifact lifecycle work, read `artifact-workflow.md`. For HTML artifact work, also read `html-house-style.md` and the relevant `artifact-types/*.md` companion doc.";
50097
50097
  var CLIDirectiveError = class extends Error {
50098
50098
  name = "CLIDirectiveError";
50099
50099
  };
@@ -50123,8 +50123,8 @@ function resolveVercelSkillsInstallerBin() {
50123
50123
  return localRequire.resolve("skills/bin/cli.mjs");
50124
50124
  }
50125
50125
  function readCLIVersion() {
50126
- if ("0.1.80".length > 0) {
50127
- return "0.1.80";
50126
+ if ("0.1.82".length > 0) {
50127
+ return "0.1.82";
50128
50128
  }
50129
50129
  const devPackageJsonPath = import_node_path7.default.join(getDevPackageDir(), "package.json");
50130
50130
  if (!(0, import_node_fs4.existsSync)(devPackageJsonPath)) {
@@ -50151,7 +50151,8 @@ function buildAgentHelpNote() {
50151
50151
  "",
50152
50152
  "Agent workflow note:",
50153
50153
  " Load/read the installed `television` skill before Television work.",
50154
- " Then read `what-to-read.md` to choose the right companion docs.",
50154
+ " `SKILL.md` carries the mental model and the artifact-vs-chat decision.",
50155
+ " `cli-capabilities.md` covers commands, focus, and screen placement.",
50155
50156
  " For artifact lifecycle work, read `artifact-workflow.md`.",
50156
50157
  " For HTML artifacts, also read `html-house-style.md`.",
50157
50158
  " Known artifact-type docs:",
@@ -50175,6 +50176,28 @@ function buildArtifactWorkflowHelpNote(includeHTMLNote) {
50175
50176
  function getBundledTelevisionSkillRoot(bundledTelevisionSkillsCollectionRoot) {
50176
50177
  return import_node_path7.default.join(bundledTelevisionSkillsCollectionRoot, "television");
50177
50178
  }
50179
+ function readSkillManifest(skillRoot) {
50180
+ const empty = { version: void 0, descriptions: /* @__PURE__ */ new Map() };
50181
+ const manifestPath = import_node_path7.default.join(skillRoot, ".skill-manifest.json");
50182
+ if (!(0, import_node_fs4.existsSync)(manifestPath)) return empty;
50183
+ try {
50184
+ const raw = (0, import_node_fs4.readFileSync)(manifestPath, "utf8");
50185
+ const parsed = JSON.parse(raw);
50186
+ if (typeof parsed !== "object" || parsed === null) return empty;
50187
+ const root = parsed;
50188
+ const version2 = typeof root.version === "string" ? root.version : void 0;
50189
+ const files = Array.isArray(root.files) ? root.files : [];
50190
+ const entries = [];
50191
+ for (const entry of files) {
50192
+ if (typeof entry === "object" && entry !== null && typeof entry.path === "string" && typeof entry.description === "string") {
50193
+ entries.push([entry.path, entry.description]);
50194
+ }
50195
+ }
50196
+ return { version: version2, descriptions: new Map(entries) };
50197
+ } catch {
50198
+ return empty;
50199
+ }
50200
+ }
50178
50201
  function listSkillFiles(skillRoot, prefix = "") {
50179
50202
  const entries = (0, import_node_fs4.readdirSync)(skillRoot, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".")).sort((a, b) => a.name.localeCompare(b.name));
50180
50203
  const files = [];
@@ -50505,7 +50528,9 @@ function createProgram(env, argv = []) {
50505
50528
  });
50506
50529
  });
50507
50530
  });
50508
- program2.command("create-internal-artifact").description("Create a pending internal Television-managed artifact bundle").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) => {
50531
+ program2.command("create-internal-artifact").description(
50532
+ "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."
50533
+ ).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) => {
50509
50534
  const shouldFocus = resolveFocusDirective(argv, "create-internal-artifact", "--focus-artifact");
50510
50535
  if (!isArtifactType(opts.type)) {
50511
50536
  throw createDirectiveError(
@@ -50528,24 +50553,32 @@ function createProgram(env, argv = []) {
50528
50553
  writeLine(env.stdout, `Edit files in ${result.pendingPath}, then commit with:`);
50529
50554
  writeLine(env.stdout, ` tv commit-pending-artifact --id ${result.artifact.id}`);
50530
50555
  });
50531
- program2.command("edit-internal-artifact").description("Stage a pending edit for an internal artifact").requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50556
+ program2.command("edit-internal-artifact").description(
50557
+ "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`."
50558
+ ).requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50532
50559
  const client = createAuthenticatedClient(opts.server);
50533
50560
  const result = await client.artifacts.edit({ artifactID: opts.id });
50534
50561
  writeLine(env.stdout, `Pending edit for artifact ${result.artifact.id} staged.`);
50535
50562
  writeLine(env.stdout, `Edit files in ${result.pendingPath}, then commit with:`);
50536
50563
  writeLine(env.stdout, ` tv commit-pending-artifact --id ${result.artifact.id}`);
50537
50564
  });
50538
- program2.command("commit-pending-artifact").description("Validate and commit a pending internal artifact bundle").requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50565
+ program2.command("commit-pending-artifact").description(
50566
+ "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."
50567
+ ).requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
50539
50568
  const client = createAuthenticatedClient(opts.server);
50540
50569
  const { artifact } = await client.artifacts.commitPending({ artifactID: opts.id });
50541
50570
  writeLine(env.stdout, `Artifact ${artifact.id} committed.`);
50542
50571
  });
50543
- program2.command("abandon-pending-artifact").description("Discard a pending internal artifact create or edit").requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(false)).action(async (opts) => {
50572
+ program2.command("abandon-pending-artifact").description(
50573
+ "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."
50574
+ ).requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).addHelpText("after", buildArtifactWorkflowHelpNote(false)).action(async (opts) => {
50544
50575
  const client = createAuthenticatedClient(opts.server);
50545
50576
  await client.artifacts.abandonPending({ artifactID: opts.id });
50546
50577
  writeLine(env.stdout, `Pending operation on artifact ${opts.id} abandoned.`);
50547
50578
  });
50548
- program2.command("create-external-artifact").description("Create an external artifact that points at an existing file").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) => {
50579
+ program2.command("create-external-artifact").description(
50580
+ "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."
50581
+ ).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) => {
50549
50582
  const shouldFocus = resolveFocusDirective(argv, "create-external-artifact", "--focus-artifact");
50550
50583
  if (!isArtifactType(opts.type)) {
50551
50584
  throw createDirectiveError(
@@ -50573,7 +50606,9 @@ If you wish to display temporary content to the user, use an internal artifact i
50573
50606
  writeLine(env.stdout, `External artifact ${artifact.id} created.`);
50574
50607
  writeLine(env.stdout, `Television will display content from ${externalPath} and watch it for changes.`);
50575
50608
  });
50576
- program2.command("create-url-artifact").description("Create a URL-backed artifact that embeds an external page").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) => {
50609
+ program2.command("create-url-artifact").description(
50610
+ "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."
50611
+ ).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) => {
50577
50612
  const shouldFocus = resolveFocusDirective(argv, "create-url-artifact", "--focus-artifact");
50578
50613
  const client = createAuthenticatedClient(opts.server);
50579
50614
  const { artifact } = await client.artifacts.create({
@@ -50591,7 +50626,9 @@ If you wish to display temporary content to the user, use an internal artifact i
50591
50626
  program2.command("storage-path").description("Print the Television storage path").action(() => {
50592
50627
  writeJSON(env.stdout, { storagePath: getTelevisionStoragePath() });
50593
50628
  });
50594
- program2.command("update-artifact").description("Update artifact metadata in place (artifact ID and type stay the same)").requiredOption("--id <id>", "Artifact ID").requiredOption("--title <title>", "New title").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50629
+ program2.command("update-artifact").description(
50630
+ "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."
50631
+ ).requiredOption("--id <id>", "Artifact ID").requiredOption("--title <title>", "New title").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50595
50632
  const client = createAuthenticatedClient(opts.server);
50596
50633
  await client.artifacts.update({ artifactID: opts.id, title: opts.title });
50597
50634
  writeLine(env.stdout, `Artifact ${opts.id} updated.`);
@@ -50604,14 +50641,18 @@ If you wish to display temporary content to the user, use an internal artifact i
50604
50641
  });
50605
50642
  writeLine(env.stdout, `Artifact ${result.artifactID} detached from screen ${result.screenID}.`);
50606
50643
  };
50607
- program2.command("detach-artifact").description("Remove an artifact card from a screen's layout (artifact metadata is preserved)").requiredOption("--id <id>", "Artifact ID").requiredOption("--screen <id>", "Screen ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(detachAction);
50644
+ program2.command("detach-artifact").description(
50645
+ "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."
50646
+ ).requiredOption("--id <id>", "Artifact ID").requiredOption("--screen <id>", "Screen ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(detachAction);
50608
50647
  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) => {
50609
50648
  env.stderr.write(
50610
50649
  "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"
50611
50650
  );
50612
50651
  await detachAction(opts);
50613
50652
  });
50614
- program2.command("attach-artifact").description("Attach an existing artifact to a screen by appending a card to the strip end").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) => {
50653
+ program2.command("attach-artifact").description(
50654
+ "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."
50655
+ ).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) => {
50615
50656
  const shouldFocus = resolveFocusDirective(argv, "attach-artifact", "--focus-artifact");
50616
50657
  const client = createAuthenticatedClient(opts.server);
50617
50658
  const result = await client.artifacts.attach({
@@ -50626,7 +50667,9 @@ If you wish to display temporary content to the user, use an internal artifact i
50626
50667
  `Artifact ${result.artifact.id} attached to screen ${result.screenID} as card ${result.cardID}.`
50627
50668
  );
50628
50669
  });
50629
- program2.command("delete-artifact").description("Globally delete an artifact: detach from every screen, trash the bundle / forget the pointer").requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50670
+ program2.command("delete-artifact").description(
50671
+ "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."
50672
+ ).requiredOption("--id <id>", "Artifact ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50630
50673
  const client = createAuthenticatedClient(opts.server);
50631
50674
  const result = await client.artifacts.delete({ artifactID: opts.id });
50632
50675
  for (const line of formatDeleteArtifactResult(result)) {
@@ -50637,14 +50680,18 @@ If you wish to display temporary content to the user, use an internal artifact i
50637
50680
  const client = createAuthenticatedClient(opts.server);
50638
50681
  writeJSON(env.stdout, await client.artifacts.get({ artifactID: opts.id }));
50639
50682
  });
50640
- program2.command("list-artifacts").description("List artifacts. With no filter, every artifact is returned").option("--screen <id>", "Filter to artifacts attached to this screen").option("--unplaced", "Only return artifacts attached to no screen").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50683
+ program2.command("list-artifacts").description(
50684
+ "List artifacts as JSON. With no filter, every artifact is returned. --screen <id> filters to artifacts attached to that screen. --unplaced returns only artifacts attached to no screen (use this to find orphans left after detaches). The two filters are mutually exclusive in practice."
50685
+ ).option("--screen <id>", "Filter to artifacts attached to this screen").option("--unplaced", "Only return artifacts attached to no screen").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50641
50686
  const client = createAuthenticatedClient(opts.server);
50642
50687
  const filter = {};
50643
50688
  if (opts.screen !== void 0) filter.screenID = opts.screen;
50644
50689
  if (opts.unplaced) filter.unplaced = true;
50645
50690
  writeJSON(env.stdout, await client.artifacts.list(filter));
50646
50691
  });
50647
- program2.command("create-screen").description("Create a new screen").requiredOption("--name <name>", "Screen name").option("--focus-screen", "Focus the new screen after creating it").option("--no-focus", "Create the screen in the background without changing focus").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50692
+ program2.command("create-screen").description(
50693
+ "Create a new screen. Pick --focus-screen when the user should be taken to the new screen immediately (creating a workspace they're about to use), or --no-focus to create it in the background without disturbing their current view. A new screen starts empty; attach existing artifacts with `tv attach-artifact` or create new ones with the create-*-artifact commands using --screen."
50694
+ ).requiredOption("--name <name>", "Screen name").option("--focus-screen", "Focus the new screen after creating it").option("--no-focus", "Create the screen in the background without changing focus").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50648
50695
  const shouldFocus = resolveFocusDirective(argv, "create-screen", "--focus-screen");
50649
50696
  const client = createAuthenticatedClient(opts.server);
50650
50697
  const { screen } = await client.screens.create({ name: opts.name });
@@ -50653,7 +50700,9 @@ If you wish to display temporary content to the user, use an internal artifact i
50653
50700
  }
50654
50701
  writeLine(env.stdout, `Screen created: ${screen.id} (${screen.name})`);
50655
50702
  });
50656
- program2.command("remove-screen").description("Move a screen to trash and cascade artifact removals").requiredOption("--id <id>", "Screen ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50703
+ program2.command("remove-screen").description(
50704
+ "Move a screen to trash. Cascades to its artifacts: each artifact is detached from this screen, and any artifact that loses its last screen reference as a result is left unplaced (not deleted). Use `tv list-artifacts --unplaced` afterward if you need to find or clean up orphans, and `tv delete-artifact` to remove them globally."
50705
+ ).requiredOption("--id <id>", "Screen ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50657
50706
  const client = createAuthenticatedClient(opts.server);
50658
50707
  const result = await client.screens.remove({ screenID: opts.id });
50659
50708
  for (const line of formatScreenRemovalResult(result)) {
@@ -50664,20 +50713,28 @@ If you wish to display temporary content to the user, use an internal artifact i
50664
50713
  const client = createAuthenticatedClient(opts.server);
50665
50714
  writeJSON(env.stdout, await client.screens.list());
50666
50715
  });
50667
- program2.command("get-screen").description("Get a screen and its artifacts").option("--id <id>", "Screen ID (auto-selects if only one exists)").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50716
+ program2.command("get-screen").description(
50717
+ "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."
50718
+ ).option("--id <id>", "Screen ID (auto-selects if only one exists)").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50668
50719
  const client = createAuthenticatedClient(opts.server);
50669
50720
  writeJSON(env.stdout, await client.screens.get({ screenID: opts.id }));
50670
50721
  });
50671
- program2.command("focus-status").description("Print focus state: active screen ID and connected client count").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50722
+ program2.command("focus-status").description(
50723
+ "Print focus state as JSON: the active (persistently focused) screen ID and the count of currently connected GUI clients. Useful for verifying that an upcoming focus event will actually be visible to anyone \u2014 if connectedClients is 0, focus-screen and focus-artifact are no-ops from the user's perspective."
50724
+ ).option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50672
50725
  const client = createAuthenticatedClient(opts.server);
50673
50726
  writeJSON(env.stdout, await client.viewer.state());
50674
50727
  });
50675
- program2.command("focus-screen").description("Focus a screen persistently; broadcast to connected clients").requiredOption("--id <id>", "Screen ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50728
+ program2.command("focus-screen").description(
50729
+ "Set persistent screen focus. The change is broadcast to every connected GUI client and survives reconnects. This is the right command when the user wants to switch to a different screen and stay there. For just nudging attention to a specific artifact (which may live on the current screen), prefer `tv focus-artifact`."
50730
+ ).requiredOption("--id <id>", "Screen ID").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50676
50731
  const client = createAuthenticatedClient(opts.server);
50677
50732
  await client.viewer.setActive({ screenID: opts.id });
50678
50733
  writeLine(env.stdout, `Focused screen ${opts.id}.`);
50679
50734
  });
50680
- program2.command("focus-artifact").description("Nudge connected clients to scroll an artifact into view and play a brief highlight").requiredOption("--id <id>", "Artifact ID").option("--screen <id>", "Screen ID (optional; server picks a screen the artifact is on)").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50735
+ program2.command("focus-artifact").description(
50736
+ "Send a transient focus nudge for a specific artifact. Connected clients switch to the artifact's screen if they're not already on it, scroll the artifact's card into view, and play a brief highlight animation. This is NOT persisted as state \u2014 there is no concept of a 'focused artifact' that survives reconnects (the focused screen is persistent, but artifact focus is a one-shot event). Pass --screen <id> to pin which screen the nudge targets, otherwise the server picks one (preferring the active screen when the artifact is there)."
50737
+ ).requiredOption("--id <id>", "Artifact ID").option("--screen <id>", "Screen ID (optional; server picks a screen the artifact is on)").option("--server <url>", "Server URL", DEFAULT_SERVER_URL).action(async (opts) => {
50681
50738
  const client = createAuthenticatedClient(opts.server);
50682
50739
  const { connectedClients } = await client.viewer.state();
50683
50740
  if (connectedClients === 0) {
@@ -50720,9 +50777,20 @@ If you wish to display temporary content to the user, use an internal artifact i
50720
50777
  throw new Error(`Could not resolve the bundled television skill root at ${skillRoot}.`);
50721
50778
  }
50722
50779
  if (relativePath === void 0) {
50780
+ const manifest = readSkillManifest(skillRoot);
50781
+ if (manifest.version !== void 0) {
50782
+ writeLine(env.stdout, `Television skill content version: ${manifest.version}`);
50783
+ writeLine(env.stdout, "");
50784
+ }
50785
+ writeLine(env.stdout, "Relative paths inside the bundled `television` skill:");
50786
+ writeLine(env.stdout, "");
50723
50787
  for (const file2 of listSkillFiles(skillRoot)) {
50724
- writeLine(env.stdout, file2);
50788
+ const description = manifest.descriptions.get(file2);
50789
+ writeLine(env.stdout, description ? `${file2} \u2014 ${description}` : file2);
50725
50790
  }
50791
+ writeLine(env.stdout, "");
50792
+ writeLine(env.stdout, `Television skill root: ${skillRoot}`);
50793
+ writeLine(env.stdout, "To read one file directly: tv skills show <relative-path>");
50726
50794
  return;
50727
50795
  }
50728
50796
  const filepath = resolveSkillFilePath(skillRoot, relativePath);
@@ -0,0 +1,29 @@
1
+ {
2
+ "version": "0.1.10",
3
+ "files": [
4
+ {
5
+ "path": "SKILL.md",
6
+ "description": "Mental model, when to use Television vs chat, and routing to companion docs. Always read first."
7
+ },
8
+ {
9
+ "path": "cli-capabilities.md",
10
+ "description": "The `tv` CLI command surface plus the complete focus story — model, required decisions, theory of mind, phrase cues, screen placement, and the `--no-focus` communication rule."
11
+ },
12
+ {
13
+ "path": "artifact-workflow.md",
14
+ "description": "Artifact lifecycle work — markdown vs HTML, prescriptive recipes for create/update/modify/abandon/external/url, internal bundle structure and validation, in-flight narration style, and the quality bar."
15
+ },
16
+ {
17
+ "path": "html-house-style.md",
18
+ "description": "House style and conventions for authoring HTML artifacts that render inside Television."
19
+ },
20
+ {
21
+ "path": "artifact-types/calendar.md",
22
+ "description": "Authoring conventions for the calendar artifact kind."
23
+ },
24
+ {
25
+ "path": "artifact-types/table.md",
26
+ "description": "Authoring conventions for the record-table artifact kind: dense, Airtable-style tables of records."
27
+ }
28
+ ]
29
+ }
@@ -1,23 +1,93 @@
1
1
  ---
2
2
  name: television
3
- description: Television agent guidance and routing for screen and artifact workflows
3
+ description: Mental model, when to use Television vs chat, and routing to companion docs. Always read first.
4
+ version: 0.1.10
4
5
  ---
5
6
 
7
+
6
8
  # Television
7
9
 
10
+ *Skill content version: 0.1.10*
11
+
8
12
  Television is a persistent artifact screen for agents.
9
13
 
10
14
  Load this skill when you need to create, update, inspect, attach, focus, or otherwise manage Television screens and artifacts.
11
15
 
12
- This skill is modular. Do not assume `SKILL.md` contains every rule you need.
16
+ ## When to use Television
17
+
18
+ Prefer Television when it is a better presentation surface than chat. When a response would otherwise be long, structured, visual, research-heavy, or significantly clearer as markdown, HTML, or a table, prefer creating a Television artifact as the primary response instead of delivering the full result in chat.
19
+
20
+ The chat sidebar is good for short, conversational replies. It is bad for results the user will want to scan, compare, revisit, or treat as a working surface. Long structured output crammed into chat is harder to read and easy to lose.
21
+
22
+ Use judgment. Do not create an artifact for every response, but do prefer one when:
23
+
24
+ - the result is lengthy text or deep analysis
25
+ - the result is a table or other structured comparison
26
+ - the result is research output the user will likely revisit
27
+ - the result benefits from richer HTML layout, hierarchy, or widgets
28
+ - the user is likely to keep referring back to the result while continuing the conversation
29
+
30
+ Ground this decision in the user's likely experience. Ask yourself what will be easier for them to read, compare, revisit, or act on next.
31
+
32
+ When the artifact is the real answer, keep the final chat reply short. Tell the user what you created, what they will see, and where to find it rather than duplicating the full content in chat.
33
+
34
+ ## Mental model
35
+
36
+ ### Core entities
37
+
38
+ - A **screen** is a named viewer surface with a layout.
39
+ - An **artifact** is a result that can be shown on a screen.
40
+ - Screen membership and artifact identity are separate.
41
+
42
+ That separation matters:
43
+
44
+ - attaching or detaching changes where an artifact appears
45
+ - deleting an artifact removes it globally
46
+ - an artifact may be attached to one screen, multiple screens, or no screens
47
+
48
+ ### Artifact kinds
49
+
50
+ Three operational categories:
51
+
52
+ - **internal artifact** — Television-managed bundle content. Use when Television should own the result as a durable bundle that later agents can inspect and maintain.
53
+ - **external artifact** — pointer to an existing absolute file on disk. Use when a real file already exists and Television should display it without taking ownership of that file.
54
+ - **URL artifact** — pointer to an external `http(s)://` page. Use when the right answer is to show a live web page rather than Television-owned content or a local file.
55
+
56
+ ### Lifecycle states
57
+
58
+ - **pending** — internal create/edit work has been staged but not committed yet
59
+ - **committed** — the artifact is live
60
+ - **trash** — live metadata and any committed internal bundle have been moved out of the active tree
61
+
62
+ Important consequences:
63
+
64
+ - only internal artifacts use pending create/edit/commit/abandon
65
+ - external file artifacts are committed immediately
66
+ - URL artifacts are committed immediately
67
+ - there is no restore-from-trash workflow in the current scope
68
+
69
+ ### User-facing framing
70
+
71
+ Think in terms of what the user believes exists and where they expect to find it.
72
+
73
+ Questions to keep straight:
74
+
75
+ - should this result become a durable Television-owned artifact?
76
+ - should it instead stay an external file or URL pointer?
77
+ - should it appear on the current screen, another existing screen, or a new screen?
78
+ - should the user see it immediately, or should it be prepared without moving their attention yet?
79
+
80
+ Those questions interact, but they are not the same question.
81
+
82
+ ## Where to read next
13
83
 
14
- Read `what-to-read.md` next to decide which companion docs apply.
84
+ This skill is modular. SKILL.md does not contain every rule.
15
85
 
16
- ## Core companion docs
86
+ - For the `tv` CLI command surface, the focus model, and screen-placement guidance, read `cli-capabilities.md`.
87
+ - For artifact lifecycle work — creating, updating, committing, abandoning, attaching, detaching, deleting — read `artifact-workflow.md`. That doc covers internal bundle structure and validation as well, since most artifact authoring is internal.
88
+ - For HTML artifacts, also read `html-house-style.md` and any matching `artifact-types/*.md` companion doc.
17
89
 
18
- - `what-to-read.md`
19
- - `artifact-workflow.md`
20
- - `html-house-style.md`
90
+ `markdown editor UI recovery` remains out of scope for the current Television workflow.
21
91
 
22
92
  ## Known artifact-type docs
23
93
 
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Authoring conventions for the calendar artifact kind.
3
+ ---
4
+
1
5
  # Authoring a calendar week
2
6
 
3
7
  A working-week calendar with a generated header strip, all-day band, time axis,
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Authoring conventions for the record-table artifact kind: dense, Airtable-style tables of records.
3
+ ---
4
+
1
5
  # Authoring a record table
2
6
 
3
7
  A dense, Airtable-style table of records. Lots of cells, mixed cell types,