Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ CLAUDE.md
.vitepress/dist

node_modules/

.claude/settings.local.json
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ repos:
rev: v1.37.1
hooks:
- id: yamllint
exclude: ^\.vitepress/data/commands\.yaml$

- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.47.0
Expand Down
65 changes: 65 additions & 0 deletions .vitepress/data/commands.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { createMarkdownRenderer, defineLoader } from 'vitepress'
import type { MarkdownRenderer } from 'vitepress'
import fs from 'node:fs'
import { resolve } from 'node:path'
import { load } from 'js-yaml'

export interface CommandOption {
name: string
shorthand?: string
default?: string
usage?: string
}

export interface Command {
name: string
synopsis?: string
description?: string
usage?: string
aliases?: string[]
example?: string
options?: CommandOption[]
commands?: Command[]
md: {
description?: string
}
}

declare const data: Record<string, Command>
export { data }

const file = resolve(__dirname, './commands.yaml')

function flatten(
cmd: Command,
md: MarkdownRenderer,
into: Record<string, Command>
): void {
into[cmd.name] = {
...cmd,
md: {
description: cmd.description ? md.renderInline(cmd.description) : undefined
}
}

for (const child of cmd.commands ?? []) {
flatten(child, md, into)
}
}

export default defineLoader({
watch: [file],
async load(): Promise<typeof data> {
const md = await createMarkdownRenderer('.')

const root = load(fs.readFileSync(file, 'utf8')) as Command

const commands: Record<string, Command> = {}

for (const group of root.commands ?? []) {
flatten(group, md, commands)
}

return commands
}
})
418 changes: 418 additions & 0 deletions .vitepress/data/commands.yaml

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions .vitepress/theme/components/CommandSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { data as commandData } from '../../data/commands.data'
import type { Command } from '../../data/commands.data'
import { computed } from 'vue'

const props = defineProps<{ path: string }>()

const cmd = computed<Command>(() => commandData[props.path])
const anchor = computed(() => cmd.value.name.replaceAll(' ', '-'))

function flag(name: string, shorthand?: string): string {
return shorthand ? `--${name}, -${shorthand}` : `--${name}`
}
</script>

<template>
<h3 :id="anchor" class="header">
<code>{{ cmd.name }}</code>
<Badge v-if="cmd.aliases?.length" type="info" :text="`alias: ${cmd.aliases.join(', ')}`" />

<a class="header-anchor" :href="`#${anchor}`" :aria-label="`Permalink to &quot;${cmd.name}&quot;`"></a>
</h3>

<blockquote v-if="cmd.md.description" v-html="cmd.md.description" />

<p v-if="cmd.usage" class="usage"><code>{{ cmd.usage }}</code></p>

<table v-if="cmd.options?.length">
<thead>
<tr>
<th>Flag</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr v-for="option in cmd.options" :key="option.name">
<td><code>{{ flag(option.name, option.shorthand) }}</code></td>
<td>{{ option.usage }}</td>
<td><code v-if="option.default">{{ option.default }}</code></td>
</tr>
</tbody>
</table>
</template>

<style scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
}

.usage {
margin: .4rem 0;
}
</style>
2 changes: 2 additions & 0 deletions .vitepress/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { h, Fragment } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme-without-fonts'
import CommandSection from './components/CommandSection.vue'
import DockerIcon from './components/DockerIcon.vue'
import EnvVar from './components/EnvVar.vue'
import EnvVarSection from './components/EnvVarSection.vue'
Expand Down Expand Up @@ -31,6 +32,7 @@ export default {
})
},
enhanceApp({ app, router, siteData }) {
app.component('CommandSection', CommandSection)
app.component('EnvVar', EnvVar)
app.component('EnvVarSection', EnvVarSection)
}
Expand Down
1 change: 1 addition & 0 deletions docs/partials/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ environment-variables.md
extensions.md
fonts.md
fs-manifest.md
commands.md
172 changes: 1 addition & 171 deletions docs/tools/ws-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,174 +28,4 @@ This feature allows for quick access to your workspace root with a minimal comma

## Commands

### Clipboard (`ws clip`)

Interact with the browser clipboard from the terminal.

- **`paste`:** Paste clipboard content.

```sh
# Save clipboard to file
ws clip paste > out.txt

# Use in pipeline
ws clip paste | grep "pattern"
```

::: tip
For quick clipboard access, use the clipboard binaries:

```sh
# macOS-compatible
echo "copy this" | pbcopy
pbpaste

# X11-compatible
echo "copy this" | xclip -sel c
xclip -o -sel c

# Alternative X11
echo "copy this" | xsel -b
xsel -b -o
```

:::

See the [Terminal Clipboard](/editor/terminal#clipboard) section for more details.

### Features (`ws feature`)

Install and manage additional pre-configured features.

- **`list`:** List available features that can be installed.
- **`info <feature>`:** Show detailed information about a feature.
- **`install <feature>`:** Install a feature.

See our [dedicated section on installing features](/editor/features).

### Information (`ws info`)

Display workspace information.

- **`env`:** Display effective workspace environment variables.
- **`extensions`:** Display installed extensions.
- **`metrics`:** Display system resource usage *(CPU, memory, disk, GPU, networking, etc.)*.
- **`uptime`:** Display the workspace uptime.
- **`version`:** Display installed workspace version.

### Logging (`ws log`)

Log messages to the console.

- **`debug <message>`:** Log a *debug* message.
- **`error <message>`:** Log an *error* message.
- **`info <message>`:** Log an *info* message.
- **`warn <message>`:** Log a *warning* message.
- **`stamp`:** Print the current timestamp.

The `log *` functions can be supplied with an optional `--indent=*` flag, indicating the
desired number of indentations for prefixing the message. There is also a `--pipe` flag to
loop through piped output.

### Logs (`ws logs`)

Retrieve workspace logs.

```sh
ws logs --level=error --tail=100 --follow
```

### Secrets (`ws secrets`)

Manage encryption, decryption, and master key generation for secure secrets handling.

For comprehensive documentation including secret types and security best practices, see the
[dedicated secrets documentation](/settings/secrets).

#### Quick Reference

- **`generate`:**
- **`master`:** Generate a cryptographically secure master key.
- **`login`:** Generate a login password hash for authentication.
- **`encrypt <plaintext>`:** Encrypt a plaintext value.
- **`decrypt <encrypted>`:** Decrypt an encrypted value.

### Seed (`ws seed`)

Project files and secrets from a durable source directory onto the filesystem. See the
[seed documentation](/settings/seed) for the manifest schema and ownership boundary.

- **`apply [dest...]`:** Project the seed plan, optionally scoped to named destinations *(`--force`
overwrites existing destinations)*.
- **`ls`:** List the resolved seed plan.

### Serve (`ws serve`)

Serve internal assets.

- **`current`:** Serve current directory as a static site.
- **`font`:** Serve fonts for local download.

### Show (`ws show`)

Display information about the current workspace instance.

- **`env <group.prop>`:** Display the resolved value of a setting, queried by its canonical **dotted** key
(e.g. `ws-cli show env server.port`). Falls back to the default declared in `env.reference.yaml` when unset.
The matching `WS_*` variable (`WS_SERVER_PORT`) is the environment binding you `export` to set it — it is
not a query key (`ws-cli show env WS_SERVER_PORT` exits `2` with a hint pointing at the dotted form).
- *(no flags)* — pretty mode: shows the dotted key (and its `WS_*` binding), schema description,
markdown-rendered `longDescription`, resolved value, and source label (`env-set` / `deprecated-alias` / `yaml-default`).
- `--value` — print the resolved value as a single line (script-friendly). Combinable with `--check`
to emit the value only when the operator has set the variable (`--value --check`).
- `--as bool|int|list` — validate and emit the resolved value as the requested type
(`bool` exits `0` truthy / `1` falsy / error on garbage; `int` prints canonical int10;
`list` newline-splits using the YAML `delimiter:` or `--delimiter` override). Mutually exclusive with `--value`.
- `--check [--deprecated <WS_ALIAS>]` — existence probe. `--deprecated` takes a **raw `WS_*` alias** (deprecated
aliases have no dotted form). Exits `0` when the preferred variable is set;
`1` when unset (stderr carries a deprecation warning if `--deprecated` is supplied and the alias
is set); `2` when both the preferred variable and the deprecated alias are set (aborts to stderr).
- `--or-skip` — modifier on `--value` / `--value --check` / `--as bool`: exit `1` (not an error) on the
natural absence of the chosen projection, emitting a `Skipped: env [<KEY>] not set` debug breadcrumb to
stderr. Lets a script guard with `if val=$(ws-cli show env <group.prop> --value --or-skip); then …`.
- `--validate <regex>` — modifier on `--as list`: each token must full-match the anchored caller-supplied
charset; on any miss the whole list fails **closed** (no tokens emitted, exit `3`, stderr
`Rejected: invalid item [<token>]`). Centralizes token rejection for untrusted delimited sinks.
- Unknown dotted keys (not declared in `env.reference.yaml`) exit non-zero with stderr
`Unknown env var [<group.prop>]` (echoing the typed dotted form) in all non-`--check` modes.
- **`ip`:**
- **`internal`:** Display the internal IP address.
- **`node`:** Display the node/host IP address.
- **`path`:**
- **`home`:** Display the workspace home path.
- **`vscode-settings`:** Display the VS Code settings path.

A useful example could be when executing a *reverse tunnel* to the remote node:

```sh
ws_node_ip=$(ws show ip node)

ssh -N -R "3001:${ws_node_ip}:3001" "${ws_node_ip}"
```

### Templates (`ws template`)

Manage static configuration files. Many configuration files are defined
globally *(in `~` or `/etc`)* and are used system-wide without needing to be included in
the project root.

However, this approach may not work in *CI* environments or on other
machines *(when not using the workspace image)*, as they might lack these global
configurations.

- **`list`:** List all available configuration templates.
- **`show <template>`:** Display the contents of a configuration template.
- **`apply <template>`:** Apply a configuration template to the current project.

```sh
ws template apply ruff
```

### Version (`ws version`)

Display the installed workspace version.
<!--@include: ../partials/commands.md -->
1 change: 1 addition & 0 deletions scripts/generate-all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { execSync } from "node:child_process"

execSync("node ./scripts/generate-dependencies.mjs", { stdio: "inherit" })
execSync("node ./scripts/generate-deprecated-variables.mjs", { stdio: "inherit" })
execSync("node ./scripts/generate-commands.mjs", { stdio: "inherit" })
execSync("node ./scripts/generate-environment-variables.mjs", { stdio: "inherit" })
execSync("node ./scripts/generate-extensions.mjs", { stdio: "inherit" })
execSync("node ./scripts/generate-fonts.mjs", { stdio: "inherit" })
Expand Down
30 changes: 30 additions & 0 deletions scripts/generate-commands.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import fs from 'node:fs'
import { resolve } from 'node:path'
import { load } from 'js-yaml'

const root = load(
fs.readFileSync(resolve('.vitepress/data/commands.yaml'), 'utf8')
)

const sections = []

const walk = command => {
sections.push(`<CommandSection path="${command.name}" />`, '---', '')

for (const child of command.commands ?? []) {
walk(child)
}
}

for (const group of root.commands ?? []) {
walk(group)
}

if (sections.length >= 2 && sections.at(-2) === '---') {
sections.splice(-2)
}

sections.push('')

fs.writeFileSync(resolve('docs/partials/commands.md'), sections.join('\n'))
console.log('✔ docs/partials/commands.md updated')
Loading