twentythree-cli 1.0.2 → 1.1.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/dist/commands/app/list.cjs +87 -0
- package/dist/commands/app/remove-thumbnail.cjs +51 -0
- package/dist/commands/app/set-thumbnail.cjs +78 -0
- package/dist/commands/autocomplete/index.cjs +3 -3
- package/dist/commands/video/section/generate.cjs +54 -0
- package/dist/lib/base-command.cjs +30 -2
- package/dist/lib/detect-shell.cjs +13 -0
- package/oclif.manifest.json +5723 -5432
- package/package.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_lib_term_map = require("../../lib/term-map.cjs");
|
|
3
|
+
const require_lib_base_command = require("../../lib/base-command.cjs");
|
|
4
|
+
const require_lib_output = require("../../lib/output.cjs");
|
|
5
|
+
let _oclif_core = require("@oclif/core");
|
|
6
|
+
//#region src/commands/app/list.ts
|
|
7
|
+
/**
|
|
8
|
+
* App list command — returns paginated list of player design apps on the workspace.
|
|
9
|
+
*/
|
|
10
|
+
var AppList = class AppList extends require_lib_base_command.AuthenticatedCommand {
|
|
11
|
+
static description = "List player design apps on the active workspace";
|
|
12
|
+
static examples = [
|
|
13
|
+
"<%= config.bin %> app list",
|
|
14
|
+
"<%= config.bin %> app list --app-id 42",
|
|
15
|
+
"<%= config.bin %> app list --page 2 --size 50",
|
|
16
|
+
"<%= config.bin %> app list --json"
|
|
17
|
+
];
|
|
18
|
+
static enableJsonFlag = true;
|
|
19
|
+
static agentMetadata = {
|
|
20
|
+
api_endpoint: "POST /app/list",
|
|
21
|
+
auth_scope: "read",
|
|
22
|
+
output_shape: {
|
|
23
|
+
type: "table",
|
|
24
|
+
columns: [
|
|
25
|
+
"ID",
|
|
26
|
+
"Name",
|
|
27
|
+
"Type",
|
|
28
|
+
"Description"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
side_effects: "none"
|
|
32
|
+
};
|
|
33
|
+
static flags = {
|
|
34
|
+
...require_lib_base_command.AuthenticatedCommand.baseFlags,
|
|
35
|
+
"app-id": _oclif_core.Flags.integer({
|
|
36
|
+
description: "Filter results to a specific app ID",
|
|
37
|
+
required: false
|
|
38
|
+
}),
|
|
39
|
+
page: _oclif_core.Flags.integer({
|
|
40
|
+
description: "Page offset",
|
|
41
|
+
required: false
|
|
42
|
+
}),
|
|
43
|
+
size: _oclif_core.Flags.integer({
|
|
44
|
+
description: "Number of results per page (default 20, max 100)",
|
|
45
|
+
required: false
|
|
46
|
+
})
|
|
47
|
+
};
|
|
48
|
+
static args = {};
|
|
49
|
+
async run() {
|
|
50
|
+
const { flags } = await this.parse(AppList);
|
|
51
|
+
this.printWorkspaceHeader();
|
|
52
|
+
const body = {};
|
|
53
|
+
if (flags["app-id"] !== void 0) body.app_id = flags["app-id"];
|
|
54
|
+
if (flags.page !== void 0) body.p = flags.page;
|
|
55
|
+
if (flags.size !== void 0) body.size = flags.size;
|
|
56
|
+
const { data, error } = await this.apiClient.POST("/app/list", {
|
|
57
|
+
body,
|
|
58
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
59
|
+
});
|
|
60
|
+
if (error) this.error(require_lib_term_map.applyCliTerms(require_lib_output.formatApiError(error)), { exit: 1 });
|
|
61
|
+
const apps = data?.apps ?? data?.data ?? [];
|
|
62
|
+
if (this.jsonEnabled()) return require_lib_output.formatJsonOutput({
|
|
63
|
+
ok: true,
|
|
64
|
+
data: apps,
|
|
65
|
+
summary: "App list",
|
|
66
|
+
breadcrumbs: [{ domain: this.activeWorkspace.domain }, { resource: "app" }]
|
|
67
|
+
});
|
|
68
|
+
if (apps.length === 0) {
|
|
69
|
+
this.log("No apps found.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const table = require_lib_output.renderTable([
|
|
73
|
+
"ID",
|
|
74
|
+
"Name",
|
|
75
|
+
"Type",
|
|
76
|
+
"Description"
|
|
77
|
+
], apps.map((a) => [
|
|
78
|
+
String(a.app_id ?? a.id ?? ""),
|
|
79
|
+
String(a.name ?? ""),
|
|
80
|
+
String(a.type ?? ""),
|
|
81
|
+
String(a.description ?? "")
|
|
82
|
+
]));
|
|
83
|
+
this.log(table.toString());
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
module.exports = AppList;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_lib_term_map = require("../../lib/term-map.cjs");
|
|
3
|
+
const require_lib_base_command = require("../../lib/base-command.cjs");
|
|
4
|
+
const require_lib_output = require("../../lib/output.cjs");
|
|
5
|
+
let _oclif_core = require("@oclif/core");
|
|
6
|
+
let chalk = require("chalk");
|
|
7
|
+
chalk = require_runtime.__toESM(chalk);
|
|
8
|
+
//#region src/commands/app/remove-thumbnail.ts
|
|
9
|
+
/**
|
|
10
|
+
* App remove-thumbnail command — removes the custom thumbnail for an app, reverting to default.
|
|
11
|
+
*
|
|
12
|
+
* Threat mitigations:
|
|
13
|
+
* T-08-05: extends AuthenticatedCommand — anonymous mode rejected
|
|
14
|
+
*/
|
|
15
|
+
var AppRemoveThumbnail = class AppRemoveThumbnail extends require_lib_base_command.AuthenticatedCommand {
|
|
16
|
+
static description = "Remove the custom thumbnail for an app, reverting to the default";
|
|
17
|
+
static examples = ["<%= config.bin %> app remove-thumbnail 42", "<%= config.bin %> app remove-thumbnail 42 --json"];
|
|
18
|
+
static enableJsonFlag = true;
|
|
19
|
+
static agentMetadata = {
|
|
20
|
+
api_endpoint: "POST /app/remove-thumbnail",
|
|
21
|
+
auth_scope: "write",
|
|
22
|
+
output_shape: { type: "key-value" },
|
|
23
|
+
side_effects: "updates"
|
|
24
|
+
};
|
|
25
|
+
static flags = { ...require_lib_base_command.AuthenticatedCommand.baseFlags };
|
|
26
|
+
static args = { id: _oclif_core.Args.string({
|
|
27
|
+
description: "App ID",
|
|
28
|
+
required: true
|
|
29
|
+
}) };
|
|
30
|
+
async run() {
|
|
31
|
+
const { args } = await this.parse(AppRemoveThumbnail);
|
|
32
|
+
this.printWorkspaceHeader();
|
|
33
|
+
const { data, error } = await this.apiClient.POST("/app/remove-thumbnail", {
|
|
34
|
+
body: { app_id: Number(args.id) },
|
|
35
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
36
|
+
});
|
|
37
|
+
if (error) this.error(require_lib_term_map.applyCliTerms(require_lib_output.formatApiError(error)), { exit: 1 });
|
|
38
|
+
this.log(chalk.default.green(`Thumbnail removed for app ${args.id}`));
|
|
39
|
+
if (this.jsonEnabled()) return require_lib_output.formatJsonOutput({
|
|
40
|
+
ok: true,
|
|
41
|
+
data,
|
|
42
|
+
summary: `Thumbnail removed for app ${args.id}`,
|
|
43
|
+
breadcrumbs: [{ domain: this.activeWorkspace.domain }, {
|
|
44
|
+
resource: "app",
|
|
45
|
+
id: args.id
|
|
46
|
+
}]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
module.exports = AppRemoveThumbnail;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_lib_term_map = require("../../lib/term-map.cjs");
|
|
3
|
+
const require_lib_base_command = require("../../lib/base-command.cjs");
|
|
4
|
+
const require_lib_output = require("../../lib/output.cjs");
|
|
5
|
+
let _oclif_core = require("@oclif/core");
|
|
6
|
+
let chalk = require("chalk");
|
|
7
|
+
chalk = require_runtime.__toESM(chalk);
|
|
8
|
+
let node_fs = require("node:fs");
|
|
9
|
+
node_fs = require_runtime.__toESM(node_fs);
|
|
10
|
+
let node_path = require("node:path");
|
|
11
|
+
node_path = require_runtime.__toESM(node_path);
|
|
12
|
+
//#region src/commands/app/set-thumbnail.ts
|
|
13
|
+
/**
|
|
14
|
+
* App set-thumbnail command — uploads and sets a custom thumbnail image for an app.
|
|
15
|
+
*
|
|
16
|
+
* Uses direct multipart POST with bodySerializer to create FormData.
|
|
17
|
+
* Per D-3: NOT the chunked engine — thumbnail images use direct multipart upload.
|
|
18
|
+
*
|
|
19
|
+
* Threat mitigations:
|
|
20
|
+
* T-08-08: Validates file exists via fs.existsSync before reading
|
|
21
|
+
* T-08-05: extends AuthenticatedCommand — anonymous mode rejected
|
|
22
|
+
*/
|
|
23
|
+
var AppSetThumbnail = class AppSetThumbnail extends require_lib_base_command.AuthenticatedCommand {
|
|
24
|
+
static description = "Upload and set a custom thumbnail image for an app";
|
|
25
|
+
static examples = ["<%= config.bin %> app set-thumbnail ./thumbnail.png --app-id 42", "<%= config.bin %> app set-thumbnail ./thumbnail.jpg --app-id 42 --json"];
|
|
26
|
+
static enableJsonFlag = true;
|
|
27
|
+
static agentMetadata = {
|
|
28
|
+
api_endpoint: "POST /app/set-thumbnail",
|
|
29
|
+
auth_scope: "write",
|
|
30
|
+
output_shape: { type: "key-value" },
|
|
31
|
+
side_effects: "updates"
|
|
32
|
+
};
|
|
33
|
+
static flags = {
|
|
34
|
+
...require_lib_base_command.AuthenticatedCommand.baseFlags,
|
|
35
|
+
"app-id": _oclif_core.Flags.integer({
|
|
36
|
+
description: "App ID to update",
|
|
37
|
+
required: true
|
|
38
|
+
})
|
|
39
|
+
};
|
|
40
|
+
static args = { file: _oclif_core.Args.string({
|
|
41
|
+
description: "Path to the thumbnail image file",
|
|
42
|
+
required: true
|
|
43
|
+
}) };
|
|
44
|
+
async run() {
|
|
45
|
+
const { args, flags } = await this.parse(AppSetThumbnail);
|
|
46
|
+
this.printWorkspaceHeader();
|
|
47
|
+
const filePath = node_path.resolve(args.file);
|
|
48
|
+
if (!node_fs.existsSync(filePath)) this.error(`File not found: ${filePath}`, { exit: 1 });
|
|
49
|
+
const fileBuffer = node_fs.readFileSync(filePath);
|
|
50
|
+
const fileName = node_path.basename(filePath);
|
|
51
|
+
const fileBlob = new Blob([fileBuffer]);
|
|
52
|
+
const { data, error } = await this.apiClient.POST("/app/set-thumbnail", {
|
|
53
|
+
body: {
|
|
54
|
+
app_id: flags["app-id"],
|
|
55
|
+
file: fileBlob
|
|
56
|
+
},
|
|
57
|
+
bodySerializer(body) {
|
|
58
|
+
const fd = new FormData();
|
|
59
|
+
for (const [k, v] of Object.entries(body)) if (v !== void 0) if (v instanceof Blob) fd.append(k, v, fileName);
|
|
60
|
+
else fd.append(k, String(v));
|
|
61
|
+
return fd;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
if (error) this.error(require_lib_term_map.applyCliTerms(require_lib_output.formatApiError(error)), { exit: 1 });
|
|
65
|
+
this.log(chalk.default.green(`Thumbnail set for app ${flags["app-id"]}`));
|
|
66
|
+
if (this.jsonEnabled()) return require_lib_output.formatJsonOutput({
|
|
67
|
+
ok: true,
|
|
68
|
+
data,
|
|
69
|
+
summary: `Thumbnail set for app ${flags["app-id"]}`,
|
|
70
|
+
breadcrumbs: [{ domain: this.activeWorkspace.domain }, {
|
|
71
|
+
resource: "app",
|
|
72
|
+
id: String(flags["app-id"])
|
|
73
|
+
}]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
module.exports = AppSetThumbnail;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_lib_detect_shell = require("../../lib/detect-shell.cjs");
|
|
2
3
|
let _oclif_core = require("@oclif/core");
|
|
3
4
|
let _clack_prompts = require("@clack/prompts");
|
|
4
5
|
_clack_prompts = require_runtime.__toESM(_clack_prompts);
|
|
@@ -15,8 +16,7 @@ var Autocomplete = class Autocomplete extends _oclif_core.Command {
|
|
|
15
16
|
async run() {
|
|
16
17
|
await this.parse(Autocomplete);
|
|
17
18
|
_clack_prompts.intro("Tab completion setup");
|
|
18
|
-
const
|
|
19
|
-
const detectedShell = rawShell.endsWith("zsh") ? "zsh" : rawShell.endsWith("bash") ? "bash" : null;
|
|
19
|
+
const detectedShell = require_lib_detect_shell.detectShell(process.env.SHELL ?? "");
|
|
20
20
|
let shell;
|
|
21
21
|
if (detectedShell) {
|
|
22
22
|
const confirm = await _clack_prompts.confirm({ message: `Detected shell: ${detectedShell}. Set up completion for ${detectedShell}?` });
|
|
@@ -70,7 +70,7 @@ var Autocomplete = class Autocomplete extends _oclif_core.Command {
|
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
const rcFile = shell === "zsh" ? "~/.zshrc" : "~/.bashrc";
|
|
73
|
-
const evalLine = `printf "$(twentythree autocomplete script ${shell})" >> ${rcFile}; source ${rcFile}`;
|
|
73
|
+
const evalLine = `grep -qF 'twentythree autocomplete script ${shell}' ${rcFile} || printf "$(twentythree autocomplete script ${shell})" >> ${rcFile}; source ${rcFile}`;
|
|
74
74
|
_clack_prompts.note(`Add tab completion to your shell by running:\n\n ${evalLine}\n\nThen restart your terminal or run: source ${rcFile}`, "Setup instructions");
|
|
75
75
|
_clack_prompts.outro("After setup, try: twentythree video <TAB>");
|
|
76
76
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const require_runtime = require("../../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_lib_term_map = require("../../../lib/term-map.cjs");
|
|
3
|
+
const require_lib_base_command = require("../../../lib/base-command.cjs");
|
|
4
|
+
const require_lib_output = require("../../../lib/output.cjs");
|
|
5
|
+
let _oclif_core = require("@oclif/core");
|
|
6
|
+
let chalk = require("chalk");
|
|
7
|
+
chalk = require_runtime.__toESM(chalk);
|
|
8
|
+
//#region src/commands/video/section/generate.ts
|
|
9
|
+
/**
|
|
10
|
+
* Video section generate command — AI-generates sections from a video transcript.
|
|
11
|
+
*
|
|
12
|
+
* Replaces all existing sections. Requires a transcript to be available.
|
|
13
|
+
*/
|
|
14
|
+
var VideoSectionGenerate = class VideoSectionGenerate extends require_lib_base_command.AuthenticatedCommand {
|
|
15
|
+
static description = "Automatically generate sections for a video using AI (requires transcript)";
|
|
16
|
+
static examples = ["<%= config.bin %> video section generate 12345", "<%= config.bin %> video section generate 12345 --json"];
|
|
17
|
+
static enableJsonFlag = true;
|
|
18
|
+
static agentMetadata = {
|
|
19
|
+
api_endpoint: "POST /photo/section/generate",
|
|
20
|
+
auth_scope: "write",
|
|
21
|
+
output_shape: { type: "key-value" },
|
|
22
|
+
side_effects: "creates"
|
|
23
|
+
};
|
|
24
|
+
static flags = { ...require_lib_base_command.AuthenticatedCommand.baseFlags };
|
|
25
|
+
static args = { id: _oclif_core.Args.string({
|
|
26
|
+
description: "Video ID",
|
|
27
|
+
required: true
|
|
28
|
+
}) };
|
|
29
|
+
async run() {
|
|
30
|
+
const { args } = await this.parse(VideoSectionGenerate);
|
|
31
|
+
this.printWorkspaceHeader();
|
|
32
|
+
const { data, error } = await this.apiClient.POST("/photo/section/generate", {
|
|
33
|
+
body: { photo_id: Number(args.id) },
|
|
34
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
35
|
+
});
|
|
36
|
+
if (error) this.error(require_lib_term_map.applyCliTerms(require_lib_output.formatApiError(error)), { exit: 1 });
|
|
37
|
+
this.log(chalk.default.green(`Sections generated for video ${args.id}`));
|
|
38
|
+
if (this.jsonEnabled()) return require_lib_output.formatJsonOutput({
|
|
39
|
+
ok: true,
|
|
40
|
+
data,
|
|
41
|
+
summary: `Sections generated for video ${args.id}`,
|
|
42
|
+
breadcrumbs: [
|
|
43
|
+
{ domain: this.activeWorkspace.domain },
|
|
44
|
+
{
|
|
45
|
+
resource: "video",
|
|
46
|
+
id: args.id
|
|
47
|
+
},
|
|
48
|
+
{ resource: "section" }
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
module.exports = VideoSectionGenerate;
|
|
@@ -7,6 +7,7 @@ let _oclif_core = require("@oclif/core");
|
|
|
7
7
|
let chalk = require("chalk");
|
|
8
8
|
chalk = require_runtime.__toESM(chalk);
|
|
9
9
|
let _clack_prompts = require("@clack/prompts");
|
|
10
|
+
_clack_prompts = require_runtime.__toESM(_clack_prompts);
|
|
10
11
|
//#region src/lib/base-command.ts
|
|
11
12
|
var BaseCommand = class extends _oclif_core.Command {
|
|
12
13
|
static enableJsonFlag = true;
|
|
@@ -71,7 +72,7 @@ var BaseCommand = class extends _oclif_core.Command {
|
|
|
71
72
|
const result = require_auth_workspace_config.findWorkspace(workspaceFlagValue, require_auth_workspace_config.getWorkspaces());
|
|
72
73
|
if (result === null) this.error(`No workspace matching '${workspaceFlagValue}' found — run \`twentythree workspace list\` to see available workspaces`, { exit: 1 });
|
|
73
74
|
else if (Array.isArray(result)) {
|
|
74
|
-
const chosen = await
|
|
75
|
+
const chosen = await _clack_prompts.select({
|
|
75
76
|
message: `Multiple workspaces match '${workspaceFlagValue}'. Select one:`,
|
|
76
77
|
options: result.map((w) => ({
|
|
77
78
|
value: w.domain,
|
|
@@ -79,7 +80,9 @@ var BaseCommand = class extends _oclif_core.Command {
|
|
|
79
80
|
}))
|
|
80
81
|
});
|
|
81
82
|
if (typeof chosen === "symbol") this.error("Workspace selection cancelled", { exit: 1 });
|
|
82
|
-
|
|
83
|
+
const found = require_auth_workspace_config.getWorkspaceForDomain(chosen);
|
|
84
|
+
if (!found) this.error(`Workspace '${chosen}' could not be resolved — try running \`twentythree workspace list\``, { exit: 1 });
|
|
85
|
+
resolved = found;
|
|
83
86
|
} else resolved = result;
|
|
84
87
|
} else {
|
|
85
88
|
const activeDomain = require_auth_workspace_config.getActiveWorkspace();
|
|
@@ -100,6 +103,31 @@ var BaseCommand = class extends _oclif_core.Command {
|
|
|
100
103
|
token: this.activeWorkspace.bearer_token || void 0
|
|
101
104
|
});
|
|
102
105
|
}
|
|
106
|
+
async catch(err) {
|
|
107
|
+
if (!process.stdin.isTTY) return super.catch(err);
|
|
108
|
+
if (err.constructor.name !== "FailedFlagValidationError") return super.catch(err);
|
|
109
|
+
const flagNames = [...err.message.matchAll(/Missing required flag ([^\n]+)/g)].map((m) => m[1]);
|
|
110
|
+
if (flagNames.length === 0) return super.catch(err);
|
|
111
|
+
const inputFlags = err.parse?.input?.flags ?? {};
|
|
112
|
+
_clack_prompts.intro("Missing required input");
|
|
113
|
+
const extraArgv = [];
|
|
114
|
+
for (const flagName of flagNames) {
|
|
115
|
+
const flagDef = inputFlags[flagName];
|
|
116
|
+
const label = flagDef?.description ?? flagDef?.summary ?? flagName;
|
|
117
|
+
const value = await _clack_prompts.text({
|
|
118
|
+
message: label,
|
|
119
|
+
validate: (v) => !v || v.trim().length === 0 ? "Value is required" : void 0
|
|
120
|
+
});
|
|
121
|
+
if (_clack_prompts.isCancel(value)) {
|
|
122
|
+
_clack_prompts.cancel("Cancelled");
|
|
123
|
+
throw new _oclif_core.Errors.CLIError("Cancelled", { exit: 0 });
|
|
124
|
+
}
|
|
125
|
+
extraArgv.push(`--${flagName}`, value);
|
|
126
|
+
}
|
|
127
|
+
_clack_prompts.outro("Running command...");
|
|
128
|
+
const newArgv = [...this.argv ?? [], ...extraArgv];
|
|
129
|
+
await this.config.runCommand(this.id, newArgv);
|
|
130
|
+
}
|
|
103
131
|
/**
|
|
104
132
|
* Print the [domain] workspace header in dim style.
|
|
105
133
|
* Call at the top of every command's run() method (AUTH-04).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/lib/detect-shell.ts
|
|
3
|
+
/**
|
|
4
|
+
* Detect shell from the $SHELL environment variable.
|
|
5
|
+
* Returns 'zsh', 'bash', or null for unrecognized / unset shells.
|
|
6
|
+
*/
|
|
7
|
+
function detectShell(shellEnv) {
|
|
8
|
+
if (shellEnv.endsWith("zsh")) return "zsh";
|
|
9
|
+
if (shellEnv.endsWith("bash")) return "bash";
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
exports.detectShell = detectShell;
|