Skip to content

Commit 8ebca27

Browse files
committed
cli updates
1 parent 5b5f59b commit 8ebca27

17 files changed

Lines changed: 2174 additions & 173 deletions

packages/sim-cli/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ sim chats list [--search <text>] [--limit <n>]
126126
sim chats get <chatId> [--read-only]
127127
sim chats rename <chatId> --title <title>
128128

129+
sim secrets list [--scope workspace|personal]
130+
sim secrets set <name> --scope workspace|personal [--value <value> | --value-stdin]
131+
sim secrets delete <name> --scope workspace|personal --yes
132+
129133
sim workflows ls [path] [--search <text>] [--limit <n>]
130134
sim workflows list [--folder <path>] [--deployed-only] [--limit <n>]
131135
sim workflows get <id>
@@ -195,6 +199,18 @@ Organization audit logs require a personal API key. Commands with
195199
Saved chat commands also require a personal API key and use that active
196200
workspace unless `--workspace` overrides it.
197201

202+
`sim secrets set` prompts for the value without echoing it when run in an
203+
interactive terminal. For automation, pipe the value with `--value-stdin`; one
204+
trailing line ending is removed. The older inline `--value` form remains
205+
available, but can expose the value in shell history and process listings.
206+
Sensitive values are sent only to HTTPS profiles; HTTP is accepted only for
207+
localhost/loopback development endpoints.
208+
209+
```bash
210+
sim secrets set OPENAI_API_KEY --scope workspace
211+
sim secrets set OPENAI_API_KEY --scope workspace --value-stdin < "$OPENAI_API_KEY_FILE"
212+
```
213+
198214
`workflows runs get` is the lightweight status and polling resource.
199215
`--workflow` names the parent resource, while the run ID remains positional.
200216
For a paused run, its status includes the context ID needed by `resume`.
@@ -215,6 +231,13 @@ Structured questions use a separate compact panel: Up/Down moves, Enter selects,
215231
Space toggles multi-select items, typing supplies a custom answer, and Esc returns
216232
to the ordinary composer. Suggested follow-up metadata is omitted.
217233

234+
OAuth requests show the validated Sim authorization URL directly so the terminal
235+
can open it in a browser. When Sim requests an environment secret, interactive
236+
chat presents a fixed-width masked input after the response finishes and saves
237+
the value through the write-only secrets API; the value never enters the chat
238+
composer or transcript. Enter saves and Esc skips. Read-only chat never opens a
239+
secret input and instead prints the secure `sim secrets set` command to run later.
240+
218241
The composer stays editable while Sim is working. Press Enter with a follow-up
219242
to queue it and immediately steer the active turn (the TUI performs the web
220243
chat's queue-then-send-now handoff in one step); additional submitted prompts

packages/sim-cli/src/commands/protocol/chat-structured.test.ts

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,102 @@ describe('renderChatStructured', () => {
300300
expect(controlBearing.text).not.toContain(ESC)
301301
})
302302

303-
it('renders credential links as a plain action without exposing the destination', () => {
303+
it('renders only same-origin Sim credential links as visible destinations', () => {
304+
const callbackUrl = encodeURIComponent('https://sim.example/workspace/ws-1')
305+
const trusted = renderChatStructured(
306+
`<credential>{"type":"link","provider":"Slack","value":"https://sim.example/api/auth/oauth2/authorize?providerId=slack&workspaceId=ws-1&callbackURL=${callbackUrl}"}</credential>`,
307+
{ simEndpoint: 'https://sim.example', workspaceId: 'ws-1' }
308+
)
309+
const lookalike = renderChatStructured(
310+
'<credential>{"type":"link","provider":"Slack","value":"https://sim.example.evil.test/connect"}</credential>',
311+
{ simEndpoint: 'https://sim.example', workspaceId: 'ws-1' }
312+
)
313+
314+
expect(trusted.text).toBe(
315+
`Connect Slack in Sim:\nhttps://sim.example/api/auth/oauth2/authorize?providerId=slack&workspaceId=ws-1&callbackURL=${callbackUrl}`
316+
)
317+
expect(lookalike.text).toBe('Open Sim to connect Slack.')
318+
expect(lookalike.text).not.toContain('sim.example.evil.test')
319+
expect(trusted.text).not.toContain(ESC)
320+
expect(lookalike.text).not.toContain(ESC)
321+
})
322+
323+
it('rejects same-origin non-auth links, remote HTTP, and links for another workspace', () => {
324+
const options = { simEndpoint: 'https://sim.example', workspaceId: 'ws-1' }
325+
const wrongPath = renderChatStructured(
326+
'<credential>{"type":"link","provider":"Slack","value":"https://sim.example/workspace/ws-1/settings"}</credential>',
327+
options
328+
)
329+
const remoteHttp = renderChatStructured(
330+
'<credential>{"type":"link","provider":"Slack","value":"http://sim.example/api/auth/trello/authorize"}</credential>',
331+
{ simEndpoint: 'http://sim.example', workspaceId: 'ws-1' }
332+
)
333+
const wrongWorkspace = renderChatStructured(
334+
'<credential>{"type":"link","provider":"Slack","value":"https://sim.example/api/auth/oauth2/authorize?providerId=slack&workspaceId=ws-2&callbackURL=https%3A%2F%2Fsim.example%2Fworkspace%2Fws-2"}</credential>',
335+
options
336+
)
337+
338+
expect(wrongPath.text).toBe('Open Sim to connect Slack.')
339+
expect(remoteHttp.text).toBe('Open Sim to connect Slack.')
340+
expect(wrongWorkspace.text).toBe('Open Sim to connect Slack.')
341+
})
342+
343+
it('accepts exact browser-auth routes, including HTTP on localhost only', () => {
344+
const trello = renderChatStructured(
345+
'<credential>{"type":"link","provider":"Trello","value":"http://localhost:3000/api/auth/trello/authorize"}</credential>',
346+
{ simEndpoint: 'http://localhost:3000', workspaceId: 'ws-1' }
347+
)
348+
const instagram = renderChatStructured(
349+
'<credential>{"type":"link","provider":"Instagram","value":"https://sim.example/api/auth/instagram/authorize?returnUrl=https%3A%2F%2Fsim.example%2Fworkspace%2Fws-1%2Fchat%2Fchat-1&workspaceId=ws-1"}</credential>',
350+
{ simEndpoint: 'https://sim.example', workspaceId: 'ws-1' }
351+
)
352+
353+
expect(trello.text).toContain('http://localhost:3000/api/auth/trello/authorize')
354+
expect(instagram.text).toContain('https://sim.example/api/auth/instagram/authorize?')
355+
})
356+
357+
it('turns secret inputs into interactive controls without putting a value in chat text', () => {
304358
const content =
305-
'<credential>{"type":"link","provider":"Slack","value":"https://sim.example.evil.test/connect"}</credential>'
306-
const result = renderChatStructured(content)
359+
'<credential>{"type":"secret_input","name":"OPENAI_API_KEY","scope":"personal"}</credential>'
360+
const interactive = renderChatStructured(content, { printMode: false })
361+
const readOnly = renderChatStructured(content, {
362+
printMode: false,
363+
allowSecretInput: false,
364+
})
365+
const printable = renderChatStructured(content)
366+
367+
expect(interactive.text).toBe('')
368+
expect(interactive.interactions).toEqual([
369+
{
370+
kind: 'credential',
371+
credential: {
372+
type: 'secret_input',
373+
name: 'OPENAI_API_KEY',
374+
scope: 'personal',
375+
provider: undefined,
376+
},
377+
},
378+
])
379+
expect(printable.text).toBe(
380+
'Provide OPENAI_API_KEY securely with: sim secrets set OPENAI_API_KEY --scope personal'
381+
)
382+
expect(readOnly.interactions).toEqual([])
383+
expect(readOnly.text).toBe(printable.text)
384+
})
307385

308-
expect(result.text).toBe('Open Sim to connect Slack.')
309-
expect(result.text).not.toContain('sim.example.evil.test')
310-
expect(result.text).not.toContain(ESC)
386+
it('links service-account setup to the active workspace without exposing tag values', () => {
387+
const result = renderChatStructured(
388+
'<credential>{"type":"service_account","provider":"Slack","credentialId":"cred-1"}</credential>',
389+
{
390+
simEndpoint: 'https://sim.example/api',
391+
workspaceId: 'workspace / 1',
392+
}
393+
)
394+
395+
expect(result.text).toBe(
396+
'Connect Slack with a service account in Sim:\nhttps://sim.example/workspace/workspace%20%2F%201/integrations'
397+
)
398+
expect(result.text).not.toContain('cred-1')
311399
})
312400

313401
it('sanitizes workspace titles before rendering a plain resource name', () => {

0 commit comments

Comments
 (0)