@stoneforge/quarry 0.1.0

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 (330) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +160 -0
  3. package/dist/api/index.d.ts +8 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +8 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/quarry-api.d.ts +268 -0
  8. package/dist/api/quarry-api.d.ts.map +1 -0
  9. package/dist/api/quarry-api.js +3905 -0
  10. package/dist/api/quarry-api.js.map +1 -0
  11. package/dist/api/types.d.ts +1359 -0
  12. package/dist/api/types.d.ts.map +1 -0
  13. package/dist/api/types.js +204 -0
  14. package/dist/api/types.js.map +1 -0
  15. package/dist/bin/sf.d.ts +3 -0
  16. package/dist/bin/sf.d.ts.map +1 -0
  17. package/dist/bin/sf.js +9 -0
  18. package/dist/bin/sf.js.map +1 -0
  19. package/dist/cli/commands/admin.d.ts +11 -0
  20. package/dist/cli/commands/admin.d.ts.map +1 -0
  21. package/dist/cli/commands/admin.js +465 -0
  22. package/dist/cli/commands/admin.js.map +1 -0
  23. package/dist/cli/commands/alias.d.ts +8 -0
  24. package/dist/cli/commands/alias.d.ts.map +1 -0
  25. package/dist/cli/commands/alias.js +70 -0
  26. package/dist/cli/commands/alias.js.map +1 -0
  27. package/dist/cli/commands/channel.d.ts +13 -0
  28. package/dist/cli/commands/channel.d.ts.map +1 -0
  29. package/dist/cli/commands/channel.js +680 -0
  30. package/dist/cli/commands/channel.js.map +1 -0
  31. package/dist/cli/commands/completion.d.ts +8 -0
  32. package/dist/cli/commands/completion.d.ts.map +1 -0
  33. package/dist/cli/commands/completion.js +87 -0
  34. package/dist/cli/commands/completion.js.map +1 -0
  35. package/dist/cli/commands/config.d.ts +12 -0
  36. package/dist/cli/commands/config.d.ts.map +1 -0
  37. package/dist/cli/commands/config.js +242 -0
  38. package/dist/cli/commands/config.js.map +1 -0
  39. package/dist/cli/commands/crud.d.ts +64 -0
  40. package/dist/cli/commands/crud.d.ts.map +1 -0
  41. package/dist/cli/commands/crud.js +805 -0
  42. package/dist/cli/commands/crud.js.map +1 -0
  43. package/dist/cli/commands/dep.d.ts +16 -0
  44. package/dist/cli/commands/dep.d.ts.map +1 -0
  45. package/dist/cli/commands/dep.js +499 -0
  46. package/dist/cli/commands/dep.js.map +1 -0
  47. package/dist/cli/commands/document.d.ts +12 -0
  48. package/dist/cli/commands/document.d.ts.map +1 -0
  49. package/dist/cli/commands/document.js +1039 -0
  50. package/dist/cli/commands/document.js.map +1 -0
  51. package/dist/cli/commands/embeddings.d.ts +12 -0
  52. package/dist/cli/commands/embeddings.d.ts.map +1 -0
  53. package/dist/cli/commands/embeddings.js +273 -0
  54. package/dist/cli/commands/embeddings.js.map +1 -0
  55. package/dist/cli/commands/entity.d.ts +16 -0
  56. package/dist/cli/commands/entity.d.ts.map +1 -0
  57. package/dist/cli/commands/entity.js +522 -0
  58. package/dist/cli/commands/entity.js.map +1 -0
  59. package/dist/cli/commands/gc.d.ts +10 -0
  60. package/dist/cli/commands/gc.d.ts.map +1 -0
  61. package/dist/cli/commands/gc.js +257 -0
  62. package/dist/cli/commands/gc.js.map +1 -0
  63. package/dist/cli/commands/help.d.ts +11 -0
  64. package/dist/cli/commands/help.d.ts.map +1 -0
  65. package/dist/cli/commands/help.js +169 -0
  66. package/dist/cli/commands/help.js.map +1 -0
  67. package/dist/cli/commands/history.d.ts +9 -0
  68. package/dist/cli/commands/history.d.ts.map +1 -0
  69. package/dist/cli/commands/history.js +160 -0
  70. package/dist/cli/commands/history.js.map +1 -0
  71. package/dist/cli/commands/identity.d.ts +18 -0
  72. package/dist/cli/commands/identity.d.ts.map +1 -0
  73. package/dist/cli/commands/identity.js +698 -0
  74. package/dist/cli/commands/identity.js.map +1 -0
  75. package/dist/cli/commands/inbox.d.ts +20 -0
  76. package/dist/cli/commands/inbox.d.ts.map +1 -0
  77. package/dist/cli/commands/inbox.js +493 -0
  78. package/dist/cli/commands/inbox.js.map +1 -0
  79. package/dist/cli/commands/init.d.ts +20 -0
  80. package/dist/cli/commands/init.d.ts.map +1 -0
  81. package/dist/cli/commands/init.js +144 -0
  82. package/dist/cli/commands/init.js.map +1 -0
  83. package/dist/cli/commands/install.d.ts +9 -0
  84. package/dist/cli/commands/install.d.ts.map +1 -0
  85. package/dist/cli/commands/install.js +200 -0
  86. package/dist/cli/commands/install.js.map +1 -0
  87. package/dist/cli/commands/library.d.ts +12 -0
  88. package/dist/cli/commands/library.d.ts.map +1 -0
  89. package/dist/cli/commands/library.js +665 -0
  90. package/dist/cli/commands/library.js.map +1 -0
  91. package/dist/cli/commands/message.d.ts +11 -0
  92. package/dist/cli/commands/message.d.ts.map +1 -0
  93. package/dist/cli/commands/message.js +608 -0
  94. package/dist/cli/commands/message.js.map +1 -0
  95. package/dist/cli/commands/plan.d.ts +17 -0
  96. package/dist/cli/commands/plan.d.ts.map +1 -0
  97. package/dist/cli/commands/plan.js +698 -0
  98. package/dist/cli/commands/plan.js.map +1 -0
  99. package/dist/cli/commands/playbook.d.ts +12 -0
  100. package/dist/cli/commands/playbook.d.ts.map +1 -0
  101. package/dist/cli/commands/playbook.js +730 -0
  102. package/dist/cli/commands/playbook.js.map +1 -0
  103. package/dist/cli/commands/reset.d.ts +12 -0
  104. package/dist/cli/commands/reset.d.ts.map +1 -0
  105. package/dist/cli/commands/reset.js +306 -0
  106. package/dist/cli/commands/reset.js.map +1 -0
  107. package/dist/cli/commands/serve.d.ts +11 -0
  108. package/dist/cli/commands/serve.d.ts.map +1 -0
  109. package/dist/cli/commands/serve.js +106 -0
  110. package/dist/cli/commands/serve.js.map +1 -0
  111. package/dist/cli/commands/stats.d.ts +8 -0
  112. package/dist/cli/commands/stats.d.ts.map +1 -0
  113. package/dist/cli/commands/stats.js +82 -0
  114. package/dist/cli/commands/stats.js.map +1 -0
  115. package/dist/cli/commands/sync.d.ts +14 -0
  116. package/dist/cli/commands/sync.d.ts.map +1 -0
  117. package/dist/cli/commands/sync.js +370 -0
  118. package/dist/cli/commands/sync.js.map +1 -0
  119. package/dist/cli/commands/task.d.ts +25 -0
  120. package/dist/cli/commands/task.d.ts.map +1 -0
  121. package/dist/cli/commands/task.js +1153 -0
  122. package/dist/cli/commands/task.js.map +1 -0
  123. package/dist/cli/commands/team.d.ts +13 -0
  124. package/dist/cli/commands/team.d.ts.map +1 -0
  125. package/dist/cli/commands/team.js +471 -0
  126. package/dist/cli/commands/team.js.map +1 -0
  127. package/dist/cli/commands/workflow.d.ts +16 -0
  128. package/dist/cli/commands/workflow.d.ts.map +1 -0
  129. package/dist/cli/commands/workflow.js +753 -0
  130. package/dist/cli/commands/workflow.js.map +1 -0
  131. package/dist/cli/completion.d.ts +28 -0
  132. package/dist/cli/completion.d.ts.map +1 -0
  133. package/dist/cli/completion.js +295 -0
  134. package/dist/cli/completion.js.map +1 -0
  135. package/dist/cli/db.d.ts +38 -0
  136. package/dist/cli/db.d.ts.map +1 -0
  137. package/dist/cli/db.js +90 -0
  138. package/dist/cli/db.js.map +1 -0
  139. package/dist/cli/formatter.d.ts +87 -0
  140. package/dist/cli/formatter.d.ts.map +1 -0
  141. package/dist/cli/formatter.js +464 -0
  142. package/dist/cli/formatter.js.map +1 -0
  143. package/dist/cli/index.d.ts +33 -0
  144. package/dist/cli/index.d.ts.map +1 -0
  145. package/dist/cli/index.js +38 -0
  146. package/dist/cli/index.js.map +1 -0
  147. package/dist/cli/parser.d.ts +45 -0
  148. package/dist/cli/parser.d.ts.map +1 -0
  149. package/dist/cli/parser.js +256 -0
  150. package/dist/cli/parser.js.map +1 -0
  151. package/dist/cli/plugin-loader.d.ts +39 -0
  152. package/dist/cli/plugin-loader.d.ts.map +1 -0
  153. package/dist/cli/plugin-loader.js +165 -0
  154. package/dist/cli/plugin-loader.js.map +1 -0
  155. package/dist/cli/plugin-registry.d.ts +50 -0
  156. package/dist/cli/plugin-registry.d.ts.map +1 -0
  157. package/dist/cli/plugin-registry.js +206 -0
  158. package/dist/cli/plugin-registry.js.map +1 -0
  159. package/dist/cli/plugin-types.d.ts +106 -0
  160. package/dist/cli/plugin-types.d.ts.map +1 -0
  161. package/dist/cli/plugin-types.js +103 -0
  162. package/dist/cli/plugin-types.js.map +1 -0
  163. package/dist/cli/runner.d.ts +35 -0
  164. package/dist/cli/runner.d.ts.map +1 -0
  165. package/dist/cli/runner.js +340 -0
  166. package/dist/cli/runner.js.map +1 -0
  167. package/dist/cli/suggest.d.ts +15 -0
  168. package/dist/cli/suggest.d.ts.map +1 -0
  169. package/dist/cli/suggest.js +49 -0
  170. package/dist/cli/suggest.js.map +1 -0
  171. package/dist/cli/types.d.ts +138 -0
  172. package/dist/cli/types.d.ts.map +1 -0
  173. package/dist/cli/types.js +63 -0
  174. package/dist/cli/types.js.map +1 -0
  175. package/dist/config/config.d.ts +86 -0
  176. package/dist/config/config.d.ts.map +1 -0
  177. package/dist/config/config.js +348 -0
  178. package/dist/config/config.js.map +1 -0
  179. package/dist/config/defaults.d.ts +66 -0
  180. package/dist/config/defaults.d.ts.map +1 -0
  181. package/dist/config/defaults.js +114 -0
  182. package/dist/config/defaults.js.map +1 -0
  183. package/dist/config/duration.d.ts +75 -0
  184. package/dist/config/duration.d.ts.map +1 -0
  185. package/dist/config/duration.js +190 -0
  186. package/dist/config/duration.js.map +1 -0
  187. package/dist/config/env.d.ts +67 -0
  188. package/dist/config/env.d.ts.map +1 -0
  189. package/dist/config/env.js +207 -0
  190. package/dist/config/env.js.map +1 -0
  191. package/dist/config/file.d.ts +97 -0
  192. package/dist/config/file.d.ts.map +1 -0
  193. package/dist/config/file.js +365 -0
  194. package/dist/config/file.js.map +1 -0
  195. package/dist/config/index.d.ts +35 -0
  196. package/dist/config/index.d.ts.map +1 -0
  197. package/dist/config/index.js +41 -0
  198. package/dist/config/index.js.map +1 -0
  199. package/dist/config/merge.d.ts +53 -0
  200. package/dist/config/merge.d.ts.map +1 -0
  201. package/dist/config/merge.js +226 -0
  202. package/dist/config/merge.js.map +1 -0
  203. package/dist/config/types.d.ts +257 -0
  204. package/dist/config/types.d.ts.map +1 -0
  205. package/dist/config/types.js +72 -0
  206. package/dist/config/types.js.map +1 -0
  207. package/dist/config/validation.d.ts +55 -0
  208. package/dist/config/validation.d.ts.map +1 -0
  209. package/dist/config/validation.js +251 -0
  210. package/dist/config/validation.js.map +1 -0
  211. package/dist/http/index.d.ts +8 -0
  212. package/dist/http/index.d.ts.map +1 -0
  213. package/dist/http/index.js +12 -0
  214. package/dist/http/index.js.map +1 -0
  215. package/dist/http/sync-handlers.d.ts +162 -0
  216. package/dist/http/sync-handlers.d.ts.map +1 -0
  217. package/dist/http/sync-handlers.js +271 -0
  218. package/dist/http/sync-handlers.js.map +1 -0
  219. package/dist/index.d.ts +25 -0
  220. package/dist/index.d.ts.map +1 -0
  221. package/dist/index.js +69 -0
  222. package/dist/index.js.map +1 -0
  223. package/dist/server/index.d.ts +34 -0
  224. package/dist/server/index.d.ts.map +1 -0
  225. package/dist/server/index.js +3329 -0
  226. package/dist/server/index.js.map +1 -0
  227. package/dist/server/static.d.ts +18 -0
  228. package/dist/server/static.d.ts.map +1 -0
  229. package/dist/server/static.js +71 -0
  230. package/dist/server/static.js.map +1 -0
  231. package/dist/server/ws/broadcaster.d.ts +8 -0
  232. package/dist/server/ws/broadcaster.d.ts.map +1 -0
  233. package/dist/server/ws/broadcaster.js +7 -0
  234. package/dist/server/ws/broadcaster.js.map +1 -0
  235. package/dist/server/ws/handler.d.ts +55 -0
  236. package/dist/server/ws/handler.d.ts.map +1 -0
  237. package/dist/server/ws/handler.js +160 -0
  238. package/dist/server/ws/handler.js.map +1 -0
  239. package/dist/services/blocked-cache.d.ts +297 -0
  240. package/dist/services/blocked-cache.d.ts.map +1 -0
  241. package/dist/services/blocked-cache.js +755 -0
  242. package/dist/services/blocked-cache.js.map +1 -0
  243. package/dist/services/dependency.d.ts +205 -0
  244. package/dist/services/dependency.d.ts.map +1 -0
  245. package/dist/services/dependency.js +566 -0
  246. package/dist/services/dependency.js.map +1 -0
  247. package/dist/services/embeddings/fusion.d.ts +33 -0
  248. package/dist/services/embeddings/fusion.d.ts.map +1 -0
  249. package/dist/services/embeddings/fusion.js +34 -0
  250. package/dist/services/embeddings/fusion.js.map +1 -0
  251. package/dist/services/embeddings/index.d.ts +12 -0
  252. package/dist/services/embeddings/index.d.ts.map +1 -0
  253. package/dist/services/embeddings/index.js +10 -0
  254. package/dist/services/embeddings/index.js.map +1 -0
  255. package/dist/services/embeddings/local-provider.d.ts +31 -0
  256. package/dist/services/embeddings/local-provider.d.ts.map +1 -0
  257. package/dist/services/embeddings/local-provider.js +80 -0
  258. package/dist/services/embeddings/local-provider.js.map +1 -0
  259. package/dist/services/embeddings/service.d.ts +76 -0
  260. package/dist/services/embeddings/service.d.ts.map +1 -0
  261. package/dist/services/embeddings/service.js +153 -0
  262. package/dist/services/embeddings/service.js.map +1 -0
  263. package/dist/services/embeddings/types.d.ts +70 -0
  264. package/dist/services/embeddings/types.d.ts.map +1 -0
  265. package/dist/services/embeddings/types.js +8 -0
  266. package/dist/services/embeddings/types.js.map +1 -0
  267. package/dist/services/id-length-cache.d.ts +156 -0
  268. package/dist/services/id-length-cache.d.ts.map +1 -0
  269. package/dist/services/id-length-cache.js +197 -0
  270. package/dist/services/id-length-cache.js.map +1 -0
  271. package/dist/services/inbox.d.ts +147 -0
  272. package/dist/services/inbox.d.ts.map +1 -0
  273. package/dist/services/inbox.js +428 -0
  274. package/dist/services/inbox.js.map +1 -0
  275. package/dist/services/index.d.ts +10 -0
  276. package/dist/services/index.d.ts.map +1 -0
  277. package/dist/services/index.js +10 -0
  278. package/dist/services/index.js.map +1 -0
  279. package/dist/services/priority-service.d.ts +145 -0
  280. package/dist/services/priority-service.d.ts.map +1 -0
  281. package/dist/services/priority-service.js +272 -0
  282. package/dist/services/priority-service.js.map +1 -0
  283. package/dist/services/search-utils.d.ts +47 -0
  284. package/dist/services/search-utils.d.ts.map +1 -0
  285. package/dist/services/search-utils.js +83 -0
  286. package/dist/services/search-utils.js.map +1 -0
  287. package/dist/sync/hash.d.ts +48 -0
  288. package/dist/sync/hash.d.ts.map +1 -0
  289. package/dist/sync/hash.js +136 -0
  290. package/dist/sync/hash.js.map +1 -0
  291. package/dist/sync/index.d.ts +11 -0
  292. package/dist/sync/index.d.ts.map +1 -0
  293. package/dist/sync/index.js +16 -0
  294. package/dist/sync/index.js.map +1 -0
  295. package/dist/sync/merge.d.ts +80 -0
  296. package/dist/sync/merge.d.ts.map +1 -0
  297. package/dist/sync/merge.js +310 -0
  298. package/dist/sync/merge.js.map +1 -0
  299. package/dist/sync/serialization.d.ts +132 -0
  300. package/dist/sync/serialization.d.ts.map +1 -0
  301. package/dist/sync/serialization.js +306 -0
  302. package/dist/sync/serialization.js.map +1 -0
  303. package/dist/sync/service.d.ts +102 -0
  304. package/dist/sync/service.d.ts.map +1 -0
  305. package/dist/sync/service.js +493 -0
  306. package/dist/sync/service.js.map +1 -0
  307. package/dist/sync/types.d.ts +275 -0
  308. package/dist/sync/types.d.ts.map +1 -0
  309. package/dist/sync/types.js +76 -0
  310. package/dist/sync/types.js.map +1 -0
  311. package/dist/systems/identity.d.ts +479 -0
  312. package/dist/systems/identity.d.ts.map +1 -0
  313. package/dist/systems/identity.js +817 -0
  314. package/dist/systems/identity.js.map +1 -0
  315. package/dist/systems/index.d.ts +8 -0
  316. package/dist/systems/index.d.ts.map +1 -0
  317. package/dist/systems/index.js +29 -0
  318. package/dist/systems/index.js.map +1 -0
  319. package/package.json +121 -0
  320. package/web/assets/charts-vendor-D1YcbGux.js +55 -0
  321. package/web/assets/dnd-vendor-DmxE-_ZH.js +5 -0
  322. package/web/assets/editor-vendor-BxraAWts.js +279 -0
  323. package/web/assets/index-B77vv208.js +341 -0
  324. package/web/assets/index-CF_XnVLh.css +1 -0
  325. package/web/assets/router-vendor-BCKpRBrB.js +41 -0
  326. package/web/assets/ui-vendor-DUahGnbT.js +45 -0
  327. package/web/assets/utils-vendor-CfYKiENT.js +813 -0
  328. package/web/favicon.ico +0 -0
  329. package/web/index.html +23 -0
  330. package/web/logo.png +0 -0
@@ -0,0 +1,753 @@
1
+ /**
2
+ * Workflow Commands - Collection command interface for workflows
3
+ *
4
+ * Provides CLI commands for workflow operations:
5
+ * - workflow create: Instantiate a playbook into a workflow
6
+ * - workflow list: List workflows with filtering
7
+ * - workflow show: Show workflow details
8
+ * - workflow tasks: List tasks in a workflow
9
+ * - workflow progress: Show workflow progress metrics
10
+ * - workflow delete: Delete ephemeral workflow and tasks
11
+ * - workflow promote: Promote ephemeral to durable
12
+ * - workflow gc: Garbage collect old ephemeral workflows
13
+ */
14
+ import { success, failure, ExitCode } from '../types.js';
15
+ import { getFormatter, getOutputMode, getStatusIcon } from '../formatter.js';
16
+ import { createWorkflow, WorkflowStatus, promoteWorkflow, filterGarbageCollectionByAge, } from '@stoneforge/core';
17
+ import { suggestCommands } from '../suggest.js';
18
+ import { resolveActor, createAPI } from '../db.js';
19
+ // ============================================================================
20
+ // Constants
21
+ // ============================================================================
22
+ // Default GC age: 7 days in milliseconds
23
+ const DEFAULT_GC_AGE_DAYS = 7;
24
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
25
+ const workflowCreateOptions = [
26
+ {
27
+ name: 'var',
28
+ description: 'Set variable (name=value, can be repeated)',
29
+ hasValue: true,
30
+ array: true,
31
+ },
32
+ {
33
+ name: 'ephemeral',
34
+ short: 'e',
35
+ description: 'Create as ephemeral (not synced)',
36
+ hasValue: false,
37
+ },
38
+ {
39
+ name: 'title',
40
+ short: 't',
41
+ description: 'Override workflow title',
42
+ hasValue: true,
43
+ },
44
+ ];
45
+ async function workflowCreateHandler(args, options) {
46
+ const [playbookNameOrId] = args;
47
+ if (!playbookNameOrId) {
48
+ return failure('Usage: sf workflow create <playbook> [options]\nExample: sf workflow create deploy --var env=prod', ExitCode.INVALID_ARGUMENTS);
49
+ }
50
+ const { api, error } = createAPI(options, true);
51
+ if (error) {
52
+ return failure(error, ExitCode.GENERAL_ERROR);
53
+ }
54
+ try {
55
+ const actor = resolveActor(options);
56
+ // Parse variables
57
+ const variables = {};
58
+ if (options.var) {
59
+ const varArgs = Array.isArray(options.var) ? options.var : [options.var];
60
+ for (const varArg of varArgs) {
61
+ const eqIndex = varArg.indexOf('=');
62
+ if (eqIndex === -1) {
63
+ return failure(`Invalid variable format: ${varArg}. Use name=value`, ExitCode.VALIDATION);
64
+ }
65
+ const name = varArg.slice(0, eqIndex);
66
+ const value = varArg.slice(eqIndex + 1);
67
+ variables[name] = value;
68
+ }
69
+ }
70
+ // For now, create a workflow directly
71
+ // TODO: When playbook instantiation is implemented, look up playbook and create workflow
72
+ const title = options.title || `Workflow from ${playbookNameOrId}`;
73
+ const input = {
74
+ title,
75
+ createdBy: actor,
76
+ ephemeral: options.ephemeral ?? false,
77
+ variables,
78
+ // playbookId would be set here when playbook lookup is implemented
79
+ };
80
+ const workflow = await createWorkflow(input);
81
+ const created = await api.create(workflow);
82
+ const mode = getOutputMode(options);
83
+ if (mode === 'quiet') {
84
+ return success(created.id);
85
+ }
86
+ return success(created, `Created workflow ${created.id}${options.ephemeral ? ' (ephemeral)' : ''}`);
87
+ }
88
+ catch (err) {
89
+ const message = err instanceof Error ? err.message : String(err);
90
+ return failure(`Failed to create workflow: ${message}`, ExitCode.GENERAL_ERROR);
91
+ }
92
+ }
93
+ const workflowCreateCommand = {
94
+ name: 'create',
95
+ description: 'Instantiate a playbook into a workflow',
96
+ usage: 'sf workflow create <playbook> [options]',
97
+ help: `Create a workflow by instantiating a playbook template.
98
+
99
+ Arguments:
100
+ playbook Playbook name or ID to instantiate
101
+
102
+ Options:
103
+ --var <name=value> Set variable (can be repeated)
104
+ -e, --ephemeral Create as ephemeral (not synced to JSONL)
105
+ -t, --title <text> Override workflow title
106
+
107
+ Examples:
108
+ sf workflow create deploy --var env=prod --var version=1.2
109
+ sf workflow create sprint-setup --ephemeral
110
+ sf workflow create deploy --title "Production Deploy v1.2"`,
111
+ options: workflowCreateOptions,
112
+ handler: workflowCreateHandler,
113
+ };
114
+ const workflowListOptions = [
115
+ {
116
+ name: 'status',
117
+ short: 's',
118
+ description: 'Filter by status (pending, running, completed, failed, cancelled)',
119
+ hasValue: true,
120
+ },
121
+ {
122
+ name: 'ephemeral',
123
+ short: 'e',
124
+ description: 'Show only ephemeral workflows',
125
+ hasValue: false,
126
+ },
127
+ {
128
+ name: 'durable',
129
+ short: 'd',
130
+ description: 'Show only durable workflows',
131
+ hasValue: false,
132
+ },
133
+ {
134
+ name: 'limit',
135
+ short: 'l',
136
+ description: 'Maximum number of results',
137
+ hasValue: true,
138
+ },
139
+ ];
140
+ async function workflowListHandler(_args, options) {
141
+ const { api, error } = createAPI(options);
142
+ if (error) {
143
+ return failure(error, ExitCode.GENERAL_ERROR);
144
+ }
145
+ try {
146
+ // Build filter
147
+ const filter = {
148
+ type: 'workflow',
149
+ };
150
+ // Status filter
151
+ if (options.status) {
152
+ const validStatuses = Object.values(WorkflowStatus);
153
+ if (!validStatuses.includes(options.status)) {
154
+ return failure(`Invalid status: ${options.status}. Must be one of: ${validStatuses.join(', ')}`, ExitCode.VALIDATION);
155
+ }
156
+ }
157
+ // Limit
158
+ if (options.limit) {
159
+ const limit = parseInt(options.limit, 10);
160
+ if (isNaN(limit) || limit < 1) {
161
+ return failure('Limit must be a positive number', ExitCode.VALIDATION);
162
+ }
163
+ filter.limit = limit;
164
+ }
165
+ const result = await api.listPaginated(filter);
166
+ // Post-filter
167
+ let items = result.items;
168
+ // Status filter
169
+ if (options.status) {
170
+ items = items.filter((w) => w.status === options.status);
171
+ }
172
+ // Ephemeral/durable filter
173
+ if (options.ephemeral && !options.durable) {
174
+ items = items.filter((w) => w.ephemeral);
175
+ }
176
+ else if (options.durable && !options.ephemeral) {
177
+ items = items.filter((w) => !w.ephemeral);
178
+ }
179
+ const mode = getOutputMode(options);
180
+ const formatter = getFormatter(mode);
181
+ if (mode === 'json') {
182
+ return success(items);
183
+ }
184
+ if (mode === 'quiet') {
185
+ return success(items.map((w) => w.id).join('\n'));
186
+ }
187
+ if (items.length === 0) {
188
+ return success(null, 'No workflows found');
189
+ }
190
+ // Build table
191
+ const headers = ['ID', 'TITLE', 'STATUS', 'MODE', 'CREATED'];
192
+ const rows = items.map((w) => [
193
+ w.id,
194
+ w.title.length > 40 ? w.title.substring(0, 37) + '...' : w.title,
195
+ `${getStatusIcon(w.status)} ${w.status}`,
196
+ w.ephemeral ? 'ephemeral' : 'durable',
197
+ w.createdAt.split('T')[0],
198
+ ]);
199
+ const table = formatter.table(headers, rows);
200
+ const summary = `\nShowing ${items.length} of ${result.total} workflows`;
201
+ return success(items, table + summary);
202
+ }
203
+ catch (err) {
204
+ const message = err instanceof Error ? err.message : String(err);
205
+ return failure(`Failed to list workflows: ${message}`, ExitCode.GENERAL_ERROR);
206
+ }
207
+ }
208
+ const workflowListCommand = {
209
+ name: 'list',
210
+ description: 'List workflows',
211
+ usage: 'sf workflow list [options]',
212
+ help: `List workflows with optional filtering.
213
+
214
+ Options:
215
+ -s, --status <status> Filter by status: pending, running, completed, failed, cancelled
216
+ -e, --ephemeral Show only ephemeral workflows
217
+ -d, --durable Show only durable workflows
218
+ -l, --limit <n> Maximum results
219
+
220
+ Examples:
221
+ sf workflow list
222
+ sf workflow list --status running
223
+ sf workflow list --ephemeral`,
224
+ options: workflowListOptions,
225
+ handler: workflowListHandler,
226
+ };
227
+ // ============================================================================
228
+ // Workflow Show Command
229
+ // ============================================================================
230
+ async function workflowShowHandler(args, options) {
231
+ const [id] = args;
232
+ if (!id) {
233
+ return failure('Usage: sf workflow show <id>\nExample: sf workflow show el-abc123', ExitCode.INVALID_ARGUMENTS);
234
+ }
235
+ const { api, error } = createAPI(options);
236
+ if (error) {
237
+ return failure(error, ExitCode.GENERAL_ERROR);
238
+ }
239
+ try {
240
+ const workflow = await api.get(id);
241
+ if (!workflow) {
242
+ return failure(`Workflow not found: ${id}`, ExitCode.NOT_FOUND);
243
+ }
244
+ if (workflow.type !== 'workflow') {
245
+ return failure(`Element ${id} is not a workflow (type: ${workflow.type})`, ExitCode.VALIDATION);
246
+ }
247
+ // Check if workflow is deleted (tombstone)
248
+ const data = workflow;
249
+ if (data.status === 'tombstone' || data.deletedAt) {
250
+ return failure(`Workflow not found: ${id}`, ExitCode.NOT_FOUND);
251
+ }
252
+ const mode = getOutputMode(options);
253
+ const formatter = getFormatter(mode);
254
+ if (mode === 'json') {
255
+ return success(workflow);
256
+ }
257
+ if (mode === 'quiet') {
258
+ return success(workflow.id);
259
+ }
260
+ // Human-readable output
261
+ let output = formatter.element(workflow);
262
+ // Add workflow-specific info
263
+ output += '\n\n--- Workflow Info ---\n';
264
+ output += `Mode: ${workflow.ephemeral ? 'ephemeral' : 'durable'}\n`;
265
+ if (workflow.playbookId) {
266
+ output += `Playbook: ${workflow.playbookId}\n`;
267
+ }
268
+ if (workflow.startedAt) {
269
+ output += `Started: ${workflow.startedAt}\n`;
270
+ }
271
+ if (workflow.finishedAt) {
272
+ output += `Finished: ${workflow.finishedAt}\n`;
273
+ }
274
+ if (workflow.failureReason) {
275
+ output += `Failure: ${workflow.failureReason}\n`;
276
+ }
277
+ if (workflow.cancelReason) {
278
+ output += `Cancelled: ${workflow.cancelReason}\n`;
279
+ }
280
+ // Show variables if any
281
+ const varKeys = Object.keys(workflow.variables);
282
+ if (varKeys.length > 0) {
283
+ output += '\n--- Variables ---\n';
284
+ for (const key of varKeys) {
285
+ output += `${key}: ${JSON.stringify(workflow.variables[key])}\n`;
286
+ }
287
+ }
288
+ return success(workflow, output);
289
+ }
290
+ catch (err) {
291
+ const message = err instanceof Error ? err.message : String(err);
292
+ return failure(`Failed to show workflow: ${message}`, ExitCode.GENERAL_ERROR);
293
+ }
294
+ }
295
+ const workflowShowCommand = {
296
+ name: 'show',
297
+ description: 'Show workflow details',
298
+ usage: 'sf workflow show <id>',
299
+ help: `Display detailed information about a workflow.
300
+
301
+ Arguments:
302
+ id Workflow identifier (e.g., el-abc123)
303
+
304
+ Examples:
305
+ sf workflow show el-abc123
306
+ sf workflow show el-abc123 --json`,
307
+ handler: workflowShowHandler,
308
+ };
309
+ const workflowTasksOptions = [
310
+ {
311
+ name: 'ready',
312
+ short: 'r',
313
+ description: 'Show only ready tasks (not blocked, not scheduled for future)',
314
+ hasValue: false,
315
+ },
316
+ {
317
+ name: 'status',
318
+ short: 's',
319
+ description: 'Filter by status (open, in_progress, blocked, closed, deferred)',
320
+ hasValue: true,
321
+ },
322
+ {
323
+ name: 'limit',
324
+ short: 'l',
325
+ description: 'Maximum number of results',
326
+ hasValue: true,
327
+ },
328
+ ];
329
+ async function workflowTasksHandler(args, options) {
330
+ const [id] = args;
331
+ if (!id) {
332
+ return failure('Usage: sf workflow tasks <id>\nExample: sf workflow tasks el-abc123', ExitCode.INVALID_ARGUMENTS);
333
+ }
334
+ const { api, error } = createAPI(options);
335
+ if (error) {
336
+ return failure(error, ExitCode.GENERAL_ERROR);
337
+ }
338
+ try {
339
+ // Build filter
340
+ const filter = {};
341
+ // Status filter
342
+ if (options.status) {
343
+ const validStatuses = ['open', 'in_progress', 'blocked', 'closed', 'deferred', 'tombstone'];
344
+ if (!validStatuses.includes(options.status)) {
345
+ return failure(`Invalid status: ${options.status}. Must be one of: ${validStatuses.join(', ')}`, ExitCode.VALIDATION);
346
+ }
347
+ filter.status = options.status;
348
+ }
349
+ // Limit
350
+ if (options.limit) {
351
+ const limit = parseInt(options.limit, 10);
352
+ if (isNaN(limit) || limit < 1) {
353
+ return failure('Limit must be a positive number', ExitCode.VALIDATION);
354
+ }
355
+ filter.limit = limit;
356
+ }
357
+ // Get tasks based on --ready flag
358
+ const tasks = options.ready
359
+ ? await api.getReadyTasksInWorkflow(id, filter)
360
+ : await api.getTasksInWorkflow(id, filter);
361
+ const mode = getOutputMode(options);
362
+ const formatter = getFormatter(mode);
363
+ if (mode === 'json') {
364
+ return success(tasks);
365
+ }
366
+ if (mode === 'quiet') {
367
+ return success(tasks.map((t) => t.id).join('\n'));
368
+ }
369
+ if (tasks.length === 0) {
370
+ return success(null, options.ready ? 'No ready tasks in workflow' : 'No tasks in workflow');
371
+ }
372
+ // Build table
373
+ const headers = ['ID', 'TITLE', 'STATUS', 'PRIORITY', 'ASSIGNEE'];
374
+ const rows = tasks.map((t) => [
375
+ t.id,
376
+ t.title.length > 40 ? t.title.substring(0, 37) + '...' : t.title,
377
+ `${getStatusIcon(t.status)} ${t.status}`,
378
+ `P${t.priority}`,
379
+ t.assignee ?? '-',
380
+ ]);
381
+ const table = formatter.table(headers, rows);
382
+ const summary = `\n${tasks.length} task(s)`;
383
+ return success(tasks, table + summary);
384
+ }
385
+ catch (err) {
386
+ const message = err instanceof Error ? err.message : String(err);
387
+ return failure(`Failed to list workflow tasks: ${message}`, ExitCode.GENERAL_ERROR);
388
+ }
389
+ }
390
+ const workflowTasksCommand = {
391
+ name: 'tasks',
392
+ description: 'List tasks in a workflow',
393
+ usage: 'sf workflow tasks <id> [options]',
394
+ help: `List all tasks that belong to a workflow.
395
+
396
+ Arguments:
397
+ id Workflow identifier (e.g., el-abc123)
398
+
399
+ Options:
400
+ -r, --ready Show only ready tasks (not blocked, not scheduled for future)
401
+ -s, --status <s> Filter by status: open, in_progress, blocked, closed, deferred
402
+ -l, --limit <n> Maximum results
403
+
404
+ Examples:
405
+ sf workflow tasks el-abc123
406
+ sf workflow tasks el-abc123 --ready
407
+ sf workflow tasks el-abc123 --status open
408
+ sf workflow tasks el-abc123 --json`,
409
+ options: workflowTasksOptions,
410
+ handler: workflowTasksHandler,
411
+ };
412
+ // ============================================================================
413
+ // Workflow Progress Command
414
+ // ============================================================================
415
+ async function workflowProgressHandler(args, options) {
416
+ const [id] = args;
417
+ if (!id) {
418
+ return failure('Usage: sf workflow progress <id>\nExample: sf workflow progress el-abc123', ExitCode.INVALID_ARGUMENTS);
419
+ }
420
+ const { api, error } = createAPI(options);
421
+ if (error) {
422
+ return failure(error, ExitCode.GENERAL_ERROR);
423
+ }
424
+ try {
425
+ const progress = await api.getWorkflowProgress(id);
426
+ const mode = getOutputMode(options);
427
+ if (mode === 'json') {
428
+ return success(progress);
429
+ }
430
+ if (mode === 'quiet') {
431
+ return success(`${progress.completionPercentage}%`);
432
+ }
433
+ // Human-readable output
434
+ let output = `Workflow Progress: ${id}\n\n`;
435
+ output += `Total Tasks: ${progress.totalTasks}\n`;
436
+ output += `Completion: ${progress.completionPercentage}%\n`;
437
+ output += `Ready Tasks: ${progress.readyTasks}\n`;
438
+ output += `Blocked Tasks: ${progress.blockedTasks}\n\n`;
439
+ output += '--- Status Breakdown ---\n';
440
+ const statusOrder = ['open', 'in_progress', 'blocked', 'closed', 'deferred'];
441
+ for (const status of statusOrder) {
442
+ const count = progress.statusCounts[status] ?? 0;
443
+ if (count > 0) {
444
+ output += `${getStatusIcon(status)} ${status}: ${count}\n`;
445
+ }
446
+ }
447
+ // Visual progress bar
448
+ const barWidth = 30;
449
+ const filled = Math.round((progress.completionPercentage / 100) * barWidth);
450
+ const empty = barWidth - filled;
451
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
452
+ output += `\n[${bar}] ${progress.completionPercentage}%`;
453
+ return success(progress, output);
454
+ }
455
+ catch (err) {
456
+ const message = err instanceof Error ? err.message : String(err);
457
+ return failure(`Failed to get workflow progress: ${message}`, ExitCode.GENERAL_ERROR);
458
+ }
459
+ }
460
+ const workflowProgressCommand = {
461
+ name: 'progress',
462
+ description: 'Show workflow progress metrics',
463
+ usage: 'sf workflow progress <id>',
464
+ help: `Display progress metrics for a workflow.
465
+
466
+ Shows task status counts, completion percentage, and ready/blocked task counts.
467
+
468
+ Arguments:
469
+ id Workflow identifier (e.g., el-abc123)
470
+
471
+ Examples:
472
+ sf workflow progress el-abc123
473
+ sf workflow progress el-abc123 --json`,
474
+ handler: workflowProgressHandler,
475
+ };
476
+ const workflowDeleteOptions = [
477
+ {
478
+ name: 'force',
479
+ short: 'f',
480
+ description: 'Force delete even for durable workflows',
481
+ hasValue: false,
482
+ },
483
+ ];
484
+ async function workflowDeleteHandler(args, options) {
485
+ const [id] = args;
486
+ if (!id) {
487
+ return failure('Usage: sf workflow delete <id>\nExample: sf workflow delete el-abc123', ExitCode.INVALID_ARGUMENTS);
488
+ }
489
+ const { api, error } = createAPI(options);
490
+ if (error) {
491
+ return failure(error, ExitCode.GENERAL_ERROR);
492
+ }
493
+ try {
494
+ const workflow = await api.get(id);
495
+ if (!workflow) {
496
+ return failure(`Workflow not found: ${id}`, ExitCode.NOT_FOUND);
497
+ }
498
+ if (workflow.type !== 'workflow') {
499
+ return failure(`Element ${id} is not a workflow (type: ${workflow.type})`, ExitCode.VALIDATION);
500
+ }
501
+ // Check if workflow is deleted (tombstone)
502
+ const data = workflow;
503
+ if (data.status === 'tombstone' || data.deletedAt) {
504
+ return failure(`Workflow not found: ${id}`, ExitCode.NOT_FOUND);
505
+ }
506
+ if (!workflow.ephemeral && !options.force) {
507
+ return failure(`Workflow ${id} is durable. Use --force to delete anyway, or 'sf delete ${id}' for soft delete.`, ExitCode.VALIDATION);
508
+ }
509
+ const actor = resolveActor(options);
510
+ // Use deleteWorkflow API to delete workflow and all its tasks
511
+ const result = await api.deleteWorkflow(id, { actor });
512
+ return success(result, `Deleted workflow ${id}: ${result.tasksDeleted} task(s), ${result.dependenciesDeleted} dependency(ies) deleted`);
513
+ }
514
+ catch (err) {
515
+ const message = err instanceof Error ? err.message : String(err);
516
+ return failure(`Failed to delete workflow: ${message}`, ExitCode.GENERAL_ERROR);
517
+ }
518
+ }
519
+ const workflowDeleteCommand = {
520
+ name: 'delete',
521
+ description: 'Delete workflow and all its tasks',
522
+ usage: 'sf workflow delete <id>',
523
+ help: `Delete a workflow and all its tasks immediately (hard delete).
524
+
525
+ By default, only ephemeral workflows can be deleted. Use --force to delete
526
+ durable workflows as well.
527
+
528
+ Arguments:
529
+ id Workflow identifier
530
+
531
+ Options:
532
+ -f, --force Force delete even for durable workflows
533
+
534
+ Examples:
535
+ sf workflow delete el-abc123
536
+ sf workflow delete el-abc123 --force`,
537
+ options: workflowDeleteOptions,
538
+ handler: workflowDeleteHandler,
539
+ };
540
+ // ============================================================================
541
+ // Workflow Promote Command
542
+ // ============================================================================
543
+ async function workflowPromoteHandler(args, options) {
544
+ const [id] = args;
545
+ if (!id) {
546
+ return failure('Usage: sf workflow promote <id>\nExample: sf workflow promote el-abc123', ExitCode.INVALID_ARGUMENTS);
547
+ }
548
+ const { api, error } = createAPI(options);
549
+ if (error) {
550
+ return failure(error, ExitCode.GENERAL_ERROR);
551
+ }
552
+ try {
553
+ const workflow = await api.get(id);
554
+ if (!workflow) {
555
+ return failure(`Workflow not found: ${id}`, ExitCode.NOT_FOUND);
556
+ }
557
+ if (workflow.type !== 'workflow') {
558
+ return failure(`Element ${id} is not a workflow (type: ${workflow.type})`, ExitCode.VALIDATION);
559
+ }
560
+ // Check if workflow is deleted (tombstone)
561
+ const data = workflow;
562
+ if (data.status === 'tombstone' || data.deletedAt) {
563
+ return failure(`Workflow not found: ${id}`, ExitCode.NOT_FOUND);
564
+ }
565
+ if (!workflow.ephemeral) {
566
+ return success(workflow, `Workflow ${id} is already durable`);
567
+ }
568
+ const actor = resolveActor(options);
569
+ // Use the promoteWorkflow function to get updated values
570
+ const promoted = promoteWorkflow(workflow);
571
+ // Update in database
572
+ const updated = await api.update(id, { ephemeral: promoted.ephemeral }, { actor });
573
+ return success(updated, `Promoted workflow ${id} to durable`);
574
+ }
575
+ catch (err) {
576
+ const message = err instanceof Error ? err.message : String(err);
577
+ return failure(`Failed to promote workflow: ${message}`, ExitCode.GENERAL_ERROR);
578
+ }
579
+ }
580
+ const workflowPromoteCommand = {
581
+ name: 'promote',
582
+ description: 'Promote ephemeral workflow to durable',
583
+ usage: 'sf workflow promote <id>',
584
+ help: `Promote an ephemeral workflow to durable so it gets synced to JSONL.
585
+
586
+ After promoting, the workflow and its tasks will be included in exports
587
+ and git sync.
588
+
589
+ Arguments:
590
+ id Workflow identifier
591
+
592
+ Examples:
593
+ sf workflow promote el-abc123`,
594
+ handler: workflowPromoteHandler,
595
+ };
596
+ const workflowGcOptions = [
597
+ {
598
+ name: 'age',
599
+ short: 'a',
600
+ description: `Maximum age in days (default: ${DEFAULT_GC_AGE_DAYS})`,
601
+ hasValue: true,
602
+ },
603
+ {
604
+ name: 'dry-run',
605
+ description: 'Show what would be deleted without deleting',
606
+ hasValue: false,
607
+ },
608
+ ];
609
+ async function workflowGcHandler(_args, options) {
610
+ const { api, error } = createAPI(options);
611
+ if (error) {
612
+ return failure(error, ExitCode.GENERAL_ERROR);
613
+ }
614
+ try {
615
+ // Parse age
616
+ let ageDays = DEFAULT_GC_AGE_DAYS;
617
+ if (options.age) {
618
+ ageDays = parseInt(options.age, 10);
619
+ if (isNaN(ageDays) || ageDays < 0) {
620
+ return failure('Age must be a non-negative number', ExitCode.VALIDATION);
621
+ }
622
+ }
623
+ const maxAgeMs = ageDays * MS_PER_DAY;
624
+ // Check if dry run by getting eligible workflows first
625
+ if (options.dryRun) {
626
+ // Get all workflows for preview
627
+ const allWorkflows = await api.list({ type: 'workflow' });
628
+ // Filter to those eligible for GC
629
+ const eligible = filterGarbageCollectionByAge(allWorkflows, maxAgeMs);
630
+ if (eligible.length === 0) {
631
+ return success({ deleted: 0 }, 'No workflows eligible for garbage collection');
632
+ }
633
+ const mode = getOutputMode(options);
634
+ const formatter = getFormatter(mode);
635
+ if (mode === 'json') {
636
+ return success({ wouldDelete: eligible.map((w) => w.id), count: eligible.length });
637
+ }
638
+ if (mode === 'quiet') {
639
+ return success(eligible.map((w) => w.id).join('\n'));
640
+ }
641
+ const headers = ['ID', 'TITLE', 'STATUS', 'FINISHED'];
642
+ const rows = eligible.map((w) => [
643
+ w.id,
644
+ w.title.length > 40 ? w.title.substring(0, 37) + '...' : w.title,
645
+ w.status,
646
+ w.finishedAt ? w.finishedAt.split('T')[0] : '-',
647
+ ]);
648
+ const table = formatter.table(headers, rows);
649
+ return success({ wouldDelete: eligible.map((w) => w.id), count: eligible.length }, `Would delete ${eligible.length} workflow(s):\n${table}`);
650
+ }
651
+ // Use garbageCollectWorkflows API
652
+ const gcResult = await api.garbageCollectWorkflows({
653
+ maxAgeMs,
654
+ dryRun: false,
655
+ });
656
+ if (gcResult.workflowsDeleted === 0) {
657
+ return success({ deleted: 0 }, 'No workflows eligible for garbage collection');
658
+ }
659
+ return success(gcResult, `Garbage collected ${gcResult.workflowsDeleted} workflow(s), ${gcResult.tasksDeleted} task(s), ${gcResult.dependenciesDeleted} dependency(ies)`);
660
+ }
661
+ catch (err) {
662
+ const message = err instanceof Error ? err.message : String(err);
663
+ return failure(`Failed to garbage collect: ${message}`, ExitCode.GENERAL_ERROR);
664
+ }
665
+ }
666
+ const workflowGcCommand = {
667
+ name: 'gc',
668
+ description: 'Garbage collect old ephemeral workflows',
669
+ usage: 'sf workflow gc [options]',
670
+ help: `Delete old ephemeral workflows that have reached a terminal state.
671
+
672
+ Workflows are eligible for garbage collection if they are:
673
+ - Ephemeral (not durable)
674
+ - In a terminal state (completed, failed, or cancelled)
675
+ - Older than the specified age
676
+
677
+ Options:
678
+ -a, --age <days> Maximum age in days (default: ${DEFAULT_GC_AGE_DAYS})
679
+ --dry-run Show what would be deleted without deleting
680
+
681
+ Examples:
682
+ sf workflow gc
683
+ sf workflow gc --age 30
684
+ sf workflow gc --dry-run`,
685
+ options: workflowGcOptions,
686
+ handler: workflowGcHandler,
687
+ };
688
+ // ============================================================================
689
+ // Workflow Root Command
690
+ // ============================================================================
691
+ export const workflowCommand = {
692
+ name: 'workflow',
693
+ description: 'Manage workflows (executable task sequences)',
694
+ usage: 'sf workflow <subcommand> [options]',
695
+ help: `Manage workflows - executable sequences of tasks.
696
+
697
+ Workflows can be instantiated from playbook templates or created ad-hoc.
698
+ They support both durable (synced) and ephemeral (temporary) modes.
699
+
700
+ Subcommands:
701
+ create Instantiate a playbook into a workflow
702
+ list List workflows
703
+ show Show workflow details
704
+ tasks List tasks in a workflow
705
+ progress Show workflow progress metrics
706
+ delete Delete ephemeral workflow and tasks
707
+ promote Promote ephemeral to durable
708
+ gc Garbage collect old ephemeral workflows
709
+
710
+ Examples:
711
+ sf workflow create deploy --var env=prod
712
+ sf workflow list --status running
713
+ sf workflow show el-abc123
714
+ sf workflow tasks el-abc123
715
+ sf workflow tasks el-abc123 --ready
716
+ sf workflow progress el-abc123
717
+ sf workflow delete el-abc123
718
+ sf workflow promote el-abc123
719
+ sf workflow gc --age 30`,
720
+ subcommands: {
721
+ create: workflowCreateCommand,
722
+ list: workflowListCommand,
723
+ show: workflowShowCommand,
724
+ tasks: workflowTasksCommand,
725
+ progress: workflowProgressCommand,
726
+ delete: workflowDeleteCommand,
727
+ promote: workflowPromoteCommand,
728
+ gc: workflowGcCommand,
729
+ // Aliases (hidden from --help via dedup in getCommandHelp)
730
+ new: workflowCreateCommand,
731
+ add: workflowCreateCommand,
732
+ ls: workflowListCommand,
733
+ rm: workflowDeleteCommand,
734
+ get: workflowShowCommand,
735
+ view: workflowShowCommand,
736
+ },
737
+ handler: async (args, options) => {
738
+ // Default to list if no subcommand
739
+ if (args.length === 0) {
740
+ return workflowListHandler(args, options);
741
+ }
742
+ // Show "did you mean?" for unknown subcommands
743
+ const subNames = Object.keys(workflowCommand.subcommands);
744
+ const suggestions = suggestCommands(args[0], subNames);
745
+ let msg = `Unknown subcommand: ${args[0]}`;
746
+ if (suggestions.length > 0) {
747
+ msg += `\n\nDid you mean?\n${suggestions.map(s => ` ${s}`).join('\n')}`;
748
+ }
749
+ msg += '\n\nRun "sf workflow --help" to see available subcommands.';
750
+ return failure(msg, ExitCode.INVALID_ARGUMENTS);
751
+ },
752
+ };
753
+ //# sourceMappingURL=workflow.js.map