@kanonak-protocol/cli 1.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/README.md +52 -0
- package/dist/auth/OAuthDiscovery.d.ts +30 -0
- package/dist/auth/OAuthFlow.d.ts +33 -0
- package/dist/cache/FileCache.d.ts +18 -0
- package/dist/capabilities/CapabilityLoader.d.ts +39 -0
- package/dist/capabilities/CapabilityStore.d.ts +30 -0
- package/dist/capabilities/CommandRegistrar.d.ts +11 -0
- package/dist/capabilities/DeploymentHandlerRegistry.d.ts +45 -0
- package/dist/capabilities/GenericCapabilityHandler.d.ts +42 -0
- package/dist/commands/capability.d.ts +8 -0
- package/dist/commands/deps.d.ts +6 -0
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/login.d.ts +11 -0
- package/dist/commands/logout.d.ts +6 -0
- package/dist/commands/validate.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +74 -0
- package/dist/lock/LockFile.d.ts +14 -0
- package/dist/resolution/LocalResolution.d.ts +39 -0
- package/dist/skills/SkillDeploymentHandler.d.ts +22 -0
- package/dist/skills/SkillLock.d.ts +26 -0
- package/dist/skills/SkillPaths.d.ts +27 -0
- package/dist/skills/SkillTransformer.d.ts +20 -0
- package/dist/utils/parsePackageRef.d.ts +18 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @kanonak-protocol/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for [Kanonak Protocol](https://kanonak.org) — validate, install, and inspect semantic ontology packages.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @kanonak-protocol/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
### Validate
|
|
14
|
+
|
|
15
|
+
Check `.kan.yml` files for type errors, missing imports, and structural issues:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
kanonak validate . # Validate all files in current directory
|
|
19
|
+
kanonak validate contacts.kan.yml # Validate a specific file
|
|
20
|
+
kanonak validate ontologies/ # Validate a directory
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Install
|
|
24
|
+
|
|
25
|
+
Download Kanonak packages and their dependencies:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
kanonak install kanonak.org/core-rdf # Install a package
|
|
29
|
+
kanonak install kanonak.org/docs-complete # Install documentation bundle
|
|
30
|
+
kanonak install # Install from kanonak.lock
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Inspect dependencies
|
|
34
|
+
|
|
35
|
+
Show the resolved dependency tree:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
kanonak deps contacts.kan.yml
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Authentication
|
|
42
|
+
|
|
43
|
+
Authenticate with private package registries:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
kanonak login registry.example.com # OAuth 2.0 + PKCE + DPoP
|
|
47
|
+
kanonak logout registry.example.com
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
Apache-2.0
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 Authorization Server Metadata (RFC 8414).
|
|
3
|
+
*/
|
|
4
|
+
export interface OAuthServerMetadata {
|
|
5
|
+
issuer?: string | null;
|
|
6
|
+
authorizationEndpoint?: string | null;
|
|
7
|
+
tokenEndpoint?: string | null;
|
|
8
|
+
registrationEndpoint?: string | null;
|
|
9
|
+
revocationEndpoint?: string | null;
|
|
10
|
+
scopesSupported?: string[] | null;
|
|
11
|
+
responseTypesSupported?: string[] | null;
|
|
12
|
+
grantTypesSupported?: string[] | null;
|
|
13
|
+
codeChallengeMethodsSupported?: string[] | null;
|
|
14
|
+
tokenEndpointAuthMethodsSupported?: string[] | null;
|
|
15
|
+
dpopSigningAlgValuesSupported?: string[] | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Discovers OAuth server metadata using RFC 8414.
|
|
19
|
+
* Falls back to OpenID Connect discovery.
|
|
20
|
+
* Port of C# OAuthDiscoveryService.
|
|
21
|
+
*/
|
|
22
|
+
export declare class OAuthDiscovery {
|
|
23
|
+
private readonly cache;
|
|
24
|
+
discover(host: string): Promise<OAuthServerMetadata | null>;
|
|
25
|
+
supportsOAuth(host: string): Promise<boolean>;
|
|
26
|
+
static supportsPkceS256(m: OAuthServerMetadata): boolean;
|
|
27
|
+
static supportsDynamicRegistration(m: OAuthServerMetadata): boolean;
|
|
28
|
+
static supportsAuthorizationCode(m: OAuthServerMetadata): boolean;
|
|
29
|
+
private tryEndpoint;
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CredentialStore } from '@kanonak-protocol/sdk';
|
|
2
|
+
import { OAuthDiscovery } from './OAuthDiscovery.js';
|
|
3
|
+
/**
|
|
4
|
+
* OAuth 2.0 Authorization Code Flow with PKCE and DPoP.
|
|
5
|
+
* Supports Dynamic Client Registration (RFC 7591).
|
|
6
|
+
* Port of C# OAuthFlowService.
|
|
7
|
+
*/
|
|
8
|
+
export declare class OAuthFlow {
|
|
9
|
+
private readonly discovery;
|
|
10
|
+
private readonly credentialStore;
|
|
11
|
+
constructor(discovery?: OAuthDiscovery, credentialStore?: CredentialStore);
|
|
12
|
+
/**
|
|
13
|
+
* Full OAuth authorization flow.
|
|
14
|
+
*/
|
|
15
|
+
authorize(host: string, scopes?: string[]): Promise<OAuthResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Refresh an expired access token.
|
|
18
|
+
*/
|
|
19
|
+
refresh(host: string): Promise<OAuthResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Revoke tokens and remove credentials.
|
|
22
|
+
*/
|
|
23
|
+
logout(host: string): Promise<OAuthResult>;
|
|
24
|
+
private registerClient;
|
|
25
|
+
private exchangeCode;
|
|
26
|
+
private refreshTokenRequest;
|
|
27
|
+
private revokeToken;
|
|
28
|
+
}
|
|
29
|
+
export interface OAuthResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
host?: string;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple file-based cache for fetched Kanonak documents.
|
|
3
|
+
* Stores files at: {cacheDir}/{publisher}/{package}@{version}.kan.yml
|
|
4
|
+
*
|
|
5
|
+
* Default cache location is the global cache (~/.kanonak/packages/).
|
|
6
|
+
* Override with KANONAK_PACKAGE_CACHE env var or by passing a custom path.
|
|
7
|
+
*/
|
|
8
|
+
export declare class FileCache {
|
|
9
|
+
private readonly cacheDir;
|
|
10
|
+
private gitignoreChecked;
|
|
11
|
+
private readonly isProjectLocal;
|
|
12
|
+
constructor(cacheDir?: string);
|
|
13
|
+
get(publisher: string, packageName: string, version: string): string | null;
|
|
14
|
+
put(publisher: string, packageName: string, version: string, content: string): void;
|
|
15
|
+
has(publisher: string, packageName: string, version: string): boolean;
|
|
16
|
+
private ensureGitignore;
|
|
17
|
+
private getPath;
|
|
18
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { KanonakParser } from '@kanonak-protocol/sdk';
|
|
2
|
+
import { FileCache } from '../cache/FileCache.js';
|
|
3
|
+
/**
|
|
4
|
+
* A loaded command argument definition.
|
|
5
|
+
*/
|
|
6
|
+
export interface LoadedArgument {
|
|
7
|
+
argumentName: string;
|
|
8
|
+
required: boolean;
|
|
9
|
+
isOption: boolean;
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A loaded subcommand definition.
|
|
14
|
+
*/
|
|
15
|
+
export interface LoadedCommand {
|
|
16
|
+
subcommandName: string;
|
|
17
|
+
description: string;
|
|
18
|
+
arguments: LoadedArgument[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A fully loaded capability with its command structure.
|
|
22
|
+
*/
|
|
23
|
+
export interface LoadedCapability {
|
|
24
|
+
commandName: string;
|
|
25
|
+
description: string;
|
|
26
|
+
managesType: string;
|
|
27
|
+
deploymentTargetType: string;
|
|
28
|
+
commands: LoadedCommand[];
|
|
29
|
+
publisher: string;
|
|
30
|
+
package_: string;
|
|
31
|
+
version: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Load all installed capabilities from the capability store.
|
|
35
|
+
*
|
|
36
|
+
* Reads capabilities.lock, loads each capability's .kan.yml from the
|
|
37
|
+
* file cache, parses it, and extracts the Capability instance structure.
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadInstalledCapabilities(fileCache: FileCache, parser: KanonakParser): Promise<LoadedCapability[]>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry in the capabilities lock file tracking an installed capability.
|
|
3
|
+
*/
|
|
4
|
+
export interface CapabilityStoreEntry {
|
|
5
|
+
publisher: string;
|
|
6
|
+
package_: string;
|
|
7
|
+
version: string;
|
|
8
|
+
resolved: string;
|
|
9
|
+
integrity: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The capabilities lock file structure.
|
|
13
|
+
*/
|
|
14
|
+
export interface CapabilityStoreFile {
|
|
15
|
+
version: string;
|
|
16
|
+
lastUpdated: string;
|
|
17
|
+
capabilities: Record<string, CapabilityStoreEntry>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the path to the global capabilities lock file.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getCapabilityStorePath(): string;
|
|
23
|
+
/**
|
|
24
|
+
* Load the capabilities lock file.
|
|
25
|
+
*/
|
|
26
|
+
export declare function loadCapabilityStore(): CapabilityStoreFile;
|
|
27
|
+
/**
|
|
28
|
+
* Save the capabilities lock file.
|
|
29
|
+
*/
|
|
30
|
+
export declare function saveCapabilityStore(store: CapabilityStoreFile): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { LoadedCapability } from './CapabilityLoader.js';
|
|
3
|
+
import type { GenericCapabilityHandler } from './GenericCapabilityHandler.js';
|
|
4
|
+
/**
|
|
5
|
+
* Register Commander.js commands dynamically from loaded capability definitions.
|
|
6
|
+
*
|
|
7
|
+
* For each installed capability, creates a command group (e.g., "skill") with
|
|
8
|
+
* subcommands (e.g., "add", "remove", "list") as defined in the capability's
|
|
9
|
+
* Kanonak YAML. Routes actions to the GenericCapabilityHandler.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerCapabilityCommands(program: Command, capabilities: LoadedCapability[], handler: GenericCapabilityHandler): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { KanonakDocument, InstanceSearchResult } from '@kanonak-protocol/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Information about a deployed instance.
|
|
4
|
+
*/
|
|
5
|
+
export interface DeployedInstance {
|
|
6
|
+
name: string;
|
|
7
|
+
publisher: string;
|
|
8
|
+
package_: string;
|
|
9
|
+
version: string;
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Handler that deploys/undeploys instances of a managed type to the local system.
|
|
14
|
+
*
|
|
15
|
+
* Each deployment target type (e.g., AgentSkillDeployment) has a concrete
|
|
16
|
+
* handler that knows how to transform Kanonak YAML into the target format.
|
|
17
|
+
*/
|
|
18
|
+
export interface DeploymentHandler {
|
|
19
|
+
/**
|
|
20
|
+
* Deploy instances from a Kanonak document to the local system.
|
|
21
|
+
*/
|
|
22
|
+
deploy(doc: KanonakDocument, instances: InstanceSearchResult[], options: Record<string, string>): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Remove a deployed instance by name.
|
|
25
|
+
*/
|
|
26
|
+
undeploy(name: string, options: Record<string, string>): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* List all deployed instances.
|
|
29
|
+
*/
|
|
30
|
+
list(options: Record<string, string>): Promise<DeployedInstance[]>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Registry that maps deployment target type names to handler implementations.
|
|
34
|
+
*/
|
|
35
|
+
export declare class DeploymentHandlerRegistry {
|
|
36
|
+
private readonly handlers;
|
|
37
|
+
/**
|
|
38
|
+
* Register a handler for a deployment target type.
|
|
39
|
+
*/
|
|
40
|
+
register(targetType: string, handler: DeploymentHandler): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get the handler for a deployment target type.
|
|
43
|
+
*/
|
|
44
|
+
get(targetType: string): DeploymentHandler | null;
|
|
45
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { KanonakParser, PublisherIndex } from '@kanonak-protocol/sdk';
|
|
2
|
+
import type { AuthenticatedFetchFn } from '@kanonak-protocol/sdk';
|
|
3
|
+
import { FileCache } from '../cache/FileCache.js';
|
|
4
|
+
import type { LoadedCapability } from './CapabilityLoader.js';
|
|
5
|
+
import type { DeploymentHandlerRegistry } from './DeploymentHandlerRegistry.js';
|
|
6
|
+
/**
|
|
7
|
+
* Generic command handler that implements standard operations (add, remove,
|
|
8
|
+
* list, update, search, info) in a type-agnostic way, parameterized by
|
|
9
|
+
* the capability definition.
|
|
10
|
+
*/
|
|
11
|
+
export declare class GenericCapabilityHandler {
|
|
12
|
+
private readonly fileCache;
|
|
13
|
+
private readonly parser;
|
|
14
|
+
private readonly publisherIndex;
|
|
15
|
+
private readonly fetchFn;
|
|
16
|
+
private readonly handlerRegistry;
|
|
17
|
+
constructor(fileCache: FileCache, parser: KanonakParser, publisherIndex: PublisherIndex, fetchFn: AuthenticatedFetchFn, handlerRegistry: DeploymentHandlerRegistry);
|
|
18
|
+
/**
|
|
19
|
+
* Install instances of the managed type from a Kanonak publisher.
|
|
20
|
+
*/
|
|
21
|
+
add(capability: LoadedCapability, packageRef: string, options: Record<string, string>): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Remove a deployed instance by name.
|
|
24
|
+
*/
|
|
25
|
+
remove(capability: LoadedCapability, name: string, options: Record<string, string>): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* List deployed instances.
|
|
28
|
+
*/
|
|
29
|
+
list(capability: LoadedCapability, options: Record<string, string>): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Update deployed instances to latest versions.
|
|
32
|
+
*/
|
|
33
|
+
update(capability: LoadedCapability, name: string | undefined, options: Record<string, string>): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Search for instances of the managed type from a publisher.
|
|
36
|
+
*/
|
|
37
|
+
search(capability: LoadedCapability, options: Record<string, string>): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Show details about a deployed instance.
|
|
40
|
+
*/
|
|
41
|
+
info(capability: LoadedCapability, name: string, options: Record<string, string>): Promise<void>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Create the built-in "capability" command group.
|
|
4
|
+
*
|
|
5
|
+
* This is the bootstrap mechanism — always available, not dynamically loaded.
|
|
6
|
+
* Used to install, remove, and list capabilities that extend the CLI.
|
|
7
|
+
*/
|
|
8
|
+
export declare function capabilityCommand(): Command;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install a package and its transitive dependencies to the local cache.
|
|
3
|
+
*
|
|
4
|
+
* Usage: kanonak install kanonak.org/core-rdf@1.0.1
|
|
5
|
+
* kanonak install kanonak.org/core-rdf (resolves latest compatible)
|
|
6
|
+
* kanonak install (install from kanonak.lock)
|
|
7
|
+
*/
|
|
8
|
+
export declare function installCommand(packageRef?: string): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kanonak login <publisher>
|
|
3
|
+
*
|
|
4
|
+
* Authenticate with a package publisher using OAuth 2.0.
|
|
5
|
+
* Supports Dynamic Client Registration (RFC 7591), PKCE (RFC 7636),
|
|
6
|
+
* and DPoP proof-of-possession tokens (RFC 9449).
|
|
7
|
+
*
|
|
8
|
+
* Credentials are stored in the OS credential store (macOS Keychain,
|
|
9
|
+
* Windows Credential Manager, or Linux Secret Service).
|
|
10
|
+
*/
|
|
11
|
+
export declare function loginCommand(publisher: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validateCommand(path: string): Promise<void>;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
import{Command as mn}from"commander";import{readFileSync as fn}from"fs";import{fileURLToPath as hn}from"url";import{dirname as yn,join as kn}from"path";import{KanonakParser as bn,PublisherIndex as wn,CredentialStore as vn,createAuthenticatedFetch as $n}from"@kanonak-protocol/sdk";import{readFileSync as Me,statSync as We}from"fs";import{resolve as qe,dirname as Ge}from"path";import{KanonakParser as Be,KanonakObjectValidator as Je,ValidationSeverity as oe}from"@kanonak-protocol/sdk";import{readFileSync as Le,readdirSync as Ne}from"fs";import{join as je,dirname as Ke,basename as ne}from"path";import{InMemoryKanonakDocumentRepository as Ue,FileSystemKanonakDocumentRepository as He,HttpKanonakDocumentRepository as Ve,getGlobalCachePath as ze}from"@kanonak-protocol/sdk";import{mkdirSync as Ie,readFileSync as ee,writeFileSync as te,existsSync as q,appendFileSync as Ae}from"fs";import{join as _e,dirname as Oe,isAbsolute as Fe}from"path";import{getGlobalCachePath as Ee}from"@kanonak-protocol/sdk";var $=class{constructor(e=Ee()){this.cacheDir=e;this.isProjectLocal=!Fe(e)}cacheDir;gitignoreChecked=!1;isProjectLocal;get(e,t,o){let r=this.getPath(e,t,o);return q(r)?ee(r,"utf-8"):null}put(e,t,o,r){let s=this.getPath(e,t,o);Ie(Oe(s),{recursive:!0}),te(s,r,"utf-8"),this.isProjectLocal&&this.ensureGitignore()}has(e,t,o){return q(this.getPath(e,t,o))}ensureGitignore(){if(this.gitignoreChecked)return;this.gitignoreChecked=!0;let e=".gitignore",t=this.cacheDir;if(q(e)){if(ee(e,"utf-8").split(`
|
|
4
|
+
`).some(r=>r.trim()===t||r.trim()===t+"/"))return;Ae(e,`
|
|
5
|
+
${t}/
|
|
6
|
+
`)}else te(e,`${t}/
|
|
7
|
+
`)}getPath(e,t,o){return _e(this.cacheDir,e,`${t}@${o}.kan.yml`)}};function O(n){let e=n;for(;;){let t=Ke(e);if(t===e)break;if(ne(e).includes(".")||ne(t).includes(".")){e=t;continue}break}return e}function N(n,e){for(let t of Ne(n,{withFileTypes:!0})){let o=je(n,t.name);t.isDirectory()&&t.name!=="node_modules"&&t.name!==".kanonak"?N(o,e):t.name.endsWith(".kan.yml")&&e.push(o)}}var G=class{repos;constructor(...e){this.repos=e}async getHighestCompatibleVersionAsync(e,t){for(let o of this.repos)try{let r=await o.getHighestCompatibleVersionAsync(e,t);if(r)return r}catch{}return null}async getAllDocumentsAsync(){let e=[];for(let t of this.repos)try{e.push(...await t.getAllDocumentsAsync())}catch{}return e}async getDocumentAsync(e){for(let t of this.repos)try{let o=await t.getDocumentAsync(e);if(o)return o}catch{}return null}async getDocumentsByNamespaceAsync(e,t){for(let o of this.repos)try{let r=await o.getDocumentsByNamespaceAsync(e,t);if(r.length>0)return r}catch{}return[]}async saveDocumentAsync(e,t){await this.repos[0].saveDocumentAsync(e,t)}async deleteDocumentAsync(e){await this.repos[0].deleteDocumentAsync(e)}async clearNamespaceAsync(e,t){await this.repos[0].clearNamespaceAsync(e,t)}async getAllDocumentReferencesAsync(){return this.repos[0].getAllDocumentReferencesAsync()}async getDocumentContentAsync(e){for(let t of this.repos)try{let o=await t.getDocumentContentAsync(e);if(o)return o}catch{}return null}async getDocumentUriAsync(e){for(let t of this.repos)try{let o=await t.getDocumentUriAsync(e);if(o)return o}catch{}return null}};async function j(n,e){let t=[];N(n,t);let o=new Ue(e);for(let a of t)try{let c=Le(a,"utf-8"),l=e.parse(c),d=l.metadata.namespace_;if(d){let p=`${d.publisher}/${d.package_}@${d.version}`;await o.saveDocumentAsync(l,p)}}catch{}let r=new He(ze(),!0,e),s=new $,i=new Ve({getFromCache:(a,c,l)=>s.get(a,c,l),onFetch:(a,c,l,d)=>s.put(a,c,l,d)});return new G(o,r,i)}async function re(n){let e=qe(n),t=We(e),o=t.isDirectory()?O(e):O(Ge(e)),r=new Be,s=new Je(r),i=await j(o,r),a=[];if(t.isDirectory()?N(e,a):a.push(e),a.length===0){console.log("No .kan.yml files found.");return}let c=0,l=0;for(let d of a){let p=Me(d,"utf-8"),u=r.parseWithErrors(p);if(u.errors&&u.errors.length>0){console.log(`
|
|
8
|
+
${d}:`);for(let f of u.errors)console.log(` ERROR (parse): ${f.message} [line ${f.line}:${f.column}]`),c++;continue}let y=u.document;if(!y){console.log(`
|
|
9
|
+
${d}:`),console.log(" ERROR: Failed to parse document"),c++;continue}let m=await s.validateAsync(y,i),g=m.errors.filter(f=>f.severity===oe.Error),k=m.errors.filter(f=>f.severity===oe.Warning);if(g.length>0||k.length>0){let f=y.metadata.namespace_,h=f?`${f.publisher}/${f.package_}@${f.version}`:d;console.log(`
|
|
10
|
+
${h}:`);for(let b of g)console.log(` ERROR: ${b.message}`),b.suggestion&&console.log(` -> ${b.suggestion}`);for(let b of k)console.log(` WARN: ${b.message}`)}c+=g.length,l+=k.length}console.log(`
|
|
11
|
+
${a.length} file(s) validated. ${c} error(s), ${l} warning(s).`),c>0&&process.exit(1)}import{KanonakParser as tt,PublisherIndex as nt,CredentialStore as ot,createAuthenticatedFetch as rt}from"@kanonak-protocol/sdk";import{readFileSync as Xe,writeFileSync as Ye,existsSync as Qe}from"fs";import{createHash as Ze}from"crypto";import se from"js-yaml";var B="kanonak.lock",et=`# This file is generated by Kanonak CLI. Do not edit manually.
|
|
12
|
+
`;function ie(){if(!Qe(B))return null;let n=Xe(B,"utf-8"),e=se.load(n);return!e||typeof e!="object"||e.version!=="1"?null:{version:"1",lastUpdated:e.lastUpdated??new Date().toISOString(),packages:e.packages??{}}}function ae(n){n.lastUpdated=new Date().toISOString();let e={};for(let o of Object.keys(n.packages).sort())e[o]=n.packages[o];n.packages=e;let t=se.dump(n,{lineWidth:-1,sortKeys:!1,quotingType:'"'});Ye(B,et+t,"utf-8")}function T(n){return`sha256:${Ze("sha256").update(n).digest("hex")}`}function D(n){let e=n.indexOf("@"),t,o=null;e!==-1?(t=n.substring(0,e),o=n.substring(e+1)||null):t=n;let r=t.indexOf("/");if(r===-1)return null;let s=t.substring(0,r),i=t.substring(r+1);return!s||!i?null:{publisher:s,packageName:i,version:o}}async function ce(n){let e=new $,t=new tt,o=new ot,r=rt(o),s=new nt({fetchFn:r}),i=ie()??{version:"1",lastUpdated:new Date().toISOString(),packages:{}};if(!n){Object.keys(i.packages).length===0&&(console.error("No kanonak.lock file found or lock file is empty."),console.error("Usage: kanonak install {publisher}/{package}[@{version}]"),process.exit(1)),console.log(`Installing ${Object.keys(i.packages).length} package(s) from kanonak.lock...`);for(let[y,m]of Object.entries(i.packages)){let g=y.indexOf("/"),k=y.substring(0,g),f=y.substring(g+1),h=e.get(k,f,m.version);if(h)console.log(` ${y}@${m.version} (cached)`);else{let b=m.resolved,w=await r(b,k);if(!w.ok)throw new Error(`Failed to fetch ${b} (${w.status} ${w.statusText})`);h=await w.text(),e.put(k,f,m.version,h),console.log(` ${y}@${m.version}`)}}console.log(`
|
|
13
|
+
Installed ${Object.keys(i.packages).length} package(s) from lock file.`);return}let a=D(n);a||(console.error(`Invalid package reference: ${n}`),console.error("Expected format: {publisher}/{package}@{version} or {publisher}/{package}"),process.exit(1));let{publisher:c,packageName:l,version:d}=a,p=d??await s.getHighestVersion(c,l);p||(console.error(`Could not resolve version for ${c}/${l}`),process.exit(1)),console.log(`Installing ${c}/${l}@${p}...`);let u=new Set;await le(c,l,p,e,t,s,r,u,i),ae(i),console.log(`
|
|
14
|
+
Installed ${u.size} package(s).`)}async function le(n,e,t,o,r,s,i,a,c){let l=`${n}/${e}@${t}`;if(a.has(l))return;a.add(l);let d=`${n}/${e}`,p=o.get(n,e,t),u;if(p)console.log(` ${l} (cached)`),u=await s.getPackageUrl(n,e,t);else{u=await s.getPackageUrl(n,e,t);let g=await i(u,n);if(!g.ok)throw new Error(`Failed to fetch ${u} (${g.status} ${g.statusText})`);p=await g.text(),o.put(n,e,t,p),console.log(` ${l}`)}let y={},m=r.parse(p);if(m.metadata.imports)for(let[g,k]of Object.entries(m.metadata.imports))for(let f of k){let h=await s.resolveVersion(g,f);h?(y[`${g}/${f.packageName}`]=h,await le(g,f.packageName,h,o,r,s,i,a,c)):console.error(` WARNING: Could not resolve ${g}/${f.packageName} ${f.package_}`)}c.packages[d]={version:t,resolved:u,integrity:T(p),dependencies:y}}import{readFileSync as st}from"fs";import{resolve as it,dirname as at}from"path";import{KanonakParser as ct}from"@kanonak-protocol/sdk";async function de(n){let e=it(n),t=st(e,"utf-8"),o=new ct,r=o.parse(t),s=r.metadata.namespace_,i=s?`${s.publisher}/${s.package_}@${s.version}`:n;if(console.log(i),!r.metadata.imports){console.log(" (no imports)");return}let a=O(at(e)),c=await j(a,o),l=new Set;for(let[d,p]of Object.entries(r.metadata.imports))for(let u of p)await pe(d,u,c,l," ")}async function pe(n,e,t,o,r){let s=`${n}/${e.packageName}`;if(o.has(s)){console.log(`${r}${s} (${e.package_}) [circular]`);return}o.add(s);let i;try{i=await t.getHighestCompatibleVersionAsync(n,e)}catch{console.log(`${r}${s} (${e.package_}) [fetch failed]`);return}if(!i){console.log(`${r}${s} (${e.package_}) [not found]`);return}let a=i.metadata.namespace_?.version?.toString()??"?";if(console.log(`${r}${n}/${e.packageName}@${a}`),i.metadata.imports)for(let[c,l]of Object.entries(i.metadata.imports))for(let d of l)await pe(c,d,t,o,r+" ")}import{createHash as dt,randomBytes as me}from"crypto";import{createServer as pt}from"http";import{execFile as J}from"child_process";import{CredentialStore as ut,generateDPoPKeyPair as gt,createDPoPProof as ue,serverSupportsDPoP as mt}from"@kanonak-protocol/sdk";import{normalizeHost as lt}from"@kanonak-protocol/sdk";var P=class{cache=new Map;async discover(e){let t=lt(e);if(this.cache.has(t))return this.cache.get(t);let o=`https://${t}/.well-known/oauth-authorization-server`,r=await this.tryEndpoint(o);if(!r){let s=`https://${t}/.well-known/openid-configuration`;r=await this.tryEndpoint(s)}return this.cache.set(t,r),r}async supportsOAuth(e){return await this.discover(e)!==null}static supportsPkceS256(e){return e.codeChallengeMethodsSupported?.some(t=>t.toUpperCase()==="S256")??!1}static supportsDynamicRegistration(e){return!!e.registrationEndpoint}static supportsAuthorizationCode(e){return e.responseTypesSupported?.some(t=>t.toLowerCase()==="code")??!1}async tryEndpoint(e){let t;try{t=await fetch(e)}catch(r){let s=r instanceof Error?r.message:String(r);return s.includes("ENOTFOUND")||s.includes("ECONNREFUSED")?(console.error(` OAuth discovery: ${e} \u2014 host unreachable (${s})`),console.error(" If the server is behind a VPN, ensure you are connected.")):s.includes("CERT")||s.includes("SSL")||s.includes("TLS")?(console.error(` OAuth discovery: ${e} \u2014 TLS error (${s})`),console.error(" If using a custom CA certificate, set NODE_EXTRA_CA_CERTS=/path/to/ca.pem")):console.error(` OAuth discovery: ${e} \u2014 network error: ${s}`),null}if(!t.ok)return t.status===404||(t.status===403?(console.error(` OAuth discovery: ${e} \u2014 HTTP 403 Forbidden`),console.error(" Access may be blocked by a firewall, proxy, or WAF.")):t.status>=500&&(console.error(` OAuth discovery: ${e} \u2014 HTTP ${t.status} server error`),console.error(" The authorization server returned an internal error. Contact your IDP administrator."))),null;let o;try{o=await t.json()}catch{return console.error(` OAuth discovery: ${e} \u2014 response is not valid JSON`),console.error(" The endpoint may be returning HTML instead of JSON. Check IDP configuration."),null}return{issuer:F(o.issuer),authorizationEndpoint:F(o.authorization_endpoint),tokenEndpoint:F(o.token_endpoint),registrationEndpoint:F(o.registration_endpoint),revocationEndpoint:F(o.revocation_endpoint),scopesSupported:x(o.scopes_supported),responseTypesSupported:x(o.response_types_supported),grantTypesSupported:x(o.grant_types_supported),codeChallengeMethodsSupported:x(o.code_challenge_methods_supported),tokenEndpointAuthMethodsSupported:x(o.token_endpoint_auth_methods_supported),dpopSigningAlgValuesSupported:x(o.dpop_signing_alg_values_supported)}}};function F(n){return typeof n=="string"?n:null}function x(n){return Array.isArray(n)?n.filter(e=>typeof e=="string"):null}var A=class{discovery;credentialStore;constructor(e,t){this.discovery=e??new P,this.credentialStore=t??new ut}async authorize(e,t=[]){console.log(`Starting OAuth flow for ${e}...`);let o=await this.discovery.discover(e);if(!o)return v(`No OAuth discovery endpoint found for '${e}'.
|
|
15
|
+
The server must expose one of:
|
|
16
|
+
- https://${e}/.well-known/oauth-authorization-server (RFC 8414)
|
|
17
|
+
- https://${e}/.well-known/openid-configuration (OpenID Connect)
|
|
18
|
+
Verify the hostname is correct and HTTPS is configured.
|
|
19
|
+
If using a corporate proxy, ensure it is not blocking the discovery request.`);let r=[];if(o.authorizationEndpoint||r.push("authorization_endpoint"),o.tokenEndpoint||r.push("token_endpoint"),r.length>0)return v(`OAuth metadata for '${e}' is missing required fields: ${r.join(", ")}.
|
|
20
|
+
Contact the IDP administrator to ensure these are included in the discovery document.`);let s=mt(o.dpopSigningAlgValuesSupported),i=null;if(s){console.log(" Server supports DPoP (RFC 9449) \u2014 generating key pair...");try{i=gt()}catch(C){return v(`Failed to generate DPoP key pair: ${I(C)}
|
|
21
|
+
Ensure your Node.js installation supports EC P-256 curves.`)}}let{redirectUri:a,port:c,waitForCallback:l,close:d}=await vt(),p=await this.credentialStore.getCredential(e),u=p?.clientId??null,y=p?.clientSecret??null;if(!u&&o.registrationEndpoint){console.log(" Registering dynamic OAuth client (RFC 7591)...");let C=await this.registerClient(o.registrationEndpoint,a);if(!C.success)return d(),v(C.error);u=C.clientId,y=C.clientSecret??null}if(!u)return d(),v(`No OAuth client credentials for '${e}' and the server does not support dynamic client registration (no registration_endpoint in metadata).
|
|
22
|
+
You must pre-register a client with your IDP and configure the client_id.
|
|
23
|
+
Ask your IDP administrator to enable dynamic client registration (RFC 7591),
|
|
24
|
+
or add a registration_endpoint to the OAuth metadata.`);let m=ht(),g=yt(m),k=kt(),f=bt(o.authorizationEndpoint,u,a,t,k,g);console.log(` Opening browser for authorization on port ${c}...`),console.log(" If the browser doesn't open, navigate to:"),console.log(` ${f}`),St(f);let h=await l();if(d(),!h)return v(`Authorization timed out after 5 minutes.
|
|
25
|
+
The browser authorization was not completed in time.
|
|
26
|
+
Re-run 'kanonak login ${e}' and complete the browser authorization promptly.`);if(!h.code){let C=h.error?`: ${h.error}`:"";return v(`No authorization code received${C}.
|
|
27
|
+
The IDP may have rejected the authorization request.
|
|
28
|
+
Check the IDP admin console for error details.`)}if(h.state!==k)return v(`Authorization state mismatch \u2014 possible CSRF attack.
|
|
29
|
+
The response did not match the expected session.
|
|
30
|
+
Re-run 'kanonak login ${e}' to start a fresh authorization flow.`);console.log(" Exchanging authorization code for tokens...");let b=await this.exchangeCode(o.tokenEndpoint,u,y,h.code,a,m,i);if(!b.success)return v(b.error);let w={clientId:u,clientSecret:y,accessToken:b.tokens.accessToken,refreshToken:b.tokens.refreshToken,expiresAt:b.tokens.expiresIn?new Date(Date.now()+b.tokens.expiresIn*1e3).toISOString():null,tokenEndpoint:o.tokenEndpoint,dpopKeyPair:i};return await this.credentialStore.store(e,w),console.log(` Successfully authenticated with ${e}`),s&&console.log(" DPoP proof-of-possession is active"),{success:!0,host:e}}async refresh(e){let t=await this.credentialStore.getCredential(e);if(!t)return v(`No stored credentials for '${e}'. Run 'kanonak login ${e}' to authenticate first.`);if(!t.refreshToken)return v(`No refresh token available for '${e}'.
|
|
31
|
+
The authorization server may not issue refresh tokens for this client.
|
|
32
|
+
Run 'kanonak login ${e}' to re-authenticate.`);if(!t.clientId)return v(`No client ID in stored credentials for '${e}'.
|
|
33
|
+
The stored credential may be corrupted. Run 'kanonak logout ${e}' then 'kanonak login ${e}'.`);let o=t.tokenEndpoint;if(!o){let s=await this.discovery.discover(e);if(!s?.tokenEndpoint)return v(`Cannot find token endpoint for '${e}'.
|
|
34
|
+
The OAuth discovery endpoint may be unreachable.
|
|
35
|
+
Check network connectivity and run 'kanonak login ${e}' to re-authenticate.`);o=s.tokenEndpoint}let r=await this.refreshTokenRequest(o,t.clientId,t.clientSecret,t.refreshToken,t.dpopKeyPair);return r.success?(t.accessToken=r.tokens.accessToken,r.tokens.refreshToken&&(t.refreshToken=r.tokens.refreshToken),t.expiresAt=r.tokens.expiresIn?new Date(Date.now()+r.tokens.expiresIn*1e3).toISOString():null,t.tokenEndpoint=o,await this.credentialStore.store(e,t),{success:!0,host:e}):v(`${r.error}
|
|
36
|
+
The refresh token may have expired. Run 'kanonak login ${e}' to re-authenticate.`)}async logout(e){let t=await this.credentialStore.getCredential(e);if(!t)return v(`No stored credentials for '${e}'.
|
|
37
|
+
You may not be logged in, or credentials may be stored in a different backend.`);let o=await this.discovery.discover(e);if(o?.revocationEndpoint&&t.accessToken&&t.clientId)try{await this.revokeToken(o.revocationEndpoint,t.accessToken,t.clientId)}catch{console.warn(` Warning: Token revocation failed for '${e}'. The token has been removed locally but may still be valid on the server until it expires.`)}return await this.credentialStore.remove(e),{success:!0,host:e}}async registerClient(e,t){let o;try{o=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_name:"Kanonak CLI",redirect_uris:[t],grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"none"})})}catch(i){return{success:!1,error:`Dynamic client registration request to ${e} failed: ${I(i)}
|
|
38
|
+
Ensure the IDP is reachable and HTTPS is properly configured.
|
|
39
|
+
If using a corporate proxy or custom CA, set NODE_EXTRA_CA_CERTS.`}}if(!o.ok){let i=await X(o);return{success:!1,error:`Dynamic client registration (RFC 7591) failed.
|
|
40
|
+
Endpoint: ${e}
|
|
41
|
+
HTTP ${o.status}: ${i}
|
|
42
|
+
The IDP may not support dynamic registration for public clients.
|
|
43
|
+
Ask your IDP administrator to enable RFC 7591 support, or pre-register a client manually.`}}let r=await o.json(),s=r.client_id;return s?{success:!0,clientId:s,clientSecret:r.client_secret}:{success:!1,error:`Dynamic client registration response from ${e} did not contain a client_id.
|
|
44
|
+
The IDP response may be malformed. Contact your IDP administrator.`}}async exchangeCode(e,t,o,r,s,i,a){let c=new URLSearchParams({grant_type:"authorization_code",client_id:t,code:r,redirect_uri:s,code_verifier:i});o&&c.set("client_secret",o);let l={"Content-Type":"application/x-www-form-urlencoded"};if(a)try{l.DPoP=ue(a.privateKey,a.publicKey,"POST",e)}catch(p){return{success:!1,error:`Failed to create DPoP proof for token exchange: ${I(p)}
|
|
45
|
+
The stored key pair may be corrupted. Run 'kanonak login' to re-authenticate.`}}let d;try{d=await fetch(e,{method:"POST",headers:l,body:c.toString()})}catch(p){return{success:!1,error:`Token exchange request to ${e} failed: ${I(p)}
|
|
46
|
+
Ensure the token endpoint is reachable.`}}if(!d.ok){let p=await X(d),u=ft(p);return{success:!1,error:`Token exchange failed.
|
|
47
|
+
Endpoint: ${e}
|
|
48
|
+
HTTP ${d.status}: ${p}`+(u?`
|
|
49
|
+
${u}`:"")}}return{success:!0,tokens:ge(await d.json())}}async refreshTokenRequest(e,t,o,r,s){let i=new URLSearchParams({grant_type:"refresh_token",client_id:t,refresh_token:r});o&&i.set("client_secret",o);let a={"Content-Type":"application/x-www-form-urlencoded"};if(s)try{a.DPoP=ue(s.privateKey,s.publicKey,"POST",e)}catch(l){return{success:!1,error:`Failed to create DPoP proof for token refresh: ${I(l)}`}}let c;try{c=await fetch(e,{method:"POST",headers:a,body:i.toString()})}catch(l){return{success:!1,error:`Token refresh request to ${e} failed: ${I(l)}`}}if(!c.ok){let l=await X(c);return{success:!1,error:`Token refresh failed \u2014 HTTP ${c.status}: ${l}`}}return{success:!0,tokens:ge(await c.json())}}async revokeToken(e,t,o){await fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:t,client_id:o}).toString()})}};function v(n){return{success:!1,error:n}}function ge(n){return{accessToken:n.access_token,refreshToken:n.refresh_token,expiresIn:typeof n.expires_in=="number"?n.expires_in:void 0}}function I(n){return n instanceof Error?n.message:String(n)}async function X(n){try{return await n.text()}catch{return"(could not read response body)"}}function ft(n){try{let e=JSON.parse(n);switch(e.error){case"invalid_grant":return"Hint: The authorization code may have expired or already been used. Re-run the login flow.";case"invalid_client":return"Hint: The client credentials were rejected. The client_id may be invalid or the client_secret may be wrong.";case"redirect_uri_mismatch":return"Hint: The redirect_uri does not match what was registered with the IDP.";case"unsupported_grant_type":return"Hint: The IDP does not support authorization_code grants. Contact your IDP administrator.";case"invalid_scope":return"Hint: One or more requested scopes are not allowed. Check the scopes configured on the IDP.";case"use_dpop_nonce":return"Hint: The server requires a DPoP nonce. This should be handled automatically \u2014 please report this as a bug.";default:return e.error_description?`IDP says: ${e.error_description}`:null}}catch{return null}}function ht(){return me(32).toString("base64url")}function yt(n){return dt("sha256").update(n).digest("base64url")}function kt(){return me(16).toString("base64url")}function bt(n,e,t,o,r,s){let i=new URLSearchParams({client_id:e,response_type:"code",redirect_uri:t,scope:o.join(" "),state:r,code_challenge:s,code_challenge_method:"S256"});return`${n}?${i}`}var wt=300*1e3;async function vt(){return new Promise(n=>{let e=pt((r,s)=>{let i=new URL(r.url,"http://localhost"),a=i.searchParams.get("code")??void 0,c=i.searchParams.get("state")??void 0,l=i.searchParams.get("error")??i.searchParams.get("error_description")??void 0,d=l?$t(l):null,p=d?`<html><body><h1>Authorization failed</h1><p>${d}</p></body></html>`:"<html><body><h1>Authorization successful!</h1><p>You can close this window.</p></body></html>";s.writeHead(200,{"Content-Type":"text/html"}),s.end(p),t({code:a,state:c,error:l})}),t,o=new Promise(r=>{t=r,setTimeout(()=>{r(null),e.close()},wt)});e.listen(0,"127.0.0.1",()=>{let r=e.address();n({redirectUri:`http://localhost:${r.port}/callback`,port:r.port,waitForCallback:()=>o,close:()=>e.close()})})})}function $t(n){return n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function St(n){try{process.platform==="win32"?J("cmd",["/c","start","",n]):process.platform==="darwin"?J("open",[n]):J("xdg-open",[n])}catch{console.log(" Could not open browser automatically. Please navigate to the URL above manually.")}}import{CredentialStore as Ct}from"@kanonak-protocol/sdk";async function fe(n){try{let e=new P,t=new Ct,r=await new A(e,t).authorize(n);r.success?console.log(`
|
|
50
|
+
Authenticated with ${n}.`):(console.error(`
|
|
51
|
+
Authentication failed:
|
|
52
|
+
${r.error}`),process.exit(1))}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`
|
|
53
|
+
Authentication failed unexpectedly:
|
|
54
|
+
${t}`),console.error(`
|
|
55
|
+
If this persists, file an issue at https://github.com/kanonak-protocol/typescript/issues`),process.exit(1)}}import{CredentialStore as Pt}from"@kanonak-protocol/sdk";async function he(n){try{let e=new P,t=new Pt,r=await new A(e,t).logout(n);r.success?console.log(`Logged out from ${n}.`):(console.error(`
|
|
56
|
+
Logout failed:
|
|
57
|
+
${r.error}`),process.exit(1))}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`
|
|
58
|
+
Logout failed unexpectedly:
|
|
59
|
+
${t}`),process.exit(1)}}import{Command as _t}from"commander";import{KanonakParser as Ot,PublisherIndex as Ft,CredentialStore as Et,createAuthenticatedFetch as Lt}from"@kanonak-protocol/sdk";import{readFileSync as Rt,writeFileSync as Tt,existsSync as Dt}from"fs";import{join as xt}from"path";import ye from"js-yaml";import{getGlobalCachePath as It}from"@kanonak-protocol/sdk";var At=`# This file is generated by Kanonak CLI. Do not edit manually.
|
|
60
|
+
`;function ke(){let n=It();return xt(n,"..","capabilities.lock")}function _(){let n=ke();if(!Dt(n))return{version:"1",lastUpdated:new Date().toISOString(),capabilities:{}};let e=Rt(n,"utf-8"),t=ye.load(e);return!t||typeof t!="object"||t.version!=="1"?{version:"1",lastUpdated:new Date().toISOString(),capabilities:{}}:{version:"1",lastUpdated:t.lastUpdated??new Date().toISOString(),capabilities:t.capabilities??{}}}function Y(n){n.lastUpdated=new Date().toISOString();let e={};for(let o of Object.keys(n.capabilities).sort())e[o]=n.capabilities[o];n.capabilities=e;let t=ye.dump(n,{lineWidth:-1,sortKeys:!1,quotingType:'"'});Tt(ke(),At+t,"utf-8")}function be(){let n=new _t("capability").description("Manage Kanonak CLI capabilities (pluggable command groups)");return n.command("add <package>").description("Install a capability from a Kanonak publisher").action(async e=>{await Nt(e)}),n.command("remove <name>").description("Remove an installed capability").action(async e=>{await jt(e)}),n.command("list").description("List installed capabilities").action(async()=>{await Kt()}),n}async function Nt(n){let e=D(n);e||(console.error(`Invalid package reference: ${n}`),console.error("Expected format: {publisher}/{package}@{version} or {publisher}/{package}"),process.exit(1));let{publisher:t,packageName:o,version:r}=e,s=new $,i=new Ot,a=new Et,c=Lt(a),l=new Ft({fetchFn:c}),d=r??await l.getHighestVersion(t,o);d||(console.error(`Could not resolve version for ${t}/${o}`),process.exit(1)),console.log(`Installing capability ${t}/${o}@${d}...`);let p=s.get(t,o,d);if(!p){let g=await l.getPackageUrl(t,o,d),k=await c(g,t);if(!k.ok)throw new Error(`Failed to fetch ${g} (${k.status} ${k.statusText})`);p=await k.text(),s.put(t,o,d,p)}let u=i.parse(p),y=null;for(let[,g]of Object.entries(u.body)){if(!g||typeof g!="object")continue;let k=String(g.type??"");if(k==="Capability"||k.endsWith(".Capability")){y=g.commandName??null;break}}y||(console.error(`Package ${t}/${o}@${d} does not contain a Capability instance.`),process.exit(1));let m=_();m.capabilities[y]={publisher:t,package_:o,version:d,resolved:await l.getPackageUrl(t,o,d),integrity:T(p)},Y(m),console.log(`
|
|
61
|
+
Installed capability "${y}".`),console.log(`Run "kanonak ${y} --help" to get started.`)}async function jt(n){let e=_();e.capabilities[n]||(console.error(`Capability "${n}" is not installed.`),process.exit(1));let t=e.capabilities[n];delete e.capabilities[n],Y(e),console.log(`Removed capability "${n}" (${t.publisher}/${t.package_}@${t.version}).`)}async function Kt(){let n=_(),e=Object.entries(n.capabilities);if(e.length===0){console.log("No capabilities installed."),console.log("Install one with: kanonak capability add {publisher}/{package}");return}console.log(`Installed capabilities:
|
|
62
|
+
`);for(let[t,o]of e)console.log(` ${t} ${o.publisher}/${o.package_}@${o.version}`)}async function we(n,e){let t=_(),o=[];for(let[r,s]of Object.entries(t.capabilities)){let i=n.get(s.publisher,s.package_,s.version);if(!i){console.error(` WARNING: Cached content missing for capability "${r}" (${s.publisher}/${s.package_}@${s.version})`);continue}try{let a=e.parse(i),c=Ut(a.body,r,s.publisher,s.package_,s.version);c&&o.push(c)}catch(a){console.error(` WARNING: Failed to parse capability "${r}": ${a}`)}}return o}function Ut(n,e,t,o,r){for(let[,s]of Object.entries(n)){if(!s||typeof s!="object")continue;let i=String(s.type??"");if(i!=="Capability"&&!i.endsWith(".Capability"))continue;let a=s.commandName??e,c=s.description??"",l=Ht(s.managesType),d=Vt(s.deploymentTarget),p=zt(s.hasCommand);return{commandName:a,description:c,managesType:l,deploymentTargetType:d,commands:p,publisher:t,package_:o,version:r}}return null}function Ht(n){return typeof n=="string"?n:""}function Vt(n){if(!n)return"";if(typeof n=="string")return n;if(typeof n=="object"&&n!==null){let e=n;return e.type?String(e.type):"AgentSkillDeployment"}return""}function zt(n){return Array.isArray(n)?n.map(e=>({subcommandName:String(e.subcommandName??""),description:String(e.description??""),arguments:Mt(e.hasArgument)})):[]}function Mt(n){return Array.isArray(n)?n.map(e=>({argumentName:String(e.argumentName??""),required:e.isRequired===!0,isOption:e.isOption===!0,...e.defaultValue!=null?{defaultValue:String(e.defaultValue)}:{}})):[]}import{Command as ve}from"commander";function $e(n,e,t){for(let o of e){let r=new ve(o.commandName).description(o.description.trim());for(let s of o.commands){let i=r.command(Wt(s));i.description(s.description);for(let a of s.arguments)if(a.isOption){let c=a.defaultValue!=null?`--${a.argumentName} [value]`:`--${a.argumentName} <value>`;i.option(c,"",a.defaultValue)}i.action(async(...a)=>{let c=qt(a);await Gt(t,o,s.subcommandName,a,c)})}n.addCommand(r)}}function Wt(n){let e=n.arguments.filter(t=>!t.isOption).map(t=>t.required?`<${t.argumentName}>`:`[${t.argumentName}]`);return[n.subcommandName,...e].join(" ")}function qt(n){for(let e=n.length-1;e>=0;e--)if(n[e]&&typeof n[e]=="object"&&!(n[e]instanceof ve))return n[e];return{}}async function Gt(n,e,t,o,r){let s=[];for(let i of o)if(typeof i=="string")s.push(i);else break;switch(t){case"add":await n.add(e,s[0],r);break;case"remove":await n.remove(e,s[0],r);break;case"list":await n.list(e,r);break;case"update":await n.update(e,s[0],r);break;case"search":await n.search(e,r);break;case"info":await n.info(e,s[0],r);break;default:console.error(`Unknown subcommand: ${t}`),process.exit(1)}}import{PublisherIndex as Q,PublisherConfigResolver as Bt,InMemoryKanonakDocumentRepository as Se,findInstancesByType as Ce}from"@kanonak-protocol/sdk";var K=class{constructor(e,t,o,r,s){this.fileCache=e;this.parser=t;this.publisherIndex=o;this.fetchFn=r;this.handlerRegistry=s}fileCache;parser;publisherIndex;fetchFn;handlerRegistry;async add(e,t,o){let r=D(t);r||(console.error(`Invalid package reference: ${t}`),console.error("Expected format: {publisher}/{package}@{version} or {publisher}/{package}"),process.exit(1));let{publisher:s,packageName:i,version:a}=r,c=a??await this.publisherIndex.getHighestVersion(s,i);c||(console.error(`Could not resolve version for ${s}/${i}`),process.exit(1));let l=this.fileCache.get(s,i,c);if(!l){let f=await this.publisherIndex.getPackageUrl(s,i,c),h=await this.fetchFn(f,s);if(!h.ok)throw new Error(`Failed to fetch ${f} (${h.status} ${h.statusText})`);l=await h.text(),this.fileCache.put(s,i,c,l)}let d=this.parser.parse(l),p=new Se(this.parser),u=d.metadata.namespace_,y=u?.version?`${u.version.major}.${u.version.minor}.${u.version.patch}`:"0.0.0",m=u?`${u.publisher}/${u.package_}/${y}`:t;await p.saveDocumentAsync(d,m);let g=await Ce(p,e.managesType);g.length===0&&(console.error(`Package ${s}/${i}@${c} does not contain any ${e.managesType} instances.`),process.exit(1));let k=this.handlerRegistry.get(e.deploymentTargetType);k?await k.deploy(d,g,o):console.log(`Cached ${s}/${i}@${c} (no deployment handler for "${e.deploymentTargetType}")`)}async remove(e,t,o){let r=this.handlerRegistry.get(e.deploymentTargetType);r||(console.error(`No deployment handler registered for "${e.deploymentTargetType}"`),process.exit(1)),await r.undeploy(t,o)}async list(e,t){let o=this.handlerRegistry.get(e.deploymentTargetType);o||(console.error(`No deployment handler registered for "${e.deploymentTargetType}"`),process.exit(1));let r=await o.list(t);if(r.length===0){console.log(`No ${e.managesType} instances installed.`);return}console.log(`Installed ${e.managesType} instances:
|
|
63
|
+
`);for(let s of r){let i=s.publisher?`${s.publisher}/${s.package_}@${s.version}`:"unmanaged";console.log(` ${s.name} (${i}) ${s.path}`)}}async update(e,t,o){let r=this.handlerRegistry.get(e.deploymentTargetType);r||(console.error(`No deployment handler registered for "${e.deploymentTargetType}"`),process.exit(1));let s=await r.list(o),i=t?s.filter(c=>c.name===t):s.filter(c=>c.publisher);if(i.length===0){console.log(t?`Skill "${t}" not found.`:"No managed instances to update.");return}let a=0;for(let c of i){let l=await this.publisherIndex.getHighestVersion(c.publisher,c.package_);!l||l===c.version||(console.log(`Updating ${c.name}: ${c.version} \u2192 ${l}`),await this.add(e,`${c.publisher}/${c.package_}@${l}`,o),a++)}console.log(a===0?"All instances are up to date.":`
|
|
64
|
+
Updated ${a} instance(s).`)}async search(e,t){let o=t.publisher??"kanonak.org";console.log(`Searching ${o} for ${e.managesType} instances...
|
|
65
|
+
`);let r=new Bt,s=await r.getConfig(o),i=r.resolveIndexUrl(o,s),a=await this.fetchFn(i,o);if(!a.ok){console.error(`Failed to fetch package index from ${o}.`);return}let c=await a.text(),l=Q.parseIndex(c);if(l.size===0){console.error(`No packages found for publisher "${o}".`);return}let d=new Map;for(let[m,g]of l){let k=[...g].sort((f,h)=>{let b=Q.parseVersion(f),w=Q.parseVersion(h);return!b||!w?0:b.major!==w.major?w.major-b.major:b.minor!==w.minor?w.minor-b.minor:w.patch-b.patch});d.set(m,k[0])}let p=new Se(this.parser),u=0;for(let[m,g]of d){let k=this.fileCache.get(o,m,g);if(!k)try{let f=await this.publisherIndex.getPackageUrl(o,m,g),h=await this.fetchFn(f,o);if(!h.ok)continue;k=await h.text(),this.fileCache.put(o,m,g,k)}catch{continue}try{let f=this.parser.parse(k),h=f.metadata.namespace_,b=h?.version?`${h.version.major}.${h.version.minor}.${h.version.patch}`:"0.0.0",w=h?`${h.publisher}/${h.package_}/${b}`:`${o}/${m}/${g}`;await p.saveDocumentAsync(f,w),u++}catch{continue}}let y=await Ce(p,e.managesType);if(y.length===0){console.log(`No ${e.managesType} instances found across ${u} package(s).`);return}console.log(`Found ${y.length} ${e.managesType} instance(s):
|
|
66
|
+
`);for(let m of y){let g=m.entity.description?String(m.entity.description).trim().substring(0,80):"";console.log(` ${m.publisher}/${m.package_}@${m.version}`),console.log(` ${m.entityName}: ${g}`),console.log()}}async info(e,t,o){let r=this.handlerRegistry.get(e.deploymentTargetType);r||(console.error(`No deployment handler registered for "${e.deploymentTargetType}"`),process.exit(1));let i=(await r.list(o)).find(a=>a.name===t);i||(console.error(`${e.managesType} "${t}" is not installed.`),process.exit(1)),console.log(`Name: ${i.name}`),console.log(`Type: ${e.managesType}`),console.log(`Publisher: ${i.publisher||"unmanaged"}`),console.log(`Package: ${i.package_||"n/a"}`),console.log(`Version: ${i.version||"n/a"}`),console.log(`Path: ${i.path}`)}};var U=class{handlers=new Map;register(e,t){this.handlers.set(e,t)}get(e){return this.handlers.get(e)??null}};import{mkdirSync as dn,rmSync as pn,existsSync as z,readdirSync as un,writeFileSync as gn}from"fs";import{join as M}from"path";var Jt={bash:"Bash",read:"Read",write:"Write",edit:"Edit",glob:"Glob",grep:"Grep","web-search":"Web Search","web-fetch":"Web Fetch"};function Pe(n){let e=[];for(let[,t]of Object.entries(n.body)){if(!t||typeof t!="object")continue;let o=String(t.type??"");if(!Xt(o))continue;let r=Yt(t);r&&e.push(r)}return e}function Xt(n){return n==="Skill"||n.endsWith(".Skill")}function Yt(n){let e=R(n,"name"),t=E(R(n,"description"));if(!e||!t)return null;let o=[];o.push(`name: ${e}`),o.push(`description: ${t}`);let r=en(R(n,"allowedTools"));r&&o.push(`allowed-tools: ${r}`);let s=R(n,"license");s&&o.push(`license: ${tn(s)}`);let i=R(n,"compatibility");i&&o.push(`compatibility: ${E(i)}`);let a=Qt(R(n,"hasSection"))||Zt(R(n,"hasResource"),n),c=["---",...o,"---","",a].join(`
|
|
67
|
+
`);return{name:e,description:t,skillMd:c}}function Qt(n){if(!Array.isArray(n)||n.length===0)return"";let e=[];for(let t of n){if(!t||typeof t!="object")continue;let o=t.title,r=E(t.body);o&&e.push(`## ${o}`),r&&e.push(r)}return e.join(`
|
|
68
|
+
|
|
69
|
+
`)}function Zt(n,e){if(!Array.isArray(n)||n.length===0)return"";let t=[];for(let o of n){let r=typeof o=="string"?e[o]:o;if(!r||typeof r!="object")continue;let s=String(r.type??""),i=r.title??r.resourceTitle??"";if(i&&t.push(`## ${i}`),s==="ScriptResource"||s.endsWith(".ScriptResource")){let a=r.language??"bash",c=E(r.command);c&&t.push(`\`\`\`${a}
|
|
70
|
+
${c}
|
|
71
|
+
\`\`\``)}else if(s==="ReferenceResource"||s.endsWith(".ReferenceResource")){let a=E(r.content);a&&t.push(a)}}return t.join(`
|
|
72
|
+
|
|
73
|
+
`)}function en(n){return Array.isArray(n)?n.map(e=>{let t=String(e);return Jt[t]??nn(t)}).join(", "):""}function tn(n){let e=String(n);return e.includes(".")?e.substring(e.lastIndexOf(".")+1):e}function nn(n){return n.split("-").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function R(n,e){if(e in n)return n[e];for(let t of Object.keys(n))if(t.endsWith(`.${e}`))return n[t]}function E(n){return typeof n!="string"?"":n.trimEnd()}import{join as Re}from"path";import{homedir as on}from"os";function L(n){let e=n.scope??"project",t=n.client??rn(),o=t?`.${t}`:".agents",r=e==="user"?on():process.cwd();return Re(r,o,"skills")}function H(n,e){return Re(L(e),n)}function rn(){if(process.env.CLAUDE_CODE||process.env.CLAUDE_PROJECT_DIR)return"claude";if(process.env.GEMINI_CLI)return"gemini";if(process.env.CODEX_CLI||process.env.OPENAI_CODEX)return"codex"}import{readFileSync as sn,writeFileSync as an,existsSync as cn}from"fs";import{join as Te}from"path";import De from"js-yaml";var xe="skills.lock",ln=`# This file is generated by Kanonak CLI. Do not edit manually.
|
|
74
|
+
`;function V(n){let e=Te(n,xe);if(!cn(e))return{version:"1",lastUpdated:new Date().toISOString(),skills:{}};let t=sn(e,"utf-8"),o=De.load(t);return!o||typeof o!="object"||o.version!=="1"?{version:"1",lastUpdated:new Date().toISOString(),skills:{}}:{version:"1",lastUpdated:o.lastUpdated??new Date().toISOString(),skills:o.skills??{}}}function Z(n,e){e.lastUpdated=new Date().toISOString();let t={};for(let s of Object.keys(e.skills).sort())t[s]=e.skills[s];e.skills=t;let o=Te(n,xe),r=De.dump(e,{lineWidth:-1,sortKeys:!1,quotingType:'"'});an(o,ln+r,"utf-8")}var W=class{async deploy(e,t,o){let r=Pe(e);if(r.length===0){console.error("No skills could be transformed from the package.");return}let s=L(o),i=V(s),a=e.metadata.namespace_,c=a?.publisher??"",l=a?.package_??"",d=a?.version?`${a.version.major}.${a.version.minor}.${a.version.patch}`:"";for(let p of r){let u=H(p.name,o),y=z(M(u,"SKILL.md"));dn(u,{recursive:!0}),gn(M(u,"SKILL.md"),p.skillMd,"utf-8"),i.skills[p.name]={publisher:c,package_:l,version:d,resolved:a?`kanonak://${c}/${l}@${d}`:"",integrity:T(p.skillMd)},console.log(` ${y?"Updated":"Installed"} skill "${p.name}" \u2192 ${u}`)}Z(s,i)}async undeploy(e,t){let o=H(e,t);z(o)||(console.error(`Skill "${e}" is not installed at ${o}`),process.exit(1)),pn(o,{recursive:!0,force:!0});let r=L(t),s=V(r);delete s.skills[e],Z(r,s),console.log(`Removed skill "${e}" from ${o}`)}async list(e){let t=L(e),o=V(t),r=[];for(let[s,i]of Object.entries(o.skills))r.push({name:s,publisher:i.publisher,package_:i.package_,version:i.version,path:H(s,e)});if(z(t))try{let s=un(t,{withFileTypes:!0});for(let i of s){if(!i.isDirectory()||o.skills[i.name])continue;let a=M(t,i.name,"SKILL.md");z(a)&&r.push({name:i.name,publisher:"",package_:"",version:"",path:M(t,i.name)})}}catch{}return r}};var Sn=yn(hn(import.meta.url)),Cn=JSON.parse(fn(kn(Sn,"..","package.json"),"utf-8")),S=new mn;S.name("kanonak").description("Kanonak Protocol CLI - Validate and resolve Kanonak ontology packages").version(Cn.version);S.command("validate <path>").description("Validate .kan.yml file(s). Resolves imports via HTTP from publisher domains.").action(async n=>{await re(n)});S.command("install [package]").description("Install a package and its dependencies, or install all from kanonak.lock.").action(async n=>{await ce(n)});S.command("deps <path>").description("Show resolved dependency tree for a .kan.yml file.").action(async n=>{await de(n)});S.command("login <publisher>").description("Authenticate with a package publisher using OAuth 2.0.").action(async n=>{await fe(n)});S.command("logout <publisher>").description("Revoke tokens and remove stored credentials for a publisher.").action(async n=>{await he(n)});S.addCommand(be());async function Pn(){try{let n=new $,e=new bn,t=new vn,o=$n(t),r=new wn({fetchFn:o}),s=new U;s.register("AgentSkillDeployment",new W);let i=await we(n,e),a=new K(n,e,r,o,s);$e(S,i,a)}catch{}}await Pn();S.parse();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface LockEntry {
|
|
2
|
+
version: string;
|
|
3
|
+
resolved: string;
|
|
4
|
+
integrity: string;
|
|
5
|
+
dependencies: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export interface LockFile {
|
|
8
|
+
version: string;
|
|
9
|
+
lastUpdated: string;
|
|
10
|
+
packages: Record<string, LockEntry>;
|
|
11
|
+
}
|
|
12
|
+
export declare function loadLockFile(): LockFile | null;
|
|
13
|
+
export declare function saveLockFile(lock: LockFile): void;
|
|
14
|
+
export declare function computeIntegrity(content: string): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { KanonakParser } from '@kanonak-protocol/sdk';
|
|
2
|
+
import type { IKanonakDocumentRepository } from '@kanonak-protocol/sdk';
|
|
3
|
+
import type { KanonakDocument, Import, DocumentReference } from '@kanonak-protocol/types/document/models/types';
|
|
4
|
+
/**
|
|
5
|
+
* Find the workspace root by walking up from the given path.
|
|
6
|
+
* Walks up through publisher domain directories (names containing a dot)
|
|
7
|
+
* and their subdirectories, so sibling publisher directories are included.
|
|
8
|
+
*
|
|
9
|
+
* Example: given kanonak/kanonak.com/platform-connections/,
|
|
10
|
+
* walks up through kanonak.com (publisher domain) to kanonak/.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findWorkspaceRoot(dir: string): string;
|
|
13
|
+
export declare function collectKanonakFiles(dir: string, files: string[]): void;
|
|
14
|
+
/**
|
|
15
|
+
* Repository that tries multiple backends in order.
|
|
16
|
+
* First match wins for all read operations.
|
|
17
|
+
* Write operations go to the first repository.
|
|
18
|
+
*/
|
|
19
|
+
export declare class LocalFirstRepository implements IKanonakDocumentRepository {
|
|
20
|
+
private readonly repos;
|
|
21
|
+
constructor(...repos: IKanonakDocumentRepository[]);
|
|
22
|
+
getHighestCompatibleVersionAsync(publisher: string, import_: Import): Promise<KanonakDocument | null>;
|
|
23
|
+
getAllDocumentsAsync(): Promise<KanonakDocument[]>;
|
|
24
|
+
getDocumentAsync(identifier: string): Promise<KanonakDocument | null>;
|
|
25
|
+
getDocumentsByNamespaceAsync(publisher: string, package_: string): Promise<KanonakDocument[]>;
|
|
26
|
+
saveDocumentAsync(document: KanonakDocument, identifier: string): Promise<void>;
|
|
27
|
+
deleteDocumentAsync(identifier: string): Promise<void>;
|
|
28
|
+
clearNamespaceAsync(publisher: string, package_: string): Promise<void>;
|
|
29
|
+
getAllDocumentReferencesAsync(): Promise<DocumentReference[]>;
|
|
30
|
+
getDocumentContentAsync(identifier: string): Promise<string | null>;
|
|
31
|
+
getDocumentUriAsync(identifier: string): Promise<string | null>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a three-tier repository:
|
|
35
|
+
* 1. Local workspace (InMemoryKanonakDocumentRepository from scanned .kan.yml files)
|
|
36
|
+
* 2. Global cache (~/.kanonak/packages/ via FileSystemKanonakDocumentRepository)
|
|
37
|
+
* 3. HTTP (HttpKanonakDocumentRepository with FileCache write-through)
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildRepository(workspaceRoot: string, parser: KanonakParser): Promise<IKanonakDocumentRepository>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { KanonakDocument, InstanceSearchResult } from '@kanonak-protocol/sdk';
|
|
2
|
+
import type { DeploymentHandler, DeployedInstance } from '../capabilities/DeploymentHandlerRegistry.js';
|
|
3
|
+
/**
|
|
4
|
+
* Deployment handler for AgentSkillDeployment targets.
|
|
5
|
+
*
|
|
6
|
+
* Transforms Kanonak Skill instances into SKILL.md files and places them
|
|
7
|
+
* in agent discovery directories based on the client and scope options.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SkillDeploymentHandler implements DeploymentHandler {
|
|
10
|
+
/**
|
|
11
|
+
* Deploy skills from a Kanonak document to the local filesystem.
|
|
12
|
+
*/
|
|
13
|
+
deploy(doc: KanonakDocument, instances: InstanceSearchResult[], options: Record<string, string>): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Remove a deployed skill by name.
|
|
16
|
+
*/
|
|
17
|
+
undeploy(name: string, options: Record<string, string>): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* List all deployed skills in the discovery directory.
|
|
20
|
+
*/
|
|
21
|
+
list(options: Record<string, string>): Promise<DeployedInstance[]>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry in the skills lock file tracking an installed skill.
|
|
3
|
+
*/
|
|
4
|
+
export interface SkillLockEntry {
|
|
5
|
+
publisher: string;
|
|
6
|
+
package_: string;
|
|
7
|
+
version: string;
|
|
8
|
+
resolved: string;
|
|
9
|
+
integrity: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The skills lock file structure.
|
|
13
|
+
*/
|
|
14
|
+
export interface SkillLockFile {
|
|
15
|
+
version: string;
|
|
16
|
+
lastUpdated: string;
|
|
17
|
+
skills: Record<string, SkillLockEntry>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load the skills lock file from a skills directory.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadSkillLock(skillsDir: string): SkillLockFile;
|
|
23
|
+
/**
|
|
24
|
+
* Save the skills lock file to a skills directory.
|
|
25
|
+
*/
|
|
26
|
+
export declare function saveSkillLock(skillsDir: string, lock: SkillLockFile): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for resolving skill discovery paths.
|
|
3
|
+
*/
|
|
4
|
+
export interface SkillPathOptions {
|
|
5
|
+
client?: string;
|
|
6
|
+
scope?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the skills directory based on client and scope options.
|
|
10
|
+
*
|
|
11
|
+
* Discovery paths per the agent-skills ontology:
|
|
12
|
+
* project + client: {cwd}/.<client>/skills/
|
|
13
|
+
* project + none: {cwd}/.agents/skills/
|
|
14
|
+
* user + client: ~/.<client>/skills/
|
|
15
|
+
* user + none: ~/.agents/skills/
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveSkillsDir(options: SkillPathOptions): string;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the directory for a specific skill.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveSkillDir(skillName: string, options: SkillPathOptions): string;
|
|
22
|
+
/**
|
|
23
|
+
* Auto-detect the current AI agent client from environment variables.
|
|
24
|
+
*
|
|
25
|
+
* Returns the client name (lowercase) or undefined if not detected.
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectClient(): string | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { KanonakDocument } from '@kanonak-protocol/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* A skill transformed from Kanonak YAML to SKILL.md content.
|
|
4
|
+
*/
|
|
5
|
+
export interface TransformedSkill {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
skillMd: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Transform a Kanonak document containing Skill instances into SKILL.md content.
|
|
12
|
+
*
|
|
13
|
+
* Finds all Skill-typed entities in the document body and generates a
|
|
14
|
+
* SKILL.md file for each, with YAML frontmatter and markdown body
|
|
15
|
+
* assembled from the skill's hasSection entries.
|
|
16
|
+
*
|
|
17
|
+
* @param doc - A parsed KanonakDocument (from KanonakParser.parse())
|
|
18
|
+
* @returns Array of transformed skills with their SKILL.md content
|
|
19
|
+
*/
|
|
20
|
+
export declare function transformKanonakToSkillMd(doc: KanonakDocument): TransformedSkill[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed Kanonak package reference.
|
|
3
|
+
*/
|
|
4
|
+
export interface PackageRef {
|
|
5
|
+
publisher: string;
|
|
6
|
+
packageName: string;
|
|
7
|
+
version: string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Parse a package reference string.
|
|
11
|
+
*
|
|
12
|
+
* Accepts formats:
|
|
13
|
+
* publisher/package@version (e.g., "kanonak.org/core-rdf@1.0.1")
|
|
14
|
+
* publisher/package (e.g., "kanonak.org/core-rdf")
|
|
15
|
+
*
|
|
16
|
+
* @returns Parsed reference or null if invalid
|
|
17
|
+
*/
|
|
18
|
+
export declare function parsePackageRef(ref: string): PackageRef | null;
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kanonak-protocol/cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Kanonak Protocol CLI - Validate and resolve Kanonak ontology packages",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"kanonak": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**/*",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup && tsc --emitDeclarationOnly",
|
|
17
|
+
"clean": "rimraf dist",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"registry": "https://npm.pkg.github.com"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/kanonak-protocol/typescript.git",
|
|
27
|
+
"directory": "cli"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"kanonak",
|
|
31
|
+
"ontology",
|
|
32
|
+
"cli",
|
|
33
|
+
"validation",
|
|
34
|
+
"semantic-web"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@kanonak-protocol/sdk": "^1.1.0",
|
|
38
|
+
"@kanonak-protocol/types": "^1.1.0",
|
|
39
|
+
"commander": "^13.0.0",
|
|
40
|
+
"js-yaml": "^4.1.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/js-yaml": "^4.0.9",
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"rimraf": "^5.0.0",
|
|
46
|
+
"tsup": "^8.5.1",
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18.0.0"
|
|
51
|
+
},
|
|
52
|
+
"license": "Apache-2.0"
|
|
53
|
+
}
|