@hyperdrive.bot/cli 1.0.7 → 1.0.8
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/README.md +298 -56
- package/dist/commands/account/list.d.ts +3 -0
- package/dist/commands/account/list.js +9 -2
- package/dist/commands/git/connect.js +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +20 -19
- package/dist/commands/jira/connect.d.ts +1 -0
- package/dist/commands/jira/connect.js +17 -6
- package/dist/commands/jira/hook/add.d.ts +17 -0
- package/dist/commands/jira/hook/add.js +147 -0
- package/dist/commands/jira/hook/list.d.ts +14 -0
- package/dist/commands/jira/hook/list.js +105 -0
- package/dist/commands/jira/hook/remove.d.ts +15 -0
- package/dist/commands/jira/hook/remove.js +119 -0
- package/dist/commands/jira/hook/toggle.d.ts +15 -0
- package/dist/commands/jira/hook/toggle.js +136 -0
- package/dist/commands/project/init.d.ts +21 -0
- package/dist/commands/project/init.js +576 -0
- package/dist/commands/project/list.d.ts +10 -0
- package/dist/commands/project/list.js +119 -0
- package/dist/commands/project/status.d.ts +13 -0
- package/dist/commands/project/status.js +163 -0
- package/dist/commands/project/sync.d.ts +26 -0
- package/dist/commands/project/sync.js +388 -0
- package/dist/services/hyperdrive-sigv4.d.ts +125 -0
- package/dist/services/hyperdrive-sigv4.js +45 -0
- package/dist/utils/account-flow.d.ts +2 -2
- package/dist/utils/account-flow.js +4 -4
- package/dist/utils/git-flow.d.ts +1 -0
- package/dist/utils/git-flow.js +2 -2
- package/dist/utils/hook-flow.d.ts +21 -0
- package/dist/utils/hook-flow.js +154 -0
- package/dist/utils/jira-flow.d.ts +2 -2
- package/dist/utils/jira-flow.js +4 -4
- package/oclif.manifest.json +590 -128
- package/package.json +5 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
5
|
+
import { printTable } from '../../utils/table.js';
|
|
6
|
+
export default class ProjectList extends Command {
|
|
7
|
+
static description = 'List all projects with Jira configuration status';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %>',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --domain custom.hyperdrive.bot',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
domain: Flags.string({
|
|
15
|
+
char: 'd',
|
|
16
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
17
|
+
}),
|
|
18
|
+
json: Flags.boolean({
|
|
19
|
+
description: 'Output as JSON',
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { flags } = await this.parse(ProjectList);
|
|
24
|
+
let service;
|
|
25
|
+
const spinner = ora('Checking authentication...').start();
|
|
26
|
+
try {
|
|
27
|
+
service = new HyperdriveSigV4Service(flags.domain);
|
|
28
|
+
spinner.succeed('Authenticated');
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
spinner.fail('Not authenticated');
|
|
32
|
+
this.error(`${error.message}\n\nPlease authenticate first with: ${chalk.cyan('hd auth login')}`);
|
|
33
|
+
}
|
|
34
|
+
spinner.start('Fetching projects...');
|
|
35
|
+
try {
|
|
36
|
+
const modules = await service.moduleList();
|
|
37
|
+
if (!modules || modules.length === 0) {
|
|
38
|
+
spinner.stop();
|
|
39
|
+
this.log(chalk.yellow('No projects found.'));
|
|
40
|
+
this.log(chalk.dim(`Run ${chalk.cyan('hd project init')} to create your first project.`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Enrich each project with Jira config and repos count
|
|
44
|
+
const enrichedProjects = await Promise.all(modules.map(async (mod) => {
|
|
45
|
+
let jiraKey = '—';
|
|
46
|
+
let repoCount = 0;
|
|
47
|
+
let lastSynced = '—';
|
|
48
|
+
if (mod.projectId) {
|
|
49
|
+
try {
|
|
50
|
+
const jiraConfig = await service.projectGetJiraConfig(mod.projectId);
|
|
51
|
+
jiraKey = jiraConfig.jiraProjectKey;
|
|
52
|
+
lastSynced = jiraConfig.updatedAt
|
|
53
|
+
? new Date(jiraConfig.updatedAt).toLocaleDateString()
|
|
54
|
+
: '—';
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// 404 = not linked, silently ignore
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const repos = await service.projectListRepos(mod.projectId);
|
|
61
|
+
repoCount = repos.length;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// silently ignore
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...mod,
|
|
69
|
+
hookCount: 0,
|
|
70
|
+
jiraKey,
|
|
71
|
+
lastSynced,
|
|
72
|
+
repoCount,
|
|
73
|
+
};
|
|
74
|
+
}));
|
|
75
|
+
spinner.succeed(`Found ${enrichedProjects.length} project(s)`);
|
|
76
|
+
if (flags.json) {
|
|
77
|
+
this.log(JSON.stringify(enrichedProjects, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.log('');
|
|
81
|
+
printTable(enrichedProjects, {
|
|
82
|
+
name: { header: 'Name', minWidth: 20 },
|
|
83
|
+
slug: {
|
|
84
|
+
get: (row) => chalk.cyan(row.slug),
|
|
85
|
+
header: 'Slug',
|
|
86
|
+
minWidth: 15,
|
|
87
|
+
},
|
|
88
|
+
jiraKey: { header: 'Jira Key' },
|
|
89
|
+
repoCount: {
|
|
90
|
+
get: (row) => String(row.repoCount),
|
|
91
|
+
header: 'Repos',
|
|
92
|
+
},
|
|
93
|
+
hookCount: {
|
|
94
|
+
get: (row) => String(row.hookCount),
|
|
95
|
+
header: 'Hooks',
|
|
96
|
+
},
|
|
97
|
+
lastSynced: { header: 'Last Synced' },
|
|
98
|
+
}, (msg) => this.log(msg));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
spinner.fail('Failed to fetch projects');
|
|
102
|
+
let errorMessage = error.message;
|
|
103
|
+
if (error.response) {
|
|
104
|
+
const status = error.response.status;
|
|
105
|
+
const data = error.response.data;
|
|
106
|
+
if (status === 401 || status === 403) {
|
|
107
|
+
errorMessage = `Authentication failed. Please run ${chalk.cyan('hd auth login')}`;
|
|
108
|
+
}
|
|
109
|
+
else if (data?.error) {
|
|
110
|
+
errorMessage = data.error;
|
|
111
|
+
}
|
|
112
|
+
else if (data?.message) {
|
|
113
|
+
errorMessage = data.message;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
this.error(errorMessage);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ProjectStatus extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
project: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
5
|
+
import { printTable } from '../../utils/table.js';
|
|
6
|
+
const STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
7
|
+
export default class ProjectStatus extends Command {
|
|
8
|
+
static args = {
|
|
9
|
+
project: Args.string({
|
|
10
|
+
description: 'Project slug',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Show detailed status of a project including Jira config, repos, and hooks';
|
|
15
|
+
static examples = [
|
|
16
|
+
'<%= config.bin %> <%= command.id %> my-project',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> my-project --json',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> my-project --domain custom.hyperdrive.bot',
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
domain: Flags.string({
|
|
22
|
+
char: 'd',
|
|
23
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
24
|
+
}),
|
|
25
|
+
json: Flags.boolean({
|
|
26
|
+
description: 'Output as JSON',
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const { args, flags } = await this.parse(ProjectStatus);
|
|
31
|
+
const slug = args.project;
|
|
32
|
+
let service;
|
|
33
|
+
const spinner = ora('Checking authentication...').start();
|
|
34
|
+
try {
|
|
35
|
+
service = new HyperdriveSigV4Service(flags.domain);
|
|
36
|
+
spinner.succeed('Authenticated');
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
spinner.fail('Not authenticated');
|
|
40
|
+
this.error(`${error.message}\n\nPlease authenticate first with: ${chalk.cyan('hd auth login')}`);
|
|
41
|
+
}
|
|
42
|
+
spinner.start(`Fetching project '${slug}'...`);
|
|
43
|
+
try {
|
|
44
|
+
// Find the project by slug
|
|
45
|
+
const modules = await service.moduleList();
|
|
46
|
+
const project = modules.find((m) => m.slug === slug);
|
|
47
|
+
if (!project || !project.projectId) {
|
|
48
|
+
spinner.fail('Project not found');
|
|
49
|
+
this.error(`Project '${slug}' not found. Run ${chalk.cyan('hd project list')} to see available projects.`);
|
|
50
|
+
}
|
|
51
|
+
const projectId = project.projectId;
|
|
52
|
+
// Fetch Jira config and repos in parallel
|
|
53
|
+
let jiraConfig = null;
|
|
54
|
+
let repos = [];
|
|
55
|
+
const [jiraResult, reposResult] = await Promise.allSettled([
|
|
56
|
+
service.projectGetJiraConfig(projectId),
|
|
57
|
+
service.projectListRepos(projectId),
|
|
58
|
+
]);
|
|
59
|
+
if (jiraResult.status === 'fulfilled') {
|
|
60
|
+
jiraConfig = jiraResult.value;
|
|
61
|
+
}
|
|
62
|
+
if (reposResult.status === 'fulfilled') {
|
|
63
|
+
repos = reposResult.value;
|
|
64
|
+
}
|
|
65
|
+
spinner.succeed('Project data loaded');
|
|
66
|
+
// JSON output
|
|
67
|
+
if (flags.json) {
|
|
68
|
+
this.log(JSON.stringify({ jiraConfig, project, repos }, null, 2));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Project metadata
|
|
72
|
+
this.log('');
|
|
73
|
+
this.log(chalk.bold('Project'));
|
|
74
|
+
this.log(` ${chalk.dim('Name:')} ${project.name}`);
|
|
75
|
+
this.log(` ${chalk.dim('Slug:')} ${chalk.cyan(project.slug)}`);
|
|
76
|
+
this.log(` ${chalk.dim('Created:')} ${project.createdAt ? new Date(project.createdAt).toLocaleDateString() : '—'}`);
|
|
77
|
+
this.log('');
|
|
78
|
+
// Jira config section
|
|
79
|
+
this.log(chalk.bold('Jira Configuration'));
|
|
80
|
+
if (jiraConfig) {
|
|
81
|
+
this.log(` ${chalk.dim('Jira Key:')} ${chalk.cyan(jiraConfig.jiraProjectKey)}`);
|
|
82
|
+
this.log(` ${chalk.dim('Linked:')} ${chalk.green('Yes')}`);
|
|
83
|
+
this.log('');
|
|
84
|
+
// Status mapping table
|
|
85
|
+
if (jiraConfig.statusMapping && Object.keys(jiraConfig.statusMapping).length > 0) {
|
|
86
|
+
this.log(chalk.bold('Status Mapping'));
|
|
87
|
+
const mappingData = Object.entries(jiraConfig.statusMapping).map(([jiraStatus, normalizedState]) => ({
|
|
88
|
+
jiraStatus,
|
|
89
|
+
normalizedState,
|
|
90
|
+
}));
|
|
91
|
+
printTable(mappingData, {
|
|
92
|
+
jiraStatus: { header: 'Jira Status', minWidth: 25 },
|
|
93
|
+
normalizedState: {
|
|
94
|
+
get: (row) => chalk.cyan(row.normalizedState),
|
|
95
|
+
header: 'Normalized State',
|
|
96
|
+
minWidth: 20,
|
|
97
|
+
},
|
|
98
|
+
}, (msg) => this.log(msg));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.log(chalk.yellow(` No Jira configuration. Run ${chalk.cyan(`hd project init`)} to set up.`));
|
|
103
|
+
}
|
|
104
|
+
this.log('');
|
|
105
|
+
// Repos section
|
|
106
|
+
this.log(chalk.bold('Repositories'));
|
|
107
|
+
if (repos.length > 0) {
|
|
108
|
+
for (const repo of repos) {
|
|
109
|
+
this.log(` ${chalk.cyan(repo.name)}`);
|
|
110
|
+
this.log(` ${chalk.dim('Remote:')} ${chalk.cyan(repo.gitRemote)}`);
|
|
111
|
+
this.log(` ${chalk.dim('Provider:')} ${repo.provider}`);
|
|
112
|
+
this.log(` ${chalk.dim('Branch:')} ${repo.defaultBranch}`);
|
|
113
|
+
// Architecture summary age
|
|
114
|
+
if (!repo.architectureSummary) {
|
|
115
|
+
this.log(chalk.yellow(` Architecture summary is missing for ${repo.name}. Run ${chalk.cyan(`hd project sync ${slug}`)} to update.`));
|
|
116
|
+
}
|
|
117
|
+
else if (repo.architectureSummaryUpdatedAt) {
|
|
118
|
+
const age = Date.now() - new Date(repo.architectureSummaryUpdatedAt).getTime();
|
|
119
|
+
if (age > STALE_THRESHOLD_MS) {
|
|
120
|
+
const days = Math.floor(age / (24 * 60 * 60 * 1000));
|
|
121
|
+
this.log(` ${chalk.dim('Summary Age:')} ${chalk.yellow(`${days} days`)}`);
|
|
122
|
+
this.log(chalk.yellow(` Architecture summary is stale for ${repo.name}. Run ${chalk.cyan(`hd project sync ${slug}`)} to update.`));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const days = Math.floor(age / (24 * 60 * 60 * 1000));
|
|
126
|
+
this.log(` ${chalk.dim('Summary Age:')} ${chalk.green(`${days} day${days === 1 ? '' : 's'}`)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.log('');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
this.log(chalk.dim(' No repositories linked.'));
|
|
134
|
+
this.log('');
|
|
135
|
+
}
|
|
136
|
+
// Hooks section
|
|
137
|
+
this.log(chalk.bold('Hooks'));
|
|
138
|
+
this.log(chalk.dim(' No hooks configured (coming in a future release)'));
|
|
139
|
+
this.log('');
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
spinner.fail('Failed to fetch project status');
|
|
143
|
+
let errorMessage = error.message;
|
|
144
|
+
if (error.response) {
|
|
145
|
+
const status = error.response.status;
|
|
146
|
+
const data = error.response.data;
|
|
147
|
+
if (status === 401 || status === 403) {
|
|
148
|
+
errorMessage = `Authentication failed. Please run ${chalk.cyan('hd auth login')}`;
|
|
149
|
+
}
|
|
150
|
+
else if (status === 404) {
|
|
151
|
+
errorMessage = `Project '${slug}' not found. Run ${chalk.cyan('hd project list')} to see available projects.`;
|
|
152
|
+
}
|
|
153
|
+
else if (data?.error) {
|
|
154
|
+
errorMessage = data.error;
|
|
155
|
+
}
|
|
156
|
+
else if (data?.message) {
|
|
157
|
+
errorMessage = data.message;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.error(errorMessage);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Sync Command
|
|
3
|
+
*
|
|
4
|
+
* Generates structured architecture summaries for repos in a project.
|
|
5
|
+
* For each repo: clone/pull → detect _bmad or invoke Claude → validate YAML → upload S3 → update DynamoDB.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from '@oclif/core';
|
|
8
|
+
export default class ProjectSync extends Command {
|
|
9
|
+
static args: {
|
|
10
|
+
project: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
+
};
|
|
12
|
+
static description: string;
|
|
13
|
+
static examples: string[];
|
|
14
|
+
static flags: {
|
|
15
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
repo: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
};
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
private syncRepo;
|
|
21
|
+
private detectBmadDocs;
|
|
22
|
+
private runClaude;
|
|
23
|
+
private validateYaml;
|
|
24
|
+
private readGutEntities;
|
|
25
|
+
private mergeEntityRegistries;
|
|
26
|
+
}
|