@4ge/cli 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 +21 -0
- package/README.md +228 -0
- package/bin/dev.js +3 -0
- package/bin/run.js +3 -0
- package/dist/base.js +398 -0
- package/dist/commands/auth/login.js +197 -0
- package/dist/commands/auth/logout.js +31 -0
- package/dist/commands/auth/profile/delete.js +34 -0
- package/dist/commands/auth/profile/list.js +32 -0
- package/dist/commands/auth/profile/use.js +30 -0
- package/dist/commands/auth/status.js +107 -0
- package/dist/commands/config/get.js +37 -0
- package/dist/commands/config/index.js +9 -0
- package/dist/commands/config/manage.js +165 -0
- package/dist/commands/config/set.js +42 -0
- package/dist/commands/config/show.js +46 -0
- package/dist/commands/epic/index.js +10 -0
- package/dist/commands/epic/list.js +91 -0
- package/dist/commands/epic/pull.js +137 -0
- package/dist/commands/epic/show.js +67 -0
- package/dist/commands/epic/update.js +104 -0
- package/dist/commands/feature/index.js +2 -0
- package/dist/commands/feature/list.js +93 -0
- package/dist/commands/feature/pull.js +122 -0
- package/dist/commands/feature/show.js +60 -0
- package/dist/commands/feature/update.js +103 -0
- package/dist/commands/idea/create.js +88 -0
- package/dist/commands/idea/graduate.js +89 -0
- package/dist/commands/idea/index.js +5 -0
- package/dist/commands/idea/list.js +83 -0
- package/dist/commands/idea/show.js +86 -0
- package/dist/commands/idea/update.js +108 -0
- package/dist/commands/init.js +354 -0
- package/dist/commands/login.js +5 -0
- package/dist/commands/plan/index.js +10 -0
- package/dist/commands/plan/pull.js +104 -0
- package/dist/commands/plan/versions.js +64 -0
- package/dist/commands/project/create.js +84 -0
- package/dist/commands/project/index.js +9 -0
- package/dist/commands/project/info.js +74 -0
- package/dist/commands/project/list.js +78 -0
- package/dist/commands/project/pull.js +127 -0
- package/dist/commands/project/update.js +104 -0
- package/dist/commands/root.js +13 -0
- package/dist/commands/skill/index.js +8 -0
- package/dist/commands/skill/install.js +71 -0
- package/dist/commands/story/index.js +10 -0
- package/dist/commands/story/list.js +81 -0
- package/dist/commands/story/pull.js +125 -0
- package/dist/commands/story/show.js +57 -0
- package/dist/commands/story/update.js +103 -0
- package/dist/commands/template/create.js +72 -0
- package/dist/commands/template/delete.js +62 -0
- package/dist/commands/template/feature/create.js +70 -0
- package/dist/commands/template/feature/delete.js +75 -0
- package/dist/commands/template/feature/list.js +69 -0
- package/dist/commands/template/feature/update.js +82 -0
- package/dist/commands/template/info.js +52 -0
- package/dist/commands/template/list.js +76 -0
- package/dist/commands/template/pull.js +99 -0
- package/dist/commands/template/update.js +71 -0
- package/dist/commands/whoami.js +5 -0
- package/dist/core/api/action-hints.js +17 -0
- package/dist/core/api/index.js +3 -0
- package/dist/core/api/retry.js +95 -0
- package/dist/core/api/validate-response.js +41 -0
- package/dist/core/auth/api-client.js +156 -0
- package/dist/core/auth/config.js +3 -0
- package/dist/core/auth/credentials.js +143 -0
- package/dist/core/auth/email-auth.js +30 -0
- package/dist/core/auth/errors.js +20 -0
- package/dist/core/auth/index.js +6 -0
- package/dist/core/auth/realtime.js +82 -0
- package/dist/core/auth/resolution.js +63 -0
- package/dist/core/auth/state.js +9 -0
- package/dist/core/auth/token-refresh.js +100 -0
- package/dist/core/config/defaults.js +31 -0
- package/dist/core/config/index.js +3 -0
- package/dist/core/config/parser.js +213 -0
- package/dist/core/config/schema.js +29 -0
- package/dist/core/project/markdown.js +37 -0
- package/dist/core/skill/installer.js +59 -0
- package/dist/core/skill/paths.js +52 -0
- package/dist/generated/api/client/client.gen.js +217 -0
- package/dist/generated/api/client/index.js +6 -0
- package/dist/generated/api/client/types.gen.js +2 -0
- package/dist/generated/api/client/utils.gen.js +231 -0
- package/dist/generated/api/client.gen.js +3 -0
- package/dist/generated/api/core/auth.gen.js +14 -0
- package/dist/generated/api/core/bodySerializer.gen.js +57 -0
- package/dist/generated/api/core/params.gen.js +103 -0
- package/dist/generated/api/core/pathSerializer.gen.js +114 -0
- package/dist/generated/api/core/queryKeySerializer.gen.js +99 -0
- package/dist/generated/api/core/serverSentEvents.gen.js +136 -0
- package/dist/generated/api/core/types.gen.js +2 -0
- package/dist/generated/api/core/utils.gen.js +87 -0
- package/dist/generated/api/index.js +2 -0
- package/dist/generated/api/sdk.gen.js +556 -0
- package/dist/generated/api/types.gen.js +2 -0
- package/dist/generated/api/zod.gen.js +1262 -0
- package/dist/ui/components/action-hints.js +33 -0
- package/dist/ui/components/cli-header.js +91 -0
- package/dist/ui/components/confirm-prompt.js +28 -0
- package/dist/ui/components/data-table.js +35 -0
- package/dist/ui/components/detail-view.js +5 -0
- package/dist/ui/components/error-display.js +5 -0
- package/dist/ui/components/prompt-layout.js +6 -0
- package/dist/ui/components/select-prompt.js +28 -0
- package/dist/ui/components/spinner.js +14 -0
- package/dist/ui/components/success-message.js +5 -0
- package/dist/ui/components/text-input.js +29 -0
- package/dist/ui/hooks/use-action-hints.js +21 -0
- package/dist/ui/hooks/use-status.js +22 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/prompt-helpers.js +55 -0
- package/dist/ui/render.js +9 -0
- package/package.json +89 -0
- package/skills/4ge-cli.md +113 -0
- package/usage.md +320 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base.js';
|
|
4
|
+
import { getCollectionDir } from '../../core/config/index.js';
|
|
5
|
+
import { resolveRecursive, resolveRoadmapFilter } from '../../core/config/defaults.js';
|
|
6
|
+
import { getFeatureDetail, getFeatureTree } from '../../generated/api/sdk.gen.js';
|
|
7
|
+
import { zGetFeatureTreeResponse, zGetFeatureDetailResponse } from '../../generated/api/zod.gen.js';
|
|
8
|
+
import { validateResponse } from '../../core/api/index.js';
|
|
9
|
+
import { SuccessMessage } from '../../ui/index.js';
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
export default class FeaturePull extends BaseCommand {
|
|
13
|
+
static description = 'Download feature details';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> feature:pull --id <uuid>',
|
|
16
|
+
'<%= config.bin %> feature:pull --id <uuid> --json',
|
|
17
|
+
'<%= config.bin %> feature:pull --id <uuid> --recursive',
|
|
18
|
+
'<%= config.bin %> feature:pull --id <uuid> --recursive --roadmap now --status BACKLOG',
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
...BaseCommand.baseFlags,
|
|
22
|
+
id: Flags.string({
|
|
23
|
+
description: 'Feature ID',
|
|
24
|
+
required: true,
|
|
25
|
+
}),
|
|
26
|
+
recursive: Flags.boolean({
|
|
27
|
+
description: 'Pull full hierarchy tree instead of just the feature detail',
|
|
28
|
+
allowNo: true,
|
|
29
|
+
}),
|
|
30
|
+
roadmap: Flags.string({
|
|
31
|
+
description: 'Filter hierarchy by roadmap status (now|next|later). Only used with --recursive.',
|
|
32
|
+
options: ['now', 'next', 'later'],
|
|
33
|
+
}),
|
|
34
|
+
status: Flags.string({
|
|
35
|
+
description: 'Filter hierarchy by status. Only used with --recursive.',
|
|
36
|
+
}),
|
|
37
|
+
save: Flags.boolean({
|
|
38
|
+
description: 'Force save to disk even in JSON mode',
|
|
39
|
+
default: false,
|
|
40
|
+
}),
|
|
41
|
+
stdout: Flags.boolean({
|
|
42
|
+
description: 'Print to stdout without saving',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
async run() {
|
|
47
|
+
const { flags } = await this.parse(FeaturePull);
|
|
48
|
+
if (flags.json && flags.stdout) {
|
|
49
|
+
this.error('Cannot use --json and --stdout together');
|
|
50
|
+
}
|
|
51
|
+
if (flags.save && flags.stdout) {
|
|
52
|
+
this.error('Cannot use --stdout and --save together');
|
|
53
|
+
}
|
|
54
|
+
await this.renderHeader('ready', 'compact');
|
|
55
|
+
await this.migrateAssetsIfNeeded();
|
|
56
|
+
const client = await this.getClient();
|
|
57
|
+
const projectId = await this.getProjectIdOrExit();
|
|
58
|
+
const isRecursive = resolveRecursive(flags.recursive, this.localConfig);
|
|
59
|
+
const spinnerMsg = isRecursive ? `Pulling feature ${flags.id} tree...` : `Pulling feature ${flags.id}...`;
|
|
60
|
+
const roadmapFilter = resolveRoadmapFilter(flags.roadmap, this.localConfig);
|
|
61
|
+
const response = await this.withSpinner(spinnerMsg, () => {
|
|
62
|
+
if (isRecursive) {
|
|
63
|
+
return getFeatureTree({
|
|
64
|
+
client,
|
|
65
|
+
path: {
|
|
66
|
+
projectId,
|
|
67
|
+
featureId: flags.id,
|
|
68
|
+
},
|
|
69
|
+
query: {
|
|
70
|
+
...(roadmapFilter ? { roadmap: roadmapFilter } : {}),
|
|
71
|
+
...(flags.status ? { status: flags.status } : {}),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
return getFeatureDetail({
|
|
77
|
+
client,
|
|
78
|
+
path: {
|
|
79
|
+
projectId,
|
|
80
|
+
featureId: flags.id,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
const { data, error } = response;
|
|
86
|
+
try {
|
|
87
|
+
let feature;
|
|
88
|
+
let metadata;
|
|
89
|
+
if (isRecursive) {
|
|
90
|
+
const validated = validateResponse(data, error, zGetFeatureTreeResponse);
|
|
91
|
+
feature = validated.data;
|
|
92
|
+
metadata = validated.metadata;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const validated = validateResponse(data, error, zGetFeatureDetailResponse);
|
|
96
|
+
feature = validated.data;
|
|
97
|
+
metadata = validated.metadata;
|
|
98
|
+
}
|
|
99
|
+
const savePath = path.join(getCollectionDir('features'), `${flags.id}.json`);
|
|
100
|
+
if (this.isJsonMode) {
|
|
101
|
+
this.outputJson(feature);
|
|
102
|
+
if (flags.save) {
|
|
103
|
+
await this.saveToFile(savePath, feature);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (flags.stdout) {
|
|
107
|
+
this.log(JSON.stringify(feature, null, 2));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
await this.saveToFile(savePath, feature);
|
|
111
|
+
await this.renderInk(_jsx(SuccessMessage, { message: `Feature saved to .4ge/assets/features/${flags.id}.json` }));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
this.handleError(`Failed to pull feature: ${flags.id}`, err);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async saveToFile(filePath, data) {
|
|
119
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
120
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
import { getFeatureDetail } from '../../generated/api/sdk.gen.js';
|
|
4
|
+
import { zGetFeatureDetailResponse } from '../../generated/api/zod.gen.js';
|
|
5
|
+
import { validateResponse } from '../../core/api/index.js';
|
|
6
|
+
export default class FeatureShow extends BaseCommand {
|
|
7
|
+
static description = 'Show detailed information for a specific feature';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> feature:show --id <uuid>',
|
|
10
|
+
'<%= config.bin %> feature:show --id <uuid> --json',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
...BaseCommand.baseFlags,
|
|
14
|
+
id: Flags.string({
|
|
15
|
+
description: 'Feature ID',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(FeatureShow);
|
|
21
|
+
const client = await this.getClient();
|
|
22
|
+
const projectId = await this.getProjectIdOrExit();
|
|
23
|
+
await this.renderHeader('ready', 'compact');
|
|
24
|
+
const response = await this.withSpinner(`Fetching feature ${flags.id}...`, () => getFeatureDetail({
|
|
25
|
+
client,
|
|
26
|
+
path: {
|
|
27
|
+
projectId,
|
|
28
|
+
featureId: flags.id
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
const { data, error } = response;
|
|
32
|
+
try {
|
|
33
|
+
const validated = validateResponse(data, error, zGetFeatureDetailResponse);
|
|
34
|
+
const feature = validated.data;
|
|
35
|
+
const metadata = validated.metadata;
|
|
36
|
+
if (this.isJsonMode) {
|
|
37
|
+
this.outputJson(feature, { source: 'feature show' });
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.log(`\nFeature: ${feature.label}`);
|
|
41
|
+
this.log(`ID: ${feature.id}`);
|
|
42
|
+
this.log(`Status: ${feature.status}`);
|
|
43
|
+
this.log(`Roadmap: ${feature.roadmap_status || 'N/A'}`);
|
|
44
|
+
this.log(`Priority: ${feature.priority || 'N/A'}`);
|
|
45
|
+
if (feature.parent_id) {
|
|
46
|
+
this.log(`Parent ID: ${feature.parent_id}`);
|
|
47
|
+
}
|
|
48
|
+
if (feature.description) {
|
|
49
|
+
this.log(`\nDescription:\n${feature.description}\n`);
|
|
50
|
+
}
|
|
51
|
+
if (this.isInteractiveMode && metadata?.action_hints) {
|
|
52
|
+
await this.handleActionHints(metadata.action_hints);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
this.handleError(`Failed to fetch feature detail for ID: ${flags.id}`, err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base.js';
|
|
4
|
+
import { updateFeature } from '../../generated/api/sdk.gen.js';
|
|
5
|
+
import { zUpdateFeatureResponse } from '../../generated/api/zod.gen.js';
|
|
6
|
+
import { validateResponse } from '../../core/api/index.js';
|
|
7
|
+
import { SuccessMessage } from '../../ui/index.js';
|
|
8
|
+
export default class FeatureUpdate extends BaseCommand {
|
|
9
|
+
static description = 'Update a feature';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> feature:update --id <uuid> --status IN_DEVELOPMENT',
|
|
12
|
+
'<%= config.bin %> feature:update --id <uuid> --roadmap next --priority high',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
...BaseCommand.baseFlags,
|
|
16
|
+
id: Flags.string({
|
|
17
|
+
description: 'Feature ID (UUID)',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
status: Flags.string({
|
|
21
|
+
description: 'New status (BACKLOG|NOT_STARTED|IN_PLANNING|READY_FOR_DEVELOPMENT|IN_DEVELOPMENT|BLOCKED|REVIEW|COMPLETE)',
|
|
22
|
+
options: ['BACKLOG', 'NOT_STARTED', 'IN_PLANNING', 'READY_FOR_DEVELOPMENT', 'IN_DEVELOPMENT', 'BLOCKED', 'REVIEW', 'COMPLETE'],
|
|
23
|
+
}),
|
|
24
|
+
roadmap: Flags.string({
|
|
25
|
+
description: 'New roadmap status (now, next, later)',
|
|
26
|
+
options: ['now', 'next', 'later'],
|
|
27
|
+
}),
|
|
28
|
+
priority: Flags.string({
|
|
29
|
+
description: 'New priority',
|
|
30
|
+
}),
|
|
31
|
+
title: Flags.string({
|
|
32
|
+
description: 'New title',
|
|
33
|
+
}),
|
|
34
|
+
description: Flags.string({
|
|
35
|
+
description: 'New description',
|
|
36
|
+
}),
|
|
37
|
+
force: Flags.boolean({
|
|
38
|
+
description: 'Skip confirmation prompt',
|
|
39
|
+
default: false,
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const { flags } = await this.parse(FeatureUpdate);
|
|
44
|
+
const projectId = await this.getProjectIdOrExit();
|
|
45
|
+
await this.renderHeader('ready', 'compact');
|
|
46
|
+
const body = {};
|
|
47
|
+
if (flags.status !== undefined)
|
|
48
|
+
body.status = flags.status;
|
|
49
|
+
if (flags.roadmap !== undefined)
|
|
50
|
+
body.roadmap_status = flags.roadmap;
|
|
51
|
+
if (flags.priority !== undefined)
|
|
52
|
+
body.priority = flags.priority;
|
|
53
|
+
if (flags.title !== undefined)
|
|
54
|
+
body.label = flags.title;
|
|
55
|
+
if (flags.description !== undefined)
|
|
56
|
+
body.description = flags.description;
|
|
57
|
+
if (Object.keys(body).length === 0) {
|
|
58
|
+
const message = 'At least one field must be provided to update';
|
|
59
|
+
if (this.isJsonMode) {
|
|
60
|
+
this.outputErrorAndExit({ message, code: 'MISSING_INPUT' });
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.error(message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const confirmed = await this.confirmAction(`Update feature ${flags.id}?`, flags.force, { headerVariant: 'compact' });
|
|
67
|
+
if (!confirmed)
|
|
68
|
+
return;
|
|
69
|
+
const payload = {
|
|
70
|
+
path: {
|
|
71
|
+
projectId,
|
|
72
|
+
featureId: flags.id,
|
|
73
|
+
},
|
|
74
|
+
body,
|
|
75
|
+
};
|
|
76
|
+
if (this.isDryRun) {
|
|
77
|
+
await this.handleDryRun(payload);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const client = await this.getClient();
|
|
81
|
+
const { data, error } = await this.withSpinner(`Updating feature ${flags.id}...`, () => updateFeature({
|
|
82
|
+
client,
|
|
83
|
+
...payload,
|
|
84
|
+
}));
|
|
85
|
+
try {
|
|
86
|
+
const validated = validateResponse(data, error, zUpdateFeatureResponse);
|
|
87
|
+
const feature = validated.data;
|
|
88
|
+
const metadata = validated.metadata;
|
|
89
|
+
if (this.isJsonMode) {
|
|
90
|
+
this.outputJson(feature, { source: 'feature update', ...metadata });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
await this.renderInk(_jsx(SuccessMessage, { message: `Feature ${flags.id} updated successfully!` }));
|
|
94
|
+
if (this.isInteractiveMode && metadata?.action_hints) {
|
|
95
|
+
await this.handleActionHints(metadata.action_hints);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
this.handleError(`Failed to update feature: ${flags.id}`, err);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base.js';
|
|
4
|
+
import { readConfig } from '../../core/config/index.js';
|
|
5
|
+
import { createIdea } from '../../generated/api/sdk.gen.js';
|
|
6
|
+
import { zCreateIdeaResponse } from '../../generated/api/zod.gen.js';
|
|
7
|
+
import { validateResponse } from '../../core/api/index.js';
|
|
8
|
+
import { SuccessMessage } from '../../ui/index.js';
|
|
9
|
+
export default class IdeaCreate extends BaseCommand {
|
|
10
|
+
static description = 'Create a new idea in a workspace';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> idea:create --workspace <id> --title "My Great Idea"',
|
|
13
|
+
'<%= config.bin %> idea:create --workspace <id> --title "My Great Idea" --description "Detailed description"',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
...BaseCommand.baseFlags,
|
|
17
|
+
force: Flags.boolean({ description: 'Skip confirmation prompt', default: false }),
|
|
18
|
+
workspace: Flags.string({
|
|
19
|
+
description: 'Workspace ID',
|
|
20
|
+
char: 'w',
|
|
21
|
+
}),
|
|
22
|
+
title: Flags.string({
|
|
23
|
+
description: 'Idea title',
|
|
24
|
+
required: true,
|
|
25
|
+
char: 't',
|
|
26
|
+
}),
|
|
27
|
+
description: Flags.string({
|
|
28
|
+
description: 'Idea description',
|
|
29
|
+
char: 'd',
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { flags } = await this.parse(IdeaCreate);
|
|
34
|
+
const config = await readConfig();
|
|
35
|
+
const workspaceId = flags.workspace || config?.workspace_id;
|
|
36
|
+
if (!workspaceId) {
|
|
37
|
+
this.handleError('Workspace ID is required. Use --workspace or run in an initialized project.', { code: 'MISSING_WORKSPACE' });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await this.renderHeader('ready', 'compact');
|
|
41
|
+
const confirmed = await this.confirmAction(`Create idea "${flags.title}" in workspace ${workspaceId}?`, flags.force, { headerVariant: 'compact' });
|
|
42
|
+
if (!confirmed) {
|
|
43
|
+
this.log('Operation cancelled.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const payload = {
|
|
47
|
+
path: {
|
|
48
|
+
workspaceId,
|
|
49
|
+
},
|
|
50
|
+
body: {
|
|
51
|
+
title: flags.title,
|
|
52
|
+
description: flags.description,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
if (this.isDryRun) {
|
|
56
|
+
await this.handleDryRun(payload);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const client = await this.getClient();
|
|
60
|
+
try {
|
|
61
|
+
const { data, error } = await this.withSpinner('Creating idea...', () => createIdea({
|
|
62
|
+
client,
|
|
63
|
+
...payload,
|
|
64
|
+
}));
|
|
65
|
+
const validated = validateResponse(data, error, zCreateIdeaResponse);
|
|
66
|
+
const result = validated.data;
|
|
67
|
+
const metadata = validated.metadata;
|
|
68
|
+
if (this.isJsonMode) {
|
|
69
|
+
this.outputJson(result, { source: 'idea:create', ...metadata });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
await this.renderInk(_jsx(SuccessMessage, { message: `Idea created successfully with ID: ${result.id}` }));
|
|
73
|
+
if (this.isInteractiveMode) {
|
|
74
|
+
const { default: chalk } = await import('chalk');
|
|
75
|
+
this.log(`\n 🚀 Next steps:`);
|
|
76
|
+
this.log(` - Show details: ${chalk.cyan(`4ge idea:show ${result.id} --workspace ${workspaceId}`)}`);
|
|
77
|
+
this.log(` - Update: ${chalk.cyan(`4ge idea:update ${result.id} --workspace ${workspaceId}`)}`);
|
|
78
|
+
if (metadata?.action_hints) {
|
|
79
|
+
await this.handleActionHints(metadata.action_hints);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
this.handleError('Failed to create idea', err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base.js';
|
|
4
|
+
import { readConfig } from '../../core/config/index.js';
|
|
5
|
+
import { graduateIdea } from '../../generated/api/sdk.gen.js';
|
|
6
|
+
import { zGraduateIdeaResponse } from '../../generated/api/zod.gen.js';
|
|
7
|
+
import { validateResponse } from '../../core/api/index.js';
|
|
8
|
+
import { SuccessMessage } from '../../ui/index.js';
|
|
9
|
+
export default class IdeaGraduate extends BaseCommand {
|
|
10
|
+
static description = 'Graduate an idea into a full project';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> idea:graduate <id> --workspace <workspaceId>',
|
|
13
|
+
'<%= config.bin %> idea:graduate <id> --workspace <workspaceId> --name "New Project Name"',
|
|
14
|
+
];
|
|
15
|
+
static args = {
|
|
16
|
+
id: Args.string({
|
|
17
|
+
description: 'Idea ID',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
...BaseCommand.baseFlags,
|
|
23
|
+
force: Flags.boolean({ description: 'Skip confirmation prompt', default: false }),
|
|
24
|
+
workspace: Flags.string({
|
|
25
|
+
description: 'Workspace ID',
|
|
26
|
+
char: 'w',
|
|
27
|
+
}),
|
|
28
|
+
name: Flags.string({
|
|
29
|
+
description: 'Project name (defaults to idea title if not provided)',
|
|
30
|
+
char: 'n',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
async run() {
|
|
34
|
+
const { args, flags } = await this.parse(IdeaGraduate);
|
|
35
|
+
const config = await readConfig();
|
|
36
|
+
const workspaceId = flags.workspace || config?.workspace_id;
|
|
37
|
+
if (!workspaceId) {
|
|
38
|
+
this.handleError('Workspace ID is required. Use --workspace or run in an initialized project.', { code: 'MISSING_WORKSPACE' });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await this.renderHeader('ready', 'compact');
|
|
42
|
+
const confirmed = await this.confirmAction(`Graduate idea ${args.id} into a new project? This is a significant action.`, flags.force, { headerVariant: 'compact' });
|
|
43
|
+
if (!confirmed) {
|
|
44
|
+
this.log('Operation cancelled.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const payload = {
|
|
48
|
+
path: {
|
|
49
|
+
workspaceId,
|
|
50
|
+
ideaId: args.id,
|
|
51
|
+
},
|
|
52
|
+
body: {
|
|
53
|
+
project_name: flags.name,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
if (this.isDryRun) {
|
|
57
|
+
await this.handleDryRun(payload);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const client = await this.getClient();
|
|
61
|
+
try {
|
|
62
|
+
const { data, error } = await this.withSpinner('Graduating idea...', () => graduateIdea({
|
|
63
|
+
client,
|
|
64
|
+
...payload,
|
|
65
|
+
}));
|
|
66
|
+
const validated = validateResponse(data, error, zGraduateIdeaResponse);
|
|
67
|
+
const result = validated.data;
|
|
68
|
+
const metadata = validated.metadata;
|
|
69
|
+
if (this.isJsonMode) {
|
|
70
|
+
this.outputJson(result, { source: 'idea:graduate', ...metadata });
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
await this.renderInk(_jsx(SuccessMessage, { message: `Idea graduated successfully! New project created: ${result.name} (${result.id})` }));
|
|
74
|
+
if (this.isInteractiveMode) {
|
|
75
|
+
const { default: chalk } = await import('chalk');
|
|
76
|
+
this.log(`\n 🚀 Next steps:`);
|
|
77
|
+
this.log(` - Project info: ${chalk.cyan(`4ge project info --id ${result.id}`)}`);
|
|
78
|
+
this.log(` - Pull plan: ${chalk.cyan(`4ge plan pull --project ${result.id}`)}`);
|
|
79
|
+
if (metadata?.action_hints) {
|
|
80
|
+
await this.handleActionHints(metadata.action_hints);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
this.handleError(`Failed to graduate idea: ${args.id}`, err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base.js';
|
|
4
|
+
import { readConfig } from '../../core/config/index.js';
|
|
5
|
+
import { listIdeas } from '../../generated/api/sdk.gen.js';
|
|
6
|
+
import { zListIdeasResponse } from '../../generated/api/zod.gen.js';
|
|
7
|
+
import { validateResponse } from '../../core/api/index.js';
|
|
8
|
+
import { DataTable } from '../../ui/index.js';
|
|
9
|
+
export default class IdeaList extends BaseCommand {
|
|
10
|
+
static description = 'List ideas in a workspace';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> idea:list --workspace <workspaceId>',
|
|
13
|
+
'<%= config.bin %> idea:list --workspace <workspaceId> --status backlog',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
...BaseCommand.baseFlags,
|
|
17
|
+
workspace: Flags.string({
|
|
18
|
+
description: 'Workspace ID',
|
|
19
|
+
char: 'w',
|
|
20
|
+
}),
|
|
21
|
+
status: Flags.string({
|
|
22
|
+
description: 'Filter by status',
|
|
23
|
+
char: 's',
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags } = await this.parse(IdeaList);
|
|
28
|
+
await this.renderHeader('ready', 'compact');
|
|
29
|
+
const client = await this.getClient();
|
|
30
|
+
const config = await readConfig();
|
|
31
|
+
const workspaceId = flags.workspace || config?.workspace_id;
|
|
32
|
+
if (!workspaceId) {
|
|
33
|
+
this.handleError('Workspace ID is required. Use --workspace or run in an initialized project.', { code: 'MISSING_WORKSPACE' });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const response = await this.withSpinner('Fetching ideas...', () => listIdeas({
|
|
38
|
+
client,
|
|
39
|
+
path: {
|
|
40
|
+
workspaceId,
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
const { data, error } = response;
|
|
44
|
+
const validated = validateResponse(data, error, zListIdeasResponse);
|
|
45
|
+
let items = validated.data;
|
|
46
|
+
const metadata = validated.metadata;
|
|
47
|
+
if (flags.status) {
|
|
48
|
+
items = items.filter((item) => item.status?.toLowerCase() === flags.status?.toLowerCase());
|
|
49
|
+
}
|
|
50
|
+
if (this.isJsonMode) {
|
|
51
|
+
this.outputJson(items, { source: 'idea:list' });
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
await this.renderInk(_jsx(DataTable, { data: items, columns: [
|
|
55
|
+
{ key: 'id', header: 'ID' },
|
|
56
|
+
{ key: 'title', header: 'Title', minWidth: 30 },
|
|
57
|
+
{ key: 'status', header: 'Status', color: 'auto' },
|
|
58
|
+
{ key: 'created_at', header: 'Created At' },
|
|
59
|
+
], title: `Ideas in workspace ${workspaceId}`, emptyMessage: flags.status ? `No ideas found with status: ${flags.status}` : 'No ideas found.' }));
|
|
60
|
+
if (this.isInteractiveMode) {
|
|
61
|
+
if (items.length > 0) {
|
|
62
|
+
const options = items.map((item) => ({
|
|
63
|
+
label: item.title || item.id,
|
|
64
|
+
value: item.id,
|
|
65
|
+
description: item.status || '',
|
|
66
|
+
}));
|
|
67
|
+
const selectedId = await this.selectOption('Select an idea for details:', options, { headerVariant: 'compact' });
|
|
68
|
+
if (selectedId) {
|
|
69
|
+
const { default: chalk } = await import('chalk');
|
|
70
|
+
this.log(`\n 🚀 Run: ${chalk.cyan(`4ge idea:show ${selectedId} --workspace ${workspaceId}`)}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (metadata?.action_hints) {
|
|
74
|
+
await this.handleActionHints(metadata.action_hints);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
this.handleError('An unexpected error occurred', err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base.js';
|
|
4
|
+
import { readConfig } from '../../core/config/index.js';
|
|
5
|
+
import { listIdeas } from '../../generated/api/sdk.gen.js';
|
|
6
|
+
import { zListIdeasResponse } from '../../generated/api/zod.gen.js';
|
|
7
|
+
import { validateResponse } from '../../core/api/index.js';
|
|
8
|
+
import { DetailView } from '../../ui/index.js';
|
|
9
|
+
export default class IdeaShow extends BaseCommand {
|
|
10
|
+
static description = 'Show detailed information for a specific idea';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> idea:show <id> --workspace <workspaceId>',
|
|
13
|
+
'<%= config.bin %> idea:show <id> --workspace <workspaceId> --json',
|
|
14
|
+
];
|
|
15
|
+
static args = {
|
|
16
|
+
id: Args.string({
|
|
17
|
+
description: 'Idea ID',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
...BaseCommand.baseFlags,
|
|
23
|
+
workspace: Flags.string({
|
|
24
|
+
description: 'Workspace ID',
|
|
25
|
+
char: 'w',
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
const { args, flags } = await this.parse(IdeaShow);
|
|
30
|
+
const client = await this.getClient();
|
|
31
|
+
const config = await readConfig();
|
|
32
|
+
const workspaceId = flags.workspace || config?.workspace_id;
|
|
33
|
+
if (!workspaceId) {
|
|
34
|
+
this.handleError('Workspace ID is required. Use --workspace or run in an initialized project.', { code: 'MISSING_WORKSPACE' });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await this.renderHeader('ready', 'compact');
|
|
38
|
+
try {
|
|
39
|
+
// NOTE: the SDK does not yet expose a direct getIdeaDetail endpoint.
|
|
40
|
+
// Using listIdeas + client-side filter as a workaround. The response is
|
|
41
|
+
// still validated against zListIdeasResponse so the envelope contract is
|
|
42
|
+
// enforced at the boundary.
|
|
43
|
+
const { data, error } = await this.withSpinner(`Fetching idea ${args.id}...`, () => listIdeas({
|
|
44
|
+
client,
|
|
45
|
+
path: { workspaceId },
|
|
46
|
+
}));
|
|
47
|
+
const validated = validateResponse(data, error, zListIdeasResponse);
|
|
48
|
+
const items = validated.data;
|
|
49
|
+
const metadata = validated.metadata;
|
|
50
|
+
const payload = items.find((item) => item.id === args.id);
|
|
51
|
+
if (!payload) {
|
|
52
|
+
this.handleError(`Idea not found: ${args.id}`, { code: 'NOT_FOUND' });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (this.isJsonMode) {
|
|
56
|
+
this.outputJson(payload, { source: 'idea:show' });
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
await this.renderInk(_jsx(DetailView, { title: `Idea: ${payload.title}`, rows: [
|
|
60
|
+
{ label: 'ID', value: payload.id },
|
|
61
|
+
{ label: 'Status', value: payload.status, color: 'auto' },
|
|
62
|
+
{ label: 'Workspace', value: payload.workspace_id },
|
|
63
|
+
{ label: 'Project ID', value: payload.converted_project_id },
|
|
64
|
+
{ label: 'Created At', value: payload.created_at },
|
|
65
|
+
{ label: 'Updated At', value: payload.updated_at },
|
|
66
|
+
{ label: 'Description', value: payload.description },
|
|
67
|
+
] }));
|
|
68
|
+
if (this.isInteractiveMode) {
|
|
69
|
+
if (metadata?.action_hints) {
|
|
70
|
+
await this.handleActionHints(metadata.action_hints);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Provide some default hints if metadata doesn't have them
|
|
74
|
+
const { default: chalk } = await import('chalk');
|
|
75
|
+
this.log(`\n 🚀 Actions:`);
|
|
76
|
+
this.log(` - Update: ${chalk.cyan(`4ge idea:update ${payload.id} --workspace ${workspaceId}`)}`);
|
|
77
|
+
this.log(` - Graduate: ${chalk.cyan(`4ge idea:graduate ${payload.id} --workspace ${workspaceId}`)}\n`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
this.handleError(`Failed to fetch idea ${args.id}`, err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|