Skip to content

Commit 28698ee

Browse files
feat(cli): standardize resource command syntax
1 parent da611cb commit 28698ee

12 files changed

Lines changed: 368 additions & 106 deletions

File tree

‎packages/sim-cli/README.md‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,15 @@ also accepts its singular form: for example, `sim table list`,
112112
`sim file download`, and `sim workflow get` are equivalent to their plural
113113
spellings.
114114

115-
`knowledge` also accepts the shorter `kb` alias.
115+
`knowledge` also accepts the shorter `kb` alias, and `documents` accepts
116+
`document`.
116117

117118
```bash
118119
sim workflows ls [path] [--search <text>] [--limit <n>]
119120
sim workflows list [--folder <path>] [--deployed-only] [--limit <n>]
120121
sim workflows get <id>
121-
sim workflows mv <id> --folder <path>
122+
sim workflows update <id> [--name <name>] [--description <text>] [--folder <path>]
123+
sim workflows mv <id> <folder>
122124
sim workflows deploy|undeploy|rollback <id>
123125
sim workflows run <id> [--input <json|@file>] [--select-output <path>…]
124126

@@ -129,7 +131,8 @@ sim logs execution <executionId>
129131
sim tables ls [path] [--search <text>] [--limit <n>]
130132
sim tables list [--folder <path>]
131133
sim tables get <tableId>
132-
sim tables mv <tableId> --folder <path>
134+
sim tables update <tableId> [--name <name>] [--description <text>] [--folder <path>]
135+
sim tables mv <tableId> <folder>
133136
sim tables columns <tableId>
134137
sim tables rows list <tableId> [--limit <n>]
135138
sim tables rows create <tableId> --data <json|@file>
@@ -152,11 +155,15 @@ sim files delete <fileId> --yes
152155
sim knowledge ls [path] [--search <text>] [--limit <n>]
153156
sim knowledge list [--folder <path>]
154157
sim knowledge get <id>
155-
sim knowledge mv <id> --folder <path>
156-
sim knowledge documents <id> [--search <text>]
157-
sim knowledge documents upload <id> <path> [--tag <value>...]
158+
sim knowledge update <id> [--name <name>] [--description <text>] [--folder <path>]
159+
sim knowledge mv <id> <folder>
158160
sim knowledge search --query <text> --kb <id>… [--search-mode vector|hybrid]
159161

162+
sim documents list --kb <knowledgeBaseId> [--search <text>]
163+
sim documents get <documentId> --kb <knowledgeBaseId>
164+
sim documents upload <path> --kb <knowledgeBaseId> [--tag <value>...]
165+
sim documents delete <documentId> --kb <knowledgeBaseId> --yes
166+
160167
sim billing
161168
sim billing logs [--period 7d] [--limit <n>]
162169
```

‎packages/sim-cli/src/commands/protocol/index.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ export function attachProtocolCommands(program: Command): void {
2525
createFolder: 'createFileFolder',
2626
})
2727

28+
attachKnowledgeDocumentUpload(group(program, 'documents'))
29+
2830
const knowledge = group(program, 'knowledge')
29-
attachKnowledgeDocumentUpload(group(knowledge, 'documents'))
3031
attachResourceDirectoryCommands(knowledge, {
3132
kind: 'knowledge',
3233
resources: 'listKnowledgeBases',

‎packages/sim-cli/src/commands/protocol/knowledge-document-upload.test.ts‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,16 @@ function uploadSession() {
6262
}
6363
}
6464

65-
describe('knowledge documents upload', () => {
65+
describe('documents upload', () => {
6666
it('owns the multipart protocol while hiding its low-level operations', () => {
67-
const knowledge = program().commands.find((command) => command.name() === 'knowledge')
67+
const root = program()
68+
const knowledge = root.commands.find((command) => command.name() === 'knowledge')
6869
expect(knowledge?.commands.map((command) => command.name())).not.toEqual(
69-
expect.arrayContaining(['uploads', 'parts', 'complete'])
70+
expect.arrayContaining(['documents', 'uploads', 'parts', 'complete'])
7071
)
7172

72-
const documents = knowledge?.commands.find((command) => command.name() === 'documents')
73+
const documents = root.commands.find((command) => command.name() === 'documents')
74+
expect(documents?.alias()).toBe('document')
7375
expect(documents?.commands.map((command) => command.name())).toContain('upload')
7476
})
7577

@@ -131,11 +133,11 @@ describe('knowledge documents upload', () => {
131133
await program().parseAsync([
132134
'node',
133135
'sim',
134-
'knowledge',
135136
'documents',
136137
'upload',
137-
'kb_1',
138138
path,
139+
'--kb',
140+
'kb_1',
139141
'--tag',
140142
'customer',
141143
'priority',
@@ -189,11 +191,11 @@ describe('knowledge documents upload', () => {
189191
program().parseAsync([
190192
'node',
191193
'sim',
192-
'knowledge',
193194
'documents',
194195
'upload',
195-
'kb_1',
196196
path,
197+
'--kb',
198+
'kb_1',
197199
'--tag',
198200
'1',
199201
'2',
@@ -207,4 +209,14 @@ describe('knowledge documents upload', () => {
207209
).rejects.toThrow(/at most seven/)
208210
expect(mockRequest).not.toHaveBeenCalled()
209211
})
212+
213+
it('requires an explicit knowledge-base scope before reading the file', async () => {
214+
const path = join(dir, 'notes.txt')
215+
writeFileSync(path, 'hello')
216+
217+
await expect(
218+
program().parseAsync(['node', 'sim', 'documents', 'upload', path])
219+
).rejects.toThrow(/required option '--kb <knowledgeBaseId>'/)
220+
expect(mockRequest).not.toHaveBeenCalled()
221+
})
210222
})

‎packages/sim-cli/src/commands/protocol/knowledge-document-upload.ts‎

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { finishUploadSession } from '../../transfer/upload-session.js'
1010
import { printProtocolResult } from './result.js'
1111

1212
interface KnowledgeDocumentUploadOptions {
13+
kb: string
1314
name?: string
1415
tag?: string[]
1516
recipe?: string
@@ -37,62 +38,54 @@ function uploadMetadata(options: KnowledgeDocumentUploadOptions): Record<string,
3738

3839
export function attachKnowledgeDocumentUpload(documents: Command): void {
3940
documents
40-
.command('upload <knowledgeBaseId> <path>')
41+
.command('upload <path>')
4142
.description('Upload a document to a knowledge base')
43+
.requiredOption('--kb <knowledgeBaseId>', 'Knowledge base ID (required)')
4244
.option('--name <name>', 'Store it under a different name')
4345
.option('--tag <value...>', 'Document tags, in tag1 through tag7 order')
4446
.option('--recipe <name>', 'Document processing recipe')
4547
.option('--lang <code>', 'Document language code')
46-
.action(
47-
async (
48-
knowledgeBaseId: string,
49-
path: string,
50-
options: KnowledgeDocumentUploadOptions,
51-
command: Command
52-
) => {
53-
const { client, profile } = clientFrom(command)
54-
const workspaceId = client.requireWorkspace()
55-
const { name, size } = await localFile(path, options.name)
56-
const created = await client.request<CreateKnowledgeDocumentUploadResponse>(
57-
`/api/v2/knowledge/${encodeURIComponent(knowledgeBaseId)}/documents/uploads`,
58-
{
59-
method: 'POST',
60-
body: {
61-
workspaceId,
62-
name,
63-
contentType: contentTypeFor(name),
64-
size,
65-
...uploadMetadata(options),
66-
},
67-
}
68-
)
69-
const { session, uploadToken, transfer } = created.data
70-
const completed = await finishUploadSession<
71-
CompleteKnowledgeDocumentUploadResponse['data']
72-
>(
73-
client,
74-
workspaceId,
75-
{
76-
basePath: `/api/v2/knowledge/${encodeURIComponent(
77-
knowledgeBaseId
78-
)}/documents/uploads/${encodeURIComponent(session.id)}`,
79-
uploadToken,
80-
transfer,
48+
.action(async (path: string, options: KnowledgeDocumentUploadOptions, command: Command) => {
49+
const { client, profile } = clientFrom(command)
50+
const workspaceId = client.requireWorkspace()
51+
const { name, size } = await localFile(path, options.name)
52+
const created = await client.request<CreateKnowledgeDocumentUploadResponse>(
53+
`/api/v2/knowledge/${encodeURIComponent(options.kb)}/documents/uploads`,
54+
{
55+
method: 'POST',
56+
body: {
57+
workspaceId,
58+
name,
59+
contentType: contentTypeFor(name),
8160
size,
61+
...uploadMetadata(options),
8262
},
83-
path
84-
)
85-
86-
if (!completed.document) {
87-
throw new Error(`Knowledge upload ${session.id} completed without a document`)
8863
}
89-
printProtocolResult(profile.output, {
90-
id: completed.document.id,
91-
knowledgeBaseId: completed.document.knowledgeBaseId,
92-
name: completed.document.filename,
93-
size: completed.document.fileSize,
94-
status: completed.document.processingStatus,
95-
})
64+
)
65+
const { session, uploadToken, transfer } = created.data
66+
const completed = await finishUploadSession<CompleteKnowledgeDocumentUploadResponse['data']>(
67+
client,
68+
workspaceId,
69+
{
70+
basePath: `/api/v2/knowledge/${encodeURIComponent(
71+
options.kb
72+
)}/documents/uploads/${encodeURIComponent(session.id)}`,
73+
uploadToken,
74+
transfer,
75+
size,
76+
},
77+
path
78+
)
79+
80+
if (!completed.document) {
81+
throw new Error(`Knowledge upload ${session.id} completed without a document`)
9682
}
97-
)
83+
printProtocolResult(profile.output, {
84+
id: completed.document.id,
85+
knowledgeBaseId: completed.document.knowledgeBaseId,
86+
name: completed.document.filename,
87+
size: completed.document.fileSize,
88+
status: completed.document.processingStatus,
89+
})
90+
})
9891
}

‎packages/sim-cli/src/contract/commands.ts‎

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CliContract, ColumnSpec } from './types.js'
1+
import type { CliContract, ColumnSpec, CommandVariantSpec } from './types.js'
22

33
const TABLE_NAME_HELP = 'Identifier: letters, numbers, and underscores; cannot start with a number'
44
const TABLE_FILTER_HELP =
@@ -18,13 +18,29 @@ const FOLDER_DELETE_FLAGS = {
1818
path: FOLDER_PATH_INPUT,
1919
recursive: { boolean: true, describe: 'Delete the folder and its descendants' },
2020
} as const
21+
const KNOWLEDGE_DOCUMENT_SCOPE = {
22+
id: {
23+
name: 'kb',
24+
placeholder: 'knowledgeBaseId',
25+
describe: 'Knowledge base ID',
26+
},
27+
} as const
2128
const FOLDER_LIST_COLUMNS: ColumnSpec[] = [
2229
{ header: 'path' },
2330
{ header: 'name' },
2431
{ header: 'parent', path: 'parentPath' },
2532
{ header: 'updated', path: 'updatedAt', format: 'timestamp' },
2633
]
2734

35+
function moveResource(command: string, resource: string): CommandVariantSpec {
36+
return {
37+
command,
38+
positionals: ['folderPath'],
39+
requestFields: ['folderPath'],
40+
describe: `Move a ${resource} to a folder`,
41+
}
42+
}
43+
2844
/**
2945
* The CLI contract for the v2 surface.
3046
*
@@ -34,7 +50,7 @@ const FOLDER_LIST_COLUMNS: ColumnSpec[] = [
3450
*
3551
* Derived by default:
3652
* listTables → sim tables list
37-
* getKnowledgeDocument → sim knowledge documents get <id> <documentId>
53+
* getKnowledgeDocument → sim documents get <documentId> --kb <knowledgeBaseId>
3854
* upsertTableRow → sim tables upsert <tableId>
3955
*/
4056
export const CLI_CONTRACT: CliContract = {
@@ -54,6 +70,12 @@ export const CLI_CONTRACT: CliContract = {
5470
listUsageLogs: {
5571
command: 'billing logs',
5672
describe: 'List credit usage events',
73+
flags: {
74+
source: { describe: 'Filter by usage source' },
75+
period: { describe: 'Billing period' },
76+
startDate: { describe: 'Custom period start (ISO 8601)' },
77+
endDate: { describe: 'Custom period end (ISO 8601)' },
78+
},
5779
columns: [
5880
{ header: 'at', path: 'createdAt', format: 'timestamp' },
5981
{ header: 'source' },
@@ -99,7 +121,11 @@ export const CLI_CONTRACT: CliContract = {
99121
fields: [{ header: 'remaining columns', path: 'columns', format: 'count' }],
100122
},
101123
deleteKnowledgeBase: { confirm: 'This deletes the knowledge base and every document in it.' },
102-
deleteKnowledgeDocument: { confirm: 'This deletes the document and its embeddings.' },
124+
deleteKnowledgeDocument: {
125+
command: 'documents delete',
126+
pathFlags: KNOWLEDGE_DOCUMENT_SCOPE,
127+
confirm: 'This deletes the document and its embeddings.',
128+
},
103129
deleteFile: { confirm: 'This archives the file.' },
104130
deleteSkill: { confirm: 'This deletes the skill.' },
105131
deleteCustomTool: { confirm: 'This deletes the custom tool.' },
@@ -223,17 +249,23 @@ export const CLI_CONTRACT: CliContract = {
223249
},
224250
},
225251
updateTable: {
226-
aliases: ['mv'],
252+
variants: [moveResource('tables mv', 'table')],
227253
flags: {
228254
name: { describe: TABLE_NAME_HELP },
229255
folderPath: FOLDER_PATH_FLAG,
230256
},
231257
},
232258
createFile: { flags: { folderPath: FOLDER_PATH_FLAG } },
233259
createKnowledgeBase: { flags: { folderPath: FOLDER_PATH_FLAG } },
234-
updateKnowledgeBase: { aliases: ['mv'], flags: { folderPath: FOLDER_PATH_FLAG } },
260+
updateKnowledgeBase: {
261+
variants: [moveResource('knowledge mv', 'knowledge base')],
262+
flags: { folderPath: FOLDER_PATH_FLAG },
263+
},
235264
createWorkflow: { flags: { folderPath: FOLDER_PATH_FLAG } },
236-
updateWorkflow: { aliases: ['mv'], flags: { folderPath: FOLDER_PATH_FLAG } },
265+
updateWorkflow: {
266+
variants: [moveResource('workflows mv', 'workflow')],
267+
flags: { folderPath: FOLDER_PATH_FLAG },
268+
},
237269
importWorkflow: { flags: { folderPath: FOLDER_PATH_FLAG } },
238270
createCustomTool: { flags: { schema: { json: true, describe: CUSTOM_TOOL_SCHEMA_HELP } } },
239271
updateCustomTool: { flags: { schema: { json: true, describe: CUSTOM_TOOL_SCHEMA_HELP } } },
@@ -285,7 +317,13 @@ export const CLI_CONTRACT: CliContract = {
285317
{ header: 'model', path: 'embeddingModel' },
286318
],
287319
},
320+
getKnowledgeDocument: {
321+
command: 'documents get',
322+
pathFlags: KNOWLEDGE_DOCUMENT_SCOPE,
323+
},
288324
listKnowledgeDocuments: {
325+
command: 'documents list',
326+
pathFlags: KNOWLEDGE_DOCUMENT_SCOPE,
289327
columns: [
290328
{ header: 'id' },
291329
{ header: 'filename' },
@@ -601,7 +639,7 @@ export const CLI_CONTRACT: CliContract = {
601639
},
602640

603641
// ─── Not a terminal-shaped operation ──────────────────────────────────────
604-
// Multipart upload; `sim knowledge documents upload <id> <path>` needs its
642+
// Multipart upload; `sim documents upload <path> --kb <id>` needs its
605643
// own file-reading command rather than a generated flag surface.
606644
uploadKnowledgeDocument: { hidden: true },
607645
createKnowledgeDocumentUpload: { hidden: true },

0 commit comments

Comments
 (0)