@enactprotocol/cli 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/get/index.js +4 -4
- package/dist/commands/get/index.js.map +1 -1
- package/dist/commands/publish/index.d.ts.map +1 -1
- package/dist/commands/publish/index.js +17 -1
- package/dist/commands/publish/index.js.map +1 -1
- package/dist/commands/run/index.d.ts.map +1 -1
- package/dist/commands/run/index.js +8 -1
- package/dist/commands/run/index.js.map +1 -1
- package/dist/commands/search/index.d.ts.map +1 -1
- package/dist/commands/search/index.js +22 -1
- package/dist/commands/search/index.js.map +1 -1
- package/dist/commands/sign/index.d.ts.map +1 -1
- package/dist/commands/sign/index.js +33 -17
- package/dist/commands/sign/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/auth.d.ts +14 -0
- package/dist/utils/auth.d.ts.map +1 -0
- package/dist/utils/auth.js +64 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -5
- package/src/commands/get/index.ts +5 -5
- package/src/commands/publish/index.ts +23 -0
- package/src/commands/run/index.ts +10 -1
- package/src/commands/search/index.ts +30 -1
- package/src/commands/sign/index.ts +48 -20
- package/src/index.ts +2 -4
- package/src/utils/auth.ts +87 -0
- package/src/utils/index.ts +3 -0
- package/tests/commands/get.test.ts +8 -6
- package/tests/commands/sign.test.ts +59 -0
- package/tests/utils/auth.test.ts +45 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth utilities shared across commands
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get the current authenticated username
|
|
6
|
+
* Returns null if not authenticated or username cannot be determined
|
|
7
|
+
*/
|
|
8
|
+
export declare function getCurrentUsername(): Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Extract namespace from a tool name
|
|
11
|
+
* e.g., "alice/utils/greeter" -> "alice"
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractNamespace(toolName: string): string;
|
|
14
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAcH;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyDjE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGzD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth utilities shared across commands
|
|
3
|
+
*/
|
|
4
|
+
import { getSecret } from "@enactprotocol/secrets";
|
|
5
|
+
/** Namespace for storing auth tokens in keyring */
|
|
6
|
+
const AUTH_NAMESPACE = "enact:auth";
|
|
7
|
+
const ACCESS_TOKEN_KEY = "access_token";
|
|
8
|
+
/** Supabase configuration */
|
|
9
|
+
const SUPABASE_URL = process.env.SUPABASE_URL || "https://siikwkfgsmouioodghho.supabase.co";
|
|
10
|
+
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY ??
|
|
11
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaWt3a2Znc21vdWlvb2RnaGhvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2MTkzMzksImV4cCI6MjA4MDE5NTMzOX0.kxnx6-IPFhmGx6rzNx36vbyhFMFZKP_jFqaDbKnJ_E0";
|
|
12
|
+
/**
|
|
13
|
+
* Get the current authenticated username
|
|
14
|
+
* Returns null if not authenticated or username cannot be determined
|
|
15
|
+
*/
|
|
16
|
+
export async function getCurrentUsername() {
|
|
17
|
+
const token = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
|
|
18
|
+
if (!token) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
// Get user info from Supabase
|
|
23
|
+
const userResponse = await fetch(`${SUPABASE_URL}/auth/v1/user`, {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${token}`,
|
|
26
|
+
apikey: SUPABASE_ANON_KEY,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
if (!userResponse.ok) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const user = (await userResponse.json());
|
|
33
|
+
// Get profile from database for definitive username
|
|
34
|
+
const profileResponse = await fetch(`${SUPABASE_URL}/rest/v1/profiles?id=eq.${user.id}&select=username`, {
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${token}`,
|
|
37
|
+
apikey: SUPABASE_ANON_KEY,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
if (profileResponse.ok) {
|
|
41
|
+
const profiles = (await profileResponse.json());
|
|
42
|
+
if (profiles[0]?.username) {
|
|
43
|
+
return profiles[0].username;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Fall back to user metadata
|
|
47
|
+
return (user.user_metadata?.username ||
|
|
48
|
+
user.user_metadata?.user_name ||
|
|
49
|
+
user.email?.split("@")[0] ||
|
|
50
|
+
null);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extract namespace from a tool name
|
|
58
|
+
* e.g., "alice/utils/greeter" -> "alice"
|
|
59
|
+
*/
|
|
60
|
+
export function extractNamespace(toolName) {
|
|
61
|
+
const parts = toolName.split("/");
|
|
62
|
+
return parts[0] ?? "";
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,mDAAmD;AACnD,MAAM,cAAc,GAAG,YAAY,CAAC;AACpC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAExC,6BAA6B;AAC7B,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,0CAA0C,CAAC;AAC5F,MAAM,iBAAiB,GACrB,OAAO,CAAC,GAAG,CAAC,iBAAiB;IAC7B,kNAAkN,CAAC;AAErN;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAChE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,8BAA8B;QAC9B,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,GAAG,YAAY,eAAe,EAAE;YAC/D,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,MAAM,EAAE,iBAAiB;aAC1B;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,YAAY,CAAC,IAAI,EAAE,CAQtC,CAAC;QAEF,oDAAoD;QACpD,MAAM,eAAe,GAAG,MAAM,KAAK,CACjC,GAAG,YAAY,2BAA2B,IAAI,CAAC,EAAE,kBAAkB,EACnE;YACE,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,MAAM,EAAE,iBAAiB;aAC1B;SACF,CACF,CAAC;QAEF,IAAI,eAAe,CAAC,EAAE,EAAE,CAAC;YACvB,MAAM,QAAQ,GAAG,CAAC,MAAM,eAAe,CAAC,IAAI,EAAE,CAAgC,CAAC;YAC/E,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;gBAC1B,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,6BAA6B;QAC7B,OAAO,CACL,IAAI,CAAC,aAAa,EAAE,QAAQ;YAC5B,IAAI,CAAC,aAAa,EAAE,SAAS;YAC7B,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,CACL,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export { colors, symbols, success, error, warning, info, dim, newline, header, k
|
|
|
5
5
|
export { createSpinner, withSpinner, intro, outro, confirm, text, password, select, isCancel, cancel, log, logInfo, logSuccess, logWarning, logError, logStep, type SpinnerInstance, } from "./spinner";
|
|
6
6
|
export { EXIT_SUCCESS, EXIT_FAILURE, EXIT_USAGE, EXIT_DATAERR, EXIT_NOINPUT, EXIT_NOUSER, EXIT_NOHOST, EXIT_UNAVAILABLE, EXIT_SOFTWARE, EXIT_OSERR, EXIT_OSFILE, EXIT_CANTCREAT, EXIT_IOERR, EXIT_TEMPFAIL, EXIT_PROTOCOL, EXIT_NOPERM, EXIT_CONFIG, EXIT_TOOL_NOT_FOUND, EXIT_MANIFEST_ERROR, EXIT_EXECUTION_ERROR, EXIT_TIMEOUT, EXIT_TRUST_ERROR, EXIT_REGISTRY_ERROR, EXIT_AUTH_ERROR, EXIT_VALIDATION_ERROR, EXIT_NETWORK_ERROR, EXIT_CONTAINER_ERROR, getExitCodeDescription, exit, exitSuccess, exitFailure, } from "./exit-codes";
|
|
7
7
|
export { CliError, ToolNotFoundError, ManifestError, ValidationError, AuthError, NetworkError, RegistryError, TrustError, TimeoutError, ExecutionError, ContainerError, FileNotFoundError, PermissionError, ConfigError, UsageError, handleError, withErrorHandling, categorizeError, ErrorMessages, printErrorWithSuggestions, } from "./errors";
|
|
8
|
+
export { getCurrentUsername, extractNamespace } from "./auth";
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,KAAK,EACL,OAAO,EACP,IAAI,EACJ,GAAG,EACH,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,aAAa,EACb,WAAW,EACX,KAAK,EACL,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,EACN,GAAG,EACH,OAAO,EACP,UAAU,EACV,UAAU,EACV,QAAQ,EACR,OAAO,EACP,KAAK,eAAe,GACrB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,IAAI,EACJ,WAAW,EACX,WAAW,GACZ,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,yBAAyB,GAC1B,MAAM,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,KAAK,EACL,OAAO,EACP,IAAI,EACJ,GAAG,EACH,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,aAAa,EACb,WAAW,EACX,KAAK,EACL,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,EACN,GAAG,EACH,OAAO,EACP,UAAU,EACV,UAAU,EACV,QAAQ,EACR,OAAO,EACP,KAAK,eAAe,GACrB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,IAAI,EACJ,WAAW,EACX,WAAW,GACZ,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,yBAAyB,GAC1B,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -9,4 +9,6 @@ export { createSpinner, withSpinner, intro, outro, confirm, text, password, sele
|
|
|
9
9
|
export { EXIT_SUCCESS, EXIT_FAILURE, EXIT_USAGE, EXIT_DATAERR, EXIT_NOINPUT, EXIT_NOUSER, EXIT_NOHOST, EXIT_UNAVAILABLE, EXIT_SOFTWARE, EXIT_OSERR, EXIT_OSFILE, EXIT_CANTCREAT, EXIT_IOERR, EXIT_TEMPFAIL, EXIT_PROTOCOL, EXIT_NOPERM, EXIT_CONFIG, EXIT_TOOL_NOT_FOUND, EXIT_MANIFEST_ERROR, EXIT_EXECUTION_ERROR, EXIT_TIMEOUT, EXIT_TRUST_ERROR, EXIT_REGISTRY_ERROR, EXIT_AUTH_ERROR, EXIT_VALIDATION_ERROR, EXIT_NETWORK_ERROR, EXIT_CONTAINER_ERROR, getExitCodeDescription, exit, exitSuccess, exitFailure, } from "./exit-codes";
|
|
10
10
|
// Error handling
|
|
11
11
|
export { CliError, ToolNotFoundError, ManifestError, ValidationError, AuthError, NetworkError, RegistryError, TrustError, TimeoutError, ExecutionError, ContainerError, FileNotFoundError, PermissionError, ConfigError, UsageError, handleError, withErrorHandling, categorizeError, ErrorMessages, printErrorWithSuggestions, } from "./errors";
|
|
12
|
+
// Auth utilities
|
|
13
|
+
export { getCurrentUsername, extractNamespace } from "./auth";
|
|
12
14
|
//# sourceMappingURL=index.js.map
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oBAAoB;AACpB,OAAO,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,KAAK,EACL,OAAO,EACP,IAAI,EACJ,GAAG,EACH,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,SAAS,GAEV,MAAM,UAAU,CAAC;AAElB,sBAAsB;AACtB,OAAO,EACL,aAAa,EACb,WAAW,EACX,KAAK,EACL,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,EACN,GAAG,EACH,OAAO,EACP,UAAU,EACV,UAAU,EACV,QAAQ,EACR,OAAO,GAER,MAAM,WAAW,CAAC;AAEnB,aAAa;AACb,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,IAAI,EACJ,WAAW,EACX,WAAW,GACZ,MAAM,cAAc,CAAC;AAEtB,iBAAiB;AACjB,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,yBAAyB,GAC1B,MAAM,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oBAAoB;AACpB,OAAO,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,KAAK,EACL,OAAO,EACP,IAAI,EACJ,GAAG,EACH,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,SAAS,GAEV,MAAM,UAAU,CAAC;AAElB,sBAAsB;AACtB,OAAO,EACL,aAAa,EACb,WAAW,EACX,KAAK,EACL,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,EACN,GAAG,EACH,OAAO,EACP,UAAU,EACV,UAAU,EACV,QAAQ,EACR,OAAO,GAER,MAAM,WAAW,CAAC;AAEnB,aAAa;AACb,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,IAAI,EACJ,WAAW,EACX,WAAW,GACZ,MAAM,cAAc,CAAC;AAEtB,iBAAiB;AACjB,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,yBAAyB,GAC1B,MAAM,UAAU,CAAC;AAElB,iBAAiB;AACjB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enactprotocol/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "Command-line interface for Enact - the npm for AI tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@clack/prompts": "^0.11.0",
|
|
37
|
-
"@enactprotocol/api": "2.0.
|
|
38
|
-
"@enactprotocol/execution": "2.0.
|
|
39
|
-
"@enactprotocol/secrets": "2.0.
|
|
40
|
-
"@enactprotocol/shared": "2.0.
|
|
37
|
+
"@enactprotocol/api": "2.0.5",
|
|
38
|
+
"@enactprotocol/execution": "2.0.5",
|
|
39
|
+
"@enactprotocol/secrets": "2.0.5",
|
|
40
|
+
"@enactprotocol/shared": "2.0.5",
|
|
41
41
|
"commander": "^12.1.0",
|
|
42
42
|
"picocolors": "^1.1.1"
|
|
43
43
|
},
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "../../utils";
|
|
28
28
|
|
|
29
29
|
interface GetOptions extends GlobalOptions {
|
|
30
|
-
|
|
30
|
+
ver?: string;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -65,7 +65,7 @@ function displayToolInfo(tool: ToolInfo, options: GetOptions): void {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
newline();
|
|
68
|
-
keyValue("Available Versions", tool.versions.join(", "));
|
|
68
|
+
keyValue("Available Versions", tool.versions.map((v) => v.version).join(", "));
|
|
69
69
|
|
|
70
70
|
if (options.verbose) {
|
|
71
71
|
newline();
|
|
@@ -129,9 +129,9 @@ async function getHandler(
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
try {
|
|
132
|
-
if (options.
|
|
132
|
+
if (options.ver) {
|
|
133
133
|
// Get specific version info
|
|
134
|
-
const versionInfo = await getToolVersion(client, toolName, options.
|
|
134
|
+
const versionInfo = await getToolVersion(client, toolName, options.ver);
|
|
135
135
|
|
|
136
136
|
if (options.json) {
|
|
137
137
|
json(versionInfo);
|
|
@@ -177,7 +177,7 @@ export function configureGetCommand(program: Command): void {
|
|
|
177
177
|
.command("get <tool>")
|
|
178
178
|
.alias("info")
|
|
179
179
|
.description("Show detailed information about a tool")
|
|
180
|
-
.option("
|
|
180
|
+
.option("--ver <version>", "Show info for a specific version")
|
|
181
181
|
.option("-v, --verbose", "Show detailed output")
|
|
182
182
|
.option("--json", "Output as JSON")
|
|
183
183
|
.action(async (toolName: string, options: GetOptions) => {
|
|
@@ -21,7 +21,9 @@ import type { CommandContext, GlobalOptions } from "../../types";
|
|
|
21
21
|
import {
|
|
22
22
|
dim,
|
|
23
23
|
error,
|
|
24
|
+
extractNamespace,
|
|
24
25
|
formatError,
|
|
26
|
+
getCurrentUsername,
|
|
25
27
|
header,
|
|
26
28
|
info,
|
|
27
29
|
json,
|
|
@@ -208,6 +210,27 @@ async function publishHandler(
|
|
|
208
210
|
}
|
|
209
211
|
newline();
|
|
210
212
|
|
|
213
|
+
// Pre-flight namespace check (skip in local dev mode)
|
|
214
|
+
if (!options.skipAuth) {
|
|
215
|
+
const currentUsername = await getCurrentUsername();
|
|
216
|
+
if (currentUsername) {
|
|
217
|
+
const toolNamespace = extractNamespace(toolName);
|
|
218
|
+
if (toolNamespace !== currentUsername) {
|
|
219
|
+
error(
|
|
220
|
+
`Namespace mismatch: Tool namespace "${toolNamespace}" does not match your username "${currentUsername}".`
|
|
221
|
+
);
|
|
222
|
+
newline();
|
|
223
|
+
dim("You can only publish tools under your own namespace.");
|
|
224
|
+
dim("Either:");
|
|
225
|
+
dim(
|
|
226
|
+
` 1. Change the tool name to "${currentUsername}/${toolName.split("/").slice(1).join("/")}" in your enact.md`
|
|
227
|
+
);
|
|
228
|
+
dim(` 2. Or login as a user with the "${toolNamespace}" username`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
211
234
|
// Get registry URL from config or environment
|
|
212
235
|
const config = loadConfig();
|
|
213
236
|
const registryUrl =
|
|
@@ -186,7 +186,16 @@ async function fetchAndCacheTool(
|
|
|
186
186
|
process.env.ENACT_REGISTRY_URL ??
|
|
187
187
|
config.registry?.url ??
|
|
188
188
|
"https://siikwkfgsmouioodghho.supabase.co/functions/v1";
|
|
189
|
-
|
|
189
|
+
|
|
190
|
+
// Get auth token - use user token if available, otherwise use anon key for public access
|
|
191
|
+
let authToken = config.registry?.authToken ?? process.env.ENACT_AUTH_TOKEN;
|
|
192
|
+
if (!authToken && registryUrl.includes("siikwkfgsmouioodghho.supabase.co")) {
|
|
193
|
+
// Use the official Supabase anon key for unauthenticated access
|
|
194
|
+
authToken =
|
|
195
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaWt3a2Znc21vdWlvb2RnaGhvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2MTkzMzksImV4cCI6MjA4MDE5NTMzOX0.kxnx6-IPFhmGx6rzNx36vbyhFMFZKP_jFqaDbKnJ_E0";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const client = createApiClient({ baseUrl: registryUrl, authToken });
|
|
190
199
|
|
|
191
200
|
// Get tool info to find latest version or use requested version
|
|
192
201
|
const toolInfo = await getToolInfo(client, toolName);
|
|
@@ -32,6 +32,7 @@ interface SearchOptions extends GlobalOptions {
|
|
|
32
32
|
tags?: string;
|
|
33
33
|
limit?: string;
|
|
34
34
|
offset?: string;
|
|
35
|
+
threshold?: string;
|
|
35
36
|
local?: boolean;
|
|
36
37
|
global?: boolean;
|
|
37
38
|
}
|
|
@@ -229,7 +230,15 @@ async function searchHandler(
|
|
|
229
230
|
process.env.ENACT_REGISTRY_URL ??
|
|
230
231
|
config.registry?.url ??
|
|
231
232
|
"https://siikwkfgsmouioodghho.supabase.co/functions/v1";
|
|
232
|
-
|
|
233
|
+
|
|
234
|
+
// Get auth token - use user token if available, otherwise use anon key for public access
|
|
235
|
+
let authToken = config.registry?.authToken ?? process.env.ENACT_AUTH_TOKEN;
|
|
236
|
+
if (!authToken && registryUrl.includes("siikwkfgsmouioodghho.supabase.co")) {
|
|
237
|
+
// Use the official Supabase anon key for unauthenticated access
|
|
238
|
+
authToken =
|
|
239
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaWt3a2Znc21vdWlvb2RnaGhvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2MTkzMzksImV4cCI6MjA4MDE5NTMzOX0.kxnx6-IPFhmGx6rzNx36vbyhFMFZKP_jFqaDbKnJ_E0";
|
|
240
|
+
}
|
|
241
|
+
|
|
233
242
|
const client = createApiClient({
|
|
234
243
|
baseUrl: registryUrl,
|
|
235
244
|
authToken: authToken,
|
|
@@ -237,12 +246,16 @@ async function searchHandler(
|
|
|
237
246
|
|
|
238
247
|
const limit = options.limit ? Number.parseInt(options.limit, 10) : 20;
|
|
239
248
|
const offset = options.offset ? Number.parseInt(options.offset, 10) : 0;
|
|
249
|
+
const threshold = options.threshold ? Number.parseFloat(options.threshold) : undefined;
|
|
240
250
|
|
|
241
251
|
if (ctx.options.verbose) {
|
|
242
252
|
info(`Searching for: "${query}"`);
|
|
243
253
|
if (options.tags) {
|
|
244
254
|
info(`Tags: ${options.tags}`);
|
|
245
255
|
}
|
|
256
|
+
if (threshold !== undefined) {
|
|
257
|
+
info(`Similarity threshold: ${threshold}`);
|
|
258
|
+
}
|
|
246
259
|
}
|
|
247
260
|
|
|
248
261
|
try {
|
|
@@ -251,8 +264,16 @@ async function searchHandler(
|
|
|
251
264
|
tags: options.tags,
|
|
252
265
|
limit,
|
|
253
266
|
offset,
|
|
267
|
+
threshold,
|
|
254
268
|
});
|
|
255
269
|
|
|
270
|
+
// Show search type in verbose mode
|
|
271
|
+
if (ctx.options.verbose && response.searchType) {
|
|
272
|
+
const searchTypeLabel =
|
|
273
|
+
response.searchType === "hybrid" ? "semantic + text (hybrid)" : "text only (no OpenAI key)";
|
|
274
|
+
info(`Search mode: ${searchTypeLabel}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
256
277
|
// JSON output
|
|
257
278
|
if (options.json) {
|
|
258
279
|
json({
|
|
@@ -262,6 +283,7 @@ async function searchHandler(
|
|
|
262
283
|
limit: response.limit,
|
|
263
284
|
offset: response.offset,
|
|
264
285
|
hasMore: response.hasMore,
|
|
286
|
+
searchType: response.searchType,
|
|
265
287
|
});
|
|
266
288
|
return;
|
|
267
289
|
}
|
|
@@ -269,6 +291,9 @@ async function searchHandler(
|
|
|
269
291
|
// No results
|
|
270
292
|
if (response.results.length === 0) {
|
|
271
293
|
info(`No tools found matching "${query}"`);
|
|
294
|
+
if (response.searchType === "text") {
|
|
295
|
+
dim("Note: Semantic search unavailable (OpenAI key not configured on server)");
|
|
296
|
+
}
|
|
272
297
|
dim("Try a different search term or remove tag filters");
|
|
273
298
|
return;
|
|
274
299
|
}
|
|
@@ -348,6 +373,10 @@ export function configureSearchCommand(program: Command): void {
|
|
|
348
373
|
.option("-t, --tags <tags>", "Filter by tags (comma-separated, registry only)")
|
|
349
374
|
.option("-l, --limit <number>", "Maximum results to return (default: 20, registry only)")
|
|
350
375
|
.option("-o, --offset <number>", "Pagination offset (default: 0, registry only)")
|
|
376
|
+
.option(
|
|
377
|
+
"--threshold <number>",
|
|
378
|
+
"Similarity threshold for semantic search (0.0-1.0, default: 0.1)"
|
|
379
|
+
)
|
|
351
380
|
.option("-v, --verbose", "Show detailed output")
|
|
352
381
|
.option("--json", "Output as JSON")
|
|
353
382
|
.action(async (query: string, options: SearchOptions) => {
|
|
@@ -12,11 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { dirname, join, resolve } from "node:path";
|
|
15
|
-
import {
|
|
16
|
-
createApiClient,
|
|
17
|
-
getToolVersion,
|
|
18
|
-
submitAttestation as submitAttestationToRegistry,
|
|
19
|
-
} from "@enactprotocol/api";
|
|
15
|
+
import { createApiClient, getToolVersion, submitAttestationToRegistry } from "@enactprotocol/api";
|
|
20
16
|
import { getSecret } from "@enactprotocol/secrets";
|
|
21
17
|
import {
|
|
22
18
|
addTrustedAuditor,
|
|
@@ -31,6 +27,7 @@ import {
|
|
|
31
27
|
type EnactToolAttestationOptions,
|
|
32
28
|
type SigstoreBundle,
|
|
33
29
|
createEnactToolStatement,
|
|
30
|
+
extractCertificateFromBundle,
|
|
34
31
|
signAttestation,
|
|
35
32
|
} from "@enactprotocol/trust";
|
|
36
33
|
import type { Command } from "commander";
|
|
@@ -160,7 +157,8 @@ function displayDryRun(
|
|
|
160
157
|
*/
|
|
161
158
|
async function promptAddToTrustList(
|
|
162
159
|
auditorEmail: string,
|
|
163
|
-
isInteractive: boolean
|
|
160
|
+
isInteractive: boolean,
|
|
161
|
+
issuer?: string
|
|
164
162
|
): Promise<boolean> {
|
|
165
163
|
if (!isInteractive) {
|
|
166
164
|
return false;
|
|
@@ -168,7 +166,8 @@ async function promptAddToTrustList(
|
|
|
168
166
|
|
|
169
167
|
try {
|
|
170
168
|
// Convert email to provider:identity format (e.g., github:alice)
|
|
171
|
-
|
|
169
|
+
// Pass the issuer so we can correctly determine the provider
|
|
170
|
+
const providerIdentity = emailToProviderIdentity(auditorEmail, issuer);
|
|
172
171
|
|
|
173
172
|
// Check if already in local trust list
|
|
174
173
|
const trustedAuditors = getTrustedAuditors();
|
|
@@ -387,11 +386,12 @@ async function signRemoteTool(
|
|
|
387
386
|
const attestationResult = await withSpinner(
|
|
388
387
|
"Submitting attestation to registry...",
|
|
389
388
|
async () => {
|
|
390
|
-
return await submitAttestationToRegistry(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
389
|
+
return await submitAttestationToRegistry(
|
|
390
|
+
client,
|
|
391
|
+
toolInfo.name,
|
|
392
|
+
toolInfo.version,
|
|
393
|
+
result.bundle as unknown as Record<string, unknown>
|
|
394
|
+
);
|
|
395
395
|
}
|
|
396
396
|
);
|
|
397
397
|
|
|
@@ -402,9 +402,11 @@ async function signRemoteTool(
|
|
|
402
402
|
keyValue("Rekor log index", String(attestationResult.rekorLogIndex));
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
// Prompt to add to trust list
|
|
405
|
+
// Prompt to add to trust list - extract issuer from bundle for correct identity format
|
|
406
406
|
if (_ctx.isInteractive && !options.json) {
|
|
407
|
-
|
|
407
|
+
const certificate = extractCertificateFromBundle(result.bundle);
|
|
408
|
+
const issuer = certificate?.identity?.issuer;
|
|
409
|
+
await promptAddToTrustList(attestationResult.auditor, _ctx.isInteractive, issuer);
|
|
408
410
|
}
|
|
409
411
|
|
|
410
412
|
if (options.json) {
|
|
@@ -446,6 +448,28 @@ async function signLocalTool(
|
|
|
446
448
|
|
|
447
449
|
const manifest = loaded.manifest;
|
|
448
450
|
|
|
451
|
+
// Warn about local signing workflow - attestation hash won't match published bundle
|
|
452
|
+
if (_ctx.isInteractive && !options.dryRun) {
|
|
453
|
+
newline();
|
|
454
|
+
warning("Local signing creates an attestation for the manifest content hash.");
|
|
455
|
+
dim("If you plan to publish this tool, the published bundle will have a different hash.");
|
|
456
|
+
dim("The attestation won't match and verification will fail.");
|
|
457
|
+
newline();
|
|
458
|
+
info("Recommended workflow:");
|
|
459
|
+
dim(` 1. ${colors.command(`enact publish ${pathArg}`)} # Publish first`);
|
|
460
|
+
dim(
|
|
461
|
+
` 2. ${colors.command(`enact sign ${manifest.name}@${manifest.version ?? "1.0.0"}`)} # Then sign the published version`
|
|
462
|
+
);
|
|
463
|
+
newline();
|
|
464
|
+
|
|
465
|
+
const shouldContinue = await confirm("Continue with local signing anyway?", false);
|
|
466
|
+
if (!shouldContinue) {
|
|
467
|
+
info("Signing cancelled. Use the recommended workflow above.");
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
newline();
|
|
471
|
+
}
|
|
472
|
+
|
|
449
473
|
// Validate manifest
|
|
450
474
|
const validation = validateManifest(manifest);
|
|
451
475
|
if (!validation.valid && validation.errors) {
|
|
@@ -562,11 +586,12 @@ async function signLocalTool(
|
|
|
562
586
|
"Submitting attestation to registry...",
|
|
563
587
|
async () => {
|
|
564
588
|
// Submit the Sigstore bundle directly (v2 API)
|
|
565
|
-
return await submitAttestationToRegistry(
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
589
|
+
return await submitAttestationToRegistry(
|
|
590
|
+
client,
|
|
591
|
+
manifest.name,
|
|
592
|
+
manifest.version ?? "1.0.0",
|
|
593
|
+
result.bundle as unknown as Record<string, unknown>
|
|
594
|
+
);
|
|
570
595
|
}
|
|
571
596
|
);
|
|
572
597
|
|
|
@@ -576,8 +601,11 @@ async function signLocalTool(
|
|
|
576
601
|
};
|
|
577
602
|
|
|
578
603
|
// Prompt to add auditor to trust list (if interactive and not in JSON mode)
|
|
604
|
+
// Extract issuer from bundle for correct identity format
|
|
579
605
|
if (!options.json && _ctx.isInteractive) {
|
|
580
|
-
|
|
606
|
+
const certificate = extractCertificateFromBundle(result.bundle);
|
|
607
|
+
const issuer = certificate?.identity?.issuer;
|
|
608
|
+
await promptAddToTrustList(attestationResult.auditor, _ctx.isInteractive, issuer);
|
|
581
609
|
}
|
|
582
610
|
} catch (err) {
|
|
583
611
|
warning("Failed to submit attestation to registry");
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
} from "./commands";
|
|
33
33
|
import { error, formatError } from "./utils";
|
|
34
34
|
|
|
35
|
-
export const version = "2.0.
|
|
35
|
+
export const version = "2.0.5";
|
|
36
36
|
|
|
37
37
|
// Export types for external use
|
|
38
38
|
export type { GlobalOptions, CommandContext } from "./types";
|
|
@@ -47,9 +47,7 @@ async function main() {
|
|
|
47
47
|
program
|
|
48
48
|
.name("enact")
|
|
49
49
|
.description("Enact - Verified, portable protocol for AI-executable tools")
|
|
50
|
-
.version(version)
|
|
51
|
-
.option("--json", "Output as JSON")
|
|
52
|
-
.option("-v, --verbose", "Enable verbose output");
|
|
50
|
+
.version(version);
|
|
53
51
|
|
|
54
52
|
// Configure all commands
|
|
55
53
|
configureSetupCommand(program);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth utilities shared across commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getSecret } from "@enactprotocol/secrets";
|
|
6
|
+
|
|
7
|
+
/** Namespace for storing auth tokens in keyring */
|
|
8
|
+
const AUTH_NAMESPACE = "enact:auth";
|
|
9
|
+
const ACCESS_TOKEN_KEY = "access_token";
|
|
10
|
+
|
|
11
|
+
/** Supabase configuration */
|
|
12
|
+
const SUPABASE_URL = process.env.SUPABASE_URL || "https://siikwkfgsmouioodghho.supabase.co";
|
|
13
|
+
const SUPABASE_ANON_KEY =
|
|
14
|
+
process.env.SUPABASE_ANON_KEY ??
|
|
15
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaWt3a2Znc21vdWlvb2RnaGhvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2MTkzMzksImV4cCI6MjA4MDE5NTMzOX0.kxnx6-IPFhmGx6rzNx36vbyhFMFZKP_jFqaDbKnJ_E0";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the current authenticated username
|
|
19
|
+
* Returns null if not authenticated or username cannot be determined
|
|
20
|
+
*/
|
|
21
|
+
export async function getCurrentUsername(): Promise<string | null> {
|
|
22
|
+
const token = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
|
|
23
|
+
if (!token) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Get user info from Supabase
|
|
29
|
+
const userResponse = await fetch(`${SUPABASE_URL}/auth/v1/user`, {
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: `Bearer ${token}`,
|
|
32
|
+
apikey: SUPABASE_ANON_KEY,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!userResponse.ok) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const user = (await userResponse.json()) as {
|
|
41
|
+
id: string;
|
|
42
|
+
email?: string;
|
|
43
|
+
user_metadata?: {
|
|
44
|
+
user_name?: string;
|
|
45
|
+
username?: string;
|
|
46
|
+
full_name?: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Get profile from database for definitive username
|
|
51
|
+
const profileResponse = await fetch(
|
|
52
|
+
`${SUPABASE_URL}/rest/v1/profiles?id=eq.${user.id}&select=username`,
|
|
53
|
+
{
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Bearer ${token}`,
|
|
56
|
+
apikey: SUPABASE_ANON_KEY,
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (profileResponse.ok) {
|
|
62
|
+
const profiles = (await profileResponse.json()) as Array<{ username: string }>;
|
|
63
|
+
if (profiles[0]?.username) {
|
|
64
|
+
return profiles[0].username;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Fall back to user metadata
|
|
69
|
+
return (
|
|
70
|
+
user.user_metadata?.username ||
|
|
71
|
+
user.user_metadata?.user_name ||
|
|
72
|
+
user.email?.split("@")[0] ||
|
|
73
|
+
null
|
|
74
|
+
);
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Extract namespace from a tool name
|
|
82
|
+
* e.g., "alice/utils/greeter" -> "alice"
|
|
83
|
+
*/
|
|
84
|
+
export function extractNamespace(toolName: string): string {
|
|
85
|
+
const parts = toolName.split("/");
|
|
86
|
+
return parts[0] ?? "";
|
|
87
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -42,24 +42,26 @@ describe("get command", () => {
|
|
|
42
42
|
expect(args[0]?.name()).toBe("tool");
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
test("has --
|
|
45
|
+
test("has --ver option for specifying version", () => {
|
|
46
46
|
const program = new Command();
|
|
47
47
|
configureGetCommand(program);
|
|
48
48
|
|
|
49
49
|
const getCmd = program.commands.find((cmd) => cmd.name() === "get");
|
|
50
50
|
const opts = getCmd?.options ?? [];
|
|
51
|
-
const
|
|
52
|
-
expect(
|
|
51
|
+
const verOpt = opts.find((o) => o.long === "--ver");
|
|
52
|
+
expect(verOpt).toBeDefined();
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
test("has -v short option for version", () => {
|
|
55
|
+
test("has -v short option for verbose (not version)", () => {
|
|
56
56
|
const program = new Command();
|
|
57
57
|
configureGetCommand(program);
|
|
58
58
|
|
|
59
59
|
const getCmd = program.commands.find((cmd) => cmd.name() === "get");
|
|
60
60
|
const opts = getCmd?.options ?? [];
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// -v is for verbose, not version (--ver is for version)
|
|
62
|
+
const verboseOpt = opts.find((o) => o.short === "-v");
|
|
63
|
+
expect(verboseOpt).toBeDefined();
|
|
64
|
+
expect(verboseOpt?.long).toBe("--verbose");
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
test("has --json option", () => {
|