@sixb/cli-core 0.1.8 → 0.1.10
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 +21 -16
- package/dist/api-client.d.ts +3 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/arguments.d.ts +4 -2
- package/dist/arguments.d.ts.map +1 -1
- package/dist/bin/sixb +26 -0
- package/dist/commands/data.d.ts +5 -0
- package/dist/commands/data.d.ts.map +1 -0
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/memory.d.ts +3 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/metadata.d.ts +14 -5
- package/dist/commands/metadata.d.ts.map +1 -1
- package/dist/commands/models.d.ts +79 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/commands/objects.d.ts.map +1 -1
- package/dist/commands/runs.d.ts +8 -1
- package/dist/commands/runs.d.ts.map +1 -1
- package/dist/commands/shared.d.ts +4 -0
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/system.d.ts +21 -0
- package/dist/commands/system.d.ts.map +1 -0
- package/dist/commands/users.d.ts +3 -0
- package/dist/commands/users.d.ts.map +1 -0
- package/dist/commands/workflows.d.ts.map +1 -1
- package/dist/generated/sixb.mjs +2547 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +800 -62
- package/dist/index.js.map +24 -18
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/memory.d.ts +21 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/policies.d.ts +14 -0
- package/dist/policies.d.ts.map +1 -1
- package/dist/run.d.ts +1 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/sandbox-cli.d.ts +8 -0
- package/dist/sandbox-cli.d.ts.map +1 -0
- package/package.json +15 -1
- package/src/api-client.ts +83 -4
- package/src/arguments.ts +17 -6
- package/src/bin/sixb +26 -0
- package/src/commands/actions.ts +5 -5
- package/src/commands/data.ts +90 -0
- package/src/commands/files.ts +27 -3
- package/src/commands/index.ts +24 -0
- package/src/commands/memory.ts +358 -0
- package/src/commands/metadata.ts +137 -26
- package/src/commands/models.ts +508 -0
- package/src/commands/objects.ts +47 -2
- package/src/commands/ontology.ts +2 -2
- package/src/commands/runs.ts +22 -16
- package/src/commands/shared.ts +17 -7
- package/src/commands/system.ts +67 -0
- package/src/commands/telemetry.ts +2 -2
- package/src/commands/users.ts +87 -0
- package/src/commands/workflows.ts +12 -6
- package/src/generated/sixb.mjs +2547 -0
- package/src/index.ts +12 -3
- package/src/main.ts +39 -0
- package/src/memory.ts +30 -0
- package/src/output.ts +21 -0
- package/src/policies.ts +5 -0
- package/src/run.ts +3 -2
- package/src/sandbox-cli.ts +18 -0
package/src/commands/shared.ts
CHANGED
|
@@ -4,6 +4,12 @@ import { fail } from "../output"
|
|
|
4
4
|
|
|
5
5
|
export type QueryOptions = Record<string, string | undefined>
|
|
6
6
|
|
|
7
|
+
/** An id as one segment of an API path: `.` or `..` would name another route. */
|
|
8
|
+
export function idSegment(value: string | undefined): string {
|
|
9
|
+
if (!value || value === "." || value === "..") fail(`'${value ?? ""}' is not an id.`)
|
|
10
|
+
return encodeURIComponent(value)
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
/** Parse flag/value pairs into API query parameters. */
|
|
8
14
|
|
|
9
15
|
export function parseQueryOptions(
|
|
@@ -55,13 +61,7 @@ export function singleFileOption(args: readonly string[], command: string): stri
|
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export async function readJson(source: string): Promise<unknown> {
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
text = source === "-" ? await readStdin() : await readFile(source, "utf8")
|
|
61
|
-
} catch (error) {
|
|
62
|
-
if (isFileError(error, "ENOENT")) fail(`JSON file '${source}' does not exist.`)
|
|
63
|
-
throw error
|
|
64
|
-
}
|
|
64
|
+
const text = await readText(source, "JSON file")
|
|
65
65
|
try {
|
|
66
66
|
return JSON.parse(text)
|
|
67
67
|
} catch {
|
|
@@ -74,6 +74,16 @@ export async function readJson(source: string): Promise<unknown> {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** A file's text, or standard input's when the source is `-`. */
|
|
78
|
+
export async function readText(source: string, noun: string): Promise<string> {
|
|
79
|
+
try {
|
|
80
|
+
return source === "-" ? await readStdin() : await readFile(source, "utf8")
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (isFileError(error, "ENOENT")) fail(`${noun} '${source}' does not exist.`)
|
|
83
|
+
throw error
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
export function asRecord(value: unknown): Record<string, unknown> {
|
|
78
88
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
79
89
|
? (value as Record<string, unknown>)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises"
|
|
2
|
+
import { isHelp } from "../arguments"
|
|
3
|
+
import { CliError, EXIT_API, fail, INSTANCE_CLI_VERSION, writeJson, writeText } from "../output"
|
|
4
|
+
import { createInstanceApiClient, type SandboxInstanceCliMode } from "../run"
|
|
5
|
+
import { asRecord, isFileError } from "./shared"
|
|
6
|
+
|
|
7
|
+
/** The sandbox runtime `sixb status` reports and the agent worker's preflight expects. */
|
|
8
|
+
export const AGENT_RUNTIME_PROFILE = "sixb-agent-runtime/v1" as const
|
|
9
|
+
|
|
10
|
+
/** Stable JSON emitted by `sixb status` in a sandbox and consumed by worker preflight. */
|
|
11
|
+
export interface SandboxStatusReport {
|
|
12
|
+
readonly ok: true
|
|
13
|
+
readonly profile: typeof AGENT_RUNTIME_PROFILE
|
|
14
|
+
readonly cli: { readonly version: string }
|
|
15
|
+
readonly javascript: {
|
|
16
|
+
readonly name: "bun" | "node"
|
|
17
|
+
readonly version: string
|
|
18
|
+
}
|
|
19
|
+
readonly project: { readonly id: string }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function status(args: readonly string[], mode: SandboxInstanceCliMode): Promise<void> {
|
|
23
|
+
if (isHelp(args[0])) return writeText("Usage: sixb status")
|
|
24
|
+
// Its output is JSON either way: --json, which people's `sixb status` takes, changes nothing.
|
|
25
|
+
if (args.some((arg) => arg !== "--json")) fail("status accepts no arguments but --json.")
|
|
26
|
+
const project = asRecord(await createInstanceApiClient(mode).get("/api/project"))
|
|
27
|
+
if (typeof project.id !== "string" || project.id.length === 0) {
|
|
28
|
+
throw new CliError(
|
|
29
|
+
{ code: "invalid_api_response", message: "The Sixb API returned an invalid project." },
|
|
30
|
+
EXIT_API
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
const report: SandboxStatusReport = {
|
|
34
|
+
ok: true,
|
|
35
|
+
profile: AGENT_RUNTIME_PROFILE,
|
|
36
|
+
cli: { version: INSTANCE_CLI_VERSION },
|
|
37
|
+
javascript: javascriptRuntime(),
|
|
38
|
+
project: { id: project.id },
|
|
39
|
+
}
|
|
40
|
+
writeJson(report)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function context(args: readonly string[]): Promise<void> {
|
|
44
|
+
if (isHelp(args[0])) return writeText("Usage: sixb context")
|
|
45
|
+
if (args.length !== 0) fail("context accepts no arguments.")
|
|
46
|
+
const path = process.env.SIXB_RUN_CONTEXT
|
|
47
|
+
if (!path) fail("SIXB_RUN_CONTEXT is not set.")
|
|
48
|
+
let text: string
|
|
49
|
+
try {
|
|
50
|
+
text = await readFile(path, "utf8")
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (isFileError(error, "ENOENT")) fail(`Run context '${path}' does not exist.`)
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
writeJson(JSON.parse(text))
|
|
57
|
+
} catch {
|
|
58
|
+
fail(`Run context '${path}' is not valid JSON.`, "invalid_json")
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function javascriptRuntime(): SandboxStatusReport["javascript"] {
|
|
63
|
+
if (typeof globalThis.Bun === "object") {
|
|
64
|
+
return { name: "bun", version: globalThis.Bun.version }
|
|
65
|
+
}
|
|
66
|
+
return { name: "node", version: process.versions.node }
|
|
67
|
+
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { fail, writeJson, writeText } from "../output"
|
|
12
12
|
import { CLI_LIMITS, DEFAULT_TELEMETRY_ORDER } from "../policies"
|
|
13
13
|
import { GROUP_HELP } from "./metadata"
|
|
14
|
-
import { normalizeWindowOptions, readJson, singleFileOption } from "./shared"
|
|
14
|
+
import { idSegment, normalizeWindowOptions, readJson, singleFileOption } from "./shared"
|
|
15
15
|
|
|
16
16
|
export async function telemetry(api: ApiClient, args: readonly string[]): Promise<void> {
|
|
17
17
|
const [sub, ...rest] = args
|
|
@@ -62,7 +62,7 @@ export async function telemetry(api: ApiClient, args: readonly string[]): Promis
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function telemetryPath(args: readonly string[], terminal: "latest" | "history"): string {
|
|
65
|
-
return `/api/objects/${
|
|
65
|
+
return `/api/objects/${idSegment(args[0])}/${idSegment(args[1])}/telemetry/${idSegment(args[2])}/${terminal}`
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function normalizeTelemetryQueryInput(input: unknown): Record<string, unknown> {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ApiClient } from "../api-client"
|
|
2
|
+
import {
|
|
3
|
+
enumValue,
|
|
4
|
+
integerInRange,
|
|
5
|
+
isHelp,
|
|
6
|
+
nonNegativeInteger,
|
|
7
|
+
parseCommandArgs,
|
|
8
|
+
requestsHelp,
|
|
9
|
+
} from "../arguments"
|
|
10
|
+
import { CliError, EXIT_API, fail, writeJson, writeText } from "../output"
|
|
11
|
+
import { CLI_LIMITS } from "../policies"
|
|
12
|
+
import { GROUP_HELP } from "./metadata"
|
|
13
|
+
import type { QueryOptions } from "./shared"
|
|
14
|
+
import { idSegment } from "./shared"
|
|
15
|
+
|
|
16
|
+
const USER_STATUSES = ["active", "suspended", "all"] as const
|
|
17
|
+
|
|
18
|
+
const LIST_FLAGS = {
|
|
19
|
+
"--status": "string",
|
|
20
|
+
"--limit": "string",
|
|
21
|
+
"--offset": "string",
|
|
22
|
+
} as const
|
|
23
|
+
|
|
24
|
+
export async function users(api: ApiClient, args: readonly string[]): Promise<void> {
|
|
25
|
+
const [sub, ...rest] = args
|
|
26
|
+
if (!sub || isHelp(sub) || requestsHelp(rest)) return writeText(GROUP_HELP.users)
|
|
27
|
+
|
|
28
|
+
if (sub === "get") {
|
|
29
|
+
const { positionals } = parseCommandArgs(rest, {}, "users get", 1)
|
|
30
|
+
const user = positionals[0]?.trim()
|
|
31
|
+
if (!user) fail("users get requires a user id or email.")
|
|
32
|
+
// Sign-in mints ids as `usr_<uuid>`, so an `@` marks an email.
|
|
33
|
+
if (!user.includes("@")) {
|
|
34
|
+
return writeJson(await api.get(`/api/users/${idSegment(user)}`))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const page = await api.get("/api/users", { email: user })
|
|
38
|
+
const [member] = isRecord(page) && Array.isArray(page.users) ? page.users : []
|
|
39
|
+
if (member === undefined) {
|
|
40
|
+
throw new CliError(
|
|
41
|
+
{ code: "not_found", message: `No member has the email '${user}'.` },
|
|
42
|
+
EXIT_API
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
return writeJson(member)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (sub === "list") {
|
|
49
|
+
const { options } = parseCommandArgs(rest, LIST_FLAGS, "users list")
|
|
50
|
+
return writeJson(await api.get("/api/users", listQuery(options)))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (sub === "search") {
|
|
54
|
+
const { positionals, options } = parseCommandArgs(rest, LIST_FLAGS, "users search", 1)
|
|
55
|
+
const search = positionals[0]?.trim()
|
|
56
|
+
if (!search) fail("users search requires the text to find in a name or email.")
|
|
57
|
+
|
|
58
|
+
return writeJson(await api.get("/api/users", { ...listQuery(options), search }))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fail(`Unknown users command '${sub}'.`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function listQuery(options: {
|
|
65
|
+
readonly "--status"?: string
|
|
66
|
+
readonly "--limit"?: string
|
|
67
|
+
readonly "--offset"?: string
|
|
68
|
+
}): QueryOptions {
|
|
69
|
+
const limit = integerInRange(
|
|
70
|
+
"--limit",
|
|
71
|
+
options["--limit"] ?? String(CLI_LIMITS.users.default),
|
|
72
|
+
1,
|
|
73
|
+
CLI_LIMITS.users.maximum
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
status: enumValue("--status", options["--status"] ?? "active", USER_STATUSES),
|
|
78
|
+
limit: String(limit),
|
|
79
|
+
...(options["--offset"] === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: { offset: String(nonNegativeInteger("--offset", options["--offset"])) }),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
86
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
87
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto"
|
|
1
2
|
import type { ApiClient } from "../api-client"
|
|
2
3
|
import { isHelp, parseCommandArgs, requestsHelp } from "../arguments"
|
|
3
|
-
import { fail, writeJson, writeText } from "../output"
|
|
4
|
+
import { fail, writeJson, writeRequestResult, writeText } from "../output"
|
|
4
5
|
import { GROUP_HELP } from "./metadata"
|
|
5
|
-
import { readJson } from "./shared"
|
|
6
|
+
import { idSegment, readJson } from "./shared"
|
|
6
7
|
|
|
7
8
|
export async function workflows(api: ApiClient, args: readonly string[]): Promise<void> {
|
|
8
9
|
const [sub, ...rest] = args
|
|
@@ -15,19 +16,24 @@ export async function workflows(api: ApiClient, args: readonly string[]): Promis
|
|
|
15
16
|
const {
|
|
16
17
|
positionals: [workflowId],
|
|
17
18
|
} = parseCommandArgs(rest, {}, "workflows get", 1)
|
|
18
|
-
return writeJson(await api.get(`/api/workflows/${
|
|
19
|
+
return writeJson(await api.get(`/api/workflows/${idSegment(workflowId)}`))
|
|
19
20
|
}
|
|
20
21
|
if (sub === "start") {
|
|
21
22
|
const {
|
|
22
23
|
positionals: [workflowId],
|
|
23
24
|
options,
|
|
24
|
-
} = parseCommandArgs(rest, { "--file": "string" }, "workflows start", 1)
|
|
25
|
+
} = parseCommandArgs(rest, { "--file": "string", "--run-id": "string" }, "workflows start", 1)
|
|
25
26
|
const input: unknown = options["--file"] ? await readJson(options["--file"]) : {}
|
|
26
27
|
if (Array.isArray(input) || typeof input !== "object" || input === null) {
|
|
27
28
|
fail("Workflow input must be a JSON object.")
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// Every start names its run, so a repeated request returns the same run instead of another.
|
|
31
|
+
const runId = options["--run-id"] ?? `run_${randomUUID()}`
|
|
32
|
+
return writeRequestResult(
|
|
33
|
+
await api.post(`/api/workflows/${idSegment(workflowId)}/runs`, {
|
|
34
|
+
input,
|
|
35
|
+
runId,
|
|
36
|
+
})
|
|
31
37
|
)
|
|
32
38
|
}
|
|
33
39
|
fail(`Unknown workflows command '${sub}'.`)
|