@orlan-maker/cli 0.11.0 → 0.12.1
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/README.md +2 -1
- package/dist/commands.js +123 -5
- package/package.json +1 -1
- package/skills/orlan-versions/SKILL.md +6 -0
package/README.md
CHANGED
|
@@ -67,7 +67,8 @@ orlan topics list
|
|
|
67
67
|
orlan files list [--topic <topic>]
|
|
68
68
|
orlan files add <path> [--at <x,y>] [--topic <topic>]
|
|
69
69
|
orlan files pull <file> [--version <n>] [--pages <n,n> | --since <n>] [--topic <topic>] [--out <path>] [--edit [--shared] [--ttl <time>]]
|
|
70
|
-
orlan files push <file> <path> --changelog "<what changed>" [--answers <n,n>] [--scores <scores.json>] [--base <n>] [--topic <topic>]
|
|
70
|
+
orlan files push <file> <path> --changelog "<what changed>" [--answers <n,n>] [--scores <scores.json>] [--source <folder>] [--base <n>] [--topic <topic>]
|
|
71
|
+
orlan files source <file> [--number <n>] [--out <folder>] [--topic <topic>]
|
|
71
72
|
orlan files rollback <file> --version <n> [--changelog "<why>"] [--topic <topic>]
|
|
72
73
|
orlan comments list [--topic <topic>] [--status open|resolved|all] [--file <file>]
|
|
73
74
|
orlan comment <thread> "<text>" | --file <file> --page <n> [--region <x,y,w,h>] "<text>" | --at <x,y> "<text>" [--topic <topic>]
|
package/dist/commands.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* arguments and runs one of them.
|
|
4
4
|
*/
|
|
5
5
|
import { openAsBlob } from "node:fs";
|
|
6
|
-
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
7
7
|
import { hostname } from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { agentIds, agentLabels, detectAgent, parseAgent, planOldInstall, skillsDir, } from "./agents.js";
|
|
@@ -135,6 +135,12 @@ const shotsHelp = (column) => [
|
|
|
135
135
|
'[{ "name": "Hook", "start": 0, "end": 4.5 }], in seconds. The review shows the scene list.',
|
|
136
136
|
"Orlan refuses a bad list with shots_invalid, and names the problem.",
|
|
137
137
|
].map((line, index) => (index === 0 ? "--shots <file>" : "").padEnd(column) + line);
|
|
138
|
+
/** The help of --source (F149), its text from `column` on. */
|
|
139
|
+
const sourceHelp = (column) => [
|
|
140
|
+
"For a video: its source folder, the hf/ folder of the video skill. Orlan keeps it with the",
|
|
141
|
+
"version, so a person can change its fields with Quick edit and you render it again. Files and",
|
|
142
|
+
"folders whose names start with a dot are left out. Orlan refuses a source with GSAP code.",
|
|
143
|
+
].map((line, index) => (index === 0 ? "--source <folder>" : "").padEnd(column) + line);
|
|
138
144
|
/** The help of --scores (F139), its text from `column` on. */
|
|
139
145
|
const scoresHelp = (column) => [
|
|
140
146
|
"The critic scorecard of the version: a JSON file of 1 to 20 scores, for example",
|
|
@@ -298,6 +304,33 @@ async function shotsOption(values) {
|
|
|
298
304
|
throw new OrlanError(`The shot list ${quote(file)} is not JSON.`, "shots_invalid", 'write a JSON array of { "name": text, "start": seconds, "end": seconds }');
|
|
299
305
|
}
|
|
300
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* --source of files add and files push (F149): the files of a video's source folder, as one JSON bundle
|
|
309
|
+
* { format: "orlan-video-source", files: { path: base64 } }. Names that start with a dot (hf/.hyperframes,
|
|
310
|
+
* .DS_Store) and node_modules are left out. Orlan checks the source.
|
|
311
|
+
*/
|
|
312
|
+
async function sourceOption(values) {
|
|
313
|
+
const folder = values.source;
|
|
314
|
+
if (typeof folder !== "string")
|
|
315
|
+
return undefined;
|
|
316
|
+
const entries = await readdir(folder, { recursive: true, withFileTypes: true }).catch(() => {
|
|
317
|
+
throw new OrlanError(`Cannot read the source folder ${quote(folder)}.`, "source_invalid", "check the path of --source");
|
|
318
|
+
});
|
|
319
|
+
const files = {};
|
|
320
|
+
for (const entry of entries) {
|
|
321
|
+
if (!entry.isFile())
|
|
322
|
+
continue;
|
|
323
|
+
const full = path.join(entry.parentPath, entry.name);
|
|
324
|
+
const relative = path.relative(folder, full).split(path.sep).join("/");
|
|
325
|
+
if (relative.split("/").some((part) => part.startsWith(".") || part === "node_modules"))
|
|
326
|
+
continue;
|
|
327
|
+
files[relative] = (await readFile(full)).toString("base64");
|
|
328
|
+
}
|
|
329
|
+
if (!files["index.html"]) {
|
|
330
|
+
throw new OrlanError(`The source folder ${quote(folder)} has no index.html.`, "source_invalid", "give the hf folder of the video project: --source hf");
|
|
331
|
+
}
|
|
332
|
+
return new Blob([JSON.stringify({ format: "orlan-video-source", files })], { type: "application/json" });
|
|
333
|
+
}
|
|
301
334
|
/** --scores of files add and files push (F139): the JSON of the scorecard file. Orlan checks the scores. */
|
|
302
335
|
async function scoresOption(values) {
|
|
303
336
|
const file = values.scores;
|
|
@@ -759,7 +792,7 @@ export const commands = {
|
|
|
759
792
|
}),
|
|
760
793
|
},
|
|
761
794
|
"files add": {
|
|
762
|
-
usage: "orlan files add <path>... [--carousel [--name <name>]] [--at <x,y>] [--shots <shots.json>] [--scores <scores.json>] [--topic <topic> | --new-topic <name>] [--agent <id>] [--json]",
|
|
795
|
+
usage: "orlan files add <path>... [--carousel [--name <name>]] [--at <x,y>] [--shots <shots.json>] [--scores <scores.json>] [--source <folder>] [--topic <topic> | --new-topic <name>] [--agent <id>] [--json]",
|
|
763
796
|
summary: "Post a local file as a new file on a topic, and put it on the board.",
|
|
764
797
|
details: [
|
|
765
798
|
"One request sends the file and places it on the board, right of the other items. The server",
|
|
@@ -773,6 +806,7 @@ export const commands = {
|
|
|
773
806
|
" place. `orlan board` shows where the items are.",
|
|
774
807
|
...shotsHelp(17),
|
|
775
808
|
...scoresHelp(17),
|
|
809
|
+
...sourceHelp(17),
|
|
776
810
|
"--topic <topic> The topic id or its name. Default: the topic open in Orlan now, else the one used last.",
|
|
777
811
|
...NEW_TOPIC_HELP,
|
|
778
812
|
AGENT_TOKEN_HELP,
|
|
@@ -786,6 +820,7 @@ export const commands = {
|
|
|
786
820
|
at: { type: "string" },
|
|
787
821
|
shots: { type: "string" },
|
|
788
822
|
scores: { type: "string" },
|
|
823
|
+
source: { type: "string" },
|
|
789
824
|
carousel: { type: "boolean" },
|
|
790
825
|
name: { type: "string" },
|
|
791
826
|
},
|
|
@@ -803,6 +838,10 @@ export const commands = {
|
|
|
803
838
|
if (values.carousel && shots.shots !== undefined) {
|
|
804
839
|
throw new OrlanError("--shots goes with a video, not with --carousel.", "shots_invalid", "leave out --shots");
|
|
805
840
|
}
|
|
841
|
+
if (values.carousel && values.source !== undefined) {
|
|
842
|
+
throw new OrlanError("--source goes with a video, not with --carousel.", "source_invalid", "leave out --source");
|
|
843
|
+
}
|
|
844
|
+
const source = await sourceOption(values);
|
|
806
845
|
const { topicId, newTopic } = await writeTopic(api, values, io);
|
|
807
846
|
const madeTopic = newTopic ? { newTopic } : {};
|
|
808
847
|
if (values.carousel) {
|
|
@@ -837,12 +876,17 @@ export const commands = {
|
|
|
837
876
|
`${slides.map((slide) => path.basename(slide)).join(", ")}. It is on the board.`,
|
|
838
877
|
], doneStep("comments on the file"));
|
|
839
878
|
}
|
|
840
|
-
if (at || shots.shots !== undefined || scores.scores !== undefined) {
|
|
841
|
-
// A place on the board, a shot list or a
|
|
879
|
+
if (at || shots.shots !== undefined || scores.scores !== undefined || source) {
|
|
880
|
+
// A place on the board, a shot list, a scorecard or a source: the file goes up first, then the post
|
|
881
|
+
// places it.
|
|
842
882
|
const uploadId = await api.upload(await openAsBlob(filePath), {
|
|
843
883
|
topic: topicId,
|
|
844
884
|
name: path.basename(filePath),
|
|
845
885
|
});
|
|
886
|
+
// The source goes up as its own upload, with the name of the video: it has the limit of a video.
|
|
887
|
+
const sourceId = source
|
|
888
|
+
? await api.upload(source, { topic: topicId, name: path.basename(filePath) }, "the source")
|
|
889
|
+
: undefined;
|
|
846
890
|
const posted = await api.call("post", {
|
|
847
891
|
topicId,
|
|
848
892
|
name: path.basename(filePath),
|
|
@@ -850,6 +894,7 @@ export const commands = {
|
|
|
850
894
|
...(at ? { position: { x: at[0], y: at[1] } } : {}),
|
|
851
895
|
...shots,
|
|
852
896
|
...scores,
|
|
897
|
+
...(sourceId ? { source: sourceId } : {}),
|
|
853
898
|
});
|
|
854
899
|
return finish(io, values, {
|
|
855
900
|
file: { id: posted.file.id, name: posted.file.name, version: posted.posted.number },
|
|
@@ -859,6 +904,7 @@ export const commands = {
|
|
|
859
904
|
`Added ${quote(posted.file.name)} as file ${posted.file.id}, v${posted.posted.number}. It is on the ` +
|
|
860
905
|
`board${at ? ` at ${at.join(",")}, or at the nearest free place` : ""}; its pages render now.`,
|
|
861
906
|
...(shots.shots !== undefined ? ["Its shot list shows as the scene list of the video."] : []),
|
|
907
|
+
...(sourceId ? ["Its source is in Orlan: people can change its fields with Quick edit."] : []),
|
|
862
908
|
], doneStep("comments on the file"));
|
|
863
909
|
}
|
|
864
910
|
const { nextStep, ...answer } = await api.postFile("/api/agent/files", { name: path.basename(filePath), topic: topicId }, await openAsBlob(filePath), "the new file");
|
|
@@ -962,7 +1008,7 @@ export const commands = {
|
|
|
962
1008
|
}),
|
|
963
1009
|
},
|
|
964
1010
|
"files push": {
|
|
965
|
-
usage: 'orlan files push <file> <path>... --changelog "<what changed>" [--answers <n,n>] [--base <n>] [--shots <shots.json>] [--scores <scores.json>] [--topic <topic>] [--agent <id>] [--json]',
|
|
1011
|
+
usage: 'orlan files push <file> <path>... --changelog "<what changed>" [--answers <n,n>] [--base <n>] [--shots <shots.json>] [--scores <scores.json>] [--source <folder>] [--topic <topic>] [--agent <id>] [--json]',
|
|
966
1012
|
summary: "Post a local file as a new version of a file.",
|
|
967
1013
|
details: [
|
|
968
1014
|
"Two requests: one sends the file, one posts it with the changelog. It must be the same type of",
|
|
@@ -978,6 +1024,7 @@ export const commands = {
|
|
|
978
1024
|
"--base <n> The version you edited, for example 4 or v4. Default: the version your `pull --edit` got.",
|
|
979
1025
|
...shotsHelp(20),
|
|
980
1026
|
...scoresHelp(20),
|
|
1027
|
+
...sourceHelp(20),
|
|
981
1028
|
"--topic <topic> The topic of a file name, when two topics have a file with that name.",
|
|
982
1029
|
AGENT_TOKEN_HELP,
|
|
983
1030
|
JSON_HELP,
|
|
@@ -990,6 +1037,7 @@ export const commands = {
|
|
|
990
1037
|
topic: { type: "string" },
|
|
991
1038
|
shots: { type: "string" },
|
|
992
1039
|
scores: { type: "string" },
|
|
1040
|
+
source: { type: "string" },
|
|
993
1041
|
answers: { type: "string" },
|
|
994
1042
|
},
|
|
995
1043
|
positionals: [2, CAROUSEL_SLIDES_MAX + 1],
|
|
@@ -1002,6 +1050,7 @@ export const commands = {
|
|
|
1002
1050
|
const shots = await shotsOption(values);
|
|
1003
1051
|
const scores = await scoresOption(values);
|
|
1004
1052
|
const answers = answersOption(values);
|
|
1053
|
+
const source = await sourceOption(values);
|
|
1005
1054
|
// Several paths are the slides of a new carousel version (F136), in the order of their names.
|
|
1006
1055
|
const uploadIds = [];
|
|
1007
1056
|
for (const filePath of filePaths.length > 1 ? slideOrder(filePaths) : filePaths) {
|
|
@@ -1019,12 +1068,81 @@ export const commands = {
|
|
|
1019
1068
|
...shots,
|
|
1020
1069
|
...scores,
|
|
1021
1070
|
...answers,
|
|
1071
|
+
...(source
|
|
1072
|
+
? {
|
|
1073
|
+
source: await api.upload(source, { file: fileRef, topic: values.topic }, "the source"),
|
|
1074
|
+
}
|
|
1075
|
+
: {}),
|
|
1022
1076
|
});
|
|
1023
1077
|
return finish(io, values, { file: { id: posted.file.id, name: posted.file.name }, version: posted.posted.number }, [
|
|
1024
1078
|
`Posted version ${posted.posted.number} of ${quote(posted.file.name)}. It waits for a person to make it current.`,
|
|
1025
1079
|
], doneStep("comments on the version"));
|
|
1026
1080
|
}),
|
|
1027
1081
|
},
|
|
1082
|
+
"files source": {
|
|
1083
|
+
usage: "orlan files source <file> [--number <n>] [--out <folder>] [--topic <topic>] [--agent <id>] [--json]",
|
|
1084
|
+
summary: "Pull the source of a video into a folder, to render it again.",
|
|
1085
|
+
details: [
|
|
1086
|
+
"The source is the hf/ folder that went up with `files push --source`. A person changes its fields",
|
|
1087
|
+
"with Quick edit in Orlan: that saves a new source, which waits for your render. Pull it, render",
|
|
1088
|
+
"it with the video skill, and push the mp4 with --source as the next version.",
|
|
1089
|
+
"",
|
|
1090
|
+
"<file> The file id, or its name where it is unique on the topic.",
|
|
1091
|
+
"--number <n> A source number. Default: the newest source.",
|
|
1092
|
+
"--out <folder> The folder to write it into. Default: hf. Files of the source replace the ones",
|
|
1093
|
+
" there; other files stay.",
|
|
1094
|
+
"--topic <topic> The topic of a file name, when two topics have a file with that name.",
|
|
1095
|
+
AGENT_TOKEN_HELP,
|
|
1096
|
+
JSON_HELP,
|
|
1097
|
+
],
|
|
1098
|
+
options: {
|
|
1099
|
+
...agentOption,
|
|
1100
|
+
...jsonOption,
|
|
1101
|
+
number: { type: "string" },
|
|
1102
|
+
out: { type: "string" },
|
|
1103
|
+
topic: { type: "string" },
|
|
1104
|
+
},
|
|
1105
|
+
positionals: [1, 1],
|
|
1106
|
+
run: (values, [fileRef], io) => withAgent(values, async (api) => {
|
|
1107
|
+
const number = typeof values.number === "string" ? values.number.replace(/^#/, "") : undefined;
|
|
1108
|
+
if (number !== undefined && !/^[1-9]\d*$/.test(number)) {
|
|
1109
|
+
throw new OrlanError("--number is a source number, for example 2.", undefined, "leave out --number for the newest");
|
|
1110
|
+
}
|
|
1111
|
+
const found = await api.call("get_file", {
|
|
1112
|
+
artifactId: fileRef,
|
|
1113
|
+
...topicInput(values),
|
|
1114
|
+
pageImages: "none",
|
|
1115
|
+
});
|
|
1116
|
+
const source = await api.get(`/api/artifacts/${found.file.id}/source/files`, { number }, "the source");
|
|
1117
|
+
const out = typeof values.out === "string" ? values.out : "hf";
|
|
1118
|
+
const root = path.resolve(out);
|
|
1119
|
+
for (const [name, base64] of Object.entries(source.files)) {
|
|
1120
|
+
const target = path.resolve(root, name);
|
|
1121
|
+
// Orlan checks the paths of a source; the CLI writes nothing outside the folder all the same.
|
|
1122
|
+
if (!target.startsWith(`${root}${path.sep}`))
|
|
1123
|
+
throw new OrlanError(`The source has a bad path ${quote(name)}.`);
|
|
1124
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
1125
|
+
await writeFile(target, Buffer.from(base64, "base64"));
|
|
1126
|
+
}
|
|
1127
|
+
const count = Object.keys(source.files).length;
|
|
1128
|
+
const waits = source.versionNumber === null;
|
|
1129
|
+
return finish(io, values, {
|
|
1130
|
+
file: { id: found.file.id, name: found.file.name },
|
|
1131
|
+
source: source.number,
|
|
1132
|
+
version: source.versionNumber,
|
|
1133
|
+
changes: source.changes,
|
|
1134
|
+
saved: out,
|
|
1135
|
+
files: count,
|
|
1136
|
+
}, [
|
|
1137
|
+
`Saved source ${source.number} of ${quote(found.file.name)} to ${quote(out)}: ${count} file${count === 1 ? "" : "s"}.`,
|
|
1138
|
+
waits
|
|
1139
|
+
? `It waits for a render. Changed in Quick edit: ${source.changes.join("; ") || "nothing named"}.`
|
|
1140
|
+
: `Version ${source.versionNumber} was rendered from it.`,
|
|
1141
|
+
], waits
|
|
1142
|
+
? `render it (node video.mjs audio, then node video.mjs render), then orlan files push ${found.file.id} renders/<video>.mp4 --source ${out} --changelog "Rendered source ${source.number}"`
|
|
1143
|
+
: doneStep());
|
|
1144
|
+
}),
|
|
1145
|
+
},
|
|
1028
1146
|
"files rollback": {
|
|
1029
1147
|
usage: 'orlan files rollback <file> --version <n> [--changelog "<why>"] [--topic <topic>] [--agent <id>] [--json]',
|
|
1030
1148
|
summary: "Post an older version of a file again, as its newest version.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orlan-maker/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "The orlan command: connect Claude Code, Codex, Cursor or OpenCode to Orlan with a token and skills, and work with its topics, files and comments from the terminal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,6 +38,12 @@ description: Get a file from Orlan and post a new version of it with a changelog
|
|
|
38
38
|
critic round. People see it read only in the review and in Compare, with the change from the version
|
|
39
39
|
before. Orlan refuses a bad scorecard with `scores_invalid` and names the problem. `orlan files list`
|
|
40
40
|
shows the total of the newest scored version; `orlan comments list` shows "answered in v<n>".
|
|
41
|
+
- `--source hf` on `files add` or `files push` of a video - its source folder (the hf/ folder of the
|
|
42
|
+
video skill). Orlan keeps it with the version. A person changes its fields (scene text, images, colors,
|
|
43
|
+
scene lengths) with Quick edit in Orlan. That saves a new source, and a comment asks you to render it.
|
|
44
|
+
Orlan refuses a source with GSAP code with `source_invalid`.
|
|
45
|
+
- `orlan files source <file> [--out hf]` - saves the newest source of a video into the folder. When it
|
|
46
|
+
waits for a render, render it and push the mp4 with `--source hf` as the next version.
|
|
41
47
|
- `orlan files rollback <file> --version 3 --changelog "<why>"` - posts an older version again, as the
|
|
42
48
|
newest version. It waits for a person to make it current, as a push does.
|
|
43
49
|
- `<file>` is the file id or the file name, and `<topic>` the topic id or its name. When a name is on two
|