@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.
- package/LICENSE +13 -0
- package/README.md +160 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/quarry-api.d.ts +268 -0
- package/dist/api/quarry-api.d.ts.map +1 -0
- package/dist/api/quarry-api.js +3905 -0
- package/dist/api/quarry-api.js.map +1 -0
- package/dist/api/types.d.ts +1359 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +204 -0
- package/dist/api/types.js.map +1 -0
- package/dist/bin/sf.d.ts +3 -0
- package/dist/bin/sf.d.ts.map +1 -0
- package/dist/bin/sf.js +9 -0
- package/dist/bin/sf.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +11 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +465 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cli/commands/alias.d.ts +8 -0
- package/dist/cli/commands/alias.d.ts.map +1 -0
- package/dist/cli/commands/alias.js +70 -0
- package/dist/cli/commands/alias.js.map +1 -0
- package/dist/cli/commands/channel.d.ts +13 -0
- package/dist/cli/commands/channel.d.ts.map +1 -0
- package/dist/cli/commands/channel.js +680 -0
- package/dist/cli/commands/channel.js.map +1 -0
- package/dist/cli/commands/completion.d.ts +8 -0
- package/dist/cli/commands/completion.d.ts.map +1 -0
- package/dist/cli/commands/completion.js +87 -0
- package/dist/cli/commands/completion.js.map +1 -0
- package/dist/cli/commands/config.d.ts +12 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +242 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/crud.d.ts +64 -0
- package/dist/cli/commands/crud.d.ts.map +1 -0
- package/dist/cli/commands/crud.js +805 -0
- package/dist/cli/commands/crud.js.map +1 -0
- package/dist/cli/commands/dep.d.ts +16 -0
- package/dist/cli/commands/dep.d.ts.map +1 -0
- package/dist/cli/commands/dep.js +499 -0
- package/dist/cli/commands/dep.js.map +1 -0
- package/dist/cli/commands/document.d.ts +12 -0
- package/dist/cli/commands/document.d.ts.map +1 -0
- package/dist/cli/commands/document.js +1039 -0
- package/dist/cli/commands/document.js.map +1 -0
- package/dist/cli/commands/embeddings.d.ts +12 -0
- package/dist/cli/commands/embeddings.d.ts.map +1 -0
- package/dist/cli/commands/embeddings.js +273 -0
- package/dist/cli/commands/embeddings.js.map +1 -0
- package/dist/cli/commands/entity.d.ts +16 -0
- package/dist/cli/commands/entity.d.ts.map +1 -0
- package/dist/cli/commands/entity.js +522 -0
- package/dist/cli/commands/entity.js.map +1 -0
- package/dist/cli/commands/gc.d.ts +10 -0
- package/dist/cli/commands/gc.d.ts.map +1 -0
- package/dist/cli/commands/gc.js +257 -0
- package/dist/cli/commands/gc.js.map +1 -0
- package/dist/cli/commands/help.d.ts +11 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/help.js +169 -0
- package/dist/cli/commands/help.js.map +1 -0
- package/dist/cli/commands/history.d.ts +9 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +160 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/identity.d.ts +18 -0
- package/dist/cli/commands/identity.d.ts.map +1 -0
- package/dist/cli/commands/identity.js +698 -0
- package/dist/cli/commands/identity.js.map +1 -0
- package/dist/cli/commands/inbox.d.ts +20 -0
- package/dist/cli/commands/inbox.d.ts.map +1 -0
- package/dist/cli/commands/inbox.js +493 -0
- package/dist/cli/commands/inbox.js.map +1 -0
- package/dist/cli/commands/init.d.ts +20 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +144 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install.d.ts +9 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +200 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/library.d.ts +12 -0
- package/dist/cli/commands/library.d.ts.map +1 -0
- package/dist/cli/commands/library.js +665 -0
- package/dist/cli/commands/library.js.map +1 -0
- package/dist/cli/commands/message.d.ts +11 -0
- package/dist/cli/commands/message.d.ts.map +1 -0
- package/dist/cli/commands/message.js +608 -0
- package/dist/cli/commands/message.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +17 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +698 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/playbook.d.ts +12 -0
- package/dist/cli/commands/playbook.d.ts.map +1 -0
- package/dist/cli/commands/playbook.js +730 -0
- package/dist/cli/commands/playbook.js.map +1 -0
- package/dist/cli/commands/reset.d.ts +12 -0
- package/dist/cli/commands/reset.d.ts.map +1 -0
- package/dist/cli/commands/reset.js +306 -0
- package/dist/cli/commands/reset.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +11 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +106 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +8 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +82 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +14 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +370 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/task.d.ts +25 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +1153 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/team.d.ts +13 -0
- package/dist/cli/commands/team.d.ts.map +1 -0
- package/dist/cli/commands/team.js +471 -0
- package/dist/cli/commands/team.js.map +1 -0
- package/dist/cli/commands/workflow.d.ts +16 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +753 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/completion.d.ts +28 -0
- package/dist/cli/completion.d.ts.map +1 -0
- package/dist/cli/completion.js +295 -0
- package/dist/cli/completion.js.map +1 -0
- package/dist/cli/db.d.ts +38 -0
- package/dist/cli/db.d.ts.map +1 -0
- package/dist/cli/db.js +90 -0
- package/dist/cli/db.js.map +1 -0
- package/dist/cli/formatter.d.ts +87 -0
- package/dist/cli/formatter.d.ts.map +1 -0
- package/dist/cli/formatter.js +464 -0
- package/dist/cli/formatter.js.map +1 -0
- package/dist/cli/index.d.ts +33 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +38 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parser.d.ts +45 -0
- package/dist/cli/parser.d.ts.map +1 -0
- package/dist/cli/parser.js +256 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/cli/plugin-loader.d.ts +39 -0
- package/dist/cli/plugin-loader.d.ts.map +1 -0
- package/dist/cli/plugin-loader.js +165 -0
- package/dist/cli/plugin-loader.js.map +1 -0
- package/dist/cli/plugin-registry.d.ts +50 -0
- package/dist/cli/plugin-registry.d.ts.map +1 -0
- package/dist/cli/plugin-registry.js +206 -0
- package/dist/cli/plugin-registry.js.map +1 -0
- package/dist/cli/plugin-types.d.ts +106 -0
- package/dist/cli/plugin-types.d.ts.map +1 -0
- package/dist/cli/plugin-types.js +103 -0
- package/dist/cli/plugin-types.js.map +1 -0
- package/dist/cli/runner.d.ts +35 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +340 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli/suggest.d.ts +15 -0
- package/dist/cli/suggest.d.ts.map +1 -0
- package/dist/cli/suggest.js +49 -0
- package/dist/cli/suggest.js.map +1 -0
- package/dist/cli/types.d.ts +138 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +63 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/config/config.d.ts +86 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +348 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/defaults.d.ts +66 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +114 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/duration.d.ts +75 -0
- package/dist/config/duration.d.ts.map +1 -0
- package/dist/config/duration.js +190 -0
- package/dist/config/duration.js.map +1 -0
- package/dist/config/env.d.ts +67 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +207 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/file.d.ts +97 -0
- package/dist/config/file.d.ts.map +1 -0
- package/dist/config/file.js +365 -0
- package/dist/config/file.js.map +1 -0
- package/dist/config/index.d.ts +35 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +41 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/merge.d.ts +53 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/merge.js +226 -0
- package/dist/config/merge.js.map +1 -0
- package/dist/config/types.d.ts +257 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +72 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validation.d.ts +55 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +251 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/http/index.d.ts +8 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +12 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/sync-handlers.d.ts +162 -0
- package/dist/http/sync-handlers.d.ts.map +1 -0
- package/dist/http/sync-handlers.js +271 -0
- package/dist/http/sync-handlers.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +34 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3329 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/static.d.ts +18 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +71 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/ws/broadcaster.d.ts +8 -0
- package/dist/server/ws/broadcaster.d.ts.map +1 -0
- package/dist/server/ws/broadcaster.js +7 -0
- package/dist/server/ws/broadcaster.js.map +1 -0
- package/dist/server/ws/handler.d.ts +55 -0
- package/dist/server/ws/handler.d.ts.map +1 -0
- package/dist/server/ws/handler.js +160 -0
- package/dist/server/ws/handler.js.map +1 -0
- package/dist/services/blocked-cache.d.ts +297 -0
- package/dist/services/blocked-cache.d.ts.map +1 -0
- package/dist/services/blocked-cache.js +755 -0
- package/dist/services/blocked-cache.js.map +1 -0
- package/dist/services/dependency.d.ts +205 -0
- package/dist/services/dependency.d.ts.map +1 -0
- package/dist/services/dependency.js +566 -0
- package/dist/services/dependency.js.map +1 -0
- package/dist/services/embeddings/fusion.d.ts +33 -0
- package/dist/services/embeddings/fusion.d.ts.map +1 -0
- package/dist/services/embeddings/fusion.js +34 -0
- package/dist/services/embeddings/fusion.js.map +1 -0
- package/dist/services/embeddings/index.d.ts +12 -0
- package/dist/services/embeddings/index.d.ts.map +1 -0
- package/dist/services/embeddings/index.js +10 -0
- package/dist/services/embeddings/index.js.map +1 -0
- package/dist/services/embeddings/local-provider.d.ts +31 -0
- package/dist/services/embeddings/local-provider.d.ts.map +1 -0
- package/dist/services/embeddings/local-provider.js +80 -0
- package/dist/services/embeddings/local-provider.js.map +1 -0
- package/dist/services/embeddings/service.d.ts +76 -0
- package/dist/services/embeddings/service.d.ts.map +1 -0
- package/dist/services/embeddings/service.js +153 -0
- package/dist/services/embeddings/service.js.map +1 -0
- package/dist/services/embeddings/types.d.ts +70 -0
- package/dist/services/embeddings/types.d.ts.map +1 -0
- package/dist/services/embeddings/types.js +8 -0
- package/dist/services/embeddings/types.js.map +1 -0
- package/dist/services/id-length-cache.d.ts +156 -0
- package/dist/services/id-length-cache.d.ts.map +1 -0
- package/dist/services/id-length-cache.js +197 -0
- package/dist/services/id-length-cache.js.map +1 -0
- package/dist/services/inbox.d.ts +147 -0
- package/dist/services/inbox.d.ts.map +1 -0
- package/dist/services/inbox.js +428 -0
- package/dist/services/inbox.js.map +1 -0
- package/dist/services/index.d.ts +10 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +10 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/priority-service.d.ts +145 -0
- package/dist/services/priority-service.d.ts.map +1 -0
- package/dist/services/priority-service.js +272 -0
- package/dist/services/priority-service.js.map +1 -0
- package/dist/services/search-utils.d.ts +47 -0
- package/dist/services/search-utils.d.ts.map +1 -0
- package/dist/services/search-utils.js +83 -0
- package/dist/services/search-utils.js.map +1 -0
- package/dist/sync/hash.d.ts +48 -0
- package/dist/sync/hash.d.ts.map +1 -0
- package/dist/sync/hash.js +136 -0
- package/dist/sync/hash.js.map +1 -0
- package/dist/sync/index.d.ts +11 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +16 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/merge.d.ts +80 -0
- package/dist/sync/merge.d.ts.map +1 -0
- package/dist/sync/merge.js +310 -0
- package/dist/sync/merge.js.map +1 -0
- package/dist/sync/serialization.d.ts +132 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/serialization.js +306 -0
- package/dist/sync/serialization.js.map +1 -0
- package/dist/sync/service.d.ts +102 -0
- package/dist/sync/service.d.ts.map +1 -0
- package/dist/sync/service.js +493 -0
- package/dist/sync/service.js.map +1 -0
- package/dist/sync/types.d.ts +275 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +76 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/systems/identity.d.ts +479 -0
- package/dist/systems/identity.d.ts.map +1 -0
- package/dist/systems/identity.js +817 -0
- package/dist/systems/identity.js.map +1 -0
- package/dist/systems/index.d.ts +8 -0
- package/dist/systems/index.d.ts.map +1 -0
- package/dist/systems/index.js +29 -0
- package/dist/systems/index.js.map +1 -0
- package/package.json +121 -0
- package/web/assets/charts-vendor-D1YcbGux.js +55 -0
- package/web/assets/dnd-vendor-DmxE-_ZH.js +5 -0
- package/web/assets/editor-vendor-BxraAWts.js +279 -0
- package/web/assets/index-B77vv208.js +341 -0
- package/web/assets/index-CF_XnVLh.css +1 -0
- package/web/assets/router-vendor-BCKpRBrB.js +41 -0
- package/web/assets/ui-vendor-DUahGnbT.js +45 -0
- package/web/assets/utils-vendor-CfYKiENT.js +813 -0
- package/web/favicon.ico +0 -0
- package/web/index.html +23 -0
- package/web/logo.png +0 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRUD Commands - Create, List, Show, Update, Delete operations
|
|
3
|
+
*
|
|
4
|
+
* Provides CLI commands for basic element operations:
|
|
5
|
+
* - create: Create new elements (tasks, etc.)
|
|
6
|
+
* - list: List elements with filtering
|
|
7
|
+
* - show: Show detailed element information
|
|
8
|
+
*/
|
|
9
|
+
import { success, failure, ExitCode } from '../types.js';
|
|
10
|
+
import { getFormatter, getOutputMode, getStatusIcon, formatEventsTable } from '../formatter.js';
|
|
11
|
+
import { createTask, createDocument, ContentType, TaskStatus, TaskTypeValue, PlanStatus } from '@stoneforge/core';
|
|
12
|
+
import { createInboxService } from '../../services/inbox.js';
|
|
13
|
+
import { resolveActor, createAPI } from '../db.js';
|
|
14
|
+
export const createOptions = [
|
|
15
|
+
{
|
|
16
|
+
name: 'title',
|
|
17
|
+
short: 't',
|
|
18
|
+
description: 'Title for the element (required for tasks)',
|
|
19
|
+
hasValue: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'name',
|
|
23
|
+
short: 'n',
|
|
24
|
+
description: 'Alias for --title',
|
|
25
|
+
hasValue: true,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'priority',
|
|
29
|
+
short: 'p',
|
|
30
|
+
description: 'Priority level (1-5, 1=critical)',
|
|
31
|
+
hasValue: true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'complexity',
|
|
35
|
+
short: 'c',
|
|
36
|
+
description: 'Complexity level (1-5, 1=trivial)',
|
|
37
|
+
hasValue: true,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'type',
|
|
41
|
+
description: 'Task type (bug, feature, task, chore)',
|
|
42
|
+
hasValue: true,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'assignee',
|
|
46
|
+
short: 'a',
|
|
47
|
+
description: 'Assignee entity ID',
|
|
48
|
+
hasValue: true,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'tag',
|
|
52
|
+
description: 'Add a tag (can be repeated)',
|
|
53
|
+
hasValue: true,
|
|
54
|
+
array: true,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'plan',
|
|
58
|
+
description: 'Plan ID or name to attach this task to',
|
|
59
|
+
hasValue: true,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'description',
|
|
63
|
+
short: 'd',
|
|
64
|
+
description: 'Task description (creates a linked document)',
|
|
65
|
+
hasValue: true,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
export async function createHandler(args, options) {
|
|
69
|
+
// First argument is the element type
|
|
70
|
+
const [elementType] = args;
|
|
71
|
+
if (!elementType) {
|
|
72
|
+
return failure('Usage: sf task create [options]\n\nUse "sf task create --help" for options.', ExitCode.INVALID_ARGUMENTS);
|
|
73
|
+
}
|
|
74
|
+
// Currently only support task creation
|
|
75
|
+
if (elementType !== 'task') {
|
|
76
|
+
return failure(`Unsupported element type: ${elementType}. Currently supported: task`, ExitCode.INVALID_ARGUMENTS);
|
|
77
|
+
}
|
|
78
|
+
// Use --name as alias for --title
|
|
79
|
+
const title = options.title ?? options.name;
|
|
80
|
+
// Validate required options for task
|
|
81
|
+
if (!title) {
|
|
82
|
+
return failure('--title (or --name) is required for creating a task', ExitCode.INVALID_ARGUMENTS);
|
|
83
|
+
}
|
|
84
|
+
// Create command should create the database if it doesn't exist
|
|
85
|
+
const { api, error } = createAPI(options, true);
|
|
86
|
+
if (error) {
|
|
87
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const actor = resolveActor(options);
|
|
91
|
+
// Parse priority
|
|
92
|
+
let priority;
|
|
93
|
+
if (options.priority) {
|
|
94
|
+
const p = parseInt(options.priority, 10);
|
|
95
|
+
if (isNaN(p) || p < 1 || p > 5) {
|
|
96
|
+
return failure('Priority must be a number from 1 to 5', ExitCode.VALIDATION);
|
|
97
|
+
}
|
|
98
|
+
priority = p;
|
|
99
|
+
}
|
|
100
|
+
// Parse complexity
|
|
101
|
+
let complexity;
|
|
102
|
+
if (options.complexity) {
|
|
103
|
+
const c = parseInt(options.complexity, 10);
|
|
104
|
+
if (isNaN(c) || c < 1 || c > 5) {
|
|
105
|
+
return failure('Complexity must be a number from 1 to 5', ExitCode.VALIDATION);
|
|
106
|
+
}
|
|
107
|
+
complexity = c;
|
|
108
|
+
}
|
|
109
|
+
let taskType;
|
|
110
|
+
if (options.type) {
|
|
111
|
+
const validTypes = Object.values(TaskTypeValue);
|
|
112
|
+
if (!validTypes.includes(options.type)) {
|
|
113
|
+
return failure(`Invalid task type: ${options.type}. Must be one of: ${validTypes.join(', ')}`, ExitCode.VALIDATION);
|
|
114
|
+
}
|
|
115
|
+
taskType = options.type;
|
|
116
|
+
}
|
|
117
|
+
// Handle tags (may come as array if --tag is specified multiple times)
|
|
118
|
+
let tags;
|
|
119
|
+
if (options.tag) {
|
|
120
|
+
tags = Array.isArray(options.tag) ? options.tag : [options.tag];
|
|
121
|
+
}
|
|
122
|
+
// Handle description: create a document and link it to the task
|
|
123
|
+
let descriptionRef;
|
|
124
|
+
if (options.description) {
|
|
125
|
+
const docInput = {
|
|
126
|
+
content: options.description,
|
|
127
|
+
contentType: ContentType.MARKDOWN,
|
|
128
|
+
createdBy: actor,
|
|
129
|
+
};
|
|
130
|
+
const newDoc = await createDocument(docInput);
|
|
131
|
+
const createdDoc = await api.create(newDoc);
|
|
132
|
+
descriptionRef = createdDoc.id;
|
|
133
|
+
}
|
|
134
|
+
// Create task input (title is guaranteed non-null from validation above)
|
|
135
|
+
const input = {
|
|
136
|
+
title: title,
|
|
137
|
+
createdBy: actor,
|
|
138
|
+
...(priority !== undefined && { priority }),
|
|
139
|
+
...(complexity !== undefined && { complexity }),
|
|
140
|
+
...(taskType !== undefined && { taskType }),
|
|
141
|
+
...(options.assignee && { assignee: options.assignee }),
|
|
142
|
+
...(tags && { tags }),
|
|
143
|
+
...(descriptionRef !== undefined && { descriptionRef }),
|
|
144
|
+
};
|
|
145
|
+
// Create the task
|
|
146
|
+
const task = await createTask(input);
|
|
147
|
+
// The API's create method expects ElementInput which Task satisfies
|
|
148
|
+
const created = await api.create(task);
|
|
149
|
+
// If --plan is provided, attach the task to the plan
|
|
150
|
+
let planWarning;
|
|
151
|
+
if (options.plan) {
|
|
152
|
+
try {
|
|
153
|
+
// First try to find by ID (if it looks like an element ID)
|
|
154
|
+
let plan = null;
|
|
155
|
+
if (options.plan.startsWith('el-') || options.plan.match(/^el[a-z0-9]+$/i)) {
|
|
156
|
+
plan = await api.get(options.plan);
|
|
157
|
+
}
|
|
158
|
+
// If not found by ID, search by title
|
|
159
|
+
if (!plan) {
|
|
160
|
+
const plans = await api.list({ type: 'plan' });
|
|
161
|
+
plan = plans.find((p) => p.title === options.plan) ?? null;
|
|
162
|
+
}
|
|
163
|
+
if (!plan) {
|
|
164
|
+
planWarning = `Warning: Plan not found: ${options.plan}. Task was created but not attached to a plan.`;
|
|
165
|
+
}
|
|
166
|
+
else if (plan.type !== 'plan') {
|
|
167
|
+
planWarning = `Warning: ${options.plan} is not a plan (type: ${plan.type}). Task was created but not attached.`;
|
|
168
|
+
}
|
|
169
|
+
else if (plan.status === PlanStatus.CANCELLED) {
|
|
170
|
+
planWarning = `Warning: Plan ${plan.id} is cancelled. Task was created but not attached.`;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
await api.addTaskToPlan(created.id, plan.id, { actor });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (attachErr) {
|
|
177
|
+
const attachMessage = attachErr instanceof Error ? attachErr.message : String(attachErr);
|
|
178
|
+
planWarning = `Warning: Failed to attach task to plan: ${attachMessage}. Task was created successfully.`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const message = planWarning
|
|
182
|
+
? `Created task ${created.id}\n${planWarning}`
|
|
183
|
+
: `Created task ${created.id}`;
|
|
184
|
+
return success(created, message);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
188
|
+
return failure(`Failed to create task: ${message}`, ExitCode.GENERAL_ERROR);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export const createCommand = {
|
|
192
|
+
name: 'create',
|
|
193
|
+
description: 'Create a new element',
|
|
194
|
+
usage: 'sf create <type> [options]',
|
|
195
|
+
help: `Create a new element of the specified type.
|
|
196
|
+
|
|
197
|
+
Supported types:
|
|
198
|
+
task Work item with status, priority, and assignment
|
|
199
|
+
|
|
200
|
+
Task options:
|
|
201
|
+
-t, --title <text> Task title (required)
|
|
202
|
+
-d, --description <text> Task description (creates a linked document)
|
|
203
|
+
-p, --priority <1-5> Priority (1=critical, 5=minimal, default=3)
|
|
204
|
+
-c, --complexity <1-5> Complexity (1=trivial, 5=very complex, default=3)
|
|
205
|
+
--type <type> Task type: bug, feature, task, chore
|
|
206
|
+
-a, --assignee <id> Assignee entity ID
|
|
207
|
+
--tag <tag> Add a tag (can be repeated)
|
|
208
|
+
--plan <id|name> Plan ID or name to attach this task to
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
sf create task --title "Fix login bug" --priority 1 --type bug
|
|
212
|
+
sf create task -t "Add dark mode" --tag ui --tag feature
|
|
213
|
+
sf create task -t "Implement feature X" --plan el-plan123
|
|
214
|
+
sf create task -t "Implement feature X" --plan "My Plan Name"
|
|
215
|
+
sf create task -t "New feature" -d "Detailed description here"`,
|
|
216
|
+
options: createOptions,
|
|
217
|
+
handler: createHandler,
|
|
218
|
+
};
|
|
219
|
+
export const listOptions = [
|
|
220
|
+
{
|
|
221
|
+
name: 'type',
|
|
222
|
+
short: 't',
|
|
223
|
+
description: 'Filter by element type',
|
|
224
|
+
hasValue: true,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'status',
|
|
228
|
+
short: 's',
|
|
229
|
+
description: 'Filter by status (for tasks)',
|
|
230
|
+
hasValue: true,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'priority',
|
|
234
|
+
short: 'p',
|
|
235
|
+
description: 'Filter by priority (for tasks)',
|
|
236
|
+
hasValue: true,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'assignee',
|
|
240
|
+
short: 'a',
|
|
241
|
+
description: 'Filter by assignee (for tasks)',
|
|
242
|
+
hasValue: true,
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'tag',
|
|
246
|
+
description: 'Filter by tag (can be repeated for AND)',
|
|
247
|
+
hasValue: true,
|
|
248
|
+
array: true,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'limit',
|
|
252
|
+
short: 'l',
|
|
253
|
+
description: 'Maximum number of results',
|
|
254
|
+
hasValue: true,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: 'offset',
|
|
258
|
+
short: 'o',
|
|
259
|
+
description: 'Number of results to skip',
|
|
260
|
+
hasValue: true,
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
export async function listHandler(args, options) {
|
|
264
|
+
const { api, error } = createAPI(options);
|
|
265
|
+
if (error) {
|
|
266
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
// Build filter from options
|
|
270
|
+
const filter = {};
|
|
271
|
+
// Type filter (can also be first positional arg)
|
|
272
|
+
const typeArg = args[0] ?? options.type;
|
|
273
|
+
if (typeArg) {
|
|
274
|
+
filter.type = typeArg;
|
|
275
|
+
}
|
|
276
|
+
// Status filter
|
|
277
|
+
if (options.status) {
|
|
278
|
+
const validStatuses = Object.values(TaskStatus);
|
|
279
|
+
if (!validStatuses.includes(options.status)) {
|
|
280
|
+
return failure(`Invalid status: ${options.status}. Must be one of: ${validStatuses.join(', ')}`, ExitCode.VALIDATION);
|
|
281
|
+
}
|
|
282
|
+
filter.status = options.status;
|
|
283
|
+
}
|
|
284
|
+
// Priority filter
|
|
285
|
+
if (options.priority) {
|
|
286
|
+
const priority = parseInt(options.priority, 10);
|
|
287
|
+
if (isNaN(priority) || priority < 1 || priority > 5) {
|
|
288
|
+
return failure('Priority must be a number from 1 to 5', ExitCode.VALIDATION);
|
|
289
|
+
}
|
|
290
|
+
filter.priority = priority;
|
|
291
|
+
}
|
|
292
|
+
// Assignee filter
|
|
293
|
+
if (options.assignee) {
|
|
294
|
+
filter.assignee = options.assignee;
|
|
295
|
+
}
|
|
296
|
+
// Tag filter
|
|
297
|
+
if (options.tag) {
|
|
298
|
+
filter.tags = Array.isArray(options.tag) ? options.tag : [options.tag];
|
|
299
|
+
}
|
|
300
|
+
// Pagination
|
|
301
|
+
if (options.limit) {
|
|
302
|
+
const limit = parseInt(options.limit, 10);
|
|
303
|
+
if (isNaN(limit) || limit < 1) {
|
|
304
|
+
return failure('Limit must be a positive number', ExitCode.VALIDATION);
|
|
305
|
+
}
|
|
306
|
+
filter.limit = limit;
|
|
307
|
+
}
|
|
308
|
+
if (options.offset) {
|
|
309
|
+
const offset = parseInt(options.offset, 10);
|
|
310
|
+
if (isNaN(offset) || offset < 0) {
|
|
311
|
+
return failure('Offset must be a non-negative number', ExitCode.VALIDATION);
|
|
312
|
+
}
|
|
313
|
+
filter.offset = offset;
|
|
314
|
+
}
|
|
315
|
+
// Query elements
|
|
316
|
+
const result = await api.listPaginated(filter);
|
|
317
|
+
// Format output based on mode
|
|
318
|
+
const mode = getOutputMode(options);
|
|
319
|
+
const formatter = getFormatter(mode);
|
|
320
|
+
if (mode === 'json') {
|
|
321
|
+
return success(result.items);
|
|
322
|
+
}
|
|
323
|
+
if (mode === 'quiet') {
|
|
324
|
+
return success(result.items.map((e) => e.id).join('\n'));
|
|
325
|
+
}
|
|
326
|
+
// Human-readable output
|
|
327
|
+
if (result.items.length === 0) {
|
|
328
|
+
return success(null, 'No elements found');
|
|
329
|
+
}
|
|
330
|
+
// Sort by priority ASC for tasks (P1 is highest priority, comes first)
|
|
331
|
+
const sortedItems = [...result.items].sort((a, b) => {
|
|
332
|
+
const dataA = a;
|
|
333
|
+
const dataB = b;
|
|
334
|
+
const priorityA = typeof dataA.priority === 'number' ? dataA.priority : 999;
|
|
335
|
+
const priorityB = typeof dataB.priority === 'number' ? dataB.priority : 999;
|
|
336
|
+
return priorityA - priorityB;
|
|
337
|
+
});
|
|
338
|
+
// Build table data with priority and assignee columns
|
|
339
|
+
const headers = ['ID', 'TYPE', 'TITLE/NAME', 'STATUS', 'PRIORITY', 'ASSIGNEE', 'CREATED'];
|
|
340
|
+
const rows = sortedItems.map((item) => {
|
|
341
|
+
const data = item;
|
|
342
|
+
const title = data.title ?? data.name ?? '-';
|
|
343
|
+
const status = data.status ? `${getStatusIcon(data.status)} ${data.status}` : '-';
|
|
344
|
+
const priority = typeof data.priority === 'number' ? `P${data.priority}` : '-';
|
|
345
|
+
const assignee = typeof data.assignee === 'string' ? data.assignee : '-';
|
|
346
|
+
const created = item.createdAt.split('T')[0];
|
|
347
|
+
return [item.id, item.type, title, status, priority, assignee, created];
|
|
348
|
+
});
|
|
349
|
+
const table = formatter.table(headers, rows);
|
|
350
|
+
const summary = `\nShowing ${result.items.length} of ${result.total} elements`;
|
|
351
|
+
return success(result.items, table + summary);
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
355
|
+
return failure(`Failed to list elements: ${message}`, ExitCode.GENERAL_ERROR);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
export const listCommand = {
|
|
359
|
+
name: 'list',
|
|
360
|
+
description: 'List elements',
|
|
361
|
+
usage: 'sf list [type] [options]',
|
|
362
|
+
help: `List elements with optional filtering.
|
|
363
|
+
|
|
364
|
+
Arguments:
|
|
365
|
+
type Element type to list (task, document, etc.)
|
|
366
|
+
|
|
367
|
+
Options:
|
|
368
|
+
-t, --type <type> Filter by element type
|
|
369
|
+
-s, --status <status> Filter by status (for tasks)
|
|
370
|
+
-p, --priority <1-5> Filter by priority (for tasks)
|
|
371
|
+
-a, --assignee <id> Filter by assignee (for tasks)
|
|
372
|
+
--tag <tag> Filter by tag (can be repeated)
|
|
373
|
+
-l, --limit <n> Maximum results (default: 50)
|
|
374
|
+
-o, --offset <n> Skip first n results
|
|
375
|
+
|
|
376
|
+
Examples:
|
|
377
|
+
sf list task
|
|
378
|
+
sf list task --status open
|
|
379
|
+
sf list --type task --priority 1 --status in_progress
|
|
380
|
+
sf list --tag urgent`,
|
|
381
|
+
options: listOptions,
|
|
382
|
+
handler: listHandler,
|
|
383
|
+
};
|
|
384
|
+
export const showOptions = [
|
|
385
|
+
{
|
|
386
|
+
name: 'events',
|
|
387
|
+
short: 'e',
|
|
388
|
+
description: 'Include recent events/history',
|
|
389
|
+
hasValue: false,
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: 'events-limit',
|
|
393
|
+
description: 'Maximum number of events to show (default: 10)',
|
|
394
|
+
hasValue: true,
|
|
395
|
+
},
|
|
396
|
+
];
|
|
397
|
+
export async function showHandler(args, options) {
|
|
398
|
+
const [id] = args;
|
|
399
|
+
if (!id) {
|
|
400
|
+
return failure('Usage: sf show <id>', ExitCode.INVALID_ARGUMENTS);
|
|
401
|
+
}
|
|
402
|
+
const { api, backend, error } = createAPI(options);
|
|
403
|
+
if (error) {
|
|
404
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
// Handle inbox item IDs (e.g., inbox-abc123)
|
|
408
|
+
if (id.startsWith('inbox-')) {
|
|
409
|
+
const inboxService = createInboxService(backend);
|
|
410
|
+
const inboxItem = inboxService.getInboxItem(id);
|
|
411
|
+
if (!inboxItem) {
|
|
412
|
+
return failure(`Inbox item not found: ${id}`, ExitCode.NOT_FOUND);
|
|
413
|
+
}
|
|
414
|
+
// Fetch the associated message with hydrated content
|
|
415
|
+
const message = await api.get(inboxItem.messageId, {
|
|
416
|
+
hydrate: { content: true }
|
|
417
|
+
});
|
|
418
|
+
// Build a combined result with inbox item info and message content
|
|
419
|
+
const result = {
|
|
420
|
+
...inboxItem,
|
|
421
|
+
messageContent: message?.content ?? null,
|
|
422
|
+
messageSender: message?.sender ?? null,
|
|
423
|
+
};
|
|
424
|
+
const mode = getOutputMode(options);
|
|
425
|
+
const formatter = getFormatter(mode);
|
|
426
|
+
if (mode === 'json') {
|
|
427
|
+
return success(result);
|
|
428
|
+
}
|
|
429
|
+
if (mode === 'quiet') {
|
|
430
|
+
return success(inboxItem.id);
|
|
431
|
+
}
|
|
432
|
+
// Human-readable output
|
|
433
|
+
const output = formatter.element(result);
|
|
434
|
+
return success(result, output);
|
|
435
|
+
}
|
|
436
|
+
// Get the element
|
|
437
|
+
const element = await api.get(id);
|
|
438
|
+
if (!element) {
|
|
439
|
+
return failure(`Element not found: ${id}`, ExitCode.NOT_FOUND);
|
|
440
|
+
}
|
|
441
|
+
// Check if element is deleted (tombstone)
|
|
442
|
+
const data = element;
|
|
443
|
+
if (data.status === 'tombstone' || data.deletedAt) {
|
|
444
|
+
return failure(`Element not found: ${id}`, ExitCode.NOT_FOUND);
|
|
445
|
+
}
|
|
446
|
+
// Format output based on mode
|
|
447
|
+
const mode = getOutputMode(options);
|
|
448
|
+
const formatter = getFormatter(mode);
|
|
449
|
+
// Get events if requested
|
|
450
|
+
let events;
|
|
451
|
+
if (options.events) {
|
|
452
|
+
const eventsLimit = options['events-limit'] ? parseInt(options['events-limit'], 10) : 10;
|
|
453
|
+
events = await api.getEvents(id, { limit: eventsLimit });
|
|
454
|
+
}
|
|
455
|
+
// Get plan progress if element is a plan
|
|
456
|
+
let planProgress;
|
|
457
|
+
if (element.type === 'plan') {
|
|
458
|
+
try {
|
|
459
|
+
planProgress = await api.getPlanProgress(id);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// Ignore errors fetching progress
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (mode === 'json') {
|
|
466
|
+
if (events || planProgress) {
|
|
467
|
+
return success({ element, ...(planProgress && { progress: planProgress }), ...(events && { events }) });
|
|
468
|
+
}
|
|
469
|
+
return success(element);
|
|
470
|
+
}
|
|
471
|
+
if (mode === 'quiet') {
|
|
472
|
+
return success(element.id);
|
|
473
|
+
}
|
|
474
|
+
// Human-readable output - format as key-value pairs
|
|
475
|
+
let output = formatter.element(element);
|
|
476
|
+
// Add plan progress if available
|
|
477
|
+
if (planProgress) {
|
|
478
|
+
output += '\n\n--- Task Progress ---\n';
|
|
479
|
+
output += `Total: ${planProgress.totalTasks}\n`;
|
|
480
|
+
output += `Completed: ${planProgress.completedTasks}\n`;
|
|
481
|
+
output += `In Progress: ${planProgress.inProgressTasks}\n`;
|
|
482
|
+
output += `Blocked: ${planProgress.blockedTasks}\n`;
|
|
483
|
+
output += `Ready: ${planProgress.remainingTasks}\n`;
|
|
484
|
+
output += `Progress: ${planProgress.completionPercentage}%`;
|
|
485
|
+
}
|
|
486
|
+
// Add events if requested
|
|
487
|
+
if (events && events.length > 0) {
|
|
488
|
+
output += '\n\n--- Recent Events ---\n';
|
|
489
|
+
output += formatEventsTable(events);
|
|
490
|
+
}
|
|
491
|
+
else if (options.events) {
|
|
492
|
+
output += '\n\n--- Recent Events ---\nNo events';
|
|
493
|
+
}
|
|
494
|
+
return success(element, output);
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
498
|
+
return failure(`Failed to get element: ${message}`, ExitCode.GENERAL_ERROR);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
export const showCommand = {
|
|
502
|
+
name: 'show',
|
|
503
|
+
description: 'Show element details',
|
|
504
|
+
usage: 'sf show <id> [options]',
|
|
505
|
+
help: `Display detailed information about an element.
|
|
506
|
+
|
|
507
|
+
Arguments:
|
|
508
|
+
id Element identifier (e.g., el-abc123) or inbox item ID (e.g., inbox-abc123)
|
|
509
|
+
|
|
510
|
+
Options:
|
|
511
|
+
-e, --events Include recent events/history
|
|
512
|
+
--events-limit <n> Maximum events to show (default: 10)
|
|
513
|
+
|
|
514
|
+
Examples:
|
|
515
|
+
sf show el-abc123
|
|
516
|
+
sf show el-abc123 --events
|
|
517
|
+
sf show el-abc123 --events --events-limit 20
|
|
518
|
+
sf show el-abc123 --json
|
|
519
|
+
sf show inbox-abc123 # Show inbox item with message content`,
|
|
520
|
+
options: showOptions,
|
|
521
|
+
handler: showHandler,
|
|
522
|
+
};
|
|
523
|
+
export const updateOptions = [
|
|
524
|
+
{
|
|
525
|
+
name: 'title',
|
|
526
|
+
short: 't',
|
|
527
|
+
description: 'New title',
|
|
528
|
+
hasValue: true,
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: 'priority',
|
|
532
|
+
short: 'p',
|
|
533
|
+
description: 'New priority level (1-5)',
|
|
534
|
+
hasValue: true,
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
name: 'complexity',
|
|
538
|
+
short: 'c',
|
|
539
|
+
description: 'New complexity level (1-5)',
|
|
540
|
+
hasValue: true,
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: 'status',
|
|
544
|
+
short: 's',
|
|
545
|
+
description: 'New status (for tasks: open, in_progress, closed, deferred)',
|
|
546
|
+
hasValue: true,
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: 'assignee',
|
|
550
|
+
short: 'a',
|
|
551
|
+
description: 'New assignee (use empty string to unassign)',
|
|
552
|
+
hasValue: true,
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: 'tag',
|
|
556
|
+
description: 'Replace all tags (can be repeated)',
|
|
557
|
+
hasValue: true,
|
|
558
|
+
array: true,
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: 'add-tag',
|
|
562
|
+
description: 'Add a tag (can be repeated)',
|
|
563
|
+
hasValue: true,
|
|
564
|
+
array: true,
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
name: 'remove-tag',
|
|
568
|
+
description: 'Remove a tag (can be repeated)',
|
|
569
|
+
hasValue: true,
|
|
570
|
+
array: true,
|
|
571
|
+
},
|
|
572
|
+
];
|
|
573
|
+
export async function updateHandler(args, options) {
|
|
574
|
+
const [id] = args;
|
|
575
|
+
if (!id) {
|
|
576
|
+
return failure('Usage: sf update <id> [options]', ExitCode.INVALID_ARGUMENTS);
|
|
577
|
+
}
|
|
578
|
+
const { api, error } = createAPI(options);
|
|
579
|
+
if (error) {
|
|
580
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
// Get existing element
|
|
584
|
+
const element = await api.get(id);
|
|
585
|
+
if (!element) {
|
|
586
|
+
return failure(`Element not found: ${id}`, ExitCode.NOT_FOUND);
|
|
587
|
+
}
|
|
588
|
+
// Check if element is deleted (tombstone)
|
|
589
|
+
const elemData = element;
|
|
590
|
+
if (elemData.status === 'tombstone' || elemData.deletedAt) {
|
|
591
|
+
return failure(`Element not found: ${id}`, ExitCode.NOT_FOUND);
|
|
592
|
+
}
|
|
593
|
+
// Build updates object
|
|
594
|
+
const updates = {};
|
|
595
|
+
// Handle title
|
|
596
|
+
if (options.title !== undefined) {
|
|
597
|
+
updates.title = options.title;
|
|
598
|
+
}
|
|
599
|
+
// Handle priority (for tasks)
|
|
600
|
+
if (options.priority !== undefined) {
|
|
601
|
+
if (element.type !== 'task') {
|
|
602
|
+
return failure('Priority can only be set on tasks', ExitCode.VALIDATION);
|
|
603
|
+
}
|
|
604
|
+
const p = parseInt(options.priority, 10);
|
|
605
|
+
if (isNaN(p) || p < 1 || p > 5) {
|
|
606
|
+
return failure('Priority must be a number from 1 to 5', ExitCode.VALIDATION);
|
|
607
|
+
}
|
|
608
|
+
updates.priority = p;
|
|
609
|
+
}
|
|
610
|
+
// Handle complexity (for tasks)
|
|
611
|
+
if (options.complexity !== undefined) {
|
|
612
|
+
if (element.type !== 'task') {
|
|
613
|
+
return failure('Complexity can only be set on tasks', ExitCode.VALIDATION);
|
|
614
|
+
}
|
|
615
|
+
const c = parseInt(options.complexity, 10);
|
|
616
|
+
if (isNaN(c) || c < 1 || c > 5) {
|
|
617
|
+
return failure('Complexity must be a number from 1 to 5', ExitCode.VALIDATION);
|
|
618
|
+
}
|
|
619
|
+
updates.complexity = c;
|
|
620
|
+
}
|
|
621
|
+
// Handle status (for tasks)
|
|
622
|
+
if (options.status !== undefined) {
|
|
623
|
+
if (element.type !== 'task') {
|
|
624
|
+
return failure('Status can only be set on tasks', ExitCode.VALIDATION);
|
|
625
|
+
}
|
|
626
|
+
const validStatuses = Object.values(TaskStatus);
|
|
627
|
+
if (!validStatuses.includes(options.status)) {
|
|
628
|
+
return failure(`Invalid status: ${options.status}. Must be one of: ${validStatuses.join(', ')}`, ExitCode.VALIDATION);
|
|
629
|
+
}
|
|
630
|
+
updates.status = options.status;
|
|
631
|
+
}
|
|
632
|
+
// Handle assignee (for tasks)
|
|
633
|
+
if (options.assignee !== undefined) {
|
|
634
|
+
if (element.type !== 'task') {
|
|
635
|
+
return failure('Assignee can only be set on tasks', ExitCode.VALIDATION);
|
|
636
|
+
}
|
|
637
|
+
// Empty string means unassign
|
|
638
|
+
updates.assignee = options.assignee === '' ? undefined : options.assignee;
|
|
639
|
+
}
|
|
640
|
+
// Handle tag operations
|
|
641
|
+
let currentTags = element.tags ?? [];
|
|
642
|
+
// Complete replacement with --tag
|
|
643
|
+
if (options.tag !== undefined) {
|
|
644
|
+
const tags = Array.isArray(options.tag) ? options.tag : [options.tag];
|
|
645
|
+
currentTags = tags;
|
|
646
|
+
}
|
|
647
|
+
// Add tags with --add-tag
|
|
648
|
+
if (options['add-tag'] !== undefined) {
|
|
649
|
+
const tagsToAdd = Array.isArray(options['add-tag']) ? options['add-tag'] : [options['add-tag']];
|
|
650
|
+
const tagSet = new Set(currentTags);
|
|
651
|
+
for (const tag of tagsToAdd) {
|
|
652
|
+
tagSet.add(tag);
|
|
653
|
+
}
|
|
654
|
+
currentTags = Array.from(tagSet);
|
|
655
|
+
}
|
|
656
|
+
// Remove tags with --remove-tag
|
|
657
|
+
if (options['remove-tag'] !== undefined) {
|
|
658
|
+
const tagsToRemove = Array.isArray(options['remove-tag']) ? options['remove-tag'] : [options['remove-tag']];
|
|
659
|
+
const removeSet = new Set(tagsToRemove);
|
|
660
|
+
currentTags = currentTags.filter(tag => !removeSet.has(tag));
|
|
661
|
+
}
|
|
662
|
+
// Only update tags if any tag option was used
|
|
663
|
+
if (options.tag !== undefined || options['add-tag'] !== undefined || options['remove-tag'] !== undefined) {
|
|
664
|
+
updates.tags = currentTags;
|
|
665
|
+
}
|
|
666
|
+
// Check if there are any updates to apply
|
|
667
|
+
if (Object.keys(updates).length === 0) {
|
|
668
|
+
return failure('No updates specified. Use --help for available options.', ExitCode.INVALID_ARGUMENTS);
|
|
669
|
+
}
|
|
670
|
+
// Resolve actor for audit trail
|
|
671
|
+
const actor = resolveActor(options);
|
|
672
|
+
// Apply the update with optimistic concurrency control
|
|
673
|
+
const updated = await api.update(id, updates, {
|
|
674
|
+
actor,
|
|
675
|
+
expectedUpdatedAt: element.updatedAt,
|
|
676
|
+
});
|
|
677
|
+
// Format output based on mode
|
|
678
|
+
const mode = getOutputMode(options);
|
|
679
|
+
const formatter = getFormatter(mode);
|
|
680
|
+
if (mode === 'json') {
|
|
681
|
+
return success(updated);
|
|
682
|
+
}
|
|
683
|
+
if (mode === 'quiet') {
|
|
684
|
+
return success(updated.id);
|
|
685
|
+
}
|
|
686
|
+
// Human-readable output
|
|
687
|
+
const output = formatter.element(updated);
|
|
688
|
+
return success(updated, `Updated ${element.type} ${id}\n\n${output}`);
|
|
689
|
+
}
|
|
690
|
+
catch (err) {
|
|
691
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
692
|
+
return failure(`Failed to update element: ${message}`, ExitCode.GENERAL_ERROR);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
export const updateCommand = {
|
|
696
|
+
name: 'update',
|
|
697
|
+
description: 'Update an element',
|
|
698
|
+
usage: 'sf update <id> [options]',
|
|
699
|
+
help: `Update fields on an existing element.
|
|
700
|
+
|
|
701
|
+
Arguments:
|
|
702
|
+
id Element identifier (e.g., el-abc123)
|
|
703
|
+
|
|
704
|
+
Options:
|
|
705
|
+
-t, --title <text> New title
|
|
706
|
+
-p, --priority <1-5> New priority (tasks only)
|
|
707
|
+
-c, --complexity <1-5> New complexity (tasks only)
|
|
708
|
+
-s, --status <status> New status (tasks only: open, in_progress, closed, deferred)
|
|
709
|
+
-a, --assignee <id> New assignee (tasks only, empty string to unassign)
|
|
710
|
+
--tag <tag> Replace all tags (can be repeated)
|
|
711
|
+
--add-tag <tag> Add a tag (can be repeated)
|
|
712
|
+
--remove-tag <tag> Remove a tag (can be repeated)
|
|
713
|
+
|
|
714
|
+
Examples:
|
|
715
|
+
sf update el-abc123 --title "New Title"
|
|
716
|
+
sf update el-abc123 --priority 1 --status in_progress
|
|
717
|
+
sf update el-abc123 --add-tag urgent --add-tag frontend
|
|
718
|
+
sf update el-abc123 --remove-tag old-tag
|
|
719
|
+
sf update el-abc123 --assignee "" # Unassign`,
|
|
720
|
+
options: updateOptions,
|
|
721
|
+
handler: updateHandler,
|
|
722
|
+
};
|
|
723
|
+
export const deleteOptions = [
|
|
724
|
+
{
|
|
725
|
+
name: 'reason',
|
|
726
|
+
short: 'r',
|
|
727
|
+
description: 'Deletion reason',
|
|
728
|
+
hasValue: true,
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
name: 'force',
|
|
732
|
+
short: 'f',
|
|
733
|
+
description: 'Skip confirmation (for scripts)',
|
|
734
|
+
},
|
|
735
|
+
];
|
|
736
|
+
export async function deleteHandler(args, options) {
|
|
737
|
+
const [id] = args;
|
|
738
|
+
if (!id) {
|
|
739
|
+
return failure('Usage: sf delete <id> [options]', ExitCode.INVALID_ARGUMENTS);
|
|
740
|
+
}
|
|
741
|
+
const { api, error } = createAPI(options);
|
|
742
|
+
if (error) {
|
|
743
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
// Get existing element to verify it exists and get its type
|
|
747
|
+
const element = await api.get(id);
|
|
748
|
+
if (!element) {
|
|
749
|
+
return failure(`Element not found: ${id}`, ExitCode.NOT_FOUND);
|
|
750
|
+
}
|
|
751
|
+
// Check if element is already deleted (tombstone)
|
|
752
|
+
const elemData = element;
|
|
753
|
+
if (elemData.status === 'tombstone' || elemData.deletedAt) {
|
|
754
|
+
return failure(`Element not found: ${id}`, ExitCode.NOT_FOUND);
|
|
755
|
+
}
|
|
756
|
+
// Check if element type supports deletion
|
|
757
|
+
if (element.type === 'message') {
|
|
758
|
+
return failure('Messages cannot be deleted (immutable)', ExitCode.VALIDATION);
|
|
759
|
+
}
|
|
760
|
+
// Resolve actor for audit trail
|
|
761
|
+
const actor = resolveActor(options);
|
|
762
|
+
// Perform the soft delete
|
|
763
|
+
await api.delete(id, { actor, reason: options.reason });
|
|
764
|
+
// Format output based on mode
|
|
765
|
+
const mode = getOutputMode(options);
|
|
766
|
+
if (mode === 'json') {
|
|
767
|
+
return success({ id, deleted: true, type: element.type });
|
|
768
|
+
}
|
|
769
|
+
if (mode === 'quiet') {
|
|
770
|
+
return success(id);
|
|
771
|
+
}
|
|
772
|
+
return success({ id, deleted: true }, `Deleted ${element.type} ${id}`);
|
|
773
|
+
}
|
|
774
|
+
catch (err) {
|
|
775
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
776
|
+
return failure(`Failed to delete element: ${message}`, ExitCode.GENERAL_ERROR);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
export const deleteCommand = {
|
|
780
|
+
name: 'delete',
|
|
781
|
+
description: 'Delete an element',
|
|
782
|
+
usage: 'sf delete <id> [options]',
|
|
783
|
+
help: `Soft-delete an element.
|
|
784
|
+
|
|
785
|
+
The element will be marked as deleted (tombstone) but not immediately removed.
|
|
786
|
+
Tombstones are retained for a configurable period (default: 30 days) to
|
|
787
|
+
allow sync operations to propagate deletions.
|
|
788
|
+
|
|
789
|
+
Note: Messages cannot be deleted as they are immutable.
|
|
790
|
+
|
|
791
|
+
Arguments:
|
|
792
|
+
id Element identifier (e.g., el-abc123)
|
|
793
|
+
|
|
794
|
+
Options:
|
|
795
|
+
-r, --reason <text> Deletion reason (recorded in audit trail)
|
|
796
|
+
-f, --force Skip confirmation (for scripts)
|
|
797
|
+
|
|
798
|
+
Examples:
|
|
799
|
+
sf delete el-abc123
|
|
800
|
+
sf delete el-abc123 --reason "Duplicate entry"
|
|
801
|
+
sf delete el-abc123 -f`,
|
|
802
|
+
options: deleteOptions,
|
|
803
|
+
handler: deleteHandler,
|
|
804
|
+
};
|
|
805
|
+
//# sourceMappingURL=crud.js.map
|