aman-intelligence 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 +116 -0
- package/dist/bin/aman.d.ts +2 -0
- package/dist/bin/aman.js +165 -0
- package/dist/cli/global-install.d.ts +7 -0
- package/dist/cli/global-install.js +36 -0
- package/dist/cli/help-text.d.ts +1 -0
- package/dist/cli/help-text.js +62 -0
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +6 -0
- package/dist/commands/backup.d.ts +11 -0
- package/dist/commands/backup.js +262 -0
- package/dist/commands/browse.d.ts +11 -0
- package/dist/commands/browse.js +641 -0
- package/dist/commands/cache.d.ts +1 -0
- package/dist/commands/cache.js +38 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +146 -0
- package/dist/commands/dashboard.d.ts +1 -0
- package/dist/commands/dashboard.js +1004 -0
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +54 -0
- package/dist/commands/export.d.ts +1 -0
- package/dist/commands/export.js +137 -0
- package/dist/commands/help.d.ts +1 -0
- package/dist/commands/help.js +47 -0
- package/dist/commands/import-wizard.d.ts +7 -0
- package/dist/commands/import-wizard.js +374 -0
- package/dist/commands/import.d.ts +9 -0
- package/dist/commands/import.js +351 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +174 -0
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/init.js +146 -0
- package/dist/commands/install.d.ts +10 -0
- package/dist/commands/install.js +342 -0
- package/dist/commands/pack.d.ts +23 -0
- package/dist/commands/pack.js +331 -0
- package/dist/commands/registry.d.ts +6 -0
- package/dist/commands/registry.js +218 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +76 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.js +295 -0
- package/dist/commands/stack.d.ts +18 -0
- package/dist/commands/stack.js +327 -0
- package/dist/commands/sync.d.ts +9 -0
- package/dist/commands/sync.js +428 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +97 -0
- package/dist/config/features.d.ts +2 -0
- package/dist/config/features.js +2 -0
- package/dist/config/index.d.ts +13 -0
- package/dist/config/index.js +80 -0
- package/dist/config/paths.d.ts +23 -0
- package/dist/config/paths.js +45 -0
- package/dist/import/adapters.d.ts +14 -0
- package/dist/import/adapters.js +580 -0
- package/dist/import/discovery.service.d.ts +8 -0
- package/dist/import/discovery.service.js +26 -0
- package/dist/import/import.service.d.ts +7 -0
- package/dist/import/import.service.js +259 -0
- package/dist/import/types.d.ts +71 -0
- package/dist/import/types.js +1 -0
- package/dist/import/utils.d.ts +36 -0
- package/dist/import/utils.js +428 -0
- package/dist/marketplace/cache.d.ts +18 -0
- package/dist/marketplace/cache.js +141 -0
- package/dist/marketplace/github-search.d.ts +17 -0
- package/dist/marketplace/github-search.js +268 -0
- package/dist/marketplace/install-from-candidate.d.ts +6 -0
- package/dist/marketplace/install-from-candidate.js +14 -0
- package/dist/marketplace/install.d.ts +15 -0
- package/dist/marketplace/install.js +54 -0
- package/dist/marketplace/metadata-validator.d.ts +8 -0
- package/dist/marketplace/metadata-validator.js +79 -0
- package/dist/marketplace/types.d.ts +34 -0
- package/dist/marketplace/types.js +1 -0
- package/dist/providers/local.provider.d.ts +9 -0
- package/dist/providers/local.provider.js +51 -0
- package/dist/providers/provider.interface.d.ts +7 -0
- package/dist/providers/provider.interface.js +1 -0
- package/dist/providers/registry.provider.d.ts +2 -0
- package/dist/providers/registry.provider.js +42 -0
- package/dist/providers/skills-sh.provider.d.ts +11 -0
- package/dist/providers/skills-sh.provider.js +56 -0
- package/dist/registry/adapter.interface.d.ts +16 -0
- package/dist/registry/adapter.interface.js +1 -0
- package/dist/registry/errors.d.ts +5 -0
- package/dist/registry/errors.js +8 -0
- package/dist/registry/filesystem-registry.adapter.d.ts +25 -0
- package/dist/registry/filesystem-registry.adapter.js +288 -0
- package/dist/registry/github-registry.adapter.d.ts +11 -0
- package/dist/registry/github-registry.adapter.js +32 -0
- package/dist/registry/index.d.ts +8 -0
- package/dist/registry/index.js +8 -0
- package/dist/registry/local-registry.adapter.d.ts +6 -0
- package/dist/registry/local-registry.adapter.js +9 -0
- package/dist/registry/registry.service.d.ts +44 -0
- package/dist/registry/registry.service.js +163 -0
- package/dist/registry/slug-utils.d.ts +12 -0
- package/dist/registry/slug-utils.js +51 -0
- package/dist/registry/types.d.ts +160 -0
- package/dist/registry/types.js +1 -0
- package/dist/services/asset.service.d.ts +12 -0
- package/dist/services/asset.service.js +142 -0
- package/dist/services/backup.service.d.ts +8 -0
- package/dist/services/backup.service.js +169 -0
- package/dist/services/classification.service.d.ts +31 -0
- package/dist/services/classification.service.js +271 -0
- package/dist/services/config.service.d.ts +9 -0
- package/dist/services/config.service.js +20 -0
- package/dist/services/doctor.service.d.ts +5 -0
- package/dist/services/doctor.service.js +186 -0
- package/dist/services/environment.service.d.ts +42 -0
- package/dist/services/environment.service.js +227 -0
- package/dist/services/github.service.d.ts +7 -0
- package/dist/services/github.service.js +42 -0
- package/dist/services/lock.service.d.ts +12 -0
- package/dist/services/lock.service.js +71 -0
- package/dist/services/marketplace.service.d.ts +40 -0
- package/dist/services/marketplace.service.js +225 -0
- package/dist/services/pack.service.d.ts +9 -0
- package/dist/services/pack.service.js +193 -0
- package/dist/services/stack.service.d.ts +9 -0
- package/dist/services/stack.service.js +94 -0
- package/dist/storage/asset-layout.d.ts +46 -0
- package/dist/storage/asset-layout.js +277 -0
- package/dist/storage/filesystem.d.ts +12 -0
- package/dist/storage/filesystem.js +113 -0
- package/dist/storage/scan-by-type.d.ts +2 -0
- package/dist/storage/scan-by-type.js +8 -0
- package/dist/storage/scanner.d.ts +11 -0
- package/dist/storage/scanner.js +188 -0
- package/dist/types/asset-metadata.d.ts +84 -0
- package/dist/types/asset-metadata.js +104 -0
- package/dist/types/index.d.ts +212 -0
- package/dist/types/index.js +1 -0
- package/dist/ui/animations/ErrorIndicator.d.ts +5 -0
- package/dist/ui/animations/ErrorIndicator.js +6 -0
- package/dist/ui/animations/GithubIndicator.d.ts +6 -0
- package/dist/ui/animations/GithubIndicator.js +9 -0
- package/dist/ui/animations/ProgressBar.d.ts +5 -0
- package/dist/ui/animations/ProgressBar.js +15 -0
- package/dist/ui/animations/Spinner.d.ts +5 -0
- package/dist/ui/animations/Spinner.js +21 -0
- package/dist/ui/animations/SuccessIndicator.d.ts +5 -0
- package/dist/ui/animations/SuccessIndicator.js +6 -0
- package/dist/ui/animations/SyncActivity.d.ts +5 -0
- package/dist/ui/animations/SyncActivity.js +21 -0
- package/dist/ui/animations/TransitionScreen.d.ts +7 -0
- package/dist/ui/animations/TransitionScreen.js +25 -0
- package/dist/ui/animations/useAnimationMode.d.ts +1 -0
- package/dist/ui/animations/useAnimationMode.js +16 -0
- package/dist/ui/assetDisplay.d.ts +19 -0
- package/dist/ui/assetDisplay.js +59 -0
- package/dist/ui/components/Confirm.d.ts +8 -0
- package/dist/ui/components/Confirm.js +14 -0
- package/dist/ui/components/CustomSelect.d.ts +19 -0
- package/dist/ui/components/CustomSelect.js +13 -0
- package/dist/ui/components/Header.d.ts +6 -0
- package/dist/ui/components/Header.js +9 -0
- package/dist/ui/components/HealthReport.d.ts +7 -0
- package/dist/ui/components/HealthReport.js +13 -0
- package/dist/ui/components/MarketplaceInstallConfirm.d.ts +19 -0
- package/dist/ui/components/MarketplaceInstallConfirm.js +23 -0
- package/dist/ui/components/Narrator.d.ts +9 -0
- package/dist/ui/components/Narrator.js +26 -0
- package/dist/ui/components/ScopePrompt.d.ts +8 -0
- package/dist/ui/components/ScopePrompt.js +23 -0
- package/dist/ui/components/TooSmallScreen.d.ts +8 -0
- package/dist/ui/components/TooSmallScreen.js +6 -0
- package/dist/ui/date.d.ts +2 -0
- package/dist/ui/date.js +33 -0
- package/dist/ui/layout.d.ts +23 -0
- package/dist/ui/layout.js +44 -0
- package/dist/ui/list-item.d.ts +12 -0
- package/dist/ui/list-item.js +1 -0
- package/dist/ui/marketplaceDisplay.d.ts +10 -0
- package/dist/ui/marketplaceDisplay.js +36 -0
- package/dist/ui/theme.d.ts +42 -0
- package/dist/ui/theme.js +47 -0
- package/dist/utils/asset-list-fields.d.ts +11 -0
- package/dist/utils/asset-list-fields.js +28 -0
- package/dist/utils/error-message.d.ts +2 -0
- package/dist/utils/error-message.js +6 -0
- package/dist/utils/integrity.d.ts +9 -0
- package/dist/utils/integrity.js +23 -0
- package/dist/utils/lock-migrate.d.ts +25 -0
- package/dist/utils/lock-migrate.js +93 -0
- package/dist/utils/mcp-local.d.ts +15 -0
- package/dist/utils/mcp-local.js +129 -0
- package/dist/utils/slug.d.ts +6 -0
- package/dist/utils/slug.js +13 -0
- package/dist/utils/stack-normalize.d.ts +3 -0
- package/dist/utils/stack-normalize.js +43 -0
- package/package.json +77 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { RegistryError } from './errors.js';
|
|
3
|
+
import { isValidSlug } from '../utils/slug.js';
|
|
4
|
+
export function parseRegistryReference(input) {
|
|
5
|
+
const trimmed = input.trim();
|
|
6
|
+
const match = trimmed.match(/^(@[a-z0-9][a-z0-9-]*(?:~private)?\/[a-z0-9]+(?:-[a-z0-9]+)*)@(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)$/);
|
|
7
|
+
if (!match)
|
|
8
|
+
return null;
|
|
9
|
+
return { slug: match[1], version: match[2] };
|
|
10
|
+
}
|
|
11
|
+
export function namespaceFromSlug(slug) {
|
|
12
|
+
const slash = slug.indexOf('/');
|
|
13
|
+
if (slash < 2 || slug[0] !== '@') {
|
|
14
|
+
throw new RegistryError('INVALID_SLUG', `Invalid slug: ${slug}`);
|
|
15
|
+
}
|
|
16
|
+
return slug.slice(1, slash);
|
|
17
|
+
}
|
|
18
|
+
export function normalizeRegistrySlug(scope, slug) {
|
|
19
|
+
if (slug.startsWith('@')) {
|
|
20
|
+
if (!isValidSlug(slug)) {
|
|
21
|
+
throw new RegistryError('INVALID_SLUG', `Invalid slug format: ${slug}`);
|
|
22
|
+
}
|
|
23
|
+
return slug;
|
|
24
|
+
}
|
|
25
|
+
if (!scope) {
|
|
26
|
+
throw new RegistryError('SCOPE_REQUIRED', `Scope required to resolve unqualified slug "${slug}"`);
|
|
27
|
+
}
|
|
28
|
+
const ns = scope.startsWith('@') ? scope.slice(1) : scope;
|
|
29
|
+
const full = `@${ns}/${slug}`;
|
|
30
|
+
if (!isValidSlug(full)) {
|
|
31
|
+
throw new RegistryError('INVALID_SLUG', `Invalid slug after scope qualification: ${full}`);
|
|
32
|
+
}
|
|
33
|
+
return full;
|
|
34
|
+
}
|
|
35
|
+
export function slugIndexFilePath(registryRoot, slug) {
|
|
36
|
+
const ns = namespaceFromSlug(slug);
|
|
37
|
+
const name = slug.slice(slug.indexOf('/') + 1);
|
|
38
|
+
return path.join(registryRoot, 'slug-index', ns, `${name}.json`);
|
|
39
|
+
}
|
|
40
|
+
export function assetRootPath(registryRoot, id) {
|
|
41
|
+
return path.join(registryRoot, 'assets', id);
|
|
42
|
+
}
|
|
43
|
+
export function versionRecordPath(registryRoot, id, version) {
|
|
44
|
+
return path.join(assetRootPath(registryRoot, id), 'versions', version, 'record.json');
|
|
45
|
+
}
|
|
46
|
+
export function versionContentPath(registryRoot, id, version) {
|
|
47
|
+
return path.join(assetRootPath(registryRoot, id), 'versions', version, 'content');
|
|
48
|
+
}
|
|
49
|
+
export function assetIndexPath(registryRoot, id) {
|
|
50
|
+
return path.join(assetRootPath(registryRoot, id), 'asset.json');
|
|
51
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { AssetType } from '../types/index.js';
|
|
2
|
+
import { AssetVisibility } from '../types/asset-metadata.js';
|
|
3
|
+
/** Publisher namespace (without leading `@`), e.g. `aman`, `acme~private`. */
|
|
4
|
+
export type RegistryNamespace = string;
|
|
5
|
+
/**
|
|
6
|
+
* Access context for scoped resolution (private org assets in future).
|
|
7
|
+
* V1 local adapter ignores `authToken` and treats all records as readable.
|
|
8
|
+
*/
|
|
9
|
+
export interface RegistryAccessContext {
|
|
10
|
+
authToken?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RegistryIntegrity {
|
|
13
|
+
algorithm: 'sha256';
|
|
14
|
+
checksum: string;
|
|
15
|
+
signature: string | null;
|
|
16
|
+
}
|
|
17
|
+
/** Exact-version dependency reference (V1 — no ranges). */
|
|
18
|
+
export interface RegistryDependencyRef {
|
|
19
|
+
slug: string;
|
|
20
|
+
version: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RegistryDeprecation {
|
|
23
|
+
at: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
successor?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface RegistryTrustSignals {
|
|
28
|
+
verified: boolean;
|
|
29
|
+
downloads: number;
|
|
30
|
+
rating: number | null;
|
|
31
|
+
publisher: string;
|
|
32
|
+
deprecated: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface RegistryVersionRecord {
|
|
35
|
+
schemaVersion: 1;
|
|
36
|
+
recordType: 'asset';
|
|
37
|
+
id: string;
|
|
38
|
+
slug: string;
|
|
39
|
+
type: AssetType;
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
version: string;
|
|
43
|
+
author: string;
|
|
44
|
+
tags: string[];
|
|
45
|
+
visibility: AssetVisibility;
|
|
46
|
+
publishedAt: string;
|
|
47
|
+
integrity: RegistryIntegrity;
|
|
48
|
+
dependencies: RegistryDependencyRef[];
|
|
49
|
+
trust: RegistryTrustSignals;
|
|
50
|
+
deprecated: RegistryDeprecation | null;
|
|
51
|
+
}
|
|
52
|
+
export interface RegistrySlugIndexEntry {
|
|
53
|
+
schemaVersion: 1;
|
|
54
|
+
slug: string;
|
|
55
|
+
id: string;
|
|
56
|
+
latestVersion: string;
|
|
57
|
+
visibility: AssetVisibility;
|
|
58
|
+
namespace: RegistryNamespace;
|
|
59
|
+
redirectFrom: string[];
|
|
60
|
+
}
|
|
61
|
+
export interface RegistryAssetIndex {
|
|
62
|
+
schemaVersion: 1;
|
|
63
|
+
id: string;
|
|
64
|
+
slug: string;
|
|
65
|
+
aliases: string[];
|
|
66
|
+
type: AssetType;
|
|
67
|
+
visibility: AssetVisibility;
|
|
68
|
+
}
|
|
69
|
+
export type RegistryContentLocation = {
|
|
70
|
+
kind: 'filesystem';
|
|
71
|
+
path: string;
|
|
72
|
+
} | {
|
|
73
|
+
kind: 'http';
|
|
74
|
+
url: string;
|
|
75
|
+
};
|
|
76
|
+
export interface RegistryResolveRequest {
|
|
77
|
+
/** Disambiguates abbreviated slugs (`react-best-practices` + scope `aman`). */
|
|
78
|
+
scope?: RegistryNamespace;
|
|
79
|
+
slug: string;
|
|
80
|
+
version: string;
|
|
81
|
+
context?: RegistryAccessContext;
|
|
82
|
+
}
|
|
83
|
+
export interface RegistryResolveByIdRequest {
|
|
84
|
+
id: string;
|
|
85
|
+
version: string;
|
|
86
|
+
context?: RegistryAccessContext;
|
|
87
|
+
}
|
|
88
|
+
export interface RegistryResolveResult {
|
|
89
|
+
record: RegistryVersionRecord;
|
|
90
|
+
content: RegistryContentLocation;
|
|
91
|
+
}
|
|
92
|
+
export interface RegistryPublishRequest {
|
|
93
|
+
scope?: RegistryNamespace;
|
|
94
|
+
slug: string;
|
|
95
|
+
version: string;
|
|
96
|
+
type: AssetType;
|
|
97
|
+
metadata: Omit<RegistryVersionRecord, 'schemaVersion' | 'recordType' | 'publishedAt' | 'integrity' | 'trust' | 'deprecated'> & {
|
|
98
|
+
integrity?: Partial<RegistryIntegrity>;
|
|
99
|
+
trust?: Partial<RegistryTrustSignals>;
|
|
100
|
+
};
|
|
101
|
+
/** Canonical asset directory on disk (SKILL.md / PROMPT.md / mcp.json layout). */
|
|
102
|
+
contentDirectory: string;
|
|
103
|
+
context?: RegistryAccessContext;
|
|
104
|
+
}
|
|
105
|
+
export interface RegistryPublishResult {
|
|
106
|
+
record: RegistryVersionRecord;
|
|
107
|
+
}
|
|
108
|
+
export interface RegistryDeprecateRequest {
|
|
109
|
+
scope?: RegistryNamespace;
|
|
110
|
+
slug: string;
|
|
111
|
+
version: string;
|
|
112
|
+
reason: string;
|
|
113
|
+
successor?: string;
|
|
114
|
+
context?: RegistryAccessContext;
|
|
115
|
+
}
|
|
116
|
+
export interface RegistryDeprecateResult {
|
|
117
|
+
record: RegistryVersionRecord;
|
|
118
|
+
}
|
|
119
|
+
export interface RegistryListRequest {
|
|
120
|
+
scope?: RegistryNamespace;
|
|
121
|
+
slug: string;
|
|
122
|
+
context?: RegistryAccessContext;
|
|
123
|
+
}
|
|
124
|
+
export interface RegistryListResult {
|
|
125
|
+
slug: string;
|
|
126
|
+
id: string;
|
|
127
|
+
versions: RegistryVersionRecord[];
|
|
128
|
+
}
|
|
129
|
+
export interface RegistrySearchFilters {
|
|
130
|
+
type?: AssetType;
|
|
131
|
+
namespace?: RegistryNamespace;
|
|
132
|
+
visibility?: AssetVisibility;
|
|
133
|
+
includeDeprecated?: boolean;
|
|
134
|
+
}
|
|
135
|
+
export interface RegistrySearchRequest {
|
|
136
|
+
query: string;
|
|
137
|
+
filters?: RegistrySearchFilters;
|
|
138
|
+
context?: RegistryAccessContext;
|
|
139
|
+
}
|
|
140
|
+
export interface RegistrySearchHit {
|
|
141
|
+
record: RegistryVersionRecord;
|
|
142
|
+
slug: string;
|
|
143
|
+
latestVersion: string;
|
|
144
|
+
}
|
|
145
|
+
export interface RegistrySearchResult {
|
|
146
|
+
hits: RegistrySearchHit[];
|
|
147
|
+
total: number;
|
|
148
|
+
}
|
|
149
|
+
export interface RegistryVerifyRequest {
|
|
150
|
+
scope?: RegistryNamespace;
|
|
151
|
+
slug: string;
|
|
152
|
+
version: string;
|
|
153
|
+
checksum: string;
|
|
154
|
+
context?: RegistryAccessContext;
|
|
155
|
+
}
|
|
156
|
+
export interface RegistryVerifyResult {
|
|
157
|
+
valid: boolean;
|
|
158
|
+
expected: string;
|
|
159
|
+
actual: string;
|
|
160
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AssetType, Scope, AssetMetadata, AssetListItem } from '../types/index.js';
|
|
2
|
+
export declare class AssetService {
|
|
3
|
+
private scopeRoot;
|
|
4
|
+
private getTypeRoot;
|
|
5
|
+
private getBundledDir;
|
|
6
|
+
private ensureCanonicalLayout;
|
|
7
|
+
install(name: string, type: AssetType, scope: Scope, sourcePath?: string, source?: string, metadataOverride?: Partial<AssetMetadata>): Promise<void>;
|
|
8
|
+
remove(name: string, type: AssetType, scope: Scope): Promise<boolean>;
|
|
9
|
+
private ensureBundledCanonical;
|
|
10
|
+
list(type: AssetType, scope?: Scope): Promise<AssetListItem[]>;
|
|
11
|
+
}
|
|
12
|
+
export declare const assetService: AssetService;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { LOCAL_DIR, BUNDLED_SKILLS, BUNDLED_PROMPTS, BUNDLED_MCPS } from '../config/paths.js';
|
|
3
|
+
import { removeDir, exists, readJson, writeJson } from '../storage/filesystem.js';
|
|
4
|
+
import { scanAssetsByType } from '../storage/scan-by-type.js';
|
|
5
|
+
import { lockService } from './lock.service.js';
|
|
6
|
+
import { createAssetMetadata, } from '../types/index.js';
|
|
7
|
+
import { environmentService } from './environment.service.js';
|
|
8
|
+
import { computeAssetChecksum } from '../utils/integrity.js';
|
|
9
|
+
import { defaultSlugForName } from '../utils/slug.js';
|
|
10
|
+
import { parseLockSource } from '../utils/lock-migrate.js';
|
|
11
|
+
import { assetDir, materializeAssetDirectory, metadataFilePath, migrateScopeLayout, migrateTypeRootLayout, resolveBundledSource, contentFilePath, } from '../storage/asset-layout.js';
|
|
12
|
+
import { ensureMcpLocalGitignore, mcpRequiresLocalConfig, scaffoldMcpLocalConfig, } from '../utils/mcp-local.js';
|
|
13
|
+
export class AssetService {
|
|
14
|
+
scopeRoot(scope) {
|
|
15
|
+
return scope === 'global' ? environmentService.getActiveEnvironmentDir() : LOCAL_DIR;
|
|
16
|
+
}
|
|
17
|
+
getTypeRoot(type, scope) {
|
|
18
|
+
const root = this.scopeRoot(scope);
|
|
19
|
+
if (type === 'skill')
|
|
20
|
+
return path.join(root, 'skills');
|
|
21
|
+
if (type === 'prompt')
|
|
22
|
+
return path.join(root, 'prompts');
|
|
23
|
+
return path.join(root, 'mcps');
|
|
24
|
+
}
|
|
25
|
+
getBundledDir(type) {
|
|
26
|
+
if (type === 'skill')
|
|
27
|
+
return BUNDLED_SKILLS;
|
|
28
|
+
if (type === 'prompt')
|
|
29
|
+
return BUNDLED_PROMPTS;
|
|
30
|
+
return BUNDLED_MCPS;
|
|
31
|
+
}
|
|
32
|
+
async ensureCanonicalLayout(scope) {
|
|
33
|
+
await migrateScopeLayout(this.scopeRoot(scope));
|
|
34
|
+
}
|
|
35
|
+
async install(name, type, scope, sourcePath, source = 'local', metadataOverride) {
|
|
36
|
+
if (scope === 'global') {
|
|
37
|
+
await environmentService.ensureActiveEnvironment();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
await environmentService.ensureProjectEnvironment();
|
|
41
|
+
}
|
|
42
|
+
await this.ensureCanonicalLayout(scope);
|
|
43
|
+
const typeRoot = this.getTypeRoot(type, scope);
|
|
44
|
+
const destDir = assetDir(type, typeRoot, name);
|
|
45
|
+
let src = sourcePath;
|
|
46
|
+
if (!src) {
|
|
47
|
+
src = resolveBundledSource(type, this.getBundledDir(type), name);
|
|
48
|
+
}
|
|
49
|
+
if (!exists(src)) {
|
|
50
|
+
throw new Error(`Asset not found: ${src}`);
|
|
51
|
+
}
|
|
52
|
+
if (exists(destDir)) {
|
|
53
|
+
await removeDir(destDir);
|
|
54
|
+
}
|
|
55
|
+
await materializeAssetDirectory(type, src, destDir);
|
|
56
|
+
const installedAt = new Date().toISOString();
|
|
57
|
+
const slug = metadataOverride?.slug ?? defaultSlugForName(metadataOverride?.originalName ?? name);
|
|
58
|
+
const checksum = await computeAssetChecksum(type, destDir);
|
|
59
|
+
let requiresLocalConfig = false;
|
|
60
|
+
if (type === 'mcp') {
|
|
61
|
+
await ensureMcpLocalGitignore(this.scopeRoot(scope));
|
|
62
|
+
const mcpData = await readJson(contentFilePath(destDir, 'mcp'));
|
|
63
|
+
requiresLocalConfig = mcpData ? mcpRequiresLocalConfig(mcpData) : false;
|
|
64
|
+
if (requiresLocalConfig) {
|
|
65
|
+
await scaffoldMcpLocalConfig(destDir);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const metadataContent = createAssetMetadata(type, name, {
|
|
69
|
+
slug,
|
|
70
|
+
source,
|
|
71
|
+
scope,
|
|
72
|
+
installedAt,
|
|
73
|
+
originalName: metadataOverride?.originalName ?? name,
|
|
74
|
+
description: metadataOverride?.description,
|
|
75
|
+
tags: metadataOverride?.tags,
|
|
76
|
+
version: metadataOverride?.version ?? '1.0.0',
|
|
77
|
+
author: metadataOverride?.author,
|
|
78
|
+
id: metadataOverride?.id,
|
|
79
|
+
visibility: metadataOverride?.visibility,
|
|
80
|
+
dependencies: metadataOverride?.dependencies,
|
|
81
|
+
trust: metadataOverride?.trust,
|
|
82
|
+
deprecated: metadataOverride?.deprecated,
|
|
83
|
+
checksum,
|
|
84
|
+
updatedAt: installedAt,
|
|
85
|
+
});
|
|
86
|
+
await writeJson(metadataFilePath(destDir), metadataContent);
|
|
87
|
+
await lockService.addEntry(scope, {
|
|
88
|
+
id: metadataContent.id,
|
|
89
|
+
slug: metadataContent.slug,
|
|
90
|
+
type,
|
|
91
|
+
localName: name,
|
|
92
|
+
version: metadataContent.version,
|
|
93
|
+
originalName: metadataContent.originalName,
|
|
94
|
+
integrity: {
|
|
95
|
+
algorithm: 'sha256',
|
|
96
|
+
checksum: metadataContent.integrity.checksum,
|
|
97
|
+
},
|
|
98
|
+
source: parseLockSource(source),
|
|
99
|
+
scope,
|
|
100
|
+
installedAt,
|
|
101
|
+
dependencies: [],
|
|
102
|
+
requiresLocalConfig,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async remove(name, type, scope) {
|
|
106
|
+
await this.ensureCanonicalLayout(scope);
|
|
107
|
+
const typeRoot = this.getTypeRoot(type, scope);
|
|
108
|
+
const destDir = assetDir(type, typeRoot, name);
|
|
109
|
+
if (!exists(destDir)) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
await removeDir(destDir);
|
|
113
|
+
await lockService.removeEntry(scope, name, type);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
async ensureBundledCanonical(type) {
|
|
117
|
+
if (type === 'skill')
|
|
118
|
+
return;
|
|
119
|
+
await migrateTypeRootLayout(type, this.getBundledDir(type));
|
|
120
|
+
}
|
|
121
|
+
async list(type, scope) {
|
|
122
|
+
const bundledDir = this.getBundledDir(type);
|
|
123
|
+
await this.ensureBundledCanonical(type);
|
|
124
|
+
const bundled = await scanAssetsByType(type, bundledDir, 'bundled');
|
|
125
|
+
let installed = [];
|
|
126
|
+
if (scope) {
|
|
127
|
+
await this.ensureCanonicalLayout(scope);
|
|
128
|
+
installed = await scanAssetsByType(type, this.getTypeRoot(type, scope), 'installed');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
await this.ensureCanonicalLayout('global');
|
|
132
|
+
installed = await scanAssetsByType(type, this.getTypeRoot(type, 'global'), 'installed');
|
|
133
|
+
}
|
|
134
|
+
const mergedMap = new Map();
|
|
135
|
+
for (const item of bundled)
|
|
136
|
+
mergedMap.set(item.name, item);
|
|
137
|
+
for (const item of installed)
|
|
138
|
+
mergedMap.set(item.name, item);
|
|
139
|
+
return Array.from(mergedMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export const assetService = new AssetService();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class BackupService {
|
|
2
|
+
save(name?: string, onProgress?: (progress: number) => void): Promise<string>;
|
|
3
|
+
list(): Promise<string[]>;
|
|
4
|
+
restore(id: string, onProgress?: (progress: number) => void): Promise<void>;
|
|
5
|
+
private verifyRestore;
|
|
6
|
+
delete(id: string): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare const backupService: BackupService;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { copyDir, removeDir, exists, ensureDir, listDirs } from '../storage/filesystem.js';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import { environmentService } from './environment.service.js';
|
|
5
|
+
export class BackupService {
|
|
6
|
+
async save(name, onProgress) {
|
|
7
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
8
|
+
const backupName = name ? `${timestamp}-${name}` : timestamp;
|
|
9
|
+
const baseDir = environmentService.getActiveEnvironmentDir();
|
|
10
|
+
const backupDir = path.join(baseDir, 'backups', backupName);
|
|
11
|
+
await ensureDir(backupDir);
|
|
12
|
+
onProgress?.(20);
|
|
13
|
+
// Copy relevant directories
|
|
14
|
+
const dirsToBackup = ['skills', 'prompts', 'mcps', 'stacks', 'config'];
|
|
15
|
+
for (const dir of dirsToBackup) {
|
|
16
|
+
const src = path.join(baseDir, dir);
|
|
17
|
+
if (exists(src)) {
|
|
18
|
+
await copyDir(src, path.join(backupDir, dir));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
onProgress?.(80);
|
|
22
|
+
// Also backup aman.lock if exists
|
|
23
|
+
const lockfile = path.join(baseDir, 'aman.lock');
|
|
24
|
+
if (exists(lockfile)) {
|
|
25
|
+
await fs.copyFile(lockfile, path.join(backupDir, 'aman.lock'));
|
|
26
|
+
}
|
|
27
|
+
onProgress?.(100);
|
|
28
|
+
return backupName;
|
|
29
|
+
}
|
|
30
|
+
async list() {
|
|
31
|
+
return await listDirs(path.join(environmentService.getActiveEnvironmentDir(), 'backups'));
|
|
32
|
+
}
|
|
33
|
+
async restore(id, onProgress) {
|
|
34
|
+
const baseDir = environmentService.getActiveEnvironmentDir();
|
|
35
|
+
const backupDir = path.join(baseDir, 'backups', id);
|
|
36
|
+
if (!exists(backupDir)) {
|
|
37
|
+
throw new Error(`Backup not found: ${id}`);
|
|
38
|
+
}
|
|
39
|
+
const manifestProbe = path.join(backupDir, 'skills');
|
|
40
|
+
const hasContent = exists(manifestProbe) ||
|
|
41
|
+
exists(path.join(backupDir, 'prompts')) ||
|
|
42
|
+
exists(path.join(backupDir, 'mcps')) ||
|
|
43
|
+
exists(path.join(backupDir, 'aman.lock'));
|
|
44
|
+
if (!hasContent) {
|
|
45
|
+
throw new Error(`Backup appears corrupted or empty: ${id}`);
|
|
46
|
+
}
|
|
47
|
+
const dirsToRestore = ['skills', 'prompts', 'mcps', 'stacks', 'config'];
|
|
48
|
+
const timestamp = Date.now();
|
|
49
|
+
const rollbackSuffix = `.rollback-${timestamp}`;
|
|
50
|
+
const backedUpDirs = [];
|
|
51
|
+
let lockfileRollbackExists = false;
|
|
52
|
+
const lockfilePath = path.join(baseDir, 'aman.lock');
|
|
53
|
+
try {
|
|
54
|
+
// 1. Staging: Move current active environment directories to rollback paths
|
|
55
|
+
for (const dir of dirsToRestore) {
|
|
56
|
+
const dest = path.join(baseDir, dir);
|
|
57
|
+
if (exists(dest)) {
|
|
58
|
+
const rollbackPath = `${dest}${rollbackSuffix}`;
|
|
59
|
+
await fs.rename(dest, rollbackPath);
|
|
60
|
+
backedUpDirs.push(dir);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Rollback lockfile
|
|
64
|
+
const lockfileRollback = `${lockfilePath}${rollbackSuffix}`;
|
|
65
|
+
if (exists(lockfilePath)) {
|
|
66
|
+
await fs.rename(lockfilePath, lockfileRollback);
|
|
67
|
+
lockfileRollbackExists = true;
|
|
68
|
+
}
|
|
69
|
+
onProgress?.(30);
|
|
70
|
+
// 2. Restore new content
|
|
71
|
+
for (const dir of dirsToRestore) {
|
|
72
|
+
const src = path.join(backupDir, dir);
|
|
73
|
+
const dest = path.join(baseDir, dir);
|
|
74
|
+
if (exists(src)) {
|
|
75
|
+
await copyDir(src, dest);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const lockfileSrc = path.join(backupDir, 'aman.lock');
|
|
79
|
+
if (exists(lockfileSrc)) {
|
|
80
|
+
await fs.copyFile(lockfileSrc, lockfilePath);
|
|
81
|
+
}
|
|
82
|
+
onProgress?.(70);
|
|
83
|
+
// 3. Verification: Explicitly verify that restore succeeded
|
|
84
|
+
await this.verifyRestore(baseDir, backupDir);
|
|
85
|
+
onProgress?.(90);
|
|
86
|
+
// 4. Cleanup: Delete rollback directories on success
|
|
87
|
+
for (const dir of backedUpDirs) {
|
|
88
|
+
const rollbackPath = `${path.join(baseDir, dir)}${rollbackSuffix}`;
|
|
89
|
+
await removeDir(rollbackPath).catch(() => { });
|
|
90
|
+
}
|
|
91
|
+
if (lockfileRollbackExists) {
|
|
92
|
+
await fs.unlink(`${lockfilePath}${rollbackSuffix}`).catch(() => { });
|
|
93
|
+
}
|
|
94
|
+
onProgress?.(100);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
// 5. Transaction-like rollback on failure!
|
|
98
|
+
// Wipe corrupt/incomplete folders
|
|
99
|
+
for (const dir of dirsToRestore) {
|
|
100
|
+
const dest = path.join(baseDir, dir);
|
|
101
|
+
if (exists(dest)) {
|
|
102
|
+
await removeDir(dest).catch(() => { });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (exists(lockfilePath)) {
|
|
106
|
+
await fs.unlink(lockfilePath).catch(() => { });
|
|
107
|
+
}
|
|
108
|
+
// Move rollback dirs back into place
|
|
109
|
+
for (const dir of backedUpDirs) {
|
|
110
|
+
const dest = path.join(baseDir, dir);
|
|
111
|
+
const rollbackPath = `${dest}${rollbackSuffix}`;
|
|
112
|
+
if (exists(rollbackPath)) {
|
|
113
|
+
await fs.rename(rollbackPath, dest).catch(() => { });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (lockfileRollbackExists) {
|
|
117
|
+
const lockfileRollback = `${lockfilePath}${rollbackSuffix}`;
|
|
118
|
+
await fs.rename(lockfileRollback, lockfilePath).catch(() => { });
|
|
119
|
+
}
|
|
120
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
121
|
+
throw new Error(`Restore failed. Environment successfully rolled back. Detail: ${detail}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async verifyRestore(baseDir, backupDir) {
|
|
125
|
+
const { readJson } = await import('../storage/filesystem.js');
|
|
126
|
+
// 1. Verify lockfile when the backup included one
|
|
127
|
+
const lockPath = path.join(baseDir, 'aman.lock');
|
|
128
|
+
const backupLockPath = path.join(backupDir, 'aman.lock');
|
|
129
|
+
if (exists(backupLockPath)) {
|
|
130
|
+
const lockfileContent = await readJson(lockPath);
|
|
131
|
+
if (!lockfileContent) {
|
|
132
|
+
throw new Error('Lockfile (aman.lock) is corrupted/unreadable after restore');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// 2. Read the backup directories and verify existence of expected assets
|
|
136
|
+
const dirsToRestore = ['skills', 'prompts', 'mcps', 'stacks', 'config'];
|
|
137
|
+
for (const dir of dirsToRestore) {
|
|
138
|
+
const srcDir = path.join(backupDir, dir);
|
|
139
|
+
const destDir = path.join(baseDir, dir);
|
|
140
|
+
if (exists(srcDir)) {
|
|
141
|
+
if (!exists(destDir)) {
|
|
142
|
+
throw new Error(`Directory ${dir} was not created during restore`);
|
|
143
|
+
}
|
|
144
|
+
const fsPromises = (await import('fs')).promises;
|
|
145
|
+
const srcFiles = await fsPromises.readdir(srcDir);
|
|
146
|
+
const destFiles = await fsPromises.readdir(destDir);
|
|
147
|
+
for (const file of srcFiles) {
|
|
148
|
+
const destFilePath = path.join(destDir, file);
|
|
149
|
+
if (!exists(destFilePath)) {
|
|
150
|
+
throw new Error(`Expected asset file/folder ${file} was not restored under ${dir}`);
|
|
151
|
+
}
|
|
152
|
+
if (dir === 'skills' || dir === 'prompts' || dir === 'mcps') {
|
|
153
|
+
const metaPath = path.join(destFilePath, 'metadata.json');
|
|
154
|
+
if (!exists(metaPath)) {
|
|
155
|
+
throw new Error(`metadata.json is missing for ${dir}/${file}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async delete(id) {
|
|
163
|
+
const backupDir = path.join(environmentService.getActiveEnvironmentDir(), 'backups', id);
|
|
164
|
+
if (exists(backupDir)) {
|
|
165
|
+
await removeDir(backupDir);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export const backupService = new BackupService();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AssetType } from '../types/index.js';
|
|
2
|
+
export interface ClassificationResult {
|
|
3
|
+
file: string;
|
|
4
|
+
type: AssetType | 'stack' | 'unknown';
|
|
5
|
+
confidence: number;
|
|
6
|
+
reason: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class ClassificationService {
|
|
9
|
+
private memory;
|
|
10
|
+
private loadMemory;
|
|
11
|
+
private saveMemory;
|
|
12
|
+
remember(relativePath: string, type: AssetType | 'stack'): Promise<void>;
|
|
13
|
+
rememberBatch(decisions: Record<string, AssetType | 'stack'>): Promise<void>;
|
|
14
|
+
getRemembered(relativePath: string): Promise<AssetType | 'stack' | undefined>;
|
|
15
|
+
private walk;
|
|
16
|
+
private classifyFile;
|
|
17
|
+
private classifyJson;
|
|
18
|
+
private classifyMarkdown;
|
|
19
|
+
classifyDirectory(rootDir: string): Promise<ClassificationResult[]>;
|
|
20
|
+
summarize(results: ClassificationResult[]): {
|
|
21
|
+
skills: number;
|
|
22
|
+
prompts: number;
|
|
23
|
+
mcps: number;
|
|
24
|
+
stacks: number;
|
|
25
|
+
unknown: number;
|
|
26
|
+
};
|
|
27
|
+
/** Auto-import threshold: 50%+ per import confidence bands; below 50 is manual review only. */
|
|
28
|
+
highConfidence(results: ClassificationResult[], threshold?: number): ClassificationResult[];
|
|
29
|
+
lowConfidence(results: ClassificationResult[], threshold?: number): ClassificationResult[];
|
|
30
|
+
}
|
|
31
|
+
export declare const classificationService: ClassificationService;
|