@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 +444 -434
- package/dist/skills/television/SKILL.md +215 -198
- package/dist/skills/television-calendar/SKILL.md +99 -0
- package/dist/skills/television-table/SKILL.md +89 -0
- package/dist/views/markdown/manifest.json +1 -1
- package/dist/web/assets/{index-Cf-KqGge.js → index-_dpb69va.js} +86 -86
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/skills/artifact-calendar/SKILL.md +0 -151
- package/dist/skills/artifact-calendar/house-style.md +0 -67
- package/dist/skills/artifact-table/SKILL.md +0 -217
- package/dist/skills/artifact-table/house-style.md +0 -67
- package/dist/views/text/index.html +0 -38
- package/dist/views/text/manifest.json +0 -5
- /package/dist/skills/{artifact-calendar → television-calendar}/calendar.css +0 -0
- /package/dist/skills/{artifact-calendar → television-calendar}/calendar.js +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -33973,11 +33973,20 @@ var ArtifactClient = class {
|
|
|
33973
33973
|
*/
|
|
33974
33974
|
create(input) {
|
|
33975
33975
|
const body = {
|
|
33976
|
-
|
|
33976
|
+
kind: input.kind,
|
|
33977
33977
|
title: input.title
|
|
33978
33978
|
};
|
|
33979
|
-
|
|
33980
|
-
|
|
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
|
|
47958
|
+
var ARTIFACT_KINDS = ["markdown", "web-bundle", "url"];
|
|
47951
47959
|
var ARTIFACT_STATUSES = ["committed", "pending"];
|
|
47952
|
-
var
|
|
47953
|
-
|
|
47954
|
-
"
|
|
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
|
-
|
|
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.
|
|
47984
|
-
|
|
47985
|
-
|
|
47986
|
-
|
|
47987
|
-
|
|
47988
|
-
|
|
47989
|
-
|
|
47990
|
-
|
|
47991
|
-
|
|
47992
|
-
|
|
47993
|
-
|
|
47994
|
-
|
|
47995
|
-
|
|
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
|
-
|
|
48056
|
+
const artifact = ArtifactSchema.parse({
|
|
48002
48057
|
id: input.id ?? ulid(),
|
|
48003
|
-
|
|
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.
|
|
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,
|
|
48050
|
-
const extension2 =
|
|
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
|
|
48152
|
-
|
|
48208
|
+
var createWebBundleArtifactSchema = external_exports.object({
|
|
48209
|
+
kind: external_exports.literal("web-bundle"),
|
|
48153
48210
|
title: external_exports.string(),
|
|
48154
|
-
|
|
48155
|
-
|
|
48156
|
-
|
|
48157
|
-
|
|
48158
|
-
|
|
48159
|
-
|
|
48160
|
-
|
|
48161
|
-
|
|
48162
|
-
|
|
48163
|
-
|
|
48164
|
-
|
|
48165
|
-
|
|
48166
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
|
48376
|
-
|
|
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.
|
|
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.
|
|
48477
|
-
|
|
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",
|
|
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?.
|
|
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
|
|
48607
|
-
|
|
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
|
-
|
|
49357
|
-
|
|
49358
|
-
|
|
49359
|
-
|
|
49360
|
-
|
|
49361
|
-
|
|
49362
|
-
|
|
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
|
|
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.
|
|
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?.
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
49490
|
-
throw new InvalidRequestError(`Only
|
|
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
|
-
|
|
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
|
-
|
|
49511
|
-
|
|
49512
|
-
|
|
49513
|
-
|
|
49514
|
-
|
|
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.
|
|
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.
|
|
49539
|
-
throw new InvalidRequestError(`Only
|
|
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
|
-
|
|
49858
|
-
|
|
49859
|
-
|
|
49860
|
-
|
|
49861
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
50067
|
-
|
|
50068
|
-
|
|
50069
|
-
|
|
50070
|
-
|
|
50071
|
-
|
|
50072
|
-
|
|
50073
|
-
|
|
50074
|
-
|
|
50075
|
-
|
|
50076
|
-
|
|
50077
|
-
|
|
50078
|
-
|
|
50079
|
-
|
|
50080
|
-
|
|
50081
|
-
|
|
50082
|
-
|
|
50083
|
-
|
|
50084
|
-
|
|
50085
|
-
|
|
50086
|
-
|
|
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
|
|
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
|
-
|
|
50271
|
-
const
|
|
50272
|
-
|
|
50273
|
-
|
|
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,
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
50434
|
-
return "0.1.
|
|
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
|
-
"
|
|
50499
|
-
"
|
|
50500
|
-
"
|
|
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
|
-
"
|
|
50503
|
+
" The main skill is `television` \u2014 read it for screens, lifecycle, and artifact workflow."
|
|
50509
50504
|
];
|
|
50510
50505
|
if (includeHTMLNote) {
|
|
50511
|
-
lines.push("
|
|
50512
|
-
lines.push("
|
|
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
|
|
50518
|
-
return
|
|
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(
|
|
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
|
|
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
|
-
|
|
50612
|
-
|
|
50613
|
-
}
|
|
50614
|
-
|
|
50615
|
-
|
|
50616
|
-
|
|
50617
|
-
|
|
50618
|
-
|
|
50619
|
-
|
|
50620
|
-
|
|
50621
|
-
|
|
50622
|
-
|
|
50623
|
-
|
|
50624
|
-
|
|
50625
|
-
|
|
50626
|
-
|
|
50627
|
-
|
|
50628
|
-
|
|
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
|
-
|
|
50639
|
-
|
|
50640
|
-
|
|
50641
|
-
|
|
50642
|
-
|
|
50643
|
-
|
|
50644
|
-
|
|
50645
|
-
|
|
50646
|
-
|
|
50647
|
-
|
|
50648
|
-
|
|
50649
|
-
|
|
50650
|
-
}
|
|
50651
|
-
|
|
50652
|
-
|
|
50653
|
-
|
|
50654
|
-
|
|
50655
|
-
|
|
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-
|
|
50823
|
-
"
|
|
50824
|
-
).requiredOption("--screen <id>", "Target screen ID").requiredOption("--
|
|
50825
|
-
const shouldFocus = resolveFocusDirective(argv, "create-
|
|
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
|
-
|
|
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 (
|
|
50841
|
-
|
|
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, `
|
|
50844
|
-
writeLine(env.stdout,
|
|
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-
|
|
50848
|
-
"Stage a pending edit against an existing
|
|
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-
|
|
50882
|
+
writeLine(env.stdout, ` tv commit-artifact --id ${result.artifact.id}`);
|
|
50855
50883
|
});
|
|
50856
|
-
program2.command("commit-
|
|
50857
|
-
"
|
|
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-
|
|
50864
|
-
"Discard pending work without committing. For a pending create, the artifact never becomes live
|
|
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-
|
|
50871
|
-
"Create
|
|
50872
|
-
).requiredOption("--screen <id>", "Target screen ID").requiredOption("--
|
|
50873
|
-
const shouldFocus = resolveFocusDirective(argv, "create-
|
|
50874
|
-
|
|
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
|
|
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, `
|
|
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.
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
51009
|
-
).option("--
|
|
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.
|
|
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
|
|
51054
|
-
"
|
|
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("
|
|
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 (
|
|
51059
|
-
|
|
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
|
-
|
|
51062
|
-
|
|
51063
|
-
|
|
51064
|
-
|
|
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
|
-
|
|
51076
|
-
|
|
51077
|
-
|
|
51078
|
-
|
|
51079
|
-
|
|
51080
|
-
|
|
51081
|
-
|
|
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 () => {
|