helloagents 3.0.12 → 3.0.15-beta.1

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.
Files changed (72) hide show
  1. package/.claude-plugin/marketplace.json +6 -4
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +169 -30
  5. package/README_CN.md +169 -30
  6. package/bootstrap-lite.md +27 -20
  7. package/bootstrap.md +30 -23
  8. package/cli.mjs +119 -11
  9. package/gemini-extension.json +1 -1
  10. package/install.ps1 +125 -0
  11. package/install.sh +118 -0
  12. package/package.json +23 -4
  13. package/scripts/advisor-state.mjs +36 -63
  14. package/scripts/capability-registry.mjs +3 -3
  15. package/scripts/cli-branch.mjs +84 -0
  16. package/scripts/cli-codex-config.mjs +11 -20
  17. package/scripts/cli-codex.mjs +32 -38
  18. package/scripts/cli-doctor-render.mjs +4 -0
  19. package/scripts/cli-doctor.mjs +40 -30
  20. package/scripts/cli-host-detect.mjs +0 -1
  21. package/scripts/cli-hosts.mjs +16 -8
  22. package/scripts/cli-lifecycle-hosts.mjs +92 -27
  23. package/scripts/cli-lifecycle.mjs +9 -7
  24. package/scripts/cli-messages.mjs +34 -16
  25. package/scripts/cli-runtime-carrier.mjs +36 -0
  26. package/scripts/cli-runtime-root.mjs +72 -0
  27. package/scripts/cli-toml.mjs +0 -79
  28. package/scripts/cli-utils.mjs +30 -4
  29. package/scripts/closeout-state.mjs +35 -62
  30. package/scripts/delivery-gate-messages.mjs +70 -0
  31. package/scripts/delivery-gate.mjs +9 -75
  32. package/scripts/guard-rules.mjs +42 -42
  33. package/scripts/guard.mjs +44 -24
  34. package/scripts/notify-context.mjs +19 -28
  35. package/scripts/notify-gates.mjs +2 -0
  36. package/scripts/notify-route.mjs +9 -7
  37. package/scripts/notify-ui.mjs +46 -33
  38. package/scripts/notify.mjs +60 -32
  39. package/scripts/project-storage.mjs +35 -66
  40. package/scripts/ralph-loop.mjs +36 -31
  41. package/scripts/replay-state.mjs +31 -128
  42. package/scripts/review-state.mjs +34 -61
  43. package/scripts/runtime-artifacts.mjs +95 -0
  44. package/scripts/runtime-context.mjs +35 -29
  45. package/scripts/runtime-scope.mjs +313 -0
  46. package/scripts/session-capsule.mjs +202 -0
  47. package/scripts/turn-state-cli.mjs +17 -0
  48. package/scripts/turn-state.mjs +185 -66
  49. package/scripts/turn-stop-gate.mjs +24 -6
  50. package/scripts/verify-state.mjs +34 -85
  51. package/scripts/visual-state.mjs +38 -65
  52. package/scripts/workflow-core.mjs +2 -2
  53. package/scripts/workflow-plan-files.mjs +1 -1
  54. package/scripts/workflow-recommendation.mjs +17 -13
  55. package/scripts/workflow-state.mjs +5 -5
  56. package/skills/commands/build/SKILL.md +1 -1
  57. package/skills/commands/commit/SKILL.md +1 -1
  58. package/skills/commands/help/SKILL.md +3 -3
  59. package/skills/commands/loop/SKILL.md +1 -1
  60. package/skills/commands/plan/SKILL.md +8 -6
  61. package/skills/commands/prd/SKILL.md +5 -3
  62. package/skills/commands/verify/SKILL.md +5 -5
  63. package/skills/hello-debug/SKILL.md +20 -3
  64. package/skills/hello-review/SKILL.md +2 -2
  65. package/skills/hello-subagent/SKILL.md +2 -2
  66. package/skills/hello-test/SKILL.md +6 -2
  67. package/skills/hello-ui/SKILL.md +4 -4
  68. package/skills/hello-verify/SKILL.md +10 -7
  69. package/skills/helloagents/SKILL.md +12 -7
  70. package/templates/context.md +6 -0
  71. package/templates/plans/plan.md +3 -0
  72. package/templates/plans/tasks.md +8 -3
@@ -0,0 +1,313 @@
1
+ import { execFileSync } from 'node:child_process'
2
+ import { createHash, randomUUID } from 'node:crypto'
3
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
4
+ import { dirname, join, normalize, resolve } from 'node:path'
5
+ import { homedir } from 'node:os'
6
+
7
+ import { resolveSessionToken } from './session-token.mjs'
8
+
9
+ export const PROJECT_DIR_NAME = '.helloagents'
10
+ export const PROJECT_SESSIONS_DIR_NAME = 'sessions'
11
+ export const PROJECT_ARTIFACTS_DIR_NAME = 'artifacts'
12
+ export const CAPSULE_FILE_NAME = 'capsule.json'
13
+ export const EVENTS_FILE_NAME = 'events.jsonl'
14
+ export const DEFAULT_STATE_SESSION_TOKEN = 'default'
15
+ export const USER_RUNTIME_DIR_NAME = 'runtime'
16
+ export const USER_RUNTIME_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000
17
+
18
+ function normalizePath(filePath = '') {
19
+ return filePath ? normalize(resolve(filePath)) : ''
20
+ }
21
+
22
+ function runGit(cwd, args = []) {
23
+ try {
24
+ return execFileSync('git', args, {
25
+ cwd,
26
+ encoding: 'utf-8',
27
+ timeout: 5_000,
28
+ stdio: ['ignore', 'pipe', 'ignore'],
29
+ }).trim()
30
+ } catch {
31
+ return ''
32
+ }
33
+ }
34
+
35
+ function getHomeDir(env = process.env) {
36
+ return env.HOME || env.USERPROFILE || homedir()
37
+ }
38
+
39
+ function normalizeComparablePath(filePath = '') {
40
+ const resolved = normalizePath(filePath)
41
+ try {
42
+ return realpathSync.native(resolved)
43
+ } catch {
44
+ return resolved
45
+ }
46
+ }
47
+
48
+ function samePath(left, right) {
49
+ const a = normalizeComparablePath(left)
50
+ const b = normalizeComparablePath(right)
51
+ return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b
52
+ }
53
+
54
+ function resolveGitTopLevel(cwd) {
55
+ const absolute = runGit(cwd, ['rev-parse', '--path-format=absolute', '--show-toplevel'])
56
+ if (absolute) return normalize(resolve(absolute))
57
+
58
+ const raw = runGit(cwd, ['rev-parse', '--show-toplevel'])
59
+ return raw ? normalize(resolve(cwd, raw)) : ''
60
+ }
61
+
62
+ function resolveGitBranchName(cwd) {
63
+ const branchName = runGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD'])
64
+ if (branchName && branchName !== 'HEAD') return branchName
65
+
66
+ const symbolicName = runGit(cwd, ['symbolic-ref', '--quiet', '--short', 'HEAD'])
67
+ return symbolicName && symbolicName !== 'HEAD' ? symbolicName : ''
68
+ }
69
+
70
+ export function sanitizeRuntimeSegment(value = '', fallback = '') {
71
+ const normalized = String(value)
72
+ .trim()
73
+ .toLowerCase()
74
+ .replace(/[^a-z0-9._-]+/g, '-')
75
+ .replace(/^-+|-+$/g, '')
76
+ .slice(0, 48)
77
+ return normalized || fallback
78
+ }
79
+
80
+ export function normalizeRuntimeOptions(options = {}) {
81
+ if (!options || typeof options !== 'object') return {}
82
+ if (options.payload && typeof options.payload === 'object') return options
83
+ return {
84
+ ...options,
85
+ payload: options,
86
+ }
87
+ }
88
+
89
+ export function getProjectActivationDir(cwd) {
90
+ const activeDir = findProjectActivationDir(cwd)
91
+ return activeDir || join(normalizePath(cwd || process.cwd()), PROJECT_DIR_NAME)
92
+ }
93
+
94
+ export function isProjectRuntimeActive(cwd) {
95
+ return Boolean(findProjectActivationDir(cwd))
96
+ }
97
+
98
+ export function getProjectRoot(cwd) {
99
+ const activeDir = findProjectActivationDir(cwd)
100
+ return activeDir ? dirname(activeDir) : normalizePath(cwd || process.cwd())
101
+ }
102
+
103
+ export function getUserRuntimeRoot(home = getHomeDir()) {
104
+ return join(home, PROJECT_DIR_NAME, USER_RUNTIME_DIR_NAME)
105
+ }
106
+
107
+ function isUserHomeHelloagentsDir(dirPath) {
108
+ const homeCandidates = [
109
+ getHomeDir(),
110
+ process.env.USERPROFILE || '',
111
+ homedir(),
112
+ ].filter(Boolean)
113
+ return homeCandidates.some((home) => samePath(dirPath, join(home, PROJECT_DIR_NAME)))
114
+ }
115
+
116
+ function isUserConfigStoreDir(dirPath) {
117
+ return existsSync(join(dirPath, 'helloagents.json'))
118
+ }
119
+
120
+ function isUserHomeDir(dirPath) {
121
+ const homeCandidates = [
122
+ getHomeDir(),
123
+ process.env.USERPROFILE || '',
124
+ homedir(),
125
+ ].filter(Boolean)
126
+ return homeCandidates.some((home) => samePath(dirPath, home))
127
+ }
128
+
129
+ function findProjectActivationDir(cwd) {
130
+ let current = normalizePath(cwd || process.cwd())
131
+ const gitRoot = resolveGitTopLevel(current)
132
+
133
+ while (current) {
134
+ const candidate = join(current, PROJECT_DIR_NAME)
135
+ if (
136
+ existsSync(candidate)
137
+ && !isUserHomeHelloagentsDir(candidate)
138
+ && !isUserConfigStoreDir(candidate)
139
+ ) {
140
+ return candidate
141
+ }
142
+ if (isUserHomeDir(current)) break
143
+ if (gitRoot && samePath(current, gitRoot)) break
144
+
145
+ const parent = dirname(current)
146
+ if (!parent || parent === current) break
147
+ current = parent
148
+ }
149
+
150
+ return ''
151
+ }
152
+
153
+ function removePathIfExists(filePath, result, bucket) {
154
+ if (!existsSync(filePath)) return
155
+ try {
156
+ rmSync(filePath, { recursive: true, force: true })
157
+ result[bucket].push(filePath)
158
+ } catch (error) {
159
+ result.errors.push(`${filePath}: ${error.message}`)
160
+ }
161
+ }
162
+
163
+ export function cleanupUserRuntimeRoot({
164
+ home = getHomeDir(),
165
+ now = Date.now(),
166
+ maxAgeMs = USER_RUNTIME_MAX_AGE_MS,
167
+ } = {}) {
168
+ const root = getUserRuntimeRoot(home)
169
+ const result = {
170
+ root,
171
+ removedExpiredDirs: [],
172
+ errors: [],
173
+ }
174
+
175
+ if (!existsSync(root)) return result
176
+
177
+ let entries = []
178
+ try {
179
+ entries = readdirSync(root, { withFileTypes: true })
180
+ } catch (error) {
181
+ result.errors.push(`${root}: ${error.message}`)
182
+ return result
183
+ }
184
+
185
+ for (const entry of entries) {
186
+ if (!entry.isDirectory()) continue
187
+ const dirPath = join(root, entry.name)
188
+ try {
189
+ if (now - statSync(dirPath).mtimeMs > maxAgeMs) {
190
+ removePathIfExists(dirPath, result, 'removedExpiredDirs')
191
+ }
192
+ } catch (error) {
193
+ result.errors.push(`${dirPath}: ${error.message}`)
194
+ }
195
+ }
196
+
197
+ return result
198
+ }
199
+
200
+ function resolveStableSessionToken({ payload = {}, env = process.env, ppid = process.ppid } = {}) {
201
+ void ppid
202
+ return resolveSessionToken({
203
+ payload,
204
+ env,
205
+ ppid: 0,
206
+ allowPpidFallback: false,
207
+ })
208
+ }
209
+
210
+ function resolveTransientSessionToken({ payload = {}, env = process.env, ppid = process.ppid } = {}) {
211
+ return resolveSessionToken({
212
+ payload,
213
+ env,
214
+ ppid,
215
+ allowPpidFallback: true,
216
+ })
217
+ }
218
+
219
+ export function getProjectSessionScope(cwd, options = {}) {
220
+ const normalizedCwd = normalizePath(cwd || process.cwd())
221
+ const projectRoot = getProjectRoot(normalizedCwd)
222
+ const { payload = {}, env = process.env, ppid = process.ppid } = normalizeRuntimeOptions(options)
223
+ const stableToken = resolveStableSessionToken({ payload, env, ppid })
224
+ const branch = sanitizeRuntimeSegment(resolveGitBranchName(projectRoot), 'detached')
225
+ const session = sanitizeRuntimeSegment(stableToken, DEFAULT_STATE_SESSION_TOKEN)
226
+ const activationDir = getProjectActivationDir(projectRoot)
227
+ const sessionDir = join(activationDir, PROJECT_SESSIONS_DIR_NAME, branch, session)
228
+
229
+ return {
230
+ cwd: projectRoot,
231
+ active: isProjectRuntimeActive(projectRoot),
232
+ branch,
233
+ session,
234
+ sessionMode: stableToken ? 'host-session' : 'default',
235
+ activationDir,
236
+ sessionDir,
237
+ statePath: join(sessionDir, 'STATE.md'),
238
+ capsulePath: join(sessionDir, CAPSULE_FILE_NAME),
239
+ eventsPath: join(sessionDir, EVENTS_FILE_NAME),
240
+ artifactsDir: join(sessionDir, PROJECT_ARTIFACTS_DIR_NAME),
241
+ key: `${normalizedCwd}::${branch}::${session}`,
242
+ }
243
+ }
244
+
245
+ function buildTransientRuntimeDir(cwd, options = {}) {
246
+ const normalizedCwd = normalizePath(cwd || process.cwd())
247
+ const { payload = {}, env = process.env, ppid = process.ppid } = normalizeRuntimeOptions(options)
248
+ const token = sanitizeRuntimeSegment(
249
+ resolveTransientSessionToken({ payload, env, ppid }),
250
+ DEFAULT_STATE_SESSION_TOKEN,
251
+ )
252
+ const hash = createHash('sha1')
253
+ .update(`${normalizedCwd.toLowerCase()}::${token}`)
254
+ .digest('hex')
255
+ .slice(0, 16)
256
+ cleanupUserRuntimeRoot()
257
+
258
+ return {
259
+ cwd: normalizedCwd,
260
+ branch: 'transient',
261
+ session: token,
262
+ sessionMode: token === DEFAULT_STATE_SESSION_TOKEN ? 'default' : 'transient-session',
263
+ sessionDir: join(getUserRuntimeRoot(), hash),
264
+ capsulePath: join(getUserRuntimeRoot(), hash, CAPSULE_FILE_NAME),
265
+ eventsPath: join(getUserRuntimeRoot(), hash, EVENTS_FILE_NAME),
266
+ artifactsDir: join(getUserRuntimeRoot(), hash, PROJECT_ARTIFACTS_DIR_NAME),
267
+ key: `${normalizedCwd}::transient::${token}`,
268
+ }
269
+ }
270
+
271
+ export function getRuntimeScope(cwd = process.cwd(), options = {}) {
272
+ const projectScope = getProjectSessionScope(cwd, options)
273
+ if (projectScope.active) {
274
+ return {
275
+ ...projectScope,
276
+ scope: 'project-session',
277
+ }
278
+ }
279
+
280
+ return {
281
+ ...buildTransientRuntimeDir(cwd, options),
282
+ active: false,
283
+ scope: 'user-runtime',
284
+ }
285
+ }
286
+
287
+ export function getRuntimeFilePath(cwd, fileName, options = {}) {
288
+ return join(getRuntimeScope(cwd, options).sessionDir, fileName)
289
+ }
290
+
291
+ export function getProjectEventsPath(cwd, options = {}) {
292
+ const scope = getProjectSessionScope(cwd, options)
293
+ return scope.active ? scope.eventsPath : ''
294
+ }
295
+
296
+ export function readJsonFile(filePath, fallback = null) {
297
+ try {
298
+ return JSON.parse(readFileSync(filePath, 'utf-8'))
299
+ } catch {
300
+ return fallback
301
+ }
302
+ }
303
+
304
+ export function writeJsonFileAtomic(filePath, value) {
305
+ mkdirSync(dirname(filePath), { recursive: true })
306
+ const tmpPath = join(dirname(filePath), `.${Date.now()}-${randomUUID()}.tmp`)
307
+ writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8')
308
+ renameSync(tmpPath, filePath)
309
+ }
310
+
311
+ export function removeRuntimeFile(filePath) {
312
+ rmSync(filePath, { force: true })
313
+ }
@@ -0,0 +1,202 @@
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
2
+ import { basename, dirname, join } from 'node:path'
3
+
4
+ import {
5
+ getProjectSessionScope,
6
+ getRuntimeScope,
7
+ readJsonFile,
8
+ removeRuntimeFile,
9
+ writeJsonFileAtomic,
10
+ } from './runtime-scope.mjs'
11
+
12
+ export { getRuntimeScope }
13
+
14
+ function buildEmptyCapsule(scope) {
15
+ return {
16
+ version: 1,
17
+ scope: scope.scope,
18
+ key: scope.key,
19
+ cwd: scope.cwd,
20
+ branch: scope.branch,
21
+ session: scope.session,
22
+ sessionMode: scope.sessionMode,
23
+ updatedAt: new Date().toISOString(),
24
+ turn: null,
25
+ route: null,
26
+ artifacts: {},
27
+ }
28
+ }
29
+
30
+ function normalizeOptions(options = {}) {
31
+ if (!options || typeof options !== 'object') return {}
32
+ if (options.payload && typeof options.payload === 'object') return options
33
+ return {
34
+ ...options,
35
+ payload: options,
36
+ }
37
+ }
38
+
39
+ function getScope(cwd, options = {}) {
40
+ const normalizedOptions = normalizeOptions(options)
41
+ if (normalizedOptions.project === true) {
42
+ return {
43
+ ...getProjectSessionScope(cwd, normalizedOptions),
44
+ scope: 'project-session',
45
+ }
46
+ }
47
+ return getRuntimeScope(cwd, normalizedOptions)
48
+ }
49
+
50
+ export function getSessionCapsulePath(cwd = process.cwd(), options = {}) {
51
+ return getScope(cwd, options).capsulePath
52
+ }
53
+
54
+ export function getSessionEventsPath(cwd = process.cwd(), options = {}) {
55
+ return getScope(cwd, options).eventsPath
56
+ }
57
+
58
+ export function getSessionArtifactsDir(cwd = process.cwd(), options = {}) {
59
+ return getScope(cwd, options).artifactsDir
60
+ }
61
+
62
+ export function getSessionArtifactPath(cwd, fileName, options = {}) {
63
+ return join(getSessionArtifactsDir(cwd, options), fileName)
64
+ }
65
+
66
+ export function getSessionArtifactRelativePath(cwd, fileName, options = {}) {
67
+ const scope = getScope(cwd, options)
68
+ if (scope.scope === 'project-session') {
69
+ return `.helloagents/sessions/${scope.branch}/${scope.session}/artifacts/${fileName}`
70
+ }
71
+ return `~/.helloagents/runtime/${basename(scope.sessionDir)}/artifacts/${fileName}`
72
+ }
73
+
74
+ export function readSessionCapsule(cwd = process.cwd(), options = {}) {
75
+ const scope = getScope(cwd, options)
76
+ const capsule = readJsonFile(scope.capsulePath, null)
77
+ if (!capsule || typeof capsule !== 'object') return buildEmptyCapsule(scope)
78
+ return {
79
+ ...buildEmptyCapsule(scope),
80
+ ...capsule,
81
+ scope: scope.scope,
82
+ key: scope.key,
83
+ cwd: scope.cwd,
84
+ branch: scope.branch,
85
+ session: scope.session,
86
+ sessionMode: scope.sessionMode,
87
+ }
88
+ }
89
+
90
+ export function writeSessionCapsule(cwd, capsule, options = {}) {
91
+ const scope = getScope(cwd, options)
92
+ const nextCapsule = {
93
+ ...buildEmptyCapsule(scope),
94
+ ...capsule,
95
+ scope: scope.scope,
96
+ key: scope.key,
97
+ cwd: scope.cwd,
98
+ branch: scope.branch,
99
+ session: scope.session,
100
+ sessionMode: scope.sessionMode,
101
+ updatedAt: new Date().toISOString(),
102
+ }
103
+ writeJsonFileAtomic(scope.capsulePath, nextCapsule)
104
+ return nextCapsule
105
+ }
106
+
107
+ export function updateSessionCapsule(cwd, updater, options = {}) {
108
+ const current = readSessionCapsule(cwd, options)
109
+ const patch = typeof updater === 'function' ? updater(current) : updater
110
+ return writeSessionCapsule(cwd, {
111
+ ...current,
112
+ ...(patch || {}),
113
+ }, options)
114
+ }
115
+
116
+ export function readCapsuleSection(cwd, section, options = {}) {
117
+ return readSessionCapsule(cwd, options)[section] || null
118
+ }
119
+
120
+ export function writeCapsuleSection(cwd, section, value, options = {}) {
121
+ return updateSessionCapsule(cwd, (capsule) => ({
122
+ [section]: value,
123
+ [`${section}UpdatedAt`]: new Date().toISOString(),
124
+ artifacts: capsule.artifacts || {},
125
+ }), options)
126
+ }
127
+
128
+ export function clearCapsuleSection(cwd, section, options = {}) {
129
+ const capsulePath = getSessionCapsulePath(cwd, options)
130
+ if (!existsSync(capsulePath)) return false
131
+
132
+ const capsule = readSessionCapsule(cwd, options)
133
+ if (!Object.prototype.hasOwnProperty.call(capsule, section)) return false
134
+ capsule[section] = null
135
+ capsule[`${section}UpdatedAt`] = new Date().toISOString()
136
+ writeSessionCapsule(cwd, capsule, options)
137
+ return true
138
+ }
139
+
140
+ export function appendSessionEvent(cwd, eventPayload, options = {}) {
141
+ const scope = getScope(cwd, options)
142
+ if (scope.scope === 'project-session' && !scope.active) return ''
143
+ const eventName = eventPayload?.event || ''
144
+ if (!eventName) return ''
145
+
146
+ mkdirSync(dirname(scope.eventsPath), { recursive: true })
147
+ const payload = {
148
+ ts: new Date().toISOString(),
149
+ scope: scope.scope,
150
+ key: scope.key,
151
+ sessionId: scope.session,
152
+ ...eventPayload,
153
+ }
154
+ writeFileSync(scope.eventsPath, `${JSON.stringify(payload)}\n`, {
155
+ encoding: 'utf-8',
156
+ flag: 'a',
157
+ })
158
+ return scope.eventsPath
159
+ }
160
+
161
+ export function resetSessionEvents(cwd, options = {}) {
162
+ const scope = getScope(cwd, options)
163
+ if (scope.scope === 'project-session' && !scope.active) return ''
164
+ mkdirSync(dirname(scope.eventsPath), { recursive: true })
165
+ writeFileSync(scope.eventsPath, '', 'utf-8')
166
+ return scope.eventsPath
167
+ }
168
+
169
+ export function readSessionArtifact(cwd, fileName, options = {}) {
170
+ return readJsonFile(getSessionArtifactPath(cwd, fileName, options), null)
171
+ }
172
+
173
+ export function writeSessionArtifact(cwd, fileName, payload, options = {}) {
174
+ const artifactPath = getSessionArtifactPath(cwd, fileName, options)
175
+ writeJsonFileAtomic(artifactPath, payload)
176
+ updateSessionCapsule(cwd, (capsule) => ({
177
+ artifacts: {
178
+ ...(capsule.artifacts || {}),
179
+ [fileName]: {
180
+ path: getSessionArtifactRelativePath(cwd, fileName, options),
181
+ updatedAt: new Date().toISOString(),
182
+ type: basename(fileName, '.json'),
183
+ },
184
+ },
185
+ }), options)
186
+ return artifactPath
187
+ }
188
+
189
+ export function clearSessionArtifact(cwd, fileName, options = {}) {
190
+ const artifactPath = getSessionArtifactPath(cwd, fileName, options)
191
+ rmSync(artifactPath, { force: true })
192
+ const capsuleOptions = normalizeOptions(options)
193
+ const capsule = readSessionCapsule(cwd, capsuleOptions)
194
+ if (capsule.artifacts && Object.prototype.hasOwnProperty.call(capsule.artifacts, fileName)) {
195
+ delete capsule.artifacts[fileName]
196
+ writeSessionCapsule(cwd, capsule, capsuleOptions)
197
+ }
198
+ }
199
+
200
+ export function removeSessionCapsule(cwd, options = {}) {
201
+ removeRuntimeFile(getSessionCapsulePath(cwd, options))
202
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const scriptPath = join(dirname(fileURLToPath(import.meta.url)), 'turn-state.mjs')
7
+ const result = spawnSync(process.execPath, [scriptPath, ...process.argv.slice(2)], {
8
+ stdio: 'inherit',
9
+ windowsHide: true,
10
+ })
11
+
12
+ if (result.error) {
13
+ console.error(result.error.message)
14
+ process.exit(1)
15
+ }
16
+
17
+ process.exit(typeof result.status === 'number' ? result.status : 1)