@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.
Files changed (38) hide show
  1. package/dist/commands/get/index.js +4 -4
  2. package/dist/commands/get/index.js.map +1 -1
  3. package/dist/commands/publish/index.d.ts.map +1 -1
  4. package/dist/commands/publish/index.js +17 -1
  5. package/dist/commands/publish/index.js.map +1 -1
  6. package/dist/commands/run/index.d.ts.map +1 -1
  7. package/dist/commands/run/index.js +8 -1
  8. package/dist/commands/run/index.js.map +1 -1
  9. package/dist/commands/search/index.d.ts.map +1 -1
  10. package/dist/commands/search/index.js +22 -1
  11. package/dist/commands/search/index.js.map +1 -1
  12. package/dist/commands/sign/index.d.ts.map +1 -1
  13. package/dist/commands/sign/index.js +33 -17
  14. package/dist/commands/sign/index.js.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +2 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/utils/auth.d.ts +14 -0
  19. package/dist/utils/auth.d.ts.map +1 -0
  20. package/dist/utils/auth.js +64 -0
  21. package/dist/utils/auth.js.map +1 -0
  22. package/dist/utils/index.d.ts +1 -0
  23. package/dist/utils/index.d.ts.map +1 -1
  24. package/dist/utils/index.js +2 -0
  25. package/dist/utils/index.js.map +1 -1
  26. package/package.json +5 -5
  27. package/src/commands/get/index.ts +5 -5
  28. package/src/commands/publish/index.ts +23 -0
  29. package/src/commands/run/index.ts +10 -1
  30. package/src/commands/search/index.ts +30 -1
  31. package/src/commands/sign/index.ts +48 -20
  32. package/src/index.ts +2 -4
  33. package/src/utils/auth.ts +87 -0
  34. package/src/utils/index.ts +3 -0
  35. package/tests/commands/get.test.ts +8 -6
  36. package/tests/commands/sign.test.ts +59 -0
  37. package/tests/utils/auth.test.ts +45 -0
  38. 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"}
@@ -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"}
@@ -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
@@ -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",
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.3",
38
- "@enactprotocol/execution": "2.0.3",
39
- "@enactprotocol/secrets": "2.0.3",
40
- "@enactprotocol/shared": "2.0.3",
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
- version?: string;
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.version) {
132
+ if (options.ver) {
133
133
  // Get specific version info
134
- const versionInfo = await getToolVersion(client, toolName, options.version);
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("-V, --version <version>", "Show info for a specific version")
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
- const client = createApiClient({ baseUrl: registryUrl });
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
- const authToken = config.registry?.authToken;
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
- const providerIdentity = emailToProviderIdentity(auditorEmail);
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(client, {
391
- name: toolInfo.name,
392
- version: toolInfo.version,
393
- sigstoreBundle: result.bundle as unknown as Record<string, unknown>,
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
- await promptAddToTrustList(attestationResult.auditor, _ctx.isInteractive);
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(client, {
566
- name: manifest.name,
567
- version: manifest.version ?? "1.0.0",
568
- sigstoreBundle: result.bundle as unknown as Record<string, unknown>,
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
- await promptAddToTrustList(attestationResult.auditor, _ctx.isInteractive);
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.3";
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
+ }
@@ -105,3 +105,6 @@ export {
105
105
  ErrorMessages,
106
106
  printErrorWithSuggestions,
107
107
  } from "./errors";
108
+
109
+ // Auth utilities
110
+ export { getCurrentUsername, extractNamespace } from "./auth";
@@ -42,24 +42,26 @@ describe("get command", () => {
42
42
  expect(args[0]?.name()).toBe("tool");
43
43
  });
44
44
 
45
- test("has --version option", () => {
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 versionOpt = opts.find((o) => o.long === "--version");
52
- expect(versionOpt).toBeDefined();
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
- const versionOpt = opts.find((o) => o.short === "-v");
62
- expect(versionOpt).toBeDefined();
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", () => {