@forgehive/forge-cli 0.3.11 → 0.3.12

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/dist/runner.js CHANGED
@@ -29,9 +29,11 @@ const add_1 = require("./tasks/auth/add");
29
29
  const switch_1 = require("./tasks/auth/switch");
30
30
  const list_2 = require("./tasks/auth/list");
31
31
  const remove_3 = require("./tasks/auth/remove");
32
+ const clear_1 = require("./tasks/auth/clear");
32
33
  const create_2 = require("./tasks/project/create");
33
34
  const link_1 = require("./tasks/project/link");
34
35
  const unlink_1 = require("./tasks/project/unlink");
36
+ const sync_1 = require("./tasks/project/sync");
35
37
  const runner = new runner_1.Runner((data) => {
36
38
  const { _, ...filteredObj } = data;
37
39
  return {
@@ -67,10 +69,12 @@ runner.load('auth:add', add_1.add);
67
69
  runner.load('auth:switch', switch_1.switchProfile);
68
70
  runner.load('auth:list', list_2.list);
69
71
  runner.load('auth:remove', remove_3.remove);
72
+ runner.load('auth:clear', clear_1.clear);
70
73
  // Project commands
71
74
  runner.load('project:create', create_2.create);
72
75
  runner.load('project:link', link_1.link);
73
76
  runner.load('project:unlink', unlink_1.unlink);
77
+ runner.load('project:sync', sync_1.sync);
74
78
  // Set handler
75
79
  runner.setHandler(async (data) => {
76
80
  const parsedArgs = runner.parseArguments(data);
@@ -81,12 +85,14 @@ runner.setHandler(async (data) => {
81
85
  let silent = false;
82
86
  const task = runner.getTask(taskName);
83
87
  if (!task) {
84
- throw new Error(`Task "${taskName}" not found`);
88
+ throw new Error(`Forge command "${taskName}" not found`);
85
89
  }
86
90
  try {
87
91
  let result;
88
92
  const commandsWithDescriptor = ['task:create', 'task:remove', 'task:publish', 'task:describe', 'task:fingerprint'];
89
93
  const commandsWithRunner = ['runner:create', 'runner:remove'];
94
+ const commandsWithoutParams = ['project:unlink', 'project:sync', 'auth:clear'];
95
+ const silentCommands = ['task:describe', 'task:list', 'auth:list', 'info'];
90
96
  if (commandsWithDescriptor.includes(taskName)) {
91
97
  result = await task.run({ descriptorName: action });
92
98
  }
@@ -175,16 +181,13 @@ runner.setHandler(async (data) => {
175
181
  uuid
176
182
  });
177
183
  }
178
- else if (taskName === 'project:unlink') {
184
+ else if (commandsWithoutParams.includes(taskName)) {
179
185
  result = await task.run({});
180
186
  }
181
187
  else {
182
188
  result = await task.run(args);
183
- if (taskName === 'info') {
184
- silent = true;
185
- }
186
189
  }
187
- if (taskName === 'task:describe' || taskName === 'task:list' || taskName === 'auth:list') {
190
+ if (silentCommands.includes(taskName)) {
188
191
  silent = true;
189
192
  }
190
193
  return {
@@ -7,10 +7,26 @@ export declare const add: import("@forgehive/task").TaskInstanceType<(argv: {
7
7
  }, boundaries: import("@forgehive/task").WrappedBoundaries<{
8
8
  loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
9
9
  persistProfiles: (profiles: Profiles) => Promise<void>;
10
+ fetchMeInfo: (apiKey: string, apiSecret: string, url: string) => Promise<{
11
+ success: boolean;
12
+ teamName?: string;
13
+ teamUuid?: string;
14
+ userName?: string;
15
+ error?: string;
16
+ }>;
10
17
  }>) => Promise<{
11
18
  status: string;
12
19
  message: string;
20
+ teamName: string | undefined;
21
+ userName: string | undefined;
13
22
  }>, {
14
23
  loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
15
24
  persistProfiles: (profiles: Profiles) => Promise<void>;
25
+ fetchMeInfo: (apiKey: string, apiSecret: string, url: string) => Promise<{
26
+ success: boolean;
27
+ teamName?: string;
28
+ teamUuid?: string;
29
+ userName?: string;
30
+ error?: string;
31
+ }>;
16
32
  }>;
@@ -25,22 +25,72 @@ const boundaries = {
25
25
  const buildsPath = path_1.default.join(os_1.default.homedir(), '.forge');
26
26
  const profilesPath = path_1.default.join(buildsPath, 'profiles.json');
27
27
  await promises_1.default.writeFile(profilesPath, JSON.stringify(profiles, null, 2));
28
+ },
29
+ fetchMeInfo: async (apiKey, apiSecret, url) => {
30
+ try {
31
+ const response = await fetch(`${url}/api/me`, {
32
+ method: 'GET',
33
+ headers: {
34
+ 'Authorization': `Bearer ${apiKey}:${apiSecret}`,
35
+ 'Content-Type': 'application/json'
36
+ }
37
+ });
38
+ if (response.ok) {
39
+ const data = await response.json();
40
+ return {
41
+ success: true,
42
+ teamName: data.team?.name,
43
+ teamUuid: data.team?.uuid,
44
+ userName: data.user?.name
45
+ };
46
+ }
47
+ else {
48
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
49
+ return { success: false, error: errorData.error || `HTTP ${response.status}` };
50
+ }
51
+ }
52
+ catch (error) {
53
+ return { success: false, error: error instanceof Error ? error.message : 'Network error' };
54
+ }
28
55
  }
29
56
  };
30
57
  exports.add = (0, task_1.createTask)({
31
58
  schema,
32
59
  boundaries,
33
- fn: async function ({ name, apiKey, apiSecret, url }, { loadProfiles, persistProfiles }) {
60
+ fn: async function ({ name, apiKey, apiSecret, url }, { loadProfiles, persistProfiles, fetchMeInfo }) {
34
61
  const profiles = await loadProfiles({});
62
+ console.log('Verifying credentials...');
63
+ // Fetch team and user information from /me endpoint
64
+ const meInfo = await fetchMeInfo(apiKey, apiSecret, url);
65
+ if (!meInfo.success) {
66
+ throw new Error(`Failed to verify credentials: ${meInfo.error}`);
67
+ }
68
+ console.log('✅ Credentials verified');
69
+ if (meInfo.userName) {
70
+ console.log(` User: ${meInfo.userName}`);
71
+ }
72
+ if (meInfo.teamName) {
73
+ console.log(` Team: ${meInfo.teamName}`);
74
+ }
75
+ // Create profile with team information
76
+ const profile = {
77
+ name,
78
+ apiKey,
79
+ apiSecret,
80
+ url,
81
+ teamName: meInfo.teamName,
82
+ teamUuid: meInfo.teamUuid,
83
+ userName: meInfo.userName
84
+ };
35
85
  // Check if profile with same name already exists
36
86
  const existingProfileIndex = profiles.profiles.findIndex(p => p.name === name);
37
87
  if (existingProfileIndex >= 0) {
38
88
  // Replace existing profile
39
- profiles.profiles[existingProfileIndex] = { name, apiKey, apiSecret, url };
89
+ profiles.profiles[existingProfileIndex] = profile;
40
90
  }
41
91
  else {
42
92
  // Add new profile
43
- profiles.profiles.push({ name, apiKey, apiSecret, url });
93
+ profiles.profiles.push(profile);
44
94
  }
45
95
  // Set as default profile
46
96
  profiles.default = name;
@@ -48,7 +98,9 @@ exports.add = (0, task_1.createTask)({
48
98
  await persistProfiles(profiles);
49
99
  return {
50
100
  status: 'Ok',
51
- message: `Profile '${name}' added and set as default`
101
+ message: `Profile '${name}' added and set as default`,
102
+ teamName: meInfo.teamName,
103
+ userName: meInfo.userName
52
104
  };
53
105
  }
54
106
  });
@@ -0,0 +1,16 @@
1
+ import { type Profiles } from '../types';
2
+ export declare const clear: import("@forgehive/task").TaskInstanceType<(argv: {}, boundaries: import("@forgehive/task").WrappedBoundaries<{
3
+ loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
4
+ clearProfiles: () => Promise<void>;
5
+ }>) => Promise<{
6
+ status: string;
7
+ message: string;
8
+ clearedCount?: undefined;
9
+ } | {
10
+ status: string;
11
+ message: string;
12
+ clearedCount: number;
13
+ }>, {
14
+ loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
15
+ clearProfiles: () => Promise<void>;
16
+ }>;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ // TASK: clear
3
+ // Run this task with:
4
+ // forge task:run auth:clear
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.clear = void 0;
10
+ const task_1 = require("@forgehive/task");
11
+ const schema_1 = require("@forgehive/schema");
12
+ const path_1 = __importDefault(require("path"));
13
+ const promises_1 = __importDefault(require("fs/promises"));
14
+ const os_1 = __importDefault(require("os"));
15
+ const load_1 = require("./load");
16
+ const schema = new schema_1.Schema({});
17
+ const boundaries = {
18
+ loadProfiles: load_1.load.asBoundary(),
19
+ clearProfiles: async () => {
20
+ const buildsPath = path_1.default.join(os_1.default.homedir(), '.forge');
21
+ const profilesPath = path_1.default.join(buildsPath, 'profiles.json');
22
+ // Create empty profiles structure
23
+ const emptyProfiles = {
24
+ default: '',
25
+ profiles: []
26
+ };
27
+ await promises_1.default.writeFile(profilesPath, JSON.stringify(emptyProfiles, null, 2));
28
+ }
29
+ };
30
+ exports.clear = (0, task_1.createTask)({
31
+ schema,
32
+ boundaries,
33
+ fn: async function (_argv, { loadProfiles, clearProfiles }) {
34
+ const profiles = await loadProfiles({});
35
+ if (profiles.profiles.length === 0) {
36
+ console.log('No profiles found to clear.');
37
+ return { status: 'Ok', message: 'No profiles found' };
38
+ }
39
+ const profileCount = profiles.profiles.length;
40
+ console.log(`Found ${profileCount} profile(s) to clear:`);
41
+ profiles.profiles.forEach(profile => {
42
+ console.log(` - ${profile.name} (${profile.teamName || 'Unknown team'})`);
43
+ });
44
+ console.log('\\nClearing all profiles...');
45
+ // Clear all profiles
46
+ await clearProfiles();
47
+ console.log('✅ All profiles cleared successfully');
48
+ return {
49
+ status: 'Ok',
50
+ message: `Cleared ${profileCount} profile(s)`,
51
+ clearedCount: profileCount
52
+ };
53
+ }
54
+ });
@@ -11,6 +11,8 @@ export declare const list: import("@forgehive/task").TaskInstanceType<(argv: {},
11
11
  Name: string;
12
12
  'API Key': string;
13
13
  URL: string;
14
+ Team: string;
15
+ User: string;
14
16
  }[];
15
17
  status?: undefined;
16
18
  }>, {
@@ -27,15 +27,23 @@ exports.list = (0, task_1.createTask)({
27
27
  console.log(` Name: ${currentProfile.name}`);
28
28
  console.log(` API Key: ${currentProfile.apiKey}`);
29
29
  console.log(` URL: ${currentProfile.url}`);
30
+ if (currentProfile.userName) {
31
+ console.log(` User: ${currentProfile.userName}`);
32
+ }
33
+ if (currentProfile.teamName) {
34
+ console.log(` Team: ${currentProfile.teamName}`);
35
+ }
30
36
  console.log('');
31
37
  }
32
38
  console.log('Available profiles:\n');
33
39
  const tableData = profiles.profiles.map(profile => ({
34
40
  Name: profile.name,
35
41
  'API Key': profile.apiKey,
36
- URL: profile.url
42
+ URL: profile.url,
43
+ Team: profile.teamName || 'Unknown',
44
+ User: profile.userName || 'Unknown'
37
45
  }));
38
- console.table(tableData, ['Name', 'API Key', 'URL']);
46
+ console.table(tableData, ['Name', 'API Key', 'URL', 'Team', 'User']);
39
47
  console.log('\nUse auth:add to create or update a profile');
40
48
  console.log('Use auth:switch [name] or auth:switch [index] to switch profiles');
41
49
  console.log('========================================');
@@ -0,0 +1,116 @@
1
+ import { type ForgeConf } from '../types';
2
+ export declare const sync: import("@forgehive/task").TaskInstanceType<(argv: {}, boundaries: import("@forgehive/task").WrappedBoundaries<{
3
+ loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
4
+ loadCurrentProfile: (args: {}) => Promise<Promise<import("../types").Profile>>;
5
+ getCwd: () => Promise<string>;
6
+ persistConf: (forge: ForgeConf, cwd: string) => Promise<void>;
7
+ syncTasksToHive: (projectUuid: string, tasks: Array<{
8
+ uuid: string;
9
+ name: string;
10
+ }>, apiKey: string, apiSecret: string, baseUrl: string) => Promise<{
11
+ success: boolean;
12
+ error?: string;
13
+ data?: {
14
+ projectUuid: string;
15
+ projectName: string;
16
+ summary: {
17
+ total: number;
18
+ created: number;
19
+ updated: number;
20
+ errors: number;
21
+ };
22
+ results: {
23
+ created: Array<{
24
+ uuid: string;
25
+ taskName: string;
26
+ action: string;
27
+ }>;
28
+ updated: Array<{
29
+ uuid: string;
30
+ taskName: string;
31
+ previousName: string;
32
+ action: string;
33
+ }>;
34
+ errors: Array<{
35
+ uuid: string;
36
+ taskName: string;
37
+ error: string;
38
+ }>;
39
+ };
40
+ };
41
+ }>;
42
+ }>) => Promise<{
43
+ status: string;
44
+ message: string;
45
+ summary?: undefined;
46
+ results?: undefined;
47
+ projectUrl?: undefined;
48
+ } | {
49
+ status: string;
50
+ summary: {
51
+ total: number;
52
+ created: number;
53
+ updated: number;
54
+ errors: number;
55
+ };
56
+ results: {
57
+ created: Array<{
58
+ uuid: string;
59
+ taskName: string;
60
+ action: string;
61
+ }>;
62
+ updated: Array<{
63
+ uuid: string;
64
+ taskName: string;
65
+ previousName: string;
66
+ action: string;
67
+ }>;
68
+ errors: Array<{
69
+ uuid: string;
70
+ taskName: string;
71
+ error: string;
72
+ }>;
73
+ };
74
+ projectUrl: string;
75
+ message?: undefined;
76
+ }>, {
77
+ loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
78
+ loadCurrentProfile: (args: {}) => Promise<Promise<import("../types").Profile>>;
79
+ getCwd: () => Promise<string>;
80
+ persistConf: (forge: ForgeConf, cwd: string) => Promise<void>;
81
+ syncTasksToHive: (projectUuid: string, tasks: Array<{
82
+ uuid: string;
83
+ name: string;
84
+ }>, apiKey: string, apiSecret: string, baseUrl: string) => Promise<{
85
+ success: boolean;
86
+ error?: string;
87
+ data?: {
88
+ projectUuid: string;
89
+ projectName: string;
90
+ summary: {
91
+ total: number;
92
+ created: number;
93
+ updated: number;
94
+ errors: number;
95
+ };
96
+ results: {
97
+ created: Array<{
98
+ uuid: string;
99
+ taskName: string;
100
+ action: string;
101
+ }>;
102
+ updated: Array<{
103
+ uuid: string;
104
+ taskName: string;
105
+ previousName: string;
106
+ action: string;
107
+ }>;
108
+ errors: Array<{
109
+ uuid: string;
110
+ taskName: string;
111
+ error: string;
112
+ }>;
113
+ };
114
+ };
115
+ }>;
116
+ }>;
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ // TASK: sync
3
+ // Run this task with:
4
+ // forge task:run project:sync
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.sync = void 0;
10
+ const task_1 = require("@forgehive/task");
11
+ const schema_1 = require("@forgehive/schema");
12
+ const uuid_1 = require("uuid");
13
+ const load_1 = require("../conf/load");
14
+ const loadCurrent_1 = require("../auth/loadCurrent");
15
+ const path_1 = __importDefault(require("path"));
16
+ const promises_1 = __importDefault(require("fs/promises"));
17
+ const schema = new schema_1.Schema({});
18
+ const boundaries = {
19
+ loadConf: load_1.load.asBoundary(),
20
+ loadCurrentProfile: loadCurrent_1.loadCurrent.asBoundary(),
21
+ getCwd: async () => {
22
+ return process.cwd();
23
+ },
24
+ persistConf: async (forge, cwd) => {
25
+ const forgePath = path_1.default.join(cwd, 'forge.json');
26
+ await promises_1.default.writeFile(forgePath, JSON.stringify(forge, null, 2));
27
+ },
28
+ syncTasksToHive: async (projectUuid, tasks, apiKey, apiSecret, baseUrl) => {
29
+ try {
30
+ const url = `${baseUrl}/api/projects/${projectUuid}/sync`;
31
+ const response = await fetch(url, {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ 'Authorization': `Bearer ${apiKey}:${apiSecret}`
36
+ },
37
+ body: JSON.stringify({ tasks })
38
+ });
39
+ if (response.ok) {
40
+ const data = await response.json();
41
+ return { success: true, data };
42
+ }
43
+ else {
44
+ const errorData = await response.json().catch(() => ({ error: `HTTP ${response.status} - ${response.statusText}` }));
45
+ return { success: false, error: errorData.error || `HTTP ${response.status} - ${response.statusText}` };
46
+ }
47
+ }
48
+ catch (error) {
49
+ return { success: false, error: error instanceof Error ? error.message : 'Network error' };
50
+ }
51
+ }
52
+ };
53
+ exports.sync = (0, task_1.createTask)({
54
+ schema,
55
+ boundaries,
56
+ fn: async function (_argv, { loadConf, loadCurrentProfile, getCwd, persistConf, syncTasksToHive }) {
57
+ const cwd = await getCwd();
58
+ const forge = await loadConf({});
59
+ // Check if project has UUID
60
+ if (!forge.project.uuid) {
61
+ throw new Error('Project does not have a UUID. Please run "forge project:link" to connect to a Hive project.');
62
+ }
63
+ console.log(`
64
+ ==================================================
65
+ Starting project sync to Hive!
66
+ Project: ${forge.project.name}
67
+ UUID: ${forge.project.uuid}
68
+ ==================================================
69
+ `);
70
+ // Ensure all tasks have UUIDs and collect them for sync
71
+ let configUpdated = false;
72
+ const tasksToSync = [];
73
+ if (!forge.tasks) {
74
+ forge.tasks = {};
75
+ }
76
+ for (const [taskDescriptor, taskData] of Object.entries(forge.tasks)) {
77
+ if (!taskData.uuid) {
78
+ taskData.uuid = (0, uuid_1.v4)();
79
+ configUpdated = true;
80
+ console.log(` ➕ Generated UUID for task: ${taskDescriptor}`);
81
+ }
82
+ // Use the task descriptor (key) as the name for the API
83
+ const taskName = taskDescriptor;
84
+ tasksToSync.push({
85
+ uuid: taskData.uuid,
86
+ name: taskName
87
+ });
88
+ }
89
+ // Save config if we added UUIDs
90
+ if (configUpdated) {
91
+ await persistConf(forge, cwd);
92
+ console.log(' 💾 Updated forge.json with new UUIDs');
93
+ }
94
+ if (tasksToSync.length === 0) {
95
+ console.log(' ℹ️ No tasks found to sync');
96
+ return { status: 'no-tasks', message: 'No tasks found in project' };
97
+ }
98
+ console.log(` 📊 Found ${tasksToSync.length} tasks to sync`);
99
+ try {
100
+ const profile = await loadCurrentProfile({});
101
+ const result = await syncTasksToHive(forge.project.uuid, tasksToSync, profile.apiKey, profile.apiSecret, profile.url);
102
+ if (result.success && result.data) {
103
+ const { summary, results } = result.data;
104
+ console.log('\\n ✅ Sync completed successfully!');
105
+ console.log(` Total tasks: ${summary.total}`);
106
+ console.log(` Created: ${summary.created}`);
107
+ console.log(` Updated: ${summary.updated}`);
108
+ console.log(` Errors: ${summary.errors}`);
109
+ if (results.created.length > 0) {
110
+ console.log('\\n 🆕 Created tasks:');
111
+ results.created.forEach(task => {
112
+ console.log(` • ${task.taskName} (${task.uuid})`);
113
+ });
114
+ }
115
+ if (results.updated.length > 0) {
116
+ console.log('\\n 🔄 Updated tasks:');
117
+ results.updated.forEach(task => {
118
+ console.log(` • ${task.taskName} (was: ${task.previousName}) (${task.uuid})`);
119
+ });
120
+ }
121
+ if (results.errors.length > 0) {
122
+ console.log('\\n ❌ Tasks with errors:');
123
+ results.errors.forEach(task => {
124
+ console.log(` • ${task.taskName}: ${task.error} (${task.uuid})`);
125
+ });
126
+ }
127
+ const projectUrl = `${profile.url}/dashboard/projects/${forge.project.uuid}`;
128
+ console.log(`\\n 🔗 View your project: ${projectUrl}`);
129
+ return {
130
+ status: 'success',
131
+ summary,
132
+ results,
133
+ projectUrl
134
+ };
135
+ }
136
+ else {
137
+ throw new Error(result.error || 'Unknown sync error');
138
+ }
139
+ }
140
+ catch (error) {
141
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
142
+ if (errorMessage.includes('No default profile')) {
143
+ console.log('\\n ⚠️ No authentication profile found. Run "forge auth:add" to configure.');
144
+ return { status: 'no-auth', message: 'No authentication profile configured' };
145
+ }
146
+ else {
147
+ console.log(`\\n ❌ Sync failed: ${errorMessage}`);
148
+ return { status: 'error', message: errorMessage };
149
+ }
150
+ }
151
+ }
152
+ });
@@ -3,6 +3,7 @@ export declare const createTaskCommand: import("@forgehive/task").TaskInstanceTy
3
3
  descriptorName: string;
4
4
  }, boundaries: import("@forgehive/task").WrappedBoundaries<{
5
5
  loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
6
+ loadCurrentProfile: (args: {}) => Promise<Promise<import("../types").Profile>>;
6
7
  loadTemplate: () => Promise<string>;
7
8
  getCwd: () => Promise<string>;
8
9
  parseTaskName: (taskDescriptor: string) => Promise<TaskName>;
@@ -10,11 +11,17 @@ export declare const createTaskCommand: import("@forgehive/task").TaskInstanceTy
10
11
  path: string;
11
12
  }>;
12
13
  persistConf: (forge: ForgeConf, cwd: string) => Promise<void>;
14
+ createTaskInHive: (projectUuid: string, taskUuid: string, taskName: string, description: string, apiKey: string, apiSecret: string, baseUrl: string) => Promise<{
15
+ success: boolean;
16
+ taskUrl?: string;
17
+ error?: string;
18
+ }>;
13
19
  }>) => Promise<{
14
20
  taskPath: string;
15
21
  fileName: string;
16
22
  }>, {
17
23
  loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
24
+ loadCurrentProfile: (args: {}) => Promise<Promise<import("../types").Profile>>;
18
25
  loadTemplate: () => Promise<string>;
19
26
  getCwd: () => Promise<string>;
20
27
  parseTaskName: (taskDescriptor: string) => Promise<TaskName>;
@@ -22,4 +29,9 @@ export declare const createTaskCommand: import("@forgehive/task").TaskInstanceTy
22
29
  path: string;
23
30
  }>;
24
31
  persistConf: (forge: ForgeConf, cwd: string) => Promise<void>;
32
+ createTaskInHive: (projectUuid: string, taskUuid: string, taskName: string, description: string, apiKey: string, apiSecret: string, baseUrl: string) => Promise<{
33
+ success: boolean;
34
+ taskUrl?: string;
35
+ error?: string;
36
+ }>;
25
37
  }>;
@@ -12,6 +12,7 @@ const path_1 = __importDefault(require("path"));
12
12
  const promises_1 = __importDefault(require("fs/promises"));
13
13
  const camelCase_1 = require("../../utils/camelCase");
14
14
  const load_1 = require("../conf/load");
15
+ const loadCurrent_1 = require("../auth/loadCurrent");
15
16
  // Define the template content directly in the code
16
17
  // This eliminates the need to find and load an external file
17
18
  const TASK_TEMPLATE = `// TASK: {{ taskName }}
@@ -55,6 +56,7 @@ const schema = new schema_1.Schema({
55
56
  const boundaries = {
56
57
  // Load boundaries
57
58
  loadConf: load_1.load.asBoundary(),
59
+ loadCurrentProfile: loadCurrent_1.loadCurrent.asBoundary(),
58
60
  loadTemplate: async () => {
59
61
  return TASK_TEMPLATE;
60
62
  },
@@ -100,12 +102,39 @@ const boundaries = {
100
102
  persistConf: async (forge, cwd) => {
101
103
  const forgePath = path_1.default.join(cwd, 'forge.json');
102
104
  await promises_1.default.writeFile(forgePath, JSON.stringify(forge, null, 2));
105
+ },
106
+ createTaskInHive: async (projectUuid, taskUuid, taskName, description, apiKey, apiSecret, baseUrl) => {
107
+ try {
108
+ const url = `${baseUrl}/api/projects/${projectUuid}/tasks/${taskUuid}`;
109
+ const response = await fetch(url, {
110
+ method: 'POST',
111
+ headers: {
112
+ 'Content-Type': 'application/json',
113
+ 'Authorization': `Bearer ${apiKey}:${apiSecret}`
114
+ },
115
+ body: JSON.stringify({
116
+ taskName,
117
+ description
118
+ })
119
+ });
120
+ if (response.ok) {
121
+ const taskUrl = `${baseUrl}/tasks/${taskUuid}`;
122
+ return { success: true, taskUrl };
123
+ }
124
+ else {
125
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
126
+ return { success: false, error: errorData.error || `HTTP ${response.status}` };
127
+ }
128
+ }
129
+ catch (error) {
130
+ return { success: false, error: error instanceof Error ? error.message : 'Network error' };
131
+ }
103
132
  }
104
133
  };
105
134
  exports.createTaskCommand = (0, task_1.createTask)({
106
135
  schema,
107
136
  boundaries,
108
- fn: async function ({ descriptorName }, { loadTemplate, persistTask, loadConf, persistConf, parseTaskName, getCwd }) {
137
+ fn: async function ({ descriptorName }, { loadTemplate, persistTask, loadConf, persistConf, parseTaskName, getCwd, loadCurrentProfile, createTaskInHive }) {
109
138
  const { taskName, fileName, descriptor, dir } = await parseTaskName(descriptorName);
110
139
  const cwd = await getCwd();
111
140
  const forge = await loadConf({});
@@ -132,12 +161,32 @@ exports.createTaskCommand = (0, task_1.createTask)({
132
161
  if (forge.tasks === undefined) {
133
162
  forge.tasks = {};
134
163
  }
164
+ const taskUuid = (0, uuid_1.v4)();
135
165
  forge.tasks[descriptor] = {
136
166
  path: `${taskPath}/${fileName}`,
137
167
  handler: taskName,
138
- uuid: (0, uuid_1.v4)()
168
+ uuid: taskUuid
139
169
  };
140
170
  await persistConf(forge, cwd);
171
+ // Try to create task in Hive if user has profile and project UUID
172
+ if (forge.project.uuid) {
173
+ try {
174
+ const profile = await loadCurrentProfile({});
175
+ const result = await createTaskInHive(forge.project.uuid, taskUuid, descriptor, 'Add task description here', profile.apiKey, profile.apiSecret, profile.url);
176
+ if (result.success && result.taskUrl) {
177
+ console.log('\\n✅ Task created successfully in Hive!');
178
+ console.log(`🔗 View your task: ${result.taskUrl}`);
179
+ }
180
+ else {
181
+ console.log(`\n⚠️ Task created locally but could not sync to Hive: ${result.error}`);
182
+ console.log(`🔗 Host: ${profile.url}`);
183
+ }
184
+ }
185
+ catch (error) {
186
+ // Silently continue if no profile is configured
187
+ console.log('\\n📝 Task created locally. Configure a profile with \'forge auth:add\' to sync with Hive.');
188
+ }
189
+ }
141
190
  return { taskPath, fileName };
142
191
  }
143
192
  });