@telepath-computer/television 0.1.81 → 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 +90 -22
- package/dist/skills/television/.skill-manifest.json +29 -0
- package/dist/skills/television/SKILL.md +77 -7
- package/dist/skills/television/artifact-types/calendar.md +4 -0
- package/dist/skills/television/artifact-types/table.md +4 -0
- package/dist/skills/television/artifact-workflow.md +280 -8
- package/dist/skills/television/cli-capabilities.md +145 -0
- package/dist/skills/television/html-house-style.md +4 -0
- package/dist/web/assets/{index-_1D4RyBM.js → index-BEXhHPow.js} +113 -113
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/skills/television/what-to-read.md +0 -27
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 `
|
|
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.
|
|
50127
|
-
return "0.1.
|
|
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
|
-
"
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
84
|
+
This skill is modular. SKILL.md does not contain every rule.
|
|
15
85
|
|
|
16
|
-
|
|
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
|
-
|
|
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
|
|