Preface

作为一个每天和 Codex CLI 对线的时长与工作时长不相上下的资深 AI 对线者,我在长时间对线的过程中养成了一个习惯,就是创建大量的临时性的工作目录用于 Codex 工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
C:\Users\Administrator\Workspace>dir

C:\Users\Administrator\Workspace 的目录

2026/07/02 11:33 <DIR> .
2026/07/02 11:33 <DIR> ..
2026/06/15 10:05 <DIR> Temp-1-260615
2026/06/18 10:11 <DIR> Temp-10-260618
2026/06/29 10:06 <DIR> Temp-11-260629
2026/06/15 10:01 <DIR> Temp-2-260615
2026/06/15 14:15 <DIR> Temp-3-260615
2026/06/15 14:52 <DIR> Temp-4-260615
2026/06/15 17:13 <DIR> Temp-5-260615
2026/06/15 18:15 <DIR> Temp-6-260615
2026/06/16 10:09 <DIR> Temp-7-260616
2026/06/16 14:31 <DIR> Temp-8-260616
2026/06/16 14:40 <DIR> Temp-9-260616

...

这非常适合于让 AI 进行临时性的、短期的且需要读写文件的工作,例如调研写报告、数据处理、探索性的项目开发,这些工作目录往往在完成后不再会被访问,因此也不需要繁杂的项目结构组织。

但随着临时工作目录越来越多,我注意到我开始反复在做同一件事:

1
2
3
4
5
打开 Windows Terminal
> cd Workspace
> mkdir Temp-12-260702
> cd Temp-12-260702
> codex

于是,写脚本的本性蠢蠢欲动

Body

Overview

ws.cmd 是一个支持快速创建和切换工作目录的 Windows CMD 脚本,以 ws.cmd 所在目录为工作区,在工作区下创建、查找目录

目录格式:[type]-[number]-[YYMMDD]

示例:Temp-12-260702Workspace-3-260702

其中 type 默认值为 Temp,默认值可修改,支持字母、数字、下划线,number在同一个type内始终递增,[YYMMDD]为目录创建时间

Usage

  1. 将脚本放在工作区之下,例如C:\Users\Administrator\Workspace\ws.cmd

  2. 将路径写入 PATH 使得脚本支持在任意位置执行

  3. 创建目录

    1
    2
    3
    ws new [type]
    # ws new 可缩写为 n
    ws n [type]

    例如工作区中number最大的目录为Temp-11-260702Workspace-3-260702,且执行命令时的日期为 2026 年 7 月 2 日,那么ws new将创建Temp-12-260702ws n Workspace将创建Workspace-4-260702

  4. cd到目录

    1
    2
    3
    4
    ws <number> [type]
    ws last [type]
    # ws last 可缩写为 l
    ws l [type]

    例如工作区中number最大的目录为Temp-11-260702Workspace-3-260702,且有其他的目录例如Temp-2-260615Workspace-1-260619等,那么ws l将cd到Temp-11-260702ws last Workspace将cd到Workspace-3-260702,而ws 2将cd到Temp-2-260615

  5. 创建并cd目录

    1
    ws nl [type]

    等效为先执行ws n [type]后执行ws l [type]

  6. 修改默认type

    1
    2
    3
    ws type <type>
    # ws type 可缩写为 t
    ws t <type>

    type的默认值为Temp,因此上述情景中ws nws l都是对Temp开头的目录进行操作,可以通过ws t Workspace修改type默认值为Workspace之后执行ws nws l将默认对Workspace开头的目录进行操作

  7. 配置文件

    该脚本会在同目录生成ws.conf配置文件,用于保存必要配置

Limit

该脚本的核心功能之一是cd到指定目录,但是由于子进程不能修改父进程的当前目录,因此该cmd脚本只能在 CMD 中生效,如果使用 PowerShell 则需要写 ps1 脚本。一个统一且完美的解决方案目前来看几乎不存在,但可能考虑通过监测父进程 shell 类型来嵌套启动同类型 shell 的方式间接实现,或使用模拟键盘输入的方式控制。

该脚本目前未支持用户自定义目录命名格式。

Appendix

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
@echo off
setlocal EnableExtensions EnableDelayedExpansion

set "WS_ROOT=%~dp0"
set "CONF_FILE=%WS_ROOT%ws.conf"
set "DEFAULT_TYPE=Temp"

if exist "%CONF_FILE%" (
for /f "usebackq tokens=1,* delims==" %%A in ("%CONF_FILE%") do (
if /i "%%A"=="type" if not "%%B"=="" set "DEFAULT_TYPE=%%B"
)
)

call :IsType "%DEFAULT_TYPE%" DEFAULT_TYPE_OK
if not "%DEFAULT_TYPE_OK%"=="1" set "DEFAULT_TYPE=Temp"

set "ACTION=%~1"

if /i "%ACTION%"=="type" goto :set_default_type
if /i "%ACTION%"=="t" goto :set_default_type

set "REQUESTED_TYPE=%~2"
if defined REQUESTED_TYPE (
set "ACTIVE_TYPE=%REQUESTED_TYPE%"
) else (
set "ACTIVE_TYPE=%DEFAULT_TYPE%"
)

call :IsType "%ACTIVE_TYPE%" TYPE_OK
if not "%TYPE_OK%"=="1" (
echo Invalid type: %ACTIVE_TYPE%
echo Type may only contain letters, numbers, and underscores.
exit /b 1
)

if /i "%ACTION%"=="new" goto :new
if /i "%ACTION%"=="n" goto :new
if /i "%ACTION%"=="last" goto :last
if /i "%ACTION%"=="l" goto :last
if /i "%ACTION%"=="nl" goto :new_last

call :IsDigits "%ACTION%" ACTION_IS_NUM
if "%ACTION_IS_NUM%"=="1" goto :by_number

echo Usage:
echo ws new ^| n [type] Create the next [type]-[number]-[YYMMDD] directory.
echo ws last ^| l [type] Change directory to the [type] directory with the largest number.
echo ws nl [type] Create the next [type] directory, then change directory to it.
echo ws ^<number^> [type] Change directory to [type]-[number]-[YYMMDD].
echo ws type ^| t ^<type^> Set the default type in ws.conf.
echo.
echo Default type: %DEFAULT_TYPE%
exit /b 1

:set_default_type
set "NEW_DEFAULT_TYPE=%~2"
if not defined NEW_DEFAULT_TYPE (
echo Current default type: %DEFAULT_TYPE%
exit /b 0
)

call :IsType "%NEW_DEFAULT_TYPE%" TYPE_OK
if not "%TYPE_OK%"=="1" (
echo Invalid type: %NEW_DEFAULT_TYPE%
echo Type may only contain letters, numbers, and underscores.
exit /b 1
)

(echo type=%NEW_DEFAULT_TYPE%) > "%CONF_FILE%"
if errorlevel 1 (
echo Failed to write "%CONF_FILE%".
exit /b 1
)

echo Default type: %NEW_DEFAULT_TYPE%
exit /b 0

:new
set "AFTER_NEW="
goto :CreateNew

:new_last
set "AFTER_NEW=last"
goto :CreateNew

:last
call :FindLast
if not defined MAX_DIR (
echo No %ACTIVE_TYPE%-[number]-[YYMMDD] directory found in "%WS_ROOT%".
exit /b 1
)

set "TARGET=%WS_ROOT%!MAX_DIR!"
endlocal & cd /d "%TARGET%"
exit /b 0

:by_number
call :FindByNumber "%ACTION%"
if not defined MATCH_DIR (
echo No %ACTIVE_TYPE%-%ACTION%-[YYMMDD] directory found in "%WS_ROOT%".
exit /b 1
)

set "TARGET=%WS_ROOT%!MATCH_DIR!"
endlocal & cd /d "%TARGET%"
exit /b 0

:CreateNew
call :FindLast
if defined MAX_NUM (
set /a NEXT_NUM=MAX_NUM+1
) else (
set "NEXT_NUM=1"
)

for /f "usebackq delims=" %%D in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-Date -Format yyMMdd"`) do set "TODAY=%%D"
if not defined TODAY (
echo Failed to get current date.
exit /b 1
)

set "NEW_DIR=!ACTIVE_TYPE!-!NEXT_NUM!-!TODAY!"
if exist "%WS_ROOT%!NEW_DIR!\" (
echo Directory already exists: !NEW_DIR!
exit /b 1
)

mkdir "%WS_ROOT%!NEW_DIR!" || exit /b 1
echo Created: !NEW_DIR!
if /i "!AFTER_NEW!"=="last" goto :last
exit /b 0

:FindLast
set "MAX_NUM="
set "MAX_DIR="

for /d %%G in ("%WS_ROOT%!ACTIVE_TYPE!-*-*") do (
set "DIR_NAME=%%~nxG"
for /f "tokens=1-4 delims=-" %%A in ("!DIR_NAME!") do (
if /i "%%A"=="!ACTIVE_TYPE!" if "%%D"=="" (
set "CAND_NUM=%%B"
set "CAND_DATE=%%C"
call :IsDigits "!CAND_NUM!" CAND_NUM_OK
call :IsDate "!CAND_DATE!" CAND_DATE_OK
if "!CAND_NUM_OK!"=="1" if "!CAND_DATE_OK!"=="1" (
call :NormalizeNumber "!CAND_NUM!" CAND_VALUE
if not defined MAX_NUM (
set "MAX_NUM=!CAND_VALUE!"
set "MAX_DIR=!DIR_NAME!"
) else if !CAND_VALUE! GTR !MAX_NUM! (
set "MAX_NUM=!CAND_VALUE!"
set "MAX_DIR=!DIR_NAME!"
)
)
)
)
)
exit /b 0

:FindByNumber
set "MATCH_DIR="
call :NormalizeNumber "%~1" LOOKUP_NUM

for /d %%G in ("%WS_ROOT%!ACTIVE_TYPE!-*-*") do (
set "DIR_NAME=%%~nxG"
for /f "tokens=1-4 delims=-" %%A in ("!DIR_NAME!") do (
if /i "%%A"=="!ACTIVE_TYPE!" if "%%D"=="" (
set "CAND_NUM=%%B"
set "CAND_DATE=%%C"
call :IsDigits "!CAND_NUM!" CAND_NUM_OK
call :IsDate "!CAND_DATE!" CAND_DATE_OK
if "!CAND_NUM_OK!"=="1" if "!CAND_DATE_OK!"=="1" (
call :NormalizeNumber "!CAND_NUM!" CAND_VALUE
if "!CAND_VALUE!"=="!LOOKUP_NUM!" set "MATCH_DIR=!DIR_NAME!"
)
)
)
)
exit /b 0

:IsDate
setlocal EnableDelayedExpansion
set "VALUE=%~1"
if "!VALUE:~5,1!"=="" (
endlocal & set "%~2=0"
exit /b 0
)
if not "!VALUE:~6,1!"=="" (
endlocal & set "%~2=0"
exit /b 0
)
call :IsDigits "!VALUE!" DATE_DIGITS
if "!DATE_DIGITS!"=="1" (
endlocal & set "%~2=1"
) else (
endlocal & set "%~2=0"
)
exit /b 0

:IsDigits
setlocal EnableDelayedExpansion
set "VALUE=%~1"
if not defined VALUE (
endlocal & set "%~2=0"
exit /b 0
)
for /f "delims=0123456789" %%N in ("!VALUE!") do (
endlocal & set "%~2=0"
exit /b 0
)
endlocal & set "%~2=1"
exit /b 0

:IsType
setlocal EnableDelayedExpansion
set "VALUE=%~1"
if not defined VALUE (
endlocal & set "%~2=0"
exit /b 0
)
echo(!VALUE!| findstr /r /c:"^[A-Za-z0-9_][A-Za-z0-9_]*$" >nul
if errorlevel 1 (
endlocal & set "%~2=0"
exit /b 0
)
endlocal & set "%~2=1"
exit /b 0

:NormalizeNumber
setlocal EnableDelayedExpansion
set "VALUE=%~1"
:NormalizeLoop
if "!VALUE!"=="0" goto :NormalizeDone
if "!VALUE:~0,1!"=="0" (
set "VALUE=!VALUE:~1!"
if not defined VALUE set "VALUE=0"
goto :NormalizeLoop
)
:NormalizeDone
endlocal & set "%~2=%VALUE%"
exit /b 0