@automagik/genie 4.260331.6 → 4.260331.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/.claude-plugin/marketplace.json +1 -1
- package/dist/genie.js +134 -253
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/genie/.claude-plugin/plugin.json +1 -1
- package/plugins/genie/package.json +1 -1
- package/src/db/migrations/015_agent_archived_state.sql +0 -20
- package/src/db/migrations/018_drop_app_store.sql +4 -0
- package/src/genie.ts +0 -11
- package/src/lib/agent-sync.ts +55 -194
- package/src/lib/export-format.ts +1 -12
- package/src/lib/import-order.ts +1 -11
- package/src/lib/task-service.ts +4 -0
- package/src/term-commands/agent/directory.ts +2 -37
- package/src/term-commands/dir.ts +3 -68
- package/src/term-commands/export.ts +0 -13
- package/src/term-commands/team.ts +14 -11
- package/src/lib/agent-cache.ts +0 -282
- package/src/lib/manifest.ts +0 -342
- package/src/term-commands/install.ts +0 -372
- package/src/term-commands/item-uninstall.ts +0 -118
- package/src/term-commands/item-update.ts +0 -205
- package/src/term-commands/publish.ts +0 -187
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Uninstall Command — `genie uninstall <name>`
|
|
3
|
-
*
|
|
4
|
-
* Removes a previously installed item: deregisters from app_store,
|
|
5
|
-
* performs type-specific cleanup, and removes the cloned directory.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
9
|
-
import { homedir } from 'node:os';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import type { Command } from 'commander';
|
|
12
|
-
import { getItemFromStore, regenerateAgentCache, removeItemFromStore } from '../lib/agent-cache.js';
|
|
13
|
-
import { getActor, recordAuditEvent } from '../lib/audit.js';
|
|
14
|
-
import { getConnection, isAvailable } from '../lib/db.js';
|
|
15
|
-
|
|
16
|
-
const GENIE_HOME = process.env.GENIE_HOME ?? join(homedir(), '.genie');
|
|
17
|
-
const ITEMS_DIR = join(GENIE_HOME, 'items');
|
|
18
|
-
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// Type-specific deregistration
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
|
-
async function deregisterByType(itemType: string, name: string): Promise<void> {
|
|
24
|
-
if (!(await isAvailable())) return;
|
|
25
|
-
const sql = await getConnection();
|
|
26
|
-
|
|
27
|
-
switch (itemType) {
|
|
28
|
-
case 'agent':
|
|
29
|
-
// Remove from legacy agents table too
|
|
30
|
-
await sql`DELETE FROM agents WHERE id = ${`dir:${name}`}`.catch(() => {});
|
|
31
|
-
await regenerateAgentCache();
|
|
32
|
-
break;
|
|
33
|
-
case 'board':
|
|
34
|
-
await sql`DELETE FROM task_types WHERE id = ${name}`.catch(() => {});
|
|
35
|
-
break;
|
|
36
|
-
case 'workflow':
|
|
37
|
-
await sql`DELETE FROM schedules WHERE id = ${`sched-${name}`}`.catch(() => {});
|
|
38
|
-
break;
|
|
39
|
-
case 'app':
|
|
40
|
-
await sql`DELETE FROM installed_apps WHERE app_store_id IN (
|
|
41
|
-
SELECT id FROM app_store WHERE name = ${name}
|
|
42
|
-
)`.catch(() => {});
|
|
43
|
-
break;
|
|
44
|
-
case 'stack': {
|
|
45
|
-
// Try to read manifest and uninstall sub-items
|
|
46
|
-
const manifestPath = join(ITEMS_DIR, name, 'genie.yaml');
|
|
47
|
-
if (existsSync(manifestPath)) {
|
|
48
|
-
try {
|
|
49
|
-
const yaml = await import('js-yaml');
|
|
50
|
-
const raw = yaml.load(readFileSync(manifestPath, 'utf-8')) as Record<string, unknown>;
|
|
51
|
-
const stack = raw.stack as { items?: Array<{ name: string; type: string }> } | undefined;
|
|
52
|
-
if (stack?.items) {
|
|
53
|
-
for (const item of stack.items) {
|
|
54
|
-
await deregisterByType(item.type, item.name);
|
|
55
|
-
await removeItemFromStore(item.name).catch(() => {});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} catch {
|
|
59
|
-
// Best effort
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ============================================================================
|
|
68
|
-
// Uninstall handler
|
|
69
|
-
// ============================================================================
|
|
70
|
-
|
|
71
|
-
async function handleUninstall(name: string): Promise<void> {
|
|
72
|
-
const existing = await getItemFromStore(name).catch(() => null);
|
|
73
|
-
if (!existing) {
|
|
74
|
-
console.error(`Item "${name}" is not installed.`);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const itemType = existing.item_type;
|
|
79
|
-
|
|
80
|
-
// Type-specific deregistration
|
|
81
|
-
await deregisterByType(itemType, name);
|
|
82
|
-
|
|
83
|
-
// Remove from app_store
|
|
84
|
-
await removeItemFromStore(name);
|
|
85
|
-
|
|
86
|
-
// Remove cloned directory
|
|
87
|
-
const installDir = join(ITEMS_DIR, name);
|
|
88
|
-
if (existsSync(installDir)) {
|
|
89
|
-
rmSync(installDir, { recursive: true, force: true });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Audit
|
|
93
|
-
recordAuditEvent('item', name, 'item_uninstalled', getActor(), {
|
|
94
|
-
type: itemType,
|
|
95
|
-
version: existing.version,
|
|
96
|
-
}).catch(() => {});
|
|
97
|
-
|
|
98
|
-
console.log(`Uninstalled ${itemType} "${name}".`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ============================================================================
|
|
102
|
-
// Command registration
|
|
103
|
-
// ============================================================================
|
|
104
|
-
|
|
105
|
-
export function registerItemUninstallCommand(parent: Command): void {
|
|
106
|
-
parent
|
|
107
|
-
.command('uninstall <name>')
|
|
108
|
-
.description('Remove an installed genie item')
|
|
109
|
-
.action(async (name: string) => {
|
|
110
|
-
try {
|
|
111
|
-
await handleUninstall(name);
|
|
112
|
-
} catch (error) {
|
|
113
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
114
|
-
console.error(`Error: ${message}`);
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Update Command — `genie update <name>` or `genie update --all`
|
|
3
|
-
*
|
|
4
|
-
* Pulls the latest version (or a specific tag) of an installed item,
|
|
5
|
-
* re-validates its manifest, and updates the app_store entry.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { execSync } from 'node:child_process';
|
|
9
|
-
import { existsSync } from 'node:fs';
|
|
10
|
-
import { homedir } from 'node:os';
|
|
11
|
-
import { join } from 'node:path';
|
|
12
|
-
import type { Command } from 'commander';
|
|
13
|
-
import { getItemFromStore, listItemsFromStore, regenerateAgentCache, updateItemInStore } from '../lib/agent-cache.js';
|
|
14
|
-
import { getActor, recordAuditEvent } from '../lib/audit.js';
|
|
15
|
-
import { getConnection, isAvailable } from '../lib/db.js';
|
|
16
|
-
import { type GenieManifest, detectManifest, validateManifest } from '../lib/manifest.js';
|
|
17
|
-
|
|
18
|
-
const GENIE_HOME = process.env.GENIE_HOME ?? join(homedir(), '.genie');
|
|
19
|
-
const ITEMS_DIR = join(GENIE_HOME, 'items');
|
|
20
|
-
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// Type-specific re-registration
|
|
23
|
-
// ============================================================================
|
|
24
|
-
|
|
25
|
-
async function reregisterByType(manifest: GenieManifest): Promise<void> {
|
|
26
|
-
if (!(await isAvailable())) return;
|
|
27
|
-
const sql = await getConnection();
|
|
28
|
-
|
|
29
|
-
switch (manifest.type) {
|
|
30
|
-
case 'agent':
|
|
31
|
-
await regenerateAgentCache();
|
|
32
|
-
break;
|
|
33
|
-
case 'board':
|
|
34
|
-
if (manifest.board?.stages) {
|
|
35
|
-
const stages = manifest.board.stages.map((s, i) => ({
|
|
36
|
-
id: crypto.randomUUID(),
|
|
37
|
-
name: s.name,
|
|
38
|
-
label: s.label ?? s.name,
|
|
39
|
-
gate: s.gate,
|
|
40
|
-
action: s.action ?? null,
|
|
41
|
-
auto_advance: s.auto_advance ?? false,
|
|
42
|
-
roles: s.roles ?? ['*'],
|
|
43
|
-
color: s.color ?? '#94a3b8',
|
|
44
|
-
parallel: false,
|
|
45
|
-
on_fail: null,
|
|
46
|
-
position: i,
|
|
47
|
-
transitions: [],
|
|
48
|
-
}));
|
|
49
|
-
await sql`
|
|
50
|
-
UPDATE task_types SET stages = ${sql.json(stages)}, updated_at = now()
|
|
51
|
-
WHERE id = ${manifest.name}
|
|
52
|
-
`.catch(() => {});
|
|
53
|
-
}
|
|
54
|
-
break;
|
|
55
|
-
case 'workflow':
|
|
56
|
-
if (manifest.workflow) {
|
|
57
|
-
await sql`
|
|
58
|
-
UPDATE schedules SET
|
|
59
|
-
cron_expression = ${manifest.workflow.cron},
|
|
60
|
-
timezone = ${manifest.workflow.timezone ?? 'UTC'},
|
|
61
|
-
command = ${manifest.workflow.command},
|
|
62
|
-
run_spec = ${sql.json(manifest.workflow.run_spec ?? {})},
|
|
63
|
-
updated_at = now()
|
|
64
|
-
WHERE id = ${`sched-${manifest.name}`}
|
|
65
|
-
`.catch(() => {});
|
|
66
|
-
}
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ============================================================================
|
|
72
|
-
// Update handler
|
|
73
|
-
// ============================================================================
|
|
74
|
-
|
|
75
|
-
interface UpdateOptions {
|
|
76
|
-
all?: boolean;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function handleUpdateSingle(name: string, version?: string): Promise<boolean> {
|
|
80
|
-
const existing = await getItemFromStore(name).catch(() => null);
|
|
81
|
-
if (!existing) {
|
|
82
|
-
console.error(`Item "${name}" is not installed.`);
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const installDir = existing.install_path ?? join(ITEMS_DIR, name);
|
|
87
|
-
if (!existsSync(installDir)) {
|
|
88
|
-
console.error(`Install directory not found: ${installDir}`);
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Pull latest or checkout specific version
|
|
93
|
-
try {
|
|
94
|
-
if (version) {
|
|
95
|
-
execSync(`git fetch --tags && git checkout ${version}`, {
|
|
96
|
-
cwd: installDir,
|
|
97
|
-
stdio: 'pipe',
|
|
98
|
-
timeout: 60_000,
|
|
99
|
-
});
|
|
100
|
-
} else {
|
|
101
|
-
execSync('git pull --ff-only', {
|
|
102
|
-
cwd: installDir,
|
|
103
|
-
stdio: 'pipe',
|
|
104
|
-
timeout: 60_000,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
} catch (err) {
|
|
108
|
-
console.error(`Git update failed for "${name}": ${(err as Error).message}`);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Re-detect and validate manifest
|
|
113
|
-
const detection = await detectManifest(installDir);
|
|
114
|
-
if ('error' in detection) {
|
|
115
|
-
console.error(`Manifest detection failed after update: ${detection.error}`);
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const { manifest } = detection;
|
|
120
|
-
const validation = validateManifest(manifest, installDir);
|
|
121
|
-
for (const w of validation.warnings) {
|
|
122
|
-
console.log(` Warning: ${w}`);
|
|
123
|
-
}
|
|
124
|
-
if (!validation.valid) {
|
|
125
|
-
console.error(`Validation failed after update:\n${validation.errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Update app_store
|
|
130
|
-
await updateItemInStore(name, {
|
|
131
|
-
version: manifest.version,
|
|
132
|
-
description: manifest.description,
|
|
133
|
-
manifest: manifest as unknown as Record<string, unknown>,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Type-specific re-registration
|
|
137
|
-
await reregisterByType(manifest);
|
|
138
|
-
|
|
139
|
-
// Audit
|
|
140
|
-
recordAuditEvent('item', name, 'item_updated', getActor(), {
|
|
141
|
-
type: manifest.type,
|
|
142
|
-
version: manifest.version,
|
|
143
|
-
previousVersion: existing.version,
|
|
144
|
-
}).catch(() => {});
|
|
145
|
-
|
|
146
|
-
console.log(`Updated ${manifest.type} "${name}" → v${manifest.version}`);
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function handleUpdate(nameOrVersion: string | undefined, options: UpdateOptions): Promise<void> {
|
|
151
|
-
if (options.all) {
|
|
152
|
-
const items = await listItemsFromStore();
|
|
153
|
-
const gitItems = items.filter((i) => i.git_url);
|
|
154
|
-
if (gitItems.length === 0) {
|
|
155
|
-
console.log('No git-installed items to update.');
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
console.log(`Updating ${gitItems.length} item(s)...`);
|
|
160
|
-
let updated = 0;
|
|
161
|
-
for (const item of gitItems) {
|
|
162
|
-
const ok = await handleUpdateSingle(item.name);
|
|
163
|
-
if (ok) updated++;
|
|
164
|
-
}
|
|
165
|
-
console.log(`\n${updated}/${gitItems.length} items updated successfully.`);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (!nameOrVersion) {
|
|
170
|
-
console.error('Usage: genie update <name>[@version] or genie update --all');
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Parse name@version
|
|
175
|
-
let name = nameOrVersion;
|
|
176
|
-
let version: string | undefined;
|
|
177
|
-
const atIdx = name.lastIndexOf('@');
|
|
178
|
-
if (atIdx > 0) {
|
|
179
|
-
version = name.slice(atIdx + 1);
|
|
180
|
-
name = name.slice(0, atIdx);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const ok = await handleUpdateSingle(name, version);
|
|
184
|
-
if (!ok) process.exit(1);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ============================================================================
|
|
188
|
-
// Command registration
|
|
189
|
-
// ============================================================================
|
|
190
|
-
|
|
191
|
-
export function registerItemUpdateCommand(parent: Command): void {
|
|
192
|
-
parent
|
|
193
|
-
.command('update [name]')
|
|
194
|
-
.description('Update an installed item to the latest version or a specific tag')
|
|
195
|
-
.option('--all', 'Update all git-installed items')
|
|
196
|
-
.action(async (name: string | undefined, options: UpdateOptions) => {
|
|
197
|
-
try {
|
|
198
|
-
await handleUpdate(name, options);
|
|
199
|
-
} catch (error) {
|
|
200
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
-
console.error(`Error: ${message}`);
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Publish Command — `genie publish`
|
|
3
|
-
*
|
|
4
|
-
* Publishes the current directory as a genie item. Must be run from a
|
|
5
|
-
* directory containing a genie.yaml. Requires a pushed git tag matching
|
|
6
|
-
* the manifest version.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { execSync } from 'node:child_process';
|
|
10
|
-
import type { Command } from 'commander';
|
|
11
|
-
import { getItemFromStore, registerItemInStore, updateItemInStore } from '../lib/agent-cache.js';
|
|
12
|
-
import { getActor, recordAuditEvent } from '../lib/audit.js';
|
|
13
|
-
import { getConnection, isAvailable } from '../lib/db.js';
|
|
14
|
-
import { detectManifest, validateManifest } from '../lib/manifest.js';
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Git tag verification
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
function getGitRemoteTags(cwd: string): string[] {
|
|
21
|
-
try {
|
|
22
|
-
const output = execSync('git tag --list --merged HEAD', {
|
|
23
|
-
cwd,
|
|
24
|
-
encoding: 'utf-8',
|
|
25
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
|
-
});
|
|
27
|
-
return output.trim().split('\n').filter(Boolean);
|
|
28
|
-
} catch {
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isTagPushed(tag: string, cwd: string): boolean {
|
|
34
|
-
try {
|
|
35
|
-
execSync(`git ls-remote --tags origin refs/tags/${tag}`, {
|
|
36
|
-
cwd,
|
|
37
|
-
encoding: 'utf-8',
|
|
38
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
39
|
-
});
|
|
40
|
-
return true;
|
|
41
|
-
} catch {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getGitSha(cwd: string): string | null {
|
|
47
|
-
try {
|
|
48
|
-
return execSync('git rev-parse HEAD', { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
49
|
-
} catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// Publish handler
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
async function handlePublish(): Promise<void> {
|
|
59
|
-
const cwd = process.cwd();
|
|
60
|
-
|
|
61
|
-
// Detect and validate manifest
|
|
62
|
-
const detection = await detectManifest(cwd);
|
|
63
|
-
if ('error' in detection) {
|
|
64
|
-
console.error(`Cannot publish: ${detection.error}`);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const { manifest, source } = detection;
|
|
69
|
-
const validation = validateManifest(manifest, cwd);
|
|
70
|
-
for (const w of validation.warnings) {
|
|
71
|
-
console.log(` Warning: ${w}`);
|
|
72
|
-
}
|
|
73
|
-
if (!validation.valid) {
|
|
74
|
-
console.error(`Validation failed:\n${validation.errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Check for git tag matching version
|
|
79
|
-
const expectedTag = `v${manifest.version}`;
|
|
80
|
-
const tags = getGitRemoteTags(cwd);
|
|
81
|
-
const hasTag = tags.includes(expectedTag) || tags.includes(manifest.version);
|
|
82
|
-
if (!hasTag) {
|
|
83
|
-
console.error(`No git tag "${expectedTag}" found. Create and push a tag first:`);
|
|
84
|
-
console.error(` git tag ${expectedTag} && git push origin ${expectedTag}`);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Verify tag is pushed to remote
|
|
89
|
-
const tagToPush = tags.includes(expectedTag) ? expectedTag : manifest.version;
|
|
90
|
-
if (!isTagPushed(tagToPush, cwd)) {
|
|
91
|
-
console.error(`Tag "${tagToPush}" exists locally but is not pushed to remote.`);
|
|
92
|
-
console.error(` git push origin ${tagToPush}`);
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const gitSha = getGitSha(cwd);
|
|
97
|
-
|
|
98
|
-
// UPSERT into app_store
|
|
99
|
-
const existing = await getItemFromStore(manifest.name).catch(() => null);
|
|
100
|
-
if (existing) {
|
|
101
|
-
await updateItemInStore(manifest.name, {
|
|
102
|
-
version: manifest.version,
|
|
103
|
-
description: manifest.description,
|
|
104
|
-
manifest: manifest as unknown as Record<string, unknown>,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Update approval status
|
|
108
|
-
if (await isAvailable()) {
|
|
109
|
-
const sql = await getConnection();
|
|
110
|
-
await sql`
|
|
111
|
-
UPDATE app_store
|
|
112
|
-
SET approval_status = 'pending', updated_at = now()
|
|
113
|
-
WHERE name = ${manifest.name}
|
|
114
|
-
`;
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
await registerItemInStore({
|
|
118
|
-
name: manifest.name,
|
|
119
|
-
itemType: manifest.type,
|
|
120
|
-
version: manifest.version,
|
|
121
|
-
description: manifest.description,
|
|
122
|
-
authorName: manifest.author?.name,
|
|
123
|
-
authorUrl: manifest.author?.url,
|
|
124
|
-
installPath: cwd,
|
|
125
|
-
manifest: manifest as unknown as Record<string, unknown>,
|
|
126
|
-
tags: manifest.tags,
|
|
127
|
-
category: manifest.category,
|
|
128
|
-
license: manifest.license,
|
|
129
|
-
dependencies: manifest.dependencies,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Record version in app_versions
|
|
134
|
-
if (await isAvailable()) {
|
|
135
|
-
const sql = await getConnection();
|
|
136
|
-
const storeItem = await getItemFromStore(manifest.name);
|
|
137
|
-
if (storeItem) {
|
|
138
|
-
await sql`
|
|
139
|
-
INSERT INTO app_versions (app_store_id, version, git_tag, git_sha, manifest)
|
|
140
|
-
VALUES (
|
|
141
|
-
${storeItem.id},
|
|
142
|
-
${manifest.version},
|
|
143
|
-
${tagToPush},
|
|
144
|
-
${gitSha},
|
|
145
|
-
${sql.json(manifest as unknown as Record<string, unknown>)}
|
|
146
|
-
)
|
|
147
|
-
ON CONFLICT (app_store_id, version) DO UPDATE SET
|
|
148
|
-
git_sha = EXCLUDED.git_sha,
|
|
149
|
-
manifest = EXCLUDED.manifest,
|
|
150
|
-
published_at = now()
|
|
151
|
-
`;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Audit
|
|
156
|
-
recordAuditEvent('item', manifest.name, 'item_published', getActor(), {
|
|
157
|
-
type: manifest.type,
|
|
158
|
-
version: manifest.version,
|
|
159
|
-
tag: tagToPush,
|
|
160
|
-
sha: gitSha,
|
|
161
|
-
manifestSource: source,
|
|
162
|
-
}).catch(() => {});
|
|
163
|
-
|
|
164
|
-
console.log(`\nPublished ${manifest.type} "${manifest.name}" v${manifest.version}`);
|
|
165
|
-
console.log(` Tag: ${tagToPush}`);
|
|
166
|
-
if (gitSha) console.log(` SHA: ${gitSha.slice(0, 8)}`);
|
|
167
|
-
console.log(' Status: pending approval');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ============================================================================
|
|
171
|
-
// Command registration
|
|
172
|
-
// ============================================================================
|
|
173
|
-
|
|
174
|
-
export function registerPublishCommand(program: Command): void {
|
|
175
|
-
program
|
|
176
|
-
.command('publish')
|
|
177
|
-
.description('Publish the current directory as a genie item (requires pushed git tag)')
|
|
178
|
-
.action(async () => {
|
|
179
|
-
try {
|
|
180
|
-
await handlePublish();
|
|
181
|
-
} catch (error) {
|
|
182
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
-
console.error(`Error: ${message}`);
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
}
|