@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,1039 @@
1
+ /**
2
+ * Document Commands - Document management CLI interface
3
+ *
4
+ * Provides CLI commands for document operations:
5
+ * - doc create: Create a new document
6
+ * - doc list: List documents
7
+ * - doc history: Show document version history
8
+ * - doc rollback: Rollback to a previous version
9
+ */
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { resolve } from 'node:path';
12
+ import { success, failure, ExitCode } from '../types.js';
13
+ import { getFormatter, getOutputMode } from '../formatter.js';
14
+ import { createDocument, ContentType, DocumentCategory, DocumentStatus, isValidDocumentCategory, isValidDocumentStatus, } from '@stoneforge/core';
15
+ import { suggestCommands } from '../suggest.js';
16
+ import { resolveActor, createAPI } from '../db.js';
17
+ const docCreateOptions = [
18
+ {
19
+ name: 'title',
20
+ description: 'Document title',
21
+ hasValue: true,
22
+ },
23
+ {
24
+ name: 'content',
25
+ short: 'c',
26
+ description: 'Document content (text)',
27
+ hasValue: true,
28
+ },
29
+ {
30
+ name: 'file',
31
+ short: 'f',
32
+ description: 'Read content from file',
33
+ hasValue: true,
34
+ },
35
+ {
36
+ name: 'type',
37
+ short: 't',
38
+ description: 'Content type: text, markdown, json (default: text)',
39
+ hasValue: true,
40
+ },
41
+ {
42
+ name: 'category',
43
+ description: 'Document category (e.g., spec, prd, reference, tutorial)',
44
+ hasValue: true,
45
+ },
46
+ {
47
+ name: 'tag',
48
+ description: 'Add tag (can be repeated)',
49
+ hasValue: true,
50
+ array: true,
51
+ },
52
+ {
53
+ name: 'metadata',
54
+ short: 'm',
55
+ description: "JSON metadata (e.g. '{\"key\": \"value\"}')",
56
+ hasValue: true,
57
+ },
58
+ ];
59
+ async function docCreateHandler(_args, options) {
60
+ // Must specify either --content or --file
61
+ if (!options.content && !options.file) {
62
+ return failure('Either --content or --file is required', ExitCode.INVALID_ARGUMENTS);
63
+ }
64
+ if (options.content && options.file) {
65
+ return failure('Cannot specify both --content and --file', ExitCode.INVALID_ARGUMENTS);
66
+ }
67
+ const { api, error } = createAPI(options, true);
68
+ if (error) {
69
+ return failure(error, ExitCode.GENERAL_ERROR);
70
+ }
71
+ try {
72
+ const actor = resolveActor(options);
73
+ // Get content
74
+ let content;
75
+ if (options.content) {
76
+ content = options.content;
77
+ }
78
+ else {
79
+ const filePath = resolve(options.file);
80
+ if (!existsSync(filePath)) {
81
+ return failure(`File not found: ${filePath}`, ExitCode.NOT_FOUND);
82
+ }
83
+ content = readFileSync(filePath, 'utf-8');
84
+ }
85
+ // Parse content type
86
+ let contentType = ContentType.TEXT;
87
+ if (options.type) {
88
+ const validTypes = Object.values(ContentType);
89
+ if (!validTypes.includes(options.type)) {
90
+ return failure(`Invalid content type: ${options.type}. Must be one of: ${validTypes.join(', ')}`, ExitCode.VALIDATION);
91
+ }
92
+ contentType = options.type;
93
+ }
94
+ // Validate category
95
+ if (options.category && !isValidDocumentCategory(options.category)) {
96
+ const validCategories = Object.values(DocumentCategory);
97
+ return failure(`Invalid category: ${options.category}. Must be one of: ${validCategories.join(', ')}`, ExitCode.VALIDATION);
98
+ }
99
+ // Handle tags
100
+ let tags;
101
+ if (options.tag) {
102
+ tags = Array.isArray(options.tag) ? options.tag : [options.tag];
103
+ }
104
+ // Parse metadata if provided
105
+ let metadata;
106
+ if (options.metadata) {
107
+ try {
108
+ metadata = JSON.parse(options.metadata);
109
+ }
110
+ catch {
111
+ return failure('Invalid JSON for --metadata', ExitCode.VALIDATION);
112
+ }
113
+ }
114
+ const input = {
115
+ content,
116
+ contentType,
117
+ createdBy: actor,
118
+ ...(options.title && { title: options.title }),
119
+ ...(tags && { tags }),
120
+ ...(metadata && { metadata }),
121
+ ...(options.category && { category: options.category }),
122
+ };
123
+ const doc = await createDocument(input);
124
+ const created = await api.create(doc);
125
+ const mode = getOutputMode(options);
126
+ if (mode === 'quiet') {
127
+ return success(created.id);
128
+ }
129
+ return success(created, `Created document ${created.id}`);
130
+ }
131
+ catch (err) {
132
+ const message = err instanceof Error ? err.message : String(err);
133
+ return failure(`Failed to create document: ${message}`, ExitCode.GENERAL_ERROR);
134
+ }
135
+ }
136
+ const docCreateCommand = {
137
+ name: 'create',
138
+ description: 'Create a new document',
139
+ usage: 'sf document create --content <text> | --file <path> [options]',
140
+ help: `Create a new document.
141
+
142
+ Options:
143
+ --title <title> Document title
144
+ -c, --content <text> Document content (inline)
145
+ -f, --file <path> Read content from file
146
+ -t, --type <type> Content type: text, markdown, json (default: text)
147
+ --category <category> Document category (e.g., spec, prd, reference)
148
+ --tag <tag> Add tag (can be repeated)
149
+ -m, --metadata <json> JSON metadata
150
+
151
+ Examples:
152
+ sf document create --title "API Reference" --content "..." --category reference
153
+ sf document create --title "Architecture" --file spec.md --type markdown --category spec
154
+ sf document create --content '{"key": "value"}' --type json --tag config
155
+ sf document create --title "Custom Doc" --content "..." --category other --metadata '{"customCategory": "design-system"}'`,
156
+ options: docCreateOptions,
157
+ handler: docCreateHandler,
158
+ };
159
+ const docListOptions = [
160
+ {
161
+ name: 'limit',
162
+ short: 'l',
163
+ description: 'Maximum number of results',
164
+ hasValue: true,
165
+ },
166
+ {
167
+ name: 'type',
168
+ short: 't',
169
+ description: 'Filter by content type (text, markdown, json)',
170
+ hasValue: true,
171
+ },
172
+ {
173
+ name: 'category',
174
+ description: 'Filter by category (e.g., spec, prd, reference)',
175
+ hasValue: true,
176
+ },
177
+ {
178
+ name: 'status',
179
+ description: 'Filter by status (active, archived)',
180
+ hasValue: true,
181
+ },
182
+ {
183
+ name: 'all',
184
+ short: 'a',
185
+ description: 'Include archived documents',
186
+ hasValue: false,
187
+ },
188
+ ];
189
+ async function docListHandler(_args, options) {
190
+ const { api, error } = createAPI(options);
191
+ if (error) {
192
+ return failure(error, ExitCode.GENERAL_ERROR);
193
+ }
194
+ try {
195
+ // Build filter
196
+ const filter = {
197
+ type: 'document',
198
+ };
199
+ // Limit
200
+ if (options.limit) {
201
+ const limit = parseInt(options.limit, 10);
202
+ if (isNaN(limit) || limit < 1) {
203
+ return failure('Limit must be a positive number', ExitCode.VALIDATION);
204
+ }
205
+ filter.limit = limit;
206
+ }
207
+ // Category filter
208
+ if (options.category) {
209
+ if (!isValidDocumentCategory(options.category)) {
210
+ const validCategories = Object.values(DocumentCategory);
211
+ return failure(`Invalid category: ${options.category}. Must be one of: ${validCategories.join(', ')}`, ExitCode.VALIDATION);
212
+ }
213
+ filter.category = options.category;
214
+ }
215
+ // Status filter: --all includes everything, --status filters explicitly, default is active only
216
+ if (options.all) {
217
+ filter.status = [DocumentStatus.ACTIVE, DocumentStatus.ARCHIVED];
218
+ }
219
+ else if (options.status) {
220
+ if (!isValidDocumentStatus(options.status)) {
221
+ return failure(`Invalid status: ${options.status}. Must be one of: active, archived`, ExitCode.VALIDATION);
222
+ }
223
+ filter.status = options.status;
224
+ }
225
+ // Default: active only (handled by buildDocumentWhereClause)
226
+ const result = await api.listPaginated(filter);
227
+ let items = result.items;
228
+ // Filter by content type if specified
229
+ if (options.type) {
230
+ items = items.filter((d) => d.contentType === options.type);
231
+ }
232
+ const mode = getOutputMode(options);
233
+ const formatter = getFormatter(mode);
234
+ if (mode === 'json') {
235
+ return success(items);
236
+ }
237
+ if (mode === 'quiet') {
238
+ return success(items.map((d) => d.id).join('\n'));
239
+ }
240
+ if (items.length === 0) {
241
+ return success(null, 'No documents found');
242
+ }
243
+ // Build table
244
+ const headers = ['ID', 'TYPE', 'CATEGORY', 'STATUS', 'VERSION', 'SIZE', 'CREATED'];
245
+ const rows = items.map((d) => [
246
+ d.id,
247
+ d.contentType,
248
+ d.category ?? 'other',
249
+ d.status ?? 'active',
250
+ `v${d.version}`,
251
+ formatSize(d.content.length),
252
+ d.createdAt.split('T')[0],
253
+ ]);
254
+ const table = formatter.table(headers, rows);
255
+ const summary = `\nShowing ${items.length} of ${result.total} documents`;
256
+ return success(items, table + summary);
257
+ }
258
+ catch (err) {
259
+ const message = err instanceof Error ? err.message : String(err);
260
+ return failure(`Failed to list documents: ${message}`, ExitCode.GENERAL_ERROR);
261
+ }
262
+ }
263
+ /**
264
+ * Format size in human-readable format
265
+ */
266
+ function formatSize(bytes) {
267
+ if (bytes < 1024)
268
+ return `${bytes}B`;
269
+ if (bytes < 1024 * 1024)
270
+ return `${(bytes / 1024).toFixed(1)}KB`;
271
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
272
+ }
273
+ const docListCommand = {
274
+ name: 'list',
275
+ description: 'List documents',
276
+ usage: 'sf document list [options]',
277
+ help: `List documents (active only by default).
278
+
279
+ Options:
280
+ -l, --limit <n> Maximum results
281
+ -t, --type <type> Filter by content type
282
+ --category <category> Filter by category (e.g., spec, prd, reference)
283
+ --status <status> Filter by status (active, archived)
284
+ -a, --all Include archived documents
285
+
286
+ Examples:
287
+ sf document list
288
+ sf document list --type markdown
289
+ sf document list --category spec
290
+ sf document list --status archived
291
+ sf document list --all
292
+ sf document list --limit 10`,
293
+ options: docListOptions,
294
+ handler: docListHandler,
295
+ };
296
+ const docHistoryOptions = [
297
+ {
298
+ name: 'limit',
299
+ short: 'l',
300
+ description: 'Maximum versions to show',
301
+ hasValue: true,
302
+ },
303
+ ];
304
+ async function docHistoryHandler(args, options) {
305
+ const [docId] = args;
306
+ if (!docId) {
307
+ return failure('Usage: sf document history <document-id>', ExitCode.INVALID_ARGUMENTS);
308
+ }
309
+ const { api, error } = createAPI(options);
310
+ if (error) {
311
+ return failure(error, ExitCode.GENERAL_ERROR);
312
+ }
313
+ try {
314
+ // Get current document to verify it exists
315
+ const current = await api.get(docId);
316
+ if (!current) {
317
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
318
+ }
319
+ if (current.type !== 'document') {
320
+ return failure(`Element ${docId} is not a document (type: ${current.type})`, ExitCode.VALIDATION);
321
+ }
322
+ // Check if document is deleted (tombstone)
323
+ const data = current;
324
+ if (data.status === 'tombstone' || data.deletedAt) {
325
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
326
+ }
327
+ // Get version history
328
+ const history = await api.getDocumentHistory(current.id);
329
+ // Apply limit
330
+ let versions = history;
331
+ if (options.limit) {
332
+ const limit = parseInt(options.limit, 10);
333
+ if (isNaN(limit) || limit < 1) {
334
+ return failure('Limit must be a positive number', ExitCode.VALIDATION);
335
+ }
336
+ versions = history.slice(0, limit);
337
+ }
338
+ const mode = getOutputMode(options);
339
+ const formatter = getFormatter(mode);
340
+ if (mode === 'json') {
341
+ return success(versions);
342
+ }
343
+ if (mode === 'quiet') {
344
+ return success(versions.map((v) => `v${v.version}`).join('\n'));
345
+ }
346
+ if (versions.length === 0) {
347
+ return success(null, 'No version history');
348
+ }
349
+ // Build table
350
+ const headers = ['VERSION', 'SIZE', 'MODIFIED', 'CURRENT'];
351
+ const rows = versions.map((v) => [
352
+ `v${v.version}`,
353
+ formatSize(v.content.length),
354
+ v.updatedAt.split('T')[0],
355
+ v.id === current.id ? 'Yes' : '',
356
+ ]);
357
+ const table = formatter.table(headers, rows);
358
+ const summary = `\nDocument ${docId} has ${history.length} version(s)`;
359
+ return success(versions, table + summary);
360
+ }
361
+ catch (err) {
362
+ const message = err instanceof Error ? err.message : String(err);
363
+ return failure(`Failed to get document history: ${message}`, ExitCode.GENERAL_ERROR);
364
+ }
365
+ }
366
+ const docHistoryCommand = {
367
+ name: 'history',
368
+ description: 'Show document version history',
369
+ usage: 'sf document history <document-id> [options]',
370
+ help: `Show the version history of a document.
371
+
372
+ Arguments:
373
+ document-id Document identifier
374
+
375
+ Options:
376
+ -l, --limit <n> Maximum versions to show
377
+
378
+ Examples:
379
+ sf document history el-doc123
380
+ sf document history el-doc123 --limit 5`,
381
+ options: docHistoryOptions,
382
+ handler: docHistoryHandler,
383
+ };
384
+ // ============================================================================
385
+ // Document Rollback Command
386
+ // ============================================================================
387
+ async function docRollbackHandler(args, options) {
388
+ const [docId, versionStr] = args;
389
+ if (!docId || !versionStr) {
390
+ return failure('Usage: sf document rollback <document-id> <version>', ExitCode.INVALID_ARGUMENTS);
391
+ }
392
+ const version = parseInt(versionStr, 10);
393
+ if (isNaN(version) || version < 1) {
394
+ return failure('Version must be a positive number', ExitCode.VALIDATION);
395
+ }
396
+ const { api, error } = createAPI(options);
397
+ if (error) {
398
+ return failure(error, ExitCode.GENERAL_ERROR);
399
+ }
400
+ try {
401
+ const actor = resolveActor(options);
402
+ // Get the target version
403
+ const targetVersion = await api.getDocumentVersion(docId, version);
404
+ if (!targetVersion) {
405
+ return failure(`Version ${version} not found for document ${docId}`, ExitCode.NOT_FOUND);
406
+ }
407
+ // Get current document
408
+ const current = await api.get(docId);
409
+ if (!current) {
410
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
411
+ }
412
+ // Check if document is deleted (tombstone)
413
+ const data = current;
414
+ if (data.status === 'tombstone' || data.deletedAt) {
415
+ return failure(`Cannot rollback deleted document: ${docId}`, ExitCode.NOT_FOUND);
416
+ }
417
+ // Already at that version?
418
+ if (current.version === version) {
419
+ return success(current, `Document is already at version ${version}`);
420
+ }
421
+ // Update document with content from target version
422
+ // This creates a new version with the old content
423
+ const updated = await api.update(docId, { content: targetVersion.content }, { actor });
424
+ return success(updated, `Rolled back document ${docId} to version ${version} (new version: ${updated.version})`);
425
+ }
426
+ catch (err) {
427
+ const message = err instanceof Error ? err.message : String(err);
428
+ const code = err.code;
429
+ if (code === 'NOT_FOUND') {
430
+ return failure(message, ExitCode.NOT_FOUND);
431
+ }
432
+ if (code === 'INVALID_INPUT') {
433
+ return failure(message, ExitCode.VALIDATION);
434
+ }
435
+ return failure(`Failed to rollback document: ${message}`, ExitCode.GENERAL_ERROR);
436
+ }
437
+ }
438
+ const docRollbackCommand = {
439
+ name: 'rollback',
440
+ description: 'Rollback document to a previous version',
441
+ usage: 'sf document rollback <document-id> <version>',
442
+ help: `Rollback a document to a previous version.
443
+
444
+ This creates a new version with the content from the specified version.
445
+ The version history is preserved.
446
+
447
+ Arguments:
448
+ document-id Document identifier
449
+ version Version number to rollback to
450
+
451
+ Examples:
452
+ sf document rollback el-doc123 2`,
453
+ handler: docRollbackHandler,
454
+ };
455
+ const docUpdateOptions = [
456
+ {
457
+ name: 'content',
458
+ short: 'c',
459
+ description: 'New document content (text)',
460
+ hasValue: true,
461
+ },
462
+ {
463
+ name: 'file',
464
+ short: 'f',
465
+ description: 'Read new content from file',
466
+ hasValue: true,
467
+ },
468
+ {
469
+ name: 'metadata',
470
+ short: 'm',
471
+ description: "JSON metadata to merge (e.g. '{\"key\": \"value\"}')",
472
+ hasValue: true,
473
+ },
474
+ ];
475
+ async function docUpdateHandler(args, options) {
476
+ const [docId] = args;
477
+ if (!docId) {
478
+ return failure('Usage: sf document update <document-id> --content <text> | --file <path> | --metadata <json>', ExitCode.INVALID_ARGUMENTS);
479
+ }
480
+ // Must specify at least one of --content, --file, or --metadata
481
+ if (!options.content && !options.file && !options.metadata) {
482
+ return failure('At least one of --content, --file, or --metadata is required', ExitCode.INVALID_ARGUMENTS);
483
+ }
484
+ if (options.content && options.file) {
485
+ return failure('Cannot specify both --content and --file', ExitCode.INVALID_ARGUMENTS);
486
+ }
487
+ const { api, error } = createAPI(options);
488
+ if (error) {
489
+ return failure(error, ExitCode.GENERAL_ERROR);
490
+ }
491
+ try {
492
+ // Verify document exists
493
+ const existing = await api.get(docId);
494
+ if (!existing) {
495
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
496
+ }
497
+ if (existing.type !== 'document') {
498
+ return failure(`Element ${docId} is not a document (type: ${existing.type})`, ExitCode.VALIDATION);
499
+ }
500
+ // Check if document is deleted (tombstone)
501
+ const data = existing;
502
+ if (data.status === 'tombstone' || data.deletedAt) {
503
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
504
+ }
505
+ const actor = resolveActor(options);
506
+ // Parse metadata if provided
507
+ let metadata;
508
+ if (options.metadata) {
509
+ try {
510
+ metadata = JSON.parse(options.metadata);
511
+ }
512
+ catch {
513
+ return failure('Invalid JSON for --metadata', ExitCode.VALIDATION);
514
+ }
515
+ }
516
+ // Get new content if provided
517
+ let content;
518
+ if (options.content) {
519
+ content = options.content;
520
+ }
521
+ else if (options.file) {
522
+ const filePath = resolve(options.file);
523
+ if (!existsSync(filePath)) {
524
+ return failure(`File not found: ${filePath}`, ExitCode.NOT_FOUND);
525
+ }
526
+ content = readFileSync(filePath, 'utf-8');
527
+ }
528
+ // Build update payload
529
+ const updatePayload = {};
530
+ if (content !== undefined)
531
+ updatePayload.content = content;
532
+ if (metadata)
533
+ updatePayload.metadata = metadata;
534
+ // Update the document (creates a new version)
535
+ const updated = await api.update(docId, updatePayload, { actor });
536
+ const mode = getOutputMode(options);
537
+ if (mode === 'json') {
538
+ return success(updated);
539
+ }
540
+ if (mode === 'quiet') {
541
+ return success(updated.id);
542
+ }
543
+ return success(updated, `Updated document ${docId} (now at version ${updated.version})`);
544
+ }
545
+ catch (err) {
546
+ const message = err instanceof Error ? err.message : String(err);
547
+ return failure(`Failed to update document: ${message}`, ExitCode.GENERAL_ERROR);
548
+ }
549
+ }
550
+ const docUpdateCommand = {
551
+ name: 'update',
552
+ description: 'Update document content',
553
+ usage: 'sf document update <document-id> --content <text> | --file <path> | --metadata <json>',
554
+ help: `Update a document's content and/or metadata, creating a new version.
555
+
556
+ Documents are versioned - each update creates a new version while preserving
557
+ the history. Use 'sf document history' to view versions and 'sf document rollback' to
558
+ revert to a previous version.
559
+
560
+ Arguments:
561
+ document-id Document identifier
562
+
563
+ Options:
564
+ -c, --content <text> New content (inline)
565
+ -f, --file <path> Read new content from file
566
+ -m, --metadata <json> JSON metadata to merge
567
+
568
+ Examples:
569
+ sf document update el-doc123 --content "Updated content"
570
+ sf document update el-doc123 --file updated-spec.md
571
+ sf document update el-doc123 -c "Quick fix"
572
+ sf document update el-doc123 --metadata '{"purpose": "api-reference"}'
573
+ sf document update el-doc123 --content "New content" --metadata '{"version": 2}'`,
574
+ options: docUpdateOptions,
575
+ handler: docUpdateHandler,
576
+ };
577
+ const docShowOptions = [
578
+ {
579
+ name: 'docVersion',
580
+ short: 'V',
581
+ description: 'Show specific version',
582
+ hasValue: true,
583
+ },
584
+ ];
585
+ async function docShowHandler(args, options) {
586
+ const [docId] = args;
587
+ if (!docId) {
588
+ return failure('Usage: sf document show <document-id> [options]', ExitCode.INVALID_ARGUMENTS);
589
+ }
590
+ const { api, error } = createAPI(options);
591
+ if (error) {
592
+ return failure(error, ExitCode.GENERAL_ERROR);
593
+ }
594
+ try {
595
+ let doc;
596
+ if (options.docVersion) {
597
+ const version = parseInt(options.docVersion, 10);
598
+ if (isNaN(version) || version < 1) {
599
+ return failure('Version must be a positive number', ExitCode.VALIDATION);
600
+ }
601
+ doc = await api.getDocumentVersion(docId, version);
602
+ if (!doc) {
603
+ return failure(`Version ${version} not found for document ${docId}`, ExitCode.NOT_FOUND);
604
+ }
605
+ }
606
+ else {
607
+ doc = await api.get(docId);
608
+ if (!doc) {
609
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
610
+ }
611
+ if (doc.type !== 'document') {
612
+ return failure(`Element ${docId} is not a document (type: ${doc.type})`, ExitCode.VALIDATION);
613
+ }
614
+ }
615
+ // Check if document is deleted (tombstone)
616
+ const data = doc;
617
+ if (data.status === 'tombstone' || data.deletedAt) {
618
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
619
+ }
620
+ const mode = getOutputMode(options);
621
+ const formatter = getFormatter(mode);
622
+ if (mode === 'json') {
623
+ return success(doc);
624
+ }
625
+ if (mode === 'quiet') {
626
+ return success(doc.content);
627
+ }
628
+ // Format document details
629
+ const output = formatter.element(doc);
630
+ return success(doc, output);
631
+ }
632
+ catch (err) {
633
+ const message = err instanceof Error ? err.message : String(err);
634
+ const code = err.code;
635
+ if (code === 'NOT_FOUND') {
636
+ return failure(message, ExitCode.NOT_FOUND);
637
+ }
638
+ if (code === 'INVALID_INPUT') {
639
+ return failure(message, ExitCode.VALIDATION);
640
+ }
641
+ return failure(`Failed to show document: ${message}`, ExitCode.GENERAL_ERROR);
642
+ }
643
+ }
644
+ const docShowCommand = {
645
+ name: 'show',
646
+ description: 'Show document details',
647
+ usage: 'sf document show <document-id> [options]',
648
+ help: `Show document details and content.
649
+
650
+ Arguments:
651
+ document-id Document identifier
652
+
653
+ Options:
654
+ -V, --docVersion <n> Show specific version
655
+
656
+ Examples:
657
+ sf document show el-doc123
658
+ sf document show el-doc123 --docVersion 2
659
+ sf document show el-doc123 --quiet # Output content only`,
660
+ options: docShowOptions,
661
+ handler: docShowHandler,
662
+ };
663
+ // ============================================================================
664
+ // Document Archive Command
665
+ // ============================================================================
666
+ async function docArchiveHandler(args, options) {
667
+ const [docId] = args;
668
+ if (!docId) {
669
+ return failure('Usage: sf document archive <document-id>', ExitCode.INVALID_ARGUMENTS);
670
+ }
671
+ const { api, error } = createAPI(options);
672
+ if (error) {
673
+ return failure(error, ExitCode.GENERAL_ERROR);
674
+ }
675
+ try {
676
+ const existing = await api.get(docId);
677
+ if (!existing) {
678
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
679
+ }
680
+ if (existing.type !== 'document') {
681
+ return failure(`Element ${docId} is not a document`, ExitCode.VALIDATION);
682
+ }
683
+ const updated = await api.update(docId, { status: DocumentStatus.ARCHIVED }, { actor: resolveActor(options) });
684
+ return success(updated, `Archived document ${docId}`);
685
+ }
686
+ catch (err) {
687
+ const message = err instanceof Error ? err.message : String(err);
688
+ return failure(`Failed to archive document: ${message}`, ExitCode.GENERAL_ERROR);
689
+ }
690
+ }
691
+ const docArchiveCommand = {
692
+ name: 'archive',
693
+ description: 'Archive a document',
694
+ usage: 'sf document archive <document-id>',
695
+ help: `Archive a document. Archived documents are hidden from default list/search.
696
+
697
+ Arguments:
698
+ document-id Document identifier
699
+
700
+ Examples:
701
+ sf document archive el-doc123`,
702
+ handler: docArchiveHandler,
703
+ };
704
+ const docDeleteOptions = [
705
+ {
706
+ name: 'reason',
707
+ short: 'r',
708
+ description: 'Reason for deletion',
709
+ hasValue: true,
710
+ },
711
+ {
712
+ name: 'force',
713
+ short: 'f',
714
+ description: 'Skip confirmation',
715
+ hasValue: false,
716
+ },
717
+ ];
718
+ async function docDeleteHandler(args, options) {
719
+ const [docId] = args;
720
+ if (!docId) {
721
+ return failure('Usage: sf document delete <document-id>', ExitCode.INVALID_ARGUMENTS);
722
+ }
723
+ const { api, error } = createAPI(options);
724
+ if (error) {
725
+ return failure(error, ExitCode.GENERAL_ERROR);
726
+ }
727
+ try {
728
+ const existing = await api.get(docId);
729
+ if (!existing) {
730
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
731
+ }
732
+ if (existing.type !== 'document') {
733
+ return failure(`Element ${docId} is not a document`, ExitCode.VALIDATION);
734
+ }
735
+ // Check if document is already deleted (tombstone)
736
+ const data = existing;
737
+ if (data.status === 'tombstone' || data.deletedAt) {
738
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
739
+ }
740
+ const actor = resolveActor(options);
741
+ await api.delete(docId, { actor, reason: options.reason });
742
+ const mode = getOutputMode(options);
743
+ if (mode === 'json') {
744
+ return success({ id: docId, deleted: true, type: 'document' });
745
+ }
746
+ if (mode === 'quiet') {
747
+ return success(docId);
748
+ }
749
+ return success(null, `Deleted document ${docId}`);
750
+ }
751
+ catch (err) {
752
+ const message = err instanceof Error ? err.message : String(err);
753
+ const code = err.code;
754
+ if (code === 'NOT_FOUND') {
755
+ return failure(message, ExitCode.NOT_FOUND);
756
+ }
757
+ return failure(`Failed to delete document: ${message}`, ExitCode.GENERAL_ERROR);
758
+ }
759
+ }
760
+ const docDeleteCommand = {
761
+ name: 'delete',
762
+ description: 'Delete a document (soft-delete)',
763
+ usage: 'sf document delete <document-id> [options]',
764
+ help: `Delete a document (soft-delete via tombstone).
765
+
766
+ Arguments:
767
+ document-id Document identifier
768
+
769
+ Options:
770
+ -r, --reason <text> Reason for deletion
771
+ -f, --force Skip confirmation
772
+
773
+ Examples:
774
+ sf document delete el-doc123
775
+ sf document delete el-doc123 --reason "outdated"
776
+ sf document delete el-doc123 --force`,
777
+ options: docDeleteOptions,
778
+ handler: docDeleteHandler,
779
+ };
780
+ // ============================================================================
781
+ // Document Unarchive Command
782
+ // ============================================================================
783
+ async function docUnarchiveHandler(args, options) {
784
+ const [docId] = args;
785
+ if (!docId) {
786
+ return failure('Usage: sf document unarchive <document-id>', ExitCode.INVALID_ARGUMENTS);
787
+ }
788
+ const { api, error } = createAPI(options);
789
+ if (error) {
790
+ return failure(error, ExitCode.GENERAL_ERROR);
791
+ }
792
+ try {
793
+ const existing = await api.get(docId);
794
+ if (!existing) {
795
+ return failure(`Document not found: ${docId}`, ExitCode.NOT_FOUND);
796
+ }
797
+ if (existing.type !== 'document') {
798
+ return failure(`Element ${docId} is not a document`, ExitCode.VALIDATION);
799
+ }
800
+ const updated = await api.update(docId, { status: DocumentStatus.ACTIVE }, { actor: resolveActor(options) });
801
+ return success(updated, `Unarchived document ${docId}`);
802
+ }
803
+ catch (err) {
804
+ const message = err instanceof Error ? err.message : String(err);
805
+ return failure(`Failed to unarchive document: ${message}`, ExitCode.GENERAL_ERROR);
806
+ }
807
+ }
808
+ const docUnarchiveCommand = {
809
+ name: 'unarchive',
810
+ description: 'Unarchive a document',
811
+ usage: 'sf document unarchive <document-id>',
812
+ help: `Unarchive a document, making it visible in default list/search again.
813
+
814
+ Arguments:
815
+ document-id Document identifier
816
+
817
+ Examples:
818
+ sf document unarchive el-doc123`,
819
+ handler: docUnarchiveHandler,
820
+ };
821
+ const docSearchOptions = [
822
+ {
823
+ name: 'category',
824
+ description: 'Filter by category',
825
+ hasValue: true,
826
+ },
827
+ {
828
+ name: 'status',
829
+ description: 'Filter by status',
830
+ hasValue: true,
831
+ },
832
+ {
833
+ name: 'limit',
834
+ short: 'l',
835
+ description: 'Maximum results',
836
+ hasValue: true,
837
+ },
838
+ ];
839
+ async function docSearchHandler(args, options) {
840
+ if (args.length === 0) {
841
+ return failure('Search query is required. Usage: sf document search <query>', ExitCode.INVALID_ARGUMENTS);
842
+ }
843
+ const { api, error } = createAPI(options);
844
+ if (error) {
845
+ return failure(error, ExitCode.GENERAL_ERROR);
846
+ }
847
+ try {
848
+ const query = args.join(' ');
849
+ // Validate category
850
+ if (options.category) {
851
+ if (!isValidDocumentCategory(options.category)) {
852
+ const validCategories = Object.values(DocumentCategory);
853
+ return failure(`Invalid category: ${options.category}. Must be one of: ${validCategories.join(', ')}`, ExitCode.VALIDATION);
854
+ }
855
+ }
856
+ // Validate status
857
+ if (options.status) {
858
+ if (!isValidDocumentStatus(options.status)) {
859
+ return failure(`Invalid status: ${options.status}. Must be one of: active, archived`, ExitCode.VALIDATION);
860
+ }
861
+ }
862
+ // Validate limit
863
+ let hardCap = 50;
864
+ if (options.limit) {
865
+ hardCap = parseInt(options.limit, 10);
866
+ if (isNaN(hardCap) || hardCap < 1) {
867
+ return failure('Limit must be a positive number', ExitCode.VALIDATION);
868
+ }
869
+ }
870
+ const searchOptions = { hardCap };
871
+ if (options.category)
872
+ searchOptions.category = options.category;
873
+ if (options.status)
874
+ searchOptions.status = options.status;
875
+ const results = await api.searchDocumentsFTS(query, searchOptions);
876
+ const mode = getOutputMode(options);
877
+ const formatter = getFormatter(mode);
878
+ if (mode === 'json') {
879
+ return success(results);
880
+ }
881
+ if (mode === 'quiet') {
882
+ return success(results.map((r) => r.document.id).join('\n'));
883
+ }
884
+ if (results.length === 0) {
885
+ return success(null, 'No documents found');
886
+ }
887
+ const headers = ['ID', 'SCORE', 'TITLE', 'CATEGORY', 'SNIPPET'];
888
+ const rows = results.map((r) => [
889
+ r.document.id,
890
+ r.score.toFixed(2),
891
+ (r.document.title ?? '').slice(0, 40),
892
+ r.document.category ?? 'other',
893
+ r.snippet.slice(0, 60).replace(/\n/g, ' '),
894
+ ]);
895
+ const table = formatter.table(headers, rows);
896
+ const summary = `\n${results.length} result${results.length !== 1 ? 's' : ''} for "${query}"`;
897
+ return success(results, table + summary);
898
+ }
899
+ catch (err) {
900
+ const message = err instanceof Error ? err.message : String(err);
901
+ return failure(`Search failed: ${message}`, ExitCode.GENERAL_ERROR);
902
+ }
903
+ }
904
+ const docSearchCommand = {
905
+ name: 'search',
906
+ description: 'Full-text search documents',
907
+ usage: 'sf document search <query> [options]',
908
+ help: `Full-text search documents using FTS5 with BM25 ranking.
909
+
910
+ Arguments:
911
+ query Search query text
912
+
913
+ Options:
914
+ -l, --limit <n> Maximum results (default: 50)
915
+ --category <category> Filter by category (e.g., spec, prd, reference)
916
+ --status <status> Filter by status (active, archived)
917
+
918
+ Examples:
919
+ sf document search "API authentication"
920
+ sf document search "migration" --category spec
921
+ sf document search "config" --limit 5`,
922
+ options: docSearchOptions,
923
+ handler: docSearchHandler,
924
+ };
925
+ // ============================================================================
926
+ // Document Reindex Command
927
+ // ============================================================================
928
+ async function docReindexHandler(_args, options) {
929
+ const { api, error } = createAPI(options);
930
+ if (error) {
931
+ return failure(error, ExitCode.GENERAL_ERROR);
932
+ }
933
+ try {
934
+ const result = api.reindexAllDocumentsFTS();
935
+ const mode = getOutputMode(options);
936
+ if (mode === 'json') {
937
+ return success(result);
938
+ }
939
+ return success(null, `Reindexed ${result.indexed} documents for FTS search${result.errors > 0 ? ` (${result.errors} errors)` : ''}`);
940
+ }
941
+ catch (err) {
942
+ const message = err instanceof Error ? err.message : String(err);
943
+ return failure(`Failed to reindex documents: ${message}`, ExitCode.GENERAL_ERROR);
944
+ }
945
+ }
946
+ const docReindexCommand = {
947
+ name: 'reindex',
948
+ description: 'Rebuild full-text search index',
949
+ usage: 'sf document reindex',
950
+ help: `Rebuild the FTS5 full-text search index for all documents.
951
+
952
+ Iterates all documents (including archived) and re-indexes them
953
+ in the FTS5 virtual table. Use this after migration or if search
954
+ results seem stale.
955
+
956
+ Examples:
957
+ sf document reindex`,
958
+ handler: docReindexHandler,
959
+ };
960
+ // ============================================================================
961
+ // Document Root Command
962
+ // ============================================================================
963
+ export const documentCommand = {
964
+ name: 'document',
965
+ description: 'Manage documents (versioned content)',
966
+ usage: 'sf document <subcommand> [options]',
967
+ help: `Manage documents - versioned content storage.
968
+
969
+ Documents store content with automatic versioning. Each update creates
970
+ a new version, and you can view history or rollback to any previous version.
971
+
972
+ Subcommands:
973
+ create Create a new document
974
+ list List documents (active only by default)
975
+ search Full-text search documents
976
+ show Show document details
977
+ update Update document content (creates new version)
978
+ history Show version history
979
+ rollback Rollback to a previous version
980
+ archive Archive a document
981
+ unarchive Unarchive a document
982
+ delete Delete a document (soft-delete)
983
+ reindex Rebuild full-text search index
984
+
985
+ Examples:
986
+ sf document create --content "Hello world"
987
+ sf document create --file notes.md --type markdown --category spec
988
+ sf document list
989
+ sf document list --category reference --all
990
+ sf document search "API authentication"
991
+ sf document search "migration" --category spec
992
+ sf document show el-doc123
993
+ sf document update el-doc123 --content "New content"
994
+ sf document update el-doc123 --file updated.md
995
+ sf document history el-doc123
996
+ sf document rollback el-doc123 2
997
+ sf document archive el-doc123
998
+ sf document unarchive el-doc123
999
+ sf document delete el-doc123
1000
+ sf document reindex`,
1001
+ subcommands: {
1002
+ create: docCreateCommand,
1003
+ list: docListCommand,
1004
+ search: docSearchCommand,
1005
+ show: docShowCommand,
1006
+ update: docUpdateCommand,
1007
+ history: docHistoryCommand,
1008
+ rollback: docRollbackCommand,
1009
+ archive: docArchiveCommand,
1010
+ unarchive: docUnarchiveCommand,
1011
+ delete: docDeleteCommand,
1012
+ reindex: docReindexCommand,
1013
+ // Aliases (hidden from --help via dedup in getCommandHelp)
1014
+ new: docCreateCommand,
1015
+ add: docCreateCommand,
1016
+ ls: docListCommand,
1017
+ rm: docDeleteCommand,
1018
+ get: docShowCommand,
1019
+ view: docShowCommand,
1020
+ edit: docUpdateCommand,
1021
+ find: docSearchCommand,
1022
+ },
1023
+ handler: async (args, options) => {
1024
+ // Default to list if no subcommand
1025
+ if (args.length === 0) {
1026
+ return docListHandler(args, options);
1027
+ }
1028
+ // Show "did you mean?" for unknown subcommands
1029
+ const subNames = Object.keys(documentCommand.subcommands);
1030
+ const suggestions = suggestCommands(args[0], subNames);
1031
+ let msg = `Unknown subcommand: ${args[0]}`;
1032
+ if (suggestions.length > 0) {
1033
+ msg += `\n\nDid you mean?\n${suggestions.map(s => ` ${s}`).join('\n')}`;
1034
+ }
1035
+ msg += '\n\nRun "sf document --help" to see available subcommands.';
1036
+ return failure(msg, ExitCode.INVALID_ARGUMENTS);
1037
+ },
1038
+ };
1039
+ //# sourceMappingURL=document.js.map