@devosurf/tesser 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +15 -7
- package/dist/index.js +840 -223
- package/dist/index.js.map +4 -4
- package/package.json +3 -3
- package/src/commands/doctor.test.ts +39 -0
- package/src/commands/doctor.ts +117 -0
- package/src/commands/init.test.ts +14 -1
- package/src/commands/init.ts +10 -4
- package/src/commands/project-docs.ts +6 -2
- package/src/completion.test.ts +14 -0
- package/src/completion.ts +92 -0
- package/src/config.ts +3 -1
- package/src/index.ts +226 -60
- package/src/inputs.test.ts +21 -0
- package/src/inputs.ts +64 -0
- package/src/login.test.ts +38 -0
- package/src/login.ts +109 -0
- package/src/output.ts +15 -3
- package/src/schema.test.ts +26 -0
- package/src/schema.ts +289 -0
package/src/login.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stderr as output } from "node:process";
|
|
3
|
+
import { DEFAULT_INSTANCE_URL } from "./config.js";
|
|
4
|
+
import { EXIT } from "./exit-codes.js";
|
|
5
|
+
import { CliError } from "./output.js";
|
|
6
|
+
|
|
7
|
+
export { DEFAULT_INSTANCE_URL };
|
|
8
|
+
|
|
9
|
+
export interface LoginCommandOptions {
|
|
10
|
+
token?: string;
|
|
11
|
+
tokenStdin?: boolean;
|
|
12
|
+
url?: string;
|
|
13
|
+
instance?: string;
|
|
14
|
+
saveProfile: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LoginGlobalOptions {
|
|
18
|
+
url?: string;
|
|
19
|
+
token?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveLoginInstance(cmdOpts: Pick<LoginCommandOptions, "url" | "instance">, globalOpts: Pick<LoginGlobalOptions, "url">): string {
|
|
23
|
+
return cmdOpts.url ?? cmdOpts.instance ?? globalOpts.url ?? DEFAULT_INSTANCE_URL;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function resolveLoginInputs(
|
|
27
|
+
cmdOpts: LoginCommandOptions,
|
|
28
|
+
globalOpts: LoginGlobalOptions,
|
|
29
|
+
): Promise<{ instance: string; token: string }> {
|
|
30
|
+
if ((cmdOpts.token !== undefined || globalOpts.token !== undefined) && cmdOpts.tokenStdin === true) {
|
|
31
|
+
throw new CliError(EXIT.USAGE, "use either --token/--token-stdin, not both");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const token = cmdOpts.token ?? globalOpts.token ?? (cmdOpts.tokenStdin === true ? await readTrimmedStdin("token") : undefined);
|
|
35
|
+
const instance = resolveLoginInstance(cmdOpts, globalOpts);
|
|
36
|
+
if (token !== undefined && token.length > 0) return { instance, token };
|
|
37
|
+
|
|
38
|
+
if (isInteractive()) {
|
|
39
|
+
const promptedInstance = await promptLine("Instance URL", instance);
|
|
40
|
+
const promptedToken = await promptSecret("API token");
|
|
41
|
+
if (promptedToken.length === 0) throw new CliError(EXIT.USAGE, "empty API token");
|
|
42
|
+
return { instance: promptedInstance || instance, token: promptedToken };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new CliError(
|
|
46
|
+
EXIT.USAGE,
|
|
47
|
+
"missing API token — pass --token-stdin, set TESSER_TOKEN, or run `tesser login` in an interactive terminal",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isInteractive(): boolean {
|
|
52
|
+
return input.isTTY === true && output.isTTY === true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function readTrimmedStdin(label: string): Promise<string> {
|
|
56
|
+
const chunks: Buffer[] = [];
|
|
57
|
+
for await (const chunk of input) chunks.push(chunk as Buffer);
|
|
58
|
+
const value = Buffer.concat(chunks).toString("utf8").trim();
|
|
59
|
+
if (value.length === 0) throw new CliError(EXIT.USAGE, `empty ${label} on stdin`);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function promptLine(label: string, defaultValue: string): Promise<string> {
|
|
64
|
+
const rl = createInterface({ input, output });
|
|
65
|
+
try {
|
|
66
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
67
|
+
return (await rl.question(`${label}${suffix}: `)).trim() || defaultValue;
|
|
68
|
+
} finally {
|
|
69
|
+
rl.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function promptSecret(label: string): Promise<string> {
|
|
74
|
+
if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") {
|
|
75
|
+
throw new CliError(EXIT.USAGE, "cannot prompt for a token without an interactive terminal; use --token-stdin");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
output.write(`${label}: `);
|
|
79
|
+
input.setRawMode(true);
|
|
80
|
+
input.resume();
|
|
81
|
+
input.setEncoding("utf8");
|
|
82
|
+
|
|
83
|
+
return await new Promise<string>((resolve, reject) => {
|
|
84
|
+
let value = "";
|
|
85
|
+
const cleanup = () => {
|
|
86
|
+
input.off("data", onData);
|
|
87
|
+
input.setRawMode(false);
|
|
88
|
+
output.write("\n");
|
|
89
|
+
};
|
|
90
|
+
const onData = (char: string) => {
|
|
91
|
+
if (char === "\u0003") {
|
|
92
|
+
cleanup();
|
|
93
|
+
reject(new CliError(EXIT.USAGE, "login cancelled"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (char === "\r" || char === "\n") {
|
|
97
|
+
cleanup();
|
|
98
|
+
resolve(value.trim());
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (char === "\u007f" || char === "\b") {
|
|
102
|
+
value = value.slice(0, -1);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
value += char;
|
|
106
|
+
};
|
|
107
|
+
input.on("data", onData);
|
|
108
|
+
});
|
|
109
|
+
}
|
package/src/output.ts
CHANGED
|
@@ -3,13 +3,24 @@
|
|
|
3
3
|
|
|
4
4
|
import { EXIT, type ExitCode } from "./exit-codes.js";
|
|
5
5
|
|
|
6
|
+
export type OutputFormat = "text" | "json" | "ndjson";
|
|
7
|
+
|
|
6
8
|
export class Output {
|
|
7
|
-
|
|
9
|
+
readonly format: OutputFormat;
|
|
10
|
+
|
|
11
|
+
constructor(formatOrJson: OutputFormat | boolean) {
|
|
12
|
+
this.format = typeof formatOrJson === "boolean" ? (formatOrJson ? "json" : "text") : formatOrJson;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get json(): boolean {
|
|
16
|
+
return this.format !== "text";
|
|
17
|
+
}
|
|
8
18
|
|
|
9
19
|
/** Emit the command's data result. `human` renders the no-JSON form. */
|
|
10
20
|
data(value: unknown, human?: (v: never) => string): void {
|
|
11
21
|
if (this.json) {
|
|
12
|
-
|
|
22
|
+
const pretty = this.format === "json";
|
|
23
|
+
process.stdout.write(JSON.stringify(value, null, pretty ? 2 : 0) + "\n");
|
|
13
24
|
} else {
|
|
14
25
|
process.stdout.write((human ? human(value as never) : JSON.stringify(value, null, 2)) + "\n");
|
|
15
26
|
}
|
|
@@ -22,7 +33,8 @@ export class Output {
|
|
|
22
33
|
|
|
23
34
|
fail(code: ExitCode, message: string, extra?: Record<string, unknown>): never {
|
|
24
35
|
if (this.json) {
|
|
25
|
-
|
|
36
|
+
const pretty = this.format === "json";
|
|
37
|
+
process.stdout.write(JSON.stringify({ error: { code, message, ...extra } }, null, pretty ? 2 : 0) + "\n");
|
|
26
38
|
} else {
|
|
27
39
|
process.stderr.write(`error: ${message}\n`);
|
|
28
40
|
if (extra) process.stderr.write(JSON.stringify(extra, null, 2) + "\n");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { cliSchema } from "./schema.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe("CLI schema", () => {
|
|
6
|
+
test("is available without runtime inputs and includes agent-critical contracts", () => {
|
|
7
|
+
const schema = cliSchema("0.0.0") as {
|
|
8
|
+
clispec: string;
|
|
9
|
+
global_args: Array<{ name: string }>;
|
|
10
|
+
commands: Array<{ name: string; mutating: boolean }>;
|
|
11
|
+
errors: Array<{ kind: string; exit_code: number }>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
expect(schema.clispec).toBe("0.2");
|
|
15
|
+
expect(schema.global_args.map((arg) => arg.name)).toContain("--output");
|
|
16
|
+
expect(schema.commands.find((command) => command.name === "runs list")?.mutating).toBe(false);
|
|
17
|
+
expect(schema.commands.find((command) => command.name === "secrets set")?.mutating).toBe(true);
|
|
18
|
+
expect(schema.errors.find((error) => error.kind === "halted_credentials")?.exit_code).toBe(4);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("can be narrowed to a command subtree", () => {
|
|
22
|
+
const schema = cliSchema("0.0.0", "runs") as { commands: Array<{ name: string }> };
|
|
23
|
+
expect(schema.commands.length).toBeGreaterThan(1);
|
|
24
|
+
expect(schema.commands.every((command) => command.name.startsWith("runs"))).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
});
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { EXIT } from "./exit-codes.js";
|
|
2
|
+
|
|
3
|
+
const errorRows = [
|
|
4
|
+
["error", EXIT.ERROR, true, "Unexpected internal error"],
|
|
5
|
+
["usage", EXIT.USAGE, false, "Bad arguments or missing Project context"],
|
|
6
|
+
["tests_failed", EXIT.TESTS_FAILED, false, "Local validation failed"],
|
|
7
|
+
["halted_credentials", EXIT.HALTED_CREDENTIALS, false, "Deploy/connect halted on missing credentials"],
|
|
8
|
+
["auth", EXIT.AUTH, false, "Cannot reach or authenticate against the Instance"],
|
|
9
|
+
["not_found", EXIT.NOT_FOUND, false, "Requested resource does not exist"],
|
|
10
|
+
["conflict", EXIT.CONFLICT, false, "Operation conflicts with current state"],
|
|
11
|
+
["deploy_failed", EXIT.DEPLOY_FAILED, false, "Build, test gate, or deploy failed"],
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
const commands = [
|
|
15
|
+
{
|
|
16
|
+
name: "schema",
|
|
17
|
+
description: "Emit the machine-readable CLI contract without auth, config, or network",
|
|
18
|
+
mutating: false,
|
|
19
|
+
args: [{ name: "command", type: "string[]", required: false }],
|
|
20
|
+
output_fields: ["clispec", "name", "version", "global_args", "commands", "errors", "outcomes"],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "completion",
|
|
24
|
+
description: "Print a shell completion script",
|
|
25
|
+
mutating: false,
|
|
26
|
+
args: [{ name: "shell", type: "string", enum: ["bash", "zsh"], required: true }],
|
|
27
|
+
output_fields: [],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "doctor",
|
|
31
|
+
description: "Agent preflight for local Project, auth, pins, and optional Instance health",
|
|
32
|
+
mutating: false,
|
|
33
|
+
args: [{ name: "--no-network", type: "boolean", required: false, default: false }],
|
|
34
|
+
output_fields: ["ok", "cliVersion", "node", "cwd", "project", "projectRoot", "instance", "auth", "checks", "network"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "init",
|
|
38
|
+
description: "Scaffold a new Project",
|
|
39
|
+
mutating: true,
|
|
40
|
+
args: [
|
|
41
|
+
{ name: "name", type: "string", required: true },
|
|
42
|
+
{ name: "--dir", type: "string", required: false },
|
|
43
|
+
{ name: "--instance", type: "string", required: false },
|
|
44
|
+
{ name: "--force", type: "boolean", required: false, default: false },
|
|
45
|
+
],
|
|
46
|
+
output_fields: ["created", "tesserVersion", "docs", "next"],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "upgrade",
|
|
50
|
+
description: "Pin Tesser packages to this CLI version and refresh generated Project docs",
|
|
51
|
+
mutating: true,
|
|
52
|
+
args: [],
|
|
53
|
+
output_fields: ["upgraded", "tesserVersion", "docs", "next"],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "login",
|
|
57
|
+
description: "Verify and store Instance API credentials in a local profile",
|
|
58
|
+
mutating: true,
|
|
59
|
+
args: [
|
|
60
|
+
{ name: "--url", type: "string", required: false, default: "http://localhost:8377" },
|
|
61
|
+
{ name: "--token-stdin", type: "boolean", required: false, default: false },
|
|
62
|
+
{ name: "--token", type: "string", required: false, secret: true, deprecated: "Prefer --token-stdin" },
|
|
63
|
+
{ name: "--save-profile", type: "string", required: false, default: "default" },
|
|
64
|
+
],
|
|
65
|
+
output_fields: ["profile", "url"],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "link",
|
|
69
|
+
description: "Register this Project on the Instance and wire git-sync",
|
|
70
|
+
mutating: true,
|
|
71
|
+
args: [{ name: "--repo", type: "string", required: false }],
|
|
72
|
+
output_fields: ["id", "name", "repoUrl", "deployKeyPublic", "webhookSetupUrl"],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "status",
|
|
76
|
+
description: "Instance and Project deploy status",
|
|
77
|
+
mutating: false,
|
|
78
|
+
args: [],
|
|
79
|
+
output_fields: ["instance", "health", "status", "project", "deploy"],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "test",
|
|
83
|
+
description: "Run colocated tests plus generated smoke tests",
|
|
84
|
+
mutating: false,
|
|
85
|
+
args: [
|
|
86
|
+
{ name: "--smoke-only", type: "boolean", required: false, default: false },
|
|
87
|
+
{ name: "--automation", type: "string", required: false },
|
|
88
|
+
],
|
|
89
|
+
output_fields: ["passed", "colocated", "smoke", "failures"],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "build",
|
|
93
|
+
description: "Build and statically extract automation manifests locally",
|
|
94
|
+
mutating: false,
|
|
95
|
+
args: [],
|
|
96
|
+
output_fields: ["automations"],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "dev",
|
|
100
|
+
description: "Run a local Instance with embedded Postgres and deploy-on-change",
|
|
101
|
+
mutating: true,
|
|
102
|
+
args: [
|
|
103
|
+
{ name: "--port", type: "integer", required: false, default: 8377 },
|
|
104
|
+
{ name: "--no-watch", type: "boolean", required: false, default: false },
|
|
105
|
+
],
|
|
106
|
+
output_fields: [],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "deploy",
|
|
110
|
+
description: "Sync git or a local tree to the Instance and promote on green",
|
|
111
|
+
mutating: true,
|
|
112
|
+
args: [
|
|
113
|
+
{ name: "--ref", type: "string", required: false },
|
|
114
|
+
{ name: "--local", type: "boolean", required: false, default: false },
|
|
115
|
+
{ name: "--no-wait", type: "boolean", required: false, default: false },
|
|
116
|
+
],
|
|
117
|
+
output_fields: ["status", "report", "live"],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "harness connect claude-code",
|
|
121
|
+
description: "Connect Claude Code as a brokered Harness (alias of auth claude-code)",
|
|
122
|
+
mutating: true,
|
|
123
|
+
args: [
|
|
124
|
+
{ name: "--connect", type: "string", required: true },
|
|
125
|
+
{ name: "--mode", type: "string", enum: ["subscription", "apiKey"], required: false, default: "subscription" },
|
|
126
|
+
{ name: "--token-stdin", type: "boolean", required: false, default: false, secret: true },
|
|
127
|
+
{ name: "--from-env", type: "string", required: false, secret: true },
|
|
128
|
+
{ name: "--scope", type: "string", enum: ["workspace", "per_user"], required: false, default: "workspace" },
|
|
129
|
+
{ name: "--end-user-id", type: "string", required: false },
|
|
130
|
+
{ name: "--bin", type: "string", required: false, default: "claude" },
|
|
131
|
+
],
|
|
132
|
+
output_fields: ["connector", "mode", "connected"],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "harness connect pi",
|
|
136
|
+
description: "Connect Pi as a brokered Harness (alias of auth pi)",
|
|
137
|
+
mutating: true,
|
|
138
|
+
args: [
|
|
139
|
+
{ name: "--connect", type: "string", required: true },
|
|
140
|
+
{ name: "--mode", type: "string", enum: ["anthropicOAuth", "anthropicApiKey"], required: false, default: "anthropicOAuth" },
|
|
141
|
+
{ name: "--token-stdin", type: "boolean", required: false, default: false, secret: true },
|
|
142
|
+
{ name: "--from-env", type: "string", required: false, secret: true },
|
|
143
|
+
{ name: "--scope", type: "string", enum: ["workspace", "per_user"], required: false, default: "workspace" },
|
|
144
|
+
{ name: "--end-user-id", type: "string", required: false },
|
|
145
|
+
],
|
|
146
|
+
output_fields: ["connector", "mode", "connected"],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "connect",
|
|
150
|
+
description: "Mint or poll the human connect link for missing credentials",
|
|
151
|
+
mutating: true,
|
|
152
|
+
args: [
|
|
153
|
+
{ name: "--wait", type: "boolean", required: false, default: false },
|
|
154
|
+
{ name: "--status", type: "string", required: false },
|
|
155
|
+
],
|
|
156
|
+
output_fields: ["url", "token", "requirements", "status"],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "secrets list",
|
|
160
|
+
description: "List Secret names only",
|
|
161
|
+
mutating: false,
|
|
162
|
+
args: [],
|
|
163
|
+
output_fields: ["secrets"],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "secrets set",
|
|
167
|
+
description: "Set a Secret from stdin; never accepts the value on argv",
|
|
168
|
+
mutating: true,
|
|
169
|
+
args: [
|
|
170
|
+
{ name: "name", type: "string", required: true },
|
|
171
|
+
{ name: "--value-stdin", type: "boolean", required: true, secret: true },
|
|
172
|
+
],
|
|
173
|
+
output_fields: ["set", "changed"],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "secrets rm",
|
|
177
|
+
description: "Delete a Secret by name",
|
|
178
|
+
mutating: true,
|
|
179
|
+
args: [{ name: "name", type: "string", required: true }],
|
|
180
|
+
output_fields: ["deleted", "changed"],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "runs list",
|
|
184
|
+
description: "List runs with bounded output",
|
|
185
|
+
mutating: false,
|
|
186
|
+
args: [
|
|
187
|
+
{ name: "--automation", type: "string", required: false },
|
|
188
|
+
{ name: "--status", type: "string", required: false },
|
|
189
|
+
{ name: "--limit", type: "integer", required: false, default: 25 },
|
|
190
|
+
{ name: "--offset", type: "integer", required: false, default: 0 },
|
|
191
|
+
{ name: "--fields", type: "string", required: false },
|
|
192
|
+
],
|
|
193
|
+
output_fields: ["runs", "total", "limit", "offset", "truncated"],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "runs show",
|
|
197
|
+
description: "Show a run with bounded log output",
|
|
198
|
+
mutating: false,
|
|
199
|
+
args: [
|
|
200
|
+
{ name: "runId", type: "string", required: true },
|
|
201
|
+
{ name: "--log-limit", type: "integer", required: false, default: 100 },
|
|
202
|
+
{ name: "--log-offset", type: "integer", required: false, default: 0 },
|
|
203
|
+
],
|
|
204
|
+
output_fields: ["run", "steps", "logs", "logsTotal", "logsLimit", "logsOffset", "logsTruncated"],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "runs trigger",
|
|
208
|
+
description: "Manually trigger an Automation",
|
|
209
|
+
mutating: true,
|
|
210
|
+
args: [
|
|
211
|
+
{ name: "automation", type: "string", required: true },
|
|
212
|
+
{ name: "--input", type: "json", required: false },
|
|
213
|
+
{ name: "--input-file", type: "path|-", required: false },
|
|
214
|
+
{ name: "--env", type: "string", required: false, default: "production" },
|
|
215
|
+
],
|
|
216
|
+
output_fields: ["runId"],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "runs signal",
|
|
220
|
+
description: "Deliver a Signal to a suspended run",
|
|
221
|
+
mutating: true,
|
|
222
|
+
args: [
|
|
223
|
+
{ name: "runId", type: "string", required: true },
|
|
224
|
+
{ name: "name", type: "string", required: true },
|
|
225
|
+
{ name: "--payload", type: "json", required: false },
|
|
226
|
+
{ name: "--payload-file", type: "path|-", required: false },
|
|
227
|
+
],
|
|
228
|
+
output_fields: ["delivered"],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "runs cancel",
|
|
232
|
+
description: "Cancel a queued or suspended run",
|
|
233
|
+
mutating: true,
|
|
234
|
+
args: [{ name: "runId", type: "string", required: true }],
|
|
235
|
+
output_fields: ["cancelled", "changed"],
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "logs",
|
|
239
|
+
description: "Print bounded run logs or follow until settle",
|
|
240
|
+
mutating: false,
|
|
241
|
+
args: [
|
|
242
|
+
{ name: "runId", type: "string", required: true },
|
|
243
|
+
{ name: "--follow", type: "boolean", required: false, default: false },
|
|
244
|
+
{ name: "--limit", type: "integer", required: false, default: 100 },
|
|
245
|
+
{ name: "--offset", type: "integer", required: false, default: 0 },
|
|
246
|
+
],
|
|
247
|
+
output_fields: ["run", "logs", "logsTotal", "logsLimit", "logsOffset", "logsTruncated"],
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "replay",
|
|
251
|
+
description: "Freeze a real run as a regression fixture and test",
|
|
252
|
+
mutating: true,
|
|
253
|
+
args: [{ name: "runId", type: "string", required: true }],
|
|
254
|
+
output_fields: ["runId", "fixture", "test"],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "rollback",
|
|
258
|
+
description: "Re-point an Automation alias to a prior immutable version",
|
|
259
|
+
mutating: true,
|
|
260
|
+
args: [
|
|
261
|
+
{ name: "automation", type: "string", required: true },
|
|
262
|
+
{ name: "--to", type: "integer", required: true },
|
|
263
|
+
{ name: "--env", type: "string", required: false, default: "production" },
|
|
264
|
+
],
|
|
265
|
+
output_fields: ["rolledBack", "automation", "env", "toVersion", "changed"],
|
|
266
|
+
},
|
|
267
|
+
] as const;
|
|
268
|
+
|
|
269
|
+
export function cliSchema(version: string, commandPrefix?: string | undefined): Record<string, unknown> {
|
|
270
|
+
const filtered = commandPrefix
|
|
271
|
+
? commands.filter((command) => command.name === commandPrefix || command.name.startsWith(`${commandPrefix} `))
|
|
272
|
+
: commands;
|
|
273
|
+
return {
|
|
274
|
+
clispec: "0.2",
|
|
275
|
+
name: "tesser",
|
|
276
|
+
version,
|
|
277
|
+
description: "Code-first, agent-native automation CLI",
|
|
278
|
+
global_args: [
|
|
279
|
+
{ name: "--json", type: "boolean", default: false, description: "Alias for --output json" },
|
|
280
|
+
{ name: "--output", short: "-o", type: "string", enum: ["auto", "text", "json", "ndjson"], default: "auto" },
|
|
281
|
+
{ name: "--url", type: "string", required: false },
|
|
282
|
+
{ name: "--token", type: "string", required: false, secret: true },
|
|
283
|
+
{ name: "--profile", type: "string", required: false },
|
|
284
|
+
],
|
|
285
|
+
commands: filtered,
|
|
286
|
+
errors: errorRows.map(([kind, exitCode, retryable, description]) => ({ kind, exit_code: exitCode, retryable, description })),
|
|
287
|
+
outcomes: [],
|
|
288
|
+
};
|
|
289
|
+
}
|