@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
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { lstat, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises"
|
|
2
|
+
import { dirname, join } from "node:path"
|
|
3
|
+
import { isHelp } from "../arguments"
|
|
4
|
+
import {
|
|
5
|
+
AGENT_MEMORY_ENTRY,
|
|
6
|
+
AGENT_MEMORY_MANIFEST,
|
|
7
|
+
AGENT_MEMORY_STARTER,
|
|
8
|
+
type AgentMemoryManifest,
|
|
9
|
+
type AgentMemoryManifestRepo,
|
|
10
|
+
type AgentMemoryRepo,
|
|
11
|
+
agentMemoryFileHash,
|
|
12
|
+
} from "../memory"
|
|
13
|
+
import { CliError, EXIT_API, fail, writeJson, writeText } from "../output"
|
|
14
|
+
import { createInstanceApiClient, type SandboxInstanceCliMode } from "../run"
|
|
15
|
+
import { asRecord, isFileError } from "./shared"
|
|
16
|
+
|
|
17
|
+
const HELP = `Usage:
|
|
18
|
+
sixb memory save <repo> -m <message> Save local edits as one revision
|
|
19
|
+
sixb memory pull [repo] Bring the local copy up to the latest revision
|
|
20
|
+
|
|
21
|
+
<repo> is user or project. Files live under $SIXB_MEMORY_DIR/<repo>/.
|
|
22
|
+
|
|
23
|
+
A save is rejected for files another conversation changed since they were loaded. Their
|
|
24
|
+
current contents are written next to yours as <file>.theirs: merge each into its file, delete
|
|
25
|
+
it, then save again. Changes others made to other files are brought in by save and pull.`
|
|
26
|
+
|
|
27
|
+
interface RemoteRepo {
|
|
28
|
+
readonly revision: number
|
|
29
|
+
readonly files: ReadonlyMap<string, string>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface MemoryConflict {
|
|
33
|
+
readonly path: string
|
|
34
|
+
/** Where their version was written, or `null` when another save deleted the file. */
|
|
35
|
+
readonly theirs: string | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const THEIRS = ".theirs"
|
|
39
|
+
const CONFLICT_HINT = "Merge each .theirs file into its file, delete it, then save again."
|
|
40
|
+
|
|
41
|
+
export async function memory(args: readonly string[], mode: SandboxInstanceCliMode): Promise<void> {
|
|
42
|
+
const [sub, ...rest] = args
|
|
43
|
+
if (!sub || isHelp(sub) || rest.some(isHelp)) return writeText(HELP)
|
|
44
|
+
const local = localMemory()
|
|
45
|
+
if (sub === "save") return save(local, rest, mode)
|
|
46
|
+
if (sub === "pull") return pull(local, rest, mode)
|
|
47
|
+
fail(`Unknown memory command '${sub}'. Run 'sixb memory --help'.`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function save(
|
|
51
|
+
local: LocalMemory,
|
|
52
|
+
args: readonly string[],
|
|
53
|
+
mode: SandboxInstanceCliMode
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const { repo, message } = parseSaveArgs(args)
|
|
56
|
+
const manifest = await local.readManifest()
|
|
57
|
+
const loaded = manifest.repos[repo]
|
|
58
|
+
if (!loaded)
|
|
59
|
+
fail(
|
|
60
|
+
`Memory repo '${repo}' is not installed.`,
|
|
61
|
+
"invalid_arguments",
|
|
62
|
+
`Run 'sixb memory pull ${repo}'.`
|
|
63
|
+
)
|
|
64
|
+
const files = await local.readRepo(repo)
|
|
65
|
+
const unresolved = [...files.keys()].filter((path) => path.endsWith(THEIRS))
|
|
66
|
+
if (unresolved.length > 0) {
|
|
67
|
+
fail(`Resolve ${unresolved.join(", ")} first.`, "unresolved_conflicts", CONFLICT_HINT)
|
|
68
|
+
}
|
|
69
|
+
const changes = [...new Set([...files.keys(), ...Object.keys(loaded.files)])]
|
|
70
|
+
.sort()
|
|
71
|
+
.flatMap((path) => {
|
|
72
|
+
const contents = files.get(path)
|
|
73
|
+
const changed =
|
|
74
|
+
(contents === undefined ? undefined : agentMemoryFileHash(contents)) !== loaded.files[path]
|
|
75
|
+
return changed ? [{ path, contents: contents ?? null }] : []
|
|
76
|
+
})
|
|
77
|
+
if (changes.length === 0) {
|
|
78
|
+
return writeJson({
|
|
79
|
+
repo,
|
|
80
|
+
saved: false,
|
|
81
|
+
changed: [],
|
|
82
|
+
hint: "No file changed since it was loaded.",
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const api = createInstanceApiClient(mode)
|
|
87
|
+
let saved: Record<string, unknown>
|
|
88
|
+
try {
|
|
89
|
+
saved = asRecord(
|
|
90
|
+
await api.post(`/api/agent-memory/${repo}/revisions`, {
|
|
91
|
+
baseRevision: loaded.revision,
|
|
92
|
+
message,
|
|
93
|
+
changes,
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (!(error instanceof CliError) || error.body.status !== 409) throw error
|
|
98
|
+
const { revision, conflicts } = await update(local, repo, manifest, await fetchRepo(api, repo))
|
|
99
|
+
process.exitCode = 1
|
|
100
|
+
return writeJson({ repo, saved: false, revision, conflicts, hint: CONFLICT_HINT })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The saved files are now what the repo holds, so updating does not report them as conflicts.
|
|
104
|
+
const savedFiles = { ...loaded.files }
|
|
105
|
+
for (const change of changes) {
|
|
106
|
+
if (change.contents === null) delete savedFiles[change.path]
|
|
107
|
+
else savedFiles[change.path] = agentMemoryFileHash(change.contents)
|
|
108
|
+
}
|
|
109
|
+
const after: AgentMemoryManifest = {
|
|
110
|
+
repos: { ...manifest.repos, [repo]: { ...loaded, files: savedFiles } },
|
|
111
|
+
}
|
|
112
|
+
const { revision, conflicts } = await update(local, repo, after, await fetchRepo(api, repo))
|
|
113
|
+
const warnings = Array.isArray(saved.warnings) ? saved.warnings : []
|
|
114
|
+
writeJson({
|
|
115
|
+
repo,
|
|
116
|
+
saved: true,
|
|
117
|
+
revision,
|
|
118
|
+
changed: changes.map((change) => change.path),
|
|
119
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
120
|
+
...(conflicts.length > 0 ? { conflicts, hint: CONFLICT_HINT } : {}),
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function pull(
|
|
125
|
+
local: LocalMemory,
|
|
126
|
+
args: readonly string[],
|
|
127
|
+
mode: SandboxInstanceCliMode
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
if (args.length > 1) fail("Invalid arguments for 'memory pull'.")
|
|
130
|
+
const manifest = await local.readManifest()
|
|
131
|
+
const repos = args[0] === undefined ? installedRepos(manifest) : [parseRepo(args[0])]
|
|
132
|
+
if (repos.length === 0) {
|
|
133
|
+
fail("No memory repo is installed.", "invalid_arguments", "Run 'sixb memory pull user'.")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const api = createInstanceApiClient(mode)
|
|
137
|
+
let current = manifest
|
|
138
|
+
for (const repo of repos) {
|
|
139
|
+
const result = await update(local, repo, current, await fetchRepo(api, repo))
|
|
140
|
+
current = result.manifest
|
|
141
|
+
writeJson({
|
|
142
|
+
repo,
|
|
143
|
+
revision: result.revision,
|
|
144
|
+
updated: result.updated,
|
|
145
|
+
conflicts: result.conflicts,
|
|
146
|
+
...(result.conflicts.length > 0 ? { hint: CONFLICT_HINT } : {}),
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Bring the local copy of `repo` to `remote`, three ways against the manifest: a file only the
|
|
153
|
+
* repo changed is updated, a file only this copy changed is kept, and a file both changed gets
|
|
154
|
+
* the repo's version next to it as `.theirs`. The manifest then records `remote`, so the next save
|
|
155
|
+
* sends what this copy holds against the latest revision.
|
|
156
|
+
*/
|
|
157
|
+
async function update(
|
|
158
|
+
local: LocalMemory,
|
|
159
|
+
repo: AgentMemoryRepo,
|
|
160
|
+
manifest: AgentMemoryManifest,
|
|
161
|
+
remote: RemoteRepo
|
|
162
|
+
): Promise<{
|
|
163
|
+
readonly manifest: AgentMemoryManifest
|
|
164
|
+
readonly revision: number
|
|
165
|
+
readonly updated: readonly string[]
|
|
166
|
+
readonly conflicts: readonly MemoryConflict[]
|
|
167
|
+
}> {
|
|
168
|
+
const base = manifest.repos[repo]?.files ?? {}
|
|
169
|
+
const files = await local.readRepo(repo)
|
|
170
|
+
const updated: string[] = []
|
|
171
|
+
const conflicts: MemoryConflict[] = []
|
|
172
|
+
|
|
173
|
+
for (const path of [
|
|
174
|
+
...new Set([...files.keys(), ...remote.files.keys(), ...Object.keys(base)]),
|
|
175
|
+
].sort()) {
|
|
176
|
+
if (path.endsWith(THEIRS)) continue
|
|
177
|
+
const mine = files.get(path)
|
|
178
|
+
const theirs = remote.files.get(path)
|
|
179
|
+
const mineHash = mine === undefined ? undefined : agentMemoryFileHash(mine)
|
|
180
|
+
const theirsHash = theirs === undefined ? undefined : agentMemoryFileHash(theirs)
|
|
181
|
+
if (theirsHash === base[path] || theirsHash === mineHash) continue
|
|
182
|
+
if (mineHash === base[path]) {
|
|
183
|
+
await local.write(repo, path, theirs)
|
|
184
|
+
updated.push(path)
|
|
185
|
+
} else if (theirs === undefined) {
|
|
186
|
+
conflicts.push({ path, theirs: null })
|
|
187
|
+
} else {
|
|
188
|
+
await local.write(repo, `${path}${THEIRS}`, theirs)
|
|
189
|
+
conflicts.push({ path, theirs: `${path}${THEIRS}` })
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (
|
|
193
|
+
!remote.files.has(AGENT_MEMORY_ENTRY) &&
|
|
194
|
+
!(await local.readRepo(repo)).has(AGENT_MEMORY_ENTRY)
|
|
195
|
+
) {
|
|
196
|
+
await local.write(repo, AGENT_MEMORY_ENTRY, AGENT_MEMORY_STARTER)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const next: AgentMemoryManifest = {
|
|
200
|
+
repos: {
|
|
201
|
+
...manifest.repos,
|
|
202
|
+
[repo]: {
|
|
203
|
+
revision: remote.revision,
|
|
204
|
+
files: Object.fromEntries(
|
|
205
|
+
[...remote.files].map(([path, contents]) => [path, agentMemoryFileHash(contents)])
|
|
206
|
+
),
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
await local.writeManifest(next)
|
|
211
|
+
return { manifest: next, revision: remote.revision, updated, conflicts }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function fetchRepo(
|
|
215
|
+
api: ReturnType<typeof createInstanceApiClient>,
|
|
216
|
+
repo: AgentMemoryRepo
|
|
217
|
+
): Promise<RemoteRepo> {
|
|
218
|
+
const body = asRecord(await api.get(`/api/agent-memory/${repo}`))
|
|
219
|
+
const files = Array.isArray(body.files) ? body.files.map(asRecord) : null
|
|
220
|
+
if (
|
|
221
|
+
typeof body.revision !== "number" ||
|
|
222
|
+
!files?.every((file) => typeof file.path === "string" && typeof file.contents === "string")
|
|
223
|
+
) {
|
|
224
|
+
throw new CliError(
|
|
225
|
+
{ code: "invalid_api_response", message: "The Sixb API returned an invalid memory repo." },
|
|
226
|
+
EXIT_API
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
revision: body.revision,
|
|
231
|
+
files: new Map(files.map((file) => [String(file.path), String(file.contents)])),
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
interface LocalMemory {
|
|
236
|
+
readManifest(): Promise<AgentMemoryManifest>
|
|
237
|
+
writeManifest(manifest: AgentMemoryManifest): Promise<void>
|
|
238
|
+
/** Every file of the local copy, by path from the repo root. */
|
|
239
|
+
readRepo(repo: AgentMemoryRepo): Promise<Map<string, string>>
|
|
240
|
+
/** `undefined` deletes the file. */
|
|
241
|
+
write(repo: AgentMemoryRepo, path: string, contents: string | undefined): Promise<void>
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function localMemory(): LocalMemory {
|
|
245
|
+
const root = process.env.SIXB_MEMORY_DIR
|
|
246
|
+
const contextDir = process.env.SIXB_CONTEXT_DIR
|
|
247
|
+
if (!root || !contextDir) {
|
|
248
|
+
fail("Agent memory is not available in this run.", "runtime_unavailable")
|
|
249
|
+
}
|
|
250
|
+
const manifestPath = join(contextDir, ...AGENT_MEMORY_MANIFEST.split("/"))
|
|
251
|
+
const fileOf = (repo: AgentMemoryRepo, path: string) => join(root, repo, ...path.split("/"))
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
async readManifest() {
|
|
255
|
+
let text: string
|
|
256
|
+
try {
|
|
257
|
+
text = await readFile(manifestPath, "utf8")
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (isFileError(error, "ENOENT")) return { repos: {} }
|
|
260
|
+
throw error
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
return parseManifest(JSON.parse(text))
|
|
264
|
+
} catch {
|
|
265
|
+
fail(`Memory manifest '${manifestPath}' is not valid.`, "invalid_json")
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
async writeManifest(manifest) {
|
|
270
|
+
await mkdir(dirname(manifestPath), { recursive: true })
|
|
271
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
async readRepo(repo) {
|
|
275
|
+
const files = new Map<string, string>()
|
|
276
|
+
const walk = async (directory: string, prefix: string): Promise<void> => {
|
|
277
|
+
let entries: string[]
|
|
278
|
+
try {
|
|
279
|
+
entries = await readdir(directory)
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (isFileError(error, "ENOENT")) return
|
|
282
|
+
throw error
|
|
283
|
+
}
|
|
284
|
+
for (const name of entries) {
|
|
285
|
+
const path = prefix ? `${prefix}/${name}` : name
|
|
286
|
+
const stats = await lstat(join(directory, name))
|
|
287
|
+
if (stats.isDirectory()) await walk(join(directory, name), path)
|
|
288
|
+
else if (stats.isFile()) files.set(path, await readFile(join(directory, name), "utf8"))
|
|
289
|
+
else fail(`Memory file '${repo}/${path}' is not a regular file.`)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
await walk(join(root, repo), "")
|
|
293
|
+
return files
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
async write(repo, path, contents) {
|
|
297
|
+
const file = fileOf(repo, path)
|
|
298
|
+
if (contents === undefined) {
|
|
299
|
+
await rm(file, { force: true })
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
await mkdir(dirname(file), { recursive: true })
|
|
303
|
+
await writeFile(file, contents)
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function parseSaveArgs(args: readonly string[]): { repo: AgentMemoryRepo; message: string } {
|
|
309
|
+
const positionals: string[] = []
|
|
310
|
+
let message: string | undefined
|
|
311
|
+
for (let index = 0; index < args.length; index++) {
|
|
312
|
+
const argument = args[index] ?? ""
|
|
313
|
+
if (argument === "-m" || argument === "--message") {
|
|
314
|
+
if (message !== undefined) fail(`${argument} may only be provided once.`)
|
|
315
|
+
message = args[++index]
|
|
316
|
+
if (!message) fail(`${argument} requires a value.`)
|
|
317
|
+
} else if (argument.startsWith("-")) {
|
|
318
|
+
fail(`Unknown memory save option '${argument}'.`)
|
|
319
|
+
} else {
|
|
320
|
+
positionals.push(argument)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (positionals.length !== 1) fail("Invalid arguments for 'memory save'.")
|
|
324
|
+
if (message === undefined || message.trim().length === 0) {
|
|
325
|
+
fail("memory save requires -m <what you learned>.")
|
|
326
|
+
}
|
|
327
|
+
return { repo: parseRepo(positionals[0] ?? ""), message }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function parseRepo(value: string): AgentMemoryRepo {
|
|
331
|
+
if (value === "user" || value === "project") return value
|
|
332
|
+
fail(`Unknown memory repo '${value}'. Use 'user' or 'project'.`)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function installedRepos(manifest: AgentMemoryManifest): AgentMemoryRepo[] {
|
|
336
|
+
return (["user", "project"] as const).filter((repo) => manifest.repos[repo] !== undefined)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function parseManifest(value: unknown): AgentMemoryManifest {
|
|
340
|
+
const repos = asRecord(asRecord(value).repos)
|
|
341
|
+
const parsed: Partial<Record<AgentMemoryRepo, AgentMemoryManifestRepo>> = {}
|
|
342
|
+
for (const repo of ["user", "project"] as const) {
|
|
343
|
+
if (repos[repo] === undefined) continue
|
|
344
|
+
const entry = asRecord(repos[repo])
|
|
345
|
+
const files = asRecord(entry.files)
|
|
346
|
+
if (
|
|
347
|
+
typeof entry.revision !== "number" ||
|
|
348
|
+
!Object.values(files).every((digest) => typeof digest === "string")
|
|
349
|
+
) {
|
|
350
|
+
throw new Error("invalid memory manifest")
|
|
351
|
+
}
|
|
352
|
+
parsed[repo] = {
|
|
353
|
+
revision: entry.revision,
|
|
354
|
+
files: files as Record<string, string>,
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return { repos: parsed }
|
|
358
|
+
}
|
package/src/commands/metadata.ts
CHANGED
|
@@ -5,17 +5,17 @@ import {
|
|
|
5
5
|
DEFAULT_TELEMETRY_ORDER,
|
|
6
6
|
} from "../policies"
|
|
7
7
|
|
|
8
|
-
const SANDBOX_MAIN_HELP = `Sixb
|
|
8
|
+
const SANDBOX_MAIN_HELP = `Sixb CLI
|
|
9
9
|
|
|
10
10
|
Usage:
|
|
11
11
|
sixb <command> [options]
|
|
12
12
|
|
|
13
|
-
The CLI uses the run
|
|
13
|
+
The CLI uses the run's SIXB_API_BASE_URL, which adds its credentials. Do not configure authentication or another origin.
|
|
14
14
|
API commands emit JSON. Help, version, and examples emit text. Downloads write to --output and
|
|
15
15
|
emit a JSON receipt.
|
|
16
16
|
|
|
17
17
|
Discovery:
|
|
18
|
-
sixb
|
|
18
|
+
sixb status Check the sandbox and its API access
|
|
19
19
|
sixb context Print the current run context
|
|
20
20
|
sixb project show Show project metadata
|
|
21
21
|
sixb ontology list Compact visible type and relationship catalog
|
|
@@ -28,6 +28,8 @@ Objects:
|
|
|
28
28
|
sixb objects inspect <type> <id> Inspect an object and its related graph in one command
|
|
29
29
|
sixb objects list [options] Browse materialized objects
|
|
30
30
|
sixb objects get <type> <id>... Exact lookup through opaque object references
|
|
31
|
+
sixb objects get-by <type> <property> <value>
|
|
32
|
+
The object holding a value of a unique property
|
|
31
33
|
sixb objects search <text> Search visible objects by id prefix and full text
|
|
32
34
|
sixb objects search <text> --type <type>
|
|
33
35
|
Semantic search through the type's vector profile
|
|
@@ -48,28 +50,29 @@ Actions and workflows:
|
|
|
48
50
|
sixb workflows list|get|start ...
|
|
49
51
|
sixb workflow-runs list|get ...
|
|
50
52
|
|
|
53
|
+
Data:
|
|
54
|
+
sixb datasets list|get|versions|rows ...
|
|
55
|
+
sixb syncs list|get ... Syncs you may run, each with its latest run
|
|
56
|
+
sixb sync-runs list ...
|
|
57
|
+
sixb pipelines list|get ... Pipelines you may run, each with its latest run
|
|
58
|
+
sixb pipeline-runs list|get ...
|
|
59
|
+
|
|
51
60
|
Files:
|
|
52
61
|
sixb files upload|download ...
|
|
53
62
|
|
|
63
|
+
Members:
|
|
64
|
+
sixb users search <text> Find members by name or email
|
|
65
|
+
sixb users list|get ...
|
|
66
|
+
|
|
67
|
+
A user parameter or property is {"type":"user","id":"<user-id>"}. Find the id with
|
|
68
|
+
\`sixb users search <name>\`.
|
|
69
|
+
|
|
54
70
|
Run \`sixb <group> --help\` or \`sixb <group> <command> --help\` for exact arguments.
|
|
55
71
|
For query IR, run \`sixb objects query --help\` and \`sixb objects query --example list\`.`
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"The CLI uses the selected local profile and its API credentials."
|
|
61
|
-
)
|
|
62
|
-
.replace(
|
|
63
|
-
" sixb doctor Check the sandbox and API gateway\n sixb context Print the current run context\n",
|
|
64
|
-
""
|
|
65
|
-
)
|
|
66
|
-
.replace(
|
|
67
|
-
"\nWhen you do not know a type's properties or links, read its file under $SIXB_ONTOLOGY_DIR. Use\n`sixb ontology get` for a type that has no file there.\n",
|
|
68
|
-
""
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
export function renderInstanceHelp(mode: "local" | "sandbox"): string {
|
|
72
|
-
return mode === "sandbox" ? SANDBOX_MAIN_HELP : LOCAL_MAIN_HELP
|
|
73
|
+
/** The sandbox CLI's help, which the agent's prompt includes. People's comes from @sixb/cli. */
|
|
74
|
+
export function renderInstanceHelp(): string {
|
|
75
|
+
return SANDBOX_MAIN_HELP
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
const SEARCH_DETAILS = `Without --type, search matches primary-id prefixes and full-text fields across visible types.
|
|
@@ -87,10 +90,19 @@ profiles.
|
|
|
87
90
|
|
|
88
91
|
${SEARCH_DETAILS}`
|
|
89
92
|
|
|
93
|
+
export const GET_BY_HELP = `Usage:
|
|
94
|
+
sixb objects get-by <object-type> <property> <value> [--json]
|
|
95
|
+
|
|
96
|
+
Prints the object of exactly <object-type> holding <value> for a property marked unique in its
|
|
97
|
+
ontology doc, or null when no visible object holds it. Values match exactly, case included.
|
|
98
|
+
The value is a string; with --json it is parsed as JSON, for an integer (42) or a user
|
|
99
|
+
reference ({"type":"user","id":"usr_1"}).`
|
|
100
|
+
|
|
90
101
|
export const OBJECTS_HELP = `Usage:
|
|
91
102
|
sixb objects inspect <object-type> <primary-id> [options]
|
|
92
103
|
sixb objects list [options]
|
|
93
104
|
sixb objects get <object-type> <primary-id>...
|
|
105
|
+
sixb objects get-by <object-type> <property> <value> [--json]
|
|
94
106
|
sixb objects search <text> [--type <object-type> [--vector-profile <name>]] [--limit <n>]
|
|
95
107
|
sixb objects query --file <path|-> [--include-total|--no-total]
|
|
96
108
|
sixb objects query --example <name>
|
|
@@ -158,7 +170,7 @@ Query nodes compose through input:
|
|
|
158
170
|
limit {"kind":"limit","input":<query>,"limit":${CLI_LIMITS.list.default}}
|
|
159
171
|
page {"kind":"page","input":<query>,"pageSize":${CLI_LIMITS.list.default},"pageToken":"from nextPageToken"}
|
|
160
172
|
project {"kind":"project","input":<query>,"properties":["name","status"]}
|
|
161
|
-
expand {"kind":"expand","input":<query>,"expansions":[{"linkId":"link","direction":"outgoing","limit":5}]}
|
|
173
|
+
expand {"kind":"expand","input":<query>,"expansions":[{"linkId":"link","direction":"outgoing","limit":5}],"users":["assignee"]}
|
|
162
174
|
vector {"kind":"vector","input":<start|filter>,"vector":"text","profile":"name","k":10}
|
|
163
175
|
rerank {"kind":"rerank","input":<vector>,"model":{"provider":"provider","modelId":"model"}}
|
|
164
176
|
|
|
@@ -169,11 +181,16 @@ Predicates use op, not kind:
|
|
|
169
181
|
{"op":"in","propertyId":"status","values":["open","blocked"]}
|
|
170
182
|
{"op":"exists","propertyId":"dueDate","value":true}
|
|
171
183
|
{"op":"contains","propertyId":"tags","value":"urgent"}
|
|
184
|
+
{"op":"eq","propertyId":"assignee","value":{"type":"currentUser"}} the caller, on a user reference: neq, in, contains too
|
|
172
185
|
|
|
173
186
|
Every field shown is required except includeSubtypes, fields, pageToken, properties, a sort
|
|
174
|
-
direction,
|
|
187
|
+
direction, an expansion's limit, and users. Sort by relevance after text or vector with
|
|
175
188
|
{"kind":"relevance"}. Wrap the outermost node in project to return only the properties you need.
|
|
176
189
|
|
|
190
|
+
An expand's users, or an expansion's, lists user-reference properties: each row then carries the
|
|
191
|
+
members' profiles under users (id, email, displayName, avatarUrl, status). Without access to the
|
|
192
|
+
member directory, users is empty.
|
|
193
|
+
|
|
177
194
|
Traversal and expansion directions are outgoing or incoming. For incoming relationships, the link
|
|
178
195
|
is declared on the child/source type; add sourceObjectTypeId when needed to disambiguate it.
|
|
179
196
|
|
|
@@ -185,7 +202,7 @@ Vector profile names are the keys of the type's search.vectors. Vector input is
|
|
|
185
202
|
optionally under filters; k bounds the ranked matches and results carry a score.`
|
|
186
203
|
|
|
187
204
|
export const GROUP_HELP = {
|
|
188
|
-
|
|
205
|
+
status: "Usage: sixb status",
|
|
189
206
|
context: "Usage: sixb context",
|
|
190
207
|
project: "Usage: sixb project show",
|
|
191
208
|
ontology: `Usage:
|
|
@@ -209,7 +226,8 @@ History options:
|
|
|
209
226
|
\`actions get\` includes inputSchema, the exact JSON shape accepted by the Action. The JSON file
|
|
210
227
|
contains that parameter object; use - to read standard input. \`actions request\` runs the Action
|
|
211
228
|
and prints its finished run, with its status and any error. It waits at most 25 seconds: a run that
|
|
212
|
-
takes longer keeps running, and the error names the run to read with \`action-runs get
|
|
229
|
+
takes longer keeps running, and the error names the run to read with \`action-runs get\`. A request
|
|
230
|
+
that waits for the user's approval prints \`"status": "awaiting_approval"\` and exits with code 4.`,
|
|
213
231
|
"action-runs": `Usage:
|
|
214
232
|
sixb action-runs list [options]
|
|
215
233
|
sixb action-runs get <run-id>
|
|
@@ -226,13 +244,54 @@ List options:
|
|
|
226
244
|
files: `Usage:
|
|
227
245
|
sixb files upload <local-path> [--logical-path <path>]
|
|
228
246
|
sixb files download object <type> <id> --path <json-pointer> --output <local-path>
|
|
229
|
-
sixb files download action-run|workflow-run <run-id> --path <json-pointer> --output <local-path
|
|
247
|
+
sixb files download action-run|workflow-run <run-id> --path <json-pointer> --output <local-path>
|
|
248
|
+
sixb files download url <content-url> --output <local-path>`,
|
|
230
249
|
workflows: `Usage:
|
|
231
250
|
sixb workflows list
|
|
232
251
|
sixb workflows get <workflow-id>
|
|
233
|
-
sixb workflows start <workflow-id> [--file <path|->]
|
|
252
|
+
sixb workflows start <workflow-id> [--file <path|->] [--run-id <id>]
|
|
253
|
+
|
|
254
|
+
The JSON file contains the workflow input object. Use - to read standard input. A start that waits
|
|
255
|
+
for the user's approval prints \`"status": "awaiting_approval"\` and exits with code 4.`,
|
|
256
|
+
users: `Usage:
|
|
257
|
+
sixb users list [options]
|
|
258
|
+
sixb users search <text> [options]
|
|
259
|
+
sixb users get <user-id|email>
|
|
260
|
+
|
|
261
|
+
Options:
|
|
262
|
+
--status active|suspended|all Defaults to active
|
|
263
|
+
--limit <1-${CLI_LIMITS.users.maximum}> Defaults to ${CLI_LIMITS.users.default}
|
|
264
|
+
--offset <n> Non-negative; defaults to 0
|
|
234
265
|
|
|
235
|
-
|
|
266
|
+
\`users search\` matches part of a display name or email, ignoring case; \`users get\` takes an
|
|
267
|
+
exact email, ignoring case, and finds suspended members too. Each member is
|
|
268
|
+
{"id","email","displayName","avatarUrl","status"}; a user parameter takes
|
|
269
|
+
{"type":"user","id":"<id>"}. Reading members requires can.view(project.users).`,
|
|
270
|
+
models: `Usage:
|
|
271
|
+
sixb models list [--kind language|image|speech|transcription]
|
|
272
|
+
sixb models get <model> [--kind <kind>]
|
|
273
|
+
sixb models text "<prompt>"|--file <path|-> [--model <tier|model>] [--instructions <text>]
|
|
274
|
+
[--max-output-tokens <n>] [--reasoning <level>]
|
|
275
|
+
sixb models image "<prompt>" --output <path> [--model <model>] [--n <n>]
|
|
276
|
+
[--size <size>] [--aspect-ratio <ratio>] [--quality <q>] [--style <s>]
|
|
277
|
+
[--seed <n>] [--image <file>]...
|
|
278
|
+
sixb models speech "<text>" --output <path> [--model <model>] [--voice <voice>]
|
|
279
|
+
[--format mp3|wav|ogg|aac|flac] [--instructions <text>]
|
|
280
|
+
[--speed <n>] [--language <code>]
|
|
281
|
+
sixb models transcribe <file> [--model <model>]
|
|
282
|
+
sixb models calls files <call-id> [--output <path>]
|
|
283
|
+
|
|
284
|
+
<model> is a model id, or provider:model-id when two providers share one; without --model, the
|
|
285
|
+
kind's default answers. \`list\` shows each model's kind, default, description and price; \`get\`
|
|
286
|
+
adds the sizes, voices and other options a request can use. Prices are in USD.
|
|
287
|
+
|
|
288
|
+
\`text\` also takes a tier (light, standard or heavy) for --model, and reads a long prompt from
|
|
289
|
+
--file: a path, or - for standard input.
|
|
290
|
+
|
|
291
|
+
--output is a file, numbered when a call returns several, or a directory ending in /. The
|
|
292
|
+
extension follows the file's type, and the result lists the paths written. --image uploads a
|
|
293
|
+
reference image; repeat it for several. \`calls files\` lists a past call's files, or downloads
|
|
294
|
+
them again.`,
|
|
236
295
|
"workflow-runs": `Usage:
|
|
237
296
|
sixb workflow-runs list [options]
|
|
238
297
|
sixb workflow-runs get <run-id>
|
|
@@ -244,6 +303,56 @@ List options:
|
|
|
244
303
|
--limit <1-${CLI_LIMITS.list.maximum}> Defaults to ${CLI_LIMITS.list.default}
|
|
245
304
|
--offset <n> Non-negative; defaults to 0
|
|
246
305
|
--order <asc|desc> Started-time order; defaults to ${DEFAULT_LIST_ORDER}`,
|
|
306
|
+
datasets: `Usage:
|
|
307
|
+
sixb datasets list
|
|
308
|
+
sixb datasets get <dataset-id>
|
|
309
|
+
sixb datasets versions <dataset-id> [--limit <1-${CLI_LIMITS.datasetVersions.maximum}>]
|
|
310
|
+
sixb datasets rows <dataset-id> [options]
|
|
311
|
+
|
|
312
|
+
Rows options:
|
|
313
|
+
--version <version-id> Defaults to the latest version
|
|
314
|
+
--columns <a,b,...> Defaults to every column
|
|
315
|
+
--limit <1-${CLI_LIMITS.datasetRows.maximum}> Defaults to ${CLI_LIMITS.datasetRows.default}
|
|
316
|
+
--offset <n> Non-negative; defaults to 0
|
|
317
|
+
|
|
318
|
+
\`list\` and \`get\` show a dataset's schema, its latest version, and the syncs and pipelines you
|
|
319
|
+
may run that read or write it. \`versions\` lists the newest first. \`rows\` leaves out the
|
|
320
|
+
columns your clearances do not cover and names them under redactions.`,
|
|
321
|
+
syncs: `Usage:
|
|
322
|
+
sixb syncs list
|
|
323
|
+
sixb syncs get <sync-id>
|
|
324
|
+
|
|
325
|
+
Lists the syncs you may run, each with its latest run. \`sixb sync-runs list\` shows the history.`,
|
|
326
|
+
"sync-runs": `Usage:
|
|
327
|
+
sixb sync-runs list [options]
|
|
328
|
+
|
|
329
|
+
List options:
|
|
330
|
+
--sync <sync-id>
|
|
331
|
+
--dataset <dataset-id>
|
|
332
|
+
--status queued|running|succeeded|failed|cancelled
|
|
333
|
+
--started-after|--started-before <RFC3339>
|
|
334
|
+
--limit <1-${CLI_LIMITS.list.maximum}> Defaults to ${CLI_LIMITS.list.default}
|
|
335
|
+
--offset <n> Non-negative; defaults to 0
|
|
336
|
+
--order <asc|desc> Started-time order; defaults to ${DEFAULT_LIST_ORDER}`,
|
|
337
|
+
pipelines: `Usage:
|
|
338
|
+
sixb pipelines list
|
|
339
|
+
sixb pipelines get <pipeline-id>
|
|
340
|
+
|
|
341
|
+
Lists the pipelines you may run, each with its latest run. \`sixb pipeline-runs\` shows the
|
|
342
|
+
history and each run's steps.`,
|
|
343
|
+
"pipeline-runs": `Usage:
|
|
344
|
+
sixb pipeline-runs list [options]
|
|
345
|
+
sixb pipeline-runs get <run-id>
|
|
346
|
+
|
|
347
|
+
List options:
|
|
348
|
+
--pipeline <pipeline-id>
|
|
349
|
+
--status queued|running|succeeded|failed|cancelled
|
|
350
|
+
--started-after|--started-before <RFC3339>
|
|
351
|
+
--limit <1-${CLI_LIMITS.list.maximum}> Defaults to ${CLI_LIMITS.list.default}
|
|
352
|
+
--offset <n> Non-negative; defaults to 0
|
|
353
|
+
--order <asc|desc> Started-time order; defaults to ${DEFAULT_LIST_ORDER}
|
|
354
|
+
|
|
355
|
+
\`get\` includes the run's steps.`,
|
|
247
356
|
} as const
|
|
248
357
|
|
|
249
358
|
export const QUERY_EXAMPLES: Readonly<Record<string, string>> = {
|
|
@@ -254,10 +363,12 @@ export const QUERY_EXAMPLES: Readonly<Record<string, string>> = {
|
|
|
254
363
|
not: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Customer"},"predicate":{"op":"not","item":{"op":"eq","propertyId":"status","value":"churned"}}},"limit":${CLI_LIMITS.list.default}}`,
|
|
255
364
|
in: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Customer"},"predicate":{"op":"in","propertyId":"status","values":["active","trial"]}},"limit":${CLI_LIMITS.list.default}}`,
|
|
256
365
|
exists: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Customer"},"predicate":{"op":"exists","propertyId":"accountManager","value":false}},"limit":${CLI_LIMITS.list.default}}`,
|
|
366
|
+
mine: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Task"},"predicate":{"op":"eq","propertyId":"assignee","value":{"type":"currentUser"}}},"limit":${CLI_LIMITS.list.default}}`,
|
|
257
367
|
project: `{"kind":"project","input":{"kind":"limit","input":{"kind":"start","objectTypeId":"Customer"},"limit":${CLI_LIMITS.list.default}},"properties":["name","status"]}`,
|
|
258
368
|
incoming:
|
|
259
369
|
'{"kind":"traverse","input":{"kind":"refs","refs":[{"objectTypeId":"RepositoryIssue","primaryId":"github:issue:owner/repo#297"}]},"linkId":"issue","direction":"incoming","sourceObjectTypeId":"RepositoryComment"}',
|
|
260
370
|
expand: `{"kind":"expand","input":{"kind":"refs","refs":[{"objectTypeId":"RepositoryIssue","primaryId":"github:issue:owner/repo#297"}]},"expansions":[{"linkId":"issue","direction":"incoming","sourceObjectTypeId":"RepositoryComment","limit":${CLI_LIMITS.list.default}}]}`,
|
|
371
|
+
users: `{"kind":"expand","input":{"kind":"limit","input":{"kind":"start","objectTypeId":"Task"},"limit":${CLI_LIMITS.list.default}},"expansions":[],"users":["assignee"]}`,
|
|
261
372
|
sort: `{"kind":"limit","input":{"kind":"sort","input":{"kind":"start","objectTypeId":"Customer"},"fields":[{"kind":"property","propertyId":"name","direction":"asc"}]},"limit":${CLI_LIMITS.list.default}}`,
|
|
262
373
|
page: `{"kind":"page","input":{"kind":"start","objectTypeId":"Customer"},"pageSize":${CLI_LIMITS.list.default}}`,
|
|
263
374
|
vector: `{"kind":"vector","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Product"},"predicate":{"op":"eq","propertyId":"status","value":"active"}},"vector":"waterproof trail shoes","profile":"content","k":${CLI_LIMITS.search.default}}`,
|