@hyperdrive.bot/sign-cli 0.1.4 → 0.1.6

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.
@@ -46,7 +46,7 @@ export default class Api extends Command {
46
46
  let fullUrl;
47
47
  if (flags.module) {
48
48
  const { getManifest } = await import('../lib/manifest.js');
49
- const manifest = await getManifest(client);
49
+ const manifest = await getManifest();
50
50
  const resource = Object.values(manifest.resources).find(r => r.module === flags.module);
51
51
  if (resource) {
52
52
  fullUrl = `${resource.apiGateway}${path}`;
@@ -6,7 +6,6 @@
6
6
  */
7
7
  import { Command, Flags } from '@oclif/core';
8
8
  import { getManifest, listResources } from '../../lib/manifest.js';
9
- import { createSignClient } from '../../lib/sign-api.js';
10
9
  export default class ModulesList extends Command {
11
10
  static description = 'List all available API resources and actions';
12
11
  static examples = [
@@ -21,8 +20,7 @@ export default class ModulesList extends Command {
21
20
  async run() {
22
21
  const { flags } = await this.parse(ModulesList);
23
22
  try {
24
- const client = createSignClient();
25
- const manifest = await getManifest(client, flags.refresh);
23
+ const manifest = await getManifest(undefined, flags.refresh);
26
24
  const resources = listResources(manifest);
27
25
  if (flags.json) {
28
26
  this.log(JSON.stringify(resources, null, 2));
@@ -24,7 +24,7 @@ const hook = async function (opts) {
24
24
  const resource = parts[0];
25
25
  try {
26
26
  const client = createSignClient();
27
- const manifest = await getManifest(client);
27
+ const manifest = await getManifest();
28
28
  const res = manifest.resources[resource];
29
29
  if (res) {
30
30
  console.log(`\nResource: ${resource} (module: ${res.module})\n`);
@@ -56,11 +56,11 @@ const hook = async function (opts) {
56
56
  if (resource === 'manifest' && action === 'refresh') {
57
57
  const { clearManifestCache } = await import('../lib/manifest.js');
58
58
  await clearManifestCache();
59
- const manifest = await getManifest(client, true);
59
+ const manifest = await getManifest(undefined, true);
60
60
  console.log(`Manifest refreshed: ${Object.keys(manifest.resources).length} resources discovered.`);
61
61
  return;
62
62
  }
63
- const manifest = await getManifest(client);
63
+ const manifest = await getManifest();
64
64
  const match = resolveAction(manifest, resource, action);
65
65
  if (!match) {
66
66
  const res = manifest.resources[resource];
@@ -90,7 +90,7 @@ const hook = async function (opts) {
90
90
  catch (error) {
91
91
  const err = error;
92
92
  if (err.message.includes('Missing required argument')) {
93
- const match = resolveAction(await getManifest(createSignClient()), resource, action);
93
+ const match = resolveAction(await getManifest(), resource, action);
94
94
  const argsStr = match?.action.args.map(a => `<${a}>`).join(' ') || '';
95
95
  console.error(`Error: ${err.message}`);
96
96
  console.error(`Usage: sign ${resource} ${action} ${argsStr}`);
@@ -1,8 +1,13 @@
1
1
  /**
2
2
  * Manifest Client
3
3
  *
4
- * Fetches and caches the API manifest from the backend.
4
+ * Fetches and caches the API manifest from the tenant domain.
5
5
  * The manifest contains all discoverable resources + actions.
6
+ *
7
+ * Flow:
8
+ * 1. After login, CLI fetches https://{domain}/tenant/manifest
9
+ * 2. Cached locally at ~/.sign/manifest.{domain}.json
10
+ * 3. Refetched daily or on "sign manifest refresh"
6
11
  */
7
12
  export interface ManifestAction {
8
13
  name: string;
@@ -23,13 +28,13 @@ export interface Manifest {
23
28
  resources: Record<string, ManifestResource>;
24
29
  }
25
30
  /**
26
- * Get the manifest from cache if fresh, otherwise from the API
31
+ * Get the manifest -- from cache if fresh, otherwise from the tenant domain
27
32
  */
28
- export declare const getManifest: (apiClient: {
33
+ export declare const getManifest: (_apiClient?: {
29
34
  get: (path: string) => Promise<unknown>;
30
35
  }, forceRefresh?: boolean) => Promise<Manifest>;
31
36
  /**
32
- * Clear the cached manifest
37
+ * Clear the cached manifest for the current domain
33
38
  */
34
39
  export declare const clearManifestCache: () => Promise<void>;
35
40
  /**
@@ -1,38 +1,79 @@
1
1
  /**
2
2
  * Manifest Client
3
3
  *
4
- * Fetches and caches the API manifest from the backend.
4
+ * Fetches and caches the API manifest from the tenant domain.
5
5
  * The manifest contains all discoverable resources + actions.
6
+ *
7
+ * Flow:
8
+ * 1. After login, CLI fetches https://{domain}/tenant/manifest
9
+ * 2. Cached locally at ~/.sign/manifest.{domain}.json
10
+ * 3. Refetched daily or on "sign manifest refresh"
6
11
  */
7
12
  import { readFile, writeFile, mkdir } from 'node:fs/promises';
8
- import { existsSync } from 'node:fs';
13
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
9
14
  import { join } from 'node:path';
10
15
  import { homedir } from 'node:os';
11
16
  // --- Config ---
12
17
  const CACHE_DIR = join(homedir(), '.sign');
13
- const CACHE_FILE = join(CACHE_DIR, 'manifest.json');
14
- const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
18
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
19
+ /**
20
+ * Get cache file path for a domain
21
+ */
22
+ const getCacheFile = (domain) => {
23
+ const safeDomain = domain.replace(/[^a-zA-Z0-9.-]/g, '_');
24
+ return join(CACHE_DIR, `manifest.${safeDomain}.json`);
25
+ };
26
+ /**
27
+ * Resolve the tenant domain from stored credentials
28
+ */
29
+ const resolveDomain = () => {
30
+ // Check default-domain file
31
+ const defaultDomainFile = join(CACHE_DIR, 'default-domain');
32
+ try {
33
+ if (existsSync(defaultDomainFile)) {
34
+ const domain = readFileSync(defaultDomainFile, 'utf-8').trim();
35
+ if (domain)
36
+ return domain;
37
+ }
38
+ }
39
+ catch { /* ignore */ }
40
+ // Check for any credential file
41
+ try {
42
+ const files = readdirSync(CACHE_DIR);
43
+ const credFile = files.find((f) => f.startsWith('credentials.'));
44
+ if (credFile) {
45
+ return credFile.replace('credentials.', '');
46
+ }
47
+ }
48
+ catch { /* ignore */ }
49
+ return null;
50
+ };
15
51
  /**
16
- * Get the manifest from cache if fresh, otherwise from the API
52
+ * Get the manifest -- from cache if fresh, otherwise from the tenant domain
17
53
  */
18
- export const getManifest = async (apiClient, forceRefresh = false) => {
54
+ export const getManifest = async (_apiClient, forceRefresh = false) => {
55
+ const domain = resolveDomain();
56
+ if (!domain) {
57
+ throw new Error('Not authenticated. Run "sign auth login --domain <domain>" first.');
58
+ }
19
59
  if (!forceRefresh) {
20
- const cached = await loadCachedManifest();
60
+ const cached = await loadCachedManifest(domain);
21
61
  if (cached)
22
62
  return cached;
23
63
  }
24
- const manifest = await fetchManifest(apiClient);
25
- await cacheManifest(manifest);
64
+ const manifest = await fetchManifest(domain);
65
+ await cacheManifest(domain, manifest);
26
66
  return manifest;
27
67
  };
28
68
  /**
29
69
  * Load manifest from local cache if it exists and is fresh
30
70
  */
31
- const loadCachedManifest = async () => {
71
+ const loadCachedManifest = async (domain) => {
72
+ const cacheFile = getCacheFile(domain);
32
73
  try {
33
- if (!existsSync(CACHE_FILE))
74
+ if (!existsSync(cacheFile))
34
75
  return null;
35
- const raw = await readFile(CACHE_FILE, 'utf-8');
76
+ const raw = await readFile(cacheFile, 'utf-8');
36
77
  const cached = JSON.parse(raw);
37
78
  const age = Date.now() - new Date(cached.fetchedAt).getTime();
38
79
  if (age > CACHE_TTL_MS)
@@ -44,47 +85,48 @@ const loadCachedManifest = async () => {
44
85
  }
45
86
  };
46
87
  /**
47
- * Fetch manifest from the tenants API Gateway (no auth required).
48
- * Uses the bootstrap URL base to reach the tenants gateway directly.
88
+ * Fetch manifest from the tenant domain.
89
+ * Uses plain HTTPS -- the manifest endpoint is on the tenant's CloudFront.
49
90
  */
50
- const fetchManifest = async (apiClient) => {
51
- // The manifest endpoint is on the tenants gateway (same as bootstrap).
52
- // It's unauthenticated, so we fetch it with plain HTTP.
53
- const bootstrapUrl = process.env.SIGN_BOOTSTRAP_URL || 'https://oo2wp0ax27.execute-api.us-east-1.amazonaws.com/dev/tenant/bootstrap-dev';
54
- const baseUrl = bootstrapUrl.replace(/\/tenant\/bootstrap.*$/, '');
55
- const manifestUrl = `${baseUrl}/tenant/manifest`;
91
+ const fetchManifest = async (domain) => {
92
+ const manifestUrl = `https://${domain}/tenant/manifest`;
56
93
  const response = await fetch(manifestUrl);
57
94
  if (!response.ok) {
58
- throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
95
+ throw new Error(`Failed to fetch manifest from ${domain}: ${response.status} ${response.statusText}`);
59
96
  }
60
97
  return await response.json();
61
98
  };
62
99
  /**
63
- * Cache manifest to local filesystem
100
+ * Cache manifest to local filesystem (per-domain)
64
101
  */
65
- const cacheManifest = async (manifest) => {
102
+ const cacheManifest = async (domain, manifest) => {
66
103
  try {
67
104
  if (!existsSync(CACHE_DIR)) {
68
105
  await mkdir(CACHE_DIR, { recursive: true });
69
106
  }
70
107
  const cached = {
71
108
  fetchedAt: new Date().toISOString(),
109
+ domain,
72
110
  manifest
73
111
  };
74
- await writeFile(CACHE_FILE, JSON.stringify(cached, null, 2));
112
+ await writeFile(getCacheFile(domain), JSON.stringify(cached, null, 2));
75
113
  }
76
114
  catch {
77
115
  // Cache write failure is non-fatal
78
116
  }
79
117
  };
80
118
  /**
81
- * Clear the cached manifest
119
+ * Clear the cached manifest for the current domain
82
120
  */
83
121
  export const clearManifestCache = async () => {
122
+ const domain = resolveDomain();
123
+ if (!domain)
124
+ return;
84
125
  try {
85
- if (existsSync(CACHE_FILE)) {
126
+ const cacheFile = getCacheFile(domain);
127
+ if (existsSync(cacheFile)) {
86
128
  const { unlink } = await import('node:fs/promises');
87
- await unlink(CACHE_FILE);
129
+ await unlink(cacheFile);
88
130
  }
89
131
  }
90
132
  catch {
@@ -11,7 +11,7 @@ import { SigV4ApiClient } from '@hyperdrive.bot/cli-auth';
11
11
  export declare class SignApiClient extends SigV4ApiClient {
12
12
  constructor(domain?: string);
13
13
  /**
14
- * GET request to the primary API (tenants gateway)
14
+ * GET request to the primary API (sign gateway)
15
15
  */
16
16
  get<T = unknown>(path: string): Promise<T>;
17
17
  /**
@@ -25,7 +25,7 @@ export class SignApiClient extends SigV4ApiClient {
25
25
  });
26
26
  }
27
27
  /**
28
- * GET request to the primary API (tenants gateway)
28
+ * GET request to the primary API (sign gateway)
29
29
  */
30
30
  async get(path) {
31
31
  return this.makeSignedRequest('GET', path);
@@ -120,5 +120,5 @@
120
120
  ]
121
121
  }
122
122
  },
123
- "version": "0.1.4"
123
+ "version": "0.1.6"
124
124
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hyperdrive.bot/sign-cli",
3
3
  "description": "Sign CLI — runtime-discovered API client for the Sign platform",
4
- "version": "0.1.4",
4
+ "version": "0.1.6",
5
5
  "author": "marcelomarra",
6
6
  "bin": {
7
7
  "sign": "./bin/run.js"