docstar-cli 0.0.0 → 1.0.3

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.
@@ -1,7 +1,7 @@
1
1
  import { checkbox } from '@inquirer/prompts';
2
2
  import { Args, Command, Flags } from '@oclif/core';
3
- import { mkdir, writeFile } from 'node:fs/promises';
4
- import { generateCommandFile, generateListCommandFile } from '../lib/codegen.js';
3
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { generateCommandFile } from '../lib/codegen.js';
5
5
  import { configDir, configPath, modulePath } from '../lib/paths.js';
6
6
  const buildBaseUrl = (domain) => {
7
7
  if (domain.startsWith('http://') || domain.startsWith('https://'))
@@ -15,11 +15,23 @@ const fetchJson = async (url) => {
15
15
  throw new Error(`Request to ${url} failed (${response.status} ${response.statusText})`);
16
16
  return (await response.json());
17
17
  };
18
+ const readSavedConfig = async () => {
19
+ try {
20
+ return JSON.parse(await readFile(configPath(), 'utf8'));
21
+ }
22
+ catch {
23
+ return { collections: [] };
24
+ }
25
+ };
18
26
  export default class Init extends Command {
19
27
  static args = {
20
28
  domain: Args.string({ description: 'DocStar docs domain, e.g. docs.msg91.com or localhost:3000', required: true }),
21
29
  };
22
- static description = 'Discover published modules for a DocStar docs site, pick which ones to install, and register their CLI commands';
30
+ static description = [
31
+ 'Discover published modules for a DocStar docs site, pick which ones to install, and register their CLI commands.',
32
+ 'You can run this against multiple docs sites/collections — each is kept separate under its own CLI name (`docstar-cli <cli-name> <module> <command>`).',
33
+ 'After installing, run `docstar-cli list` to see what got installed.',
34
+ ].join('\n');
23
35
  static examples = [
24
36
  '<%= config.bin %> <%= command.id %> docs.msg91.com',
25
37
  '<%= config.bin %> <%= command.id %> localhost:3000 --collectionId paM4R4A26Hvb',
@@ -35,13 +47,24 @@ export default class Init extends Command {
35
47
  listUrl.searchParams.set('collectionId', flags.collectionId);
36
48
  this.log(`Fetching modules from ${listUrl.toString()} ...`);
37
49
  const listResponse = await fetchJson(listUrl.toString());
50
+ if (!listResponse.collection?.cliName) {
51
+ this.error('The docs site did not return a CLI name for this collection. Please update hitman-api/hitman-ui, or set one in the collection settings.');
52
+ }
38
53
  if (!listResponse.modules || listResponse.modules.length === 0) {
39
54
  this.log('No published modules found.');
40
55
  return;
41
56
  }
57
+ const { cliName } = listResponse.collection;
42
58
  const selectedPaths = await checkbox({
43
- choices: listResponse.modules.map((module) => ({ checked: true, name: `${module.name} (${module.path})`, value: module.path })),
44
- message: 'Select modules to install (space to toggle, enter to confirm)',
59
+ choices: listResponse.modules.map((module) => {
60
+ const count = module.endpointCount ?? module.endpoints?.length ?? 0;
61
+ return {
62
+ checked: true,
63
+ name: `${module.name} (${module.path}) — ${count} endpoint${count === 1 ? '' : 's'}`,
64
+ value: module.path,
65
+ };
66
+ }),
67
+ message: `Select modules to install for "${cliName}" (space to toggle, enter to confirm)`,
45
68
  });
46
69
  if (selectedPaths.length === 0) {
47
70
  this.log('No modules selected, nothing installed.');
@@ -54,26 +77,32 @@ export default class Init extends Command {
54
77
  if (flags.collectionId)
55
78
  moduleUrl.searchParams.set('collectionId', flags.collectionId);
56
79
  this.log(`Fetching ${path} ...`);
80
+ // eslint-disable-next-line no-await-in-loop
57
81
  const moduleJson = await fetchJson(moduleUrl.toString());
58
- await mkdir(configDir() + '/modules', { recursive: true });
59
- await writeFile(modulePath(path), JSON.stringify(moduleJson, null, 2));
82
+ // eslint-disable-next-line no-await-in-loop
83
+ await mkdir(configDir() + `/modules/${cliName}`, { recursive: true });
84
+ // eslint-disable-next-line no-await-in-loop
85
+ await writeFile(modulePath(cliName, path), JSON.stringify(moduleJson, null, 2));
60
86
  for (const endpoint of moduleJson.endpoints) {
61
- const filePath = await generateCommandFile(this.config.root, path, endpoint);
62
- this.log(` registered command: ${this.config.bin} ${path} ${endpoint.cli.command.name} -> ${filePath}`);
87
+ // eslint-disable-next-line no-await-in-loop
88
+ await generateCommandFile(this.config.root, cliName, path, endpoint);
89
+ this.log(` registered command: ${this.config.bin} ${cliName} ${path} ${endpoint.cli.command.name}`);
63
90
  }
64
- await generateListCommandFile(this.config.root, path);
65
- this.log(` registered command: ${this.config.bin} ${path} list`);
66
91
  selectedModules.push({ name: moduleJson.module.name, path });
67
92
  }
68
- const savedConfig = {
93
+ const collectionConfig = {
69
94
  baseUrl,
70
- collection: listResponse.collection ?? null,
95
+ cliName,
71
96
  collectionId: flags.collectionId ?? null,
72
97
  domain: args.domain,
73
98
  modules: selectedModules,
99
+ name: listResponse.collection.name,
74
100
  updatedAt: new Date().toISOString(),
75
101
  };
102
+ const savedConfig = await readSavedConfig();
103
+ const otherCollections = savedConfig.collections.filter((collection) => collection.cliName !== cliName);
104
+ savedConfig.collections = [...otherCollections, collectionConfig];
76
105
  await writeFile(configPath(), JSON.stringify(savedConfig, null, 2));
77
- this.log(`\nInstalled ${selectedModules.length} module(s). Config saved to ${configPath()}`);
106
+ this.log(`\nInstalled ${selectedModules.length} module(s) for "${cliName}". Config saved to ${configPath()}`);
78
107
  }
79
108
  }
@@ -0,0 +1,13 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class List extends Command {
3
+ static args: {
4
+ collection: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ module: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static examples: string[];
9
+ static flags: {
10
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,71 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { loadModule } from '../lib/http.js';
4
+ import { configPath } from '../lib/paths.js';
5
+ export default class List extends Command {
6
+ static args = {
7
+ collection: Args.string({ description: 'Show modules installed for this collection (CLI name), e.g. msg91', required: false }),
8
+ module: Args.string({ description: 'Show all endpoints for this module within the collection, e.g. slack', required: false }),
9
+ };
10
+ static description = 'List installed collections, the modules within one, or every endpoint of one module';
11
+ static examples = [
12
+ '<%= config.bin %> <%= command.id %> # list every installed collection',
13
+ '<%= config.bin %> <%= command.id %> --all # same as above',
14
+ '<%= config.bin %> <%= command.id %> msg91 # list modules installed for the "msg91" collection',
15
+ '<%= config.bin %> <%= command.id %> msg91 slack # list all of Slack\'s installed endpoints in "msg91"',
16
+ ];
17
+ static flags = {
18
+ all: Flags.boolean({ description: 'List every installed collection (default when no arguments are given)' }),
19
+ };
20
+ async run() {
21
+ const { args } = await this.parse(List);
22
+ let savedConfig;
23
+ try {
24
+ savedConfig = JSON.parse(await readFile(configPath(), 'utf8'));
25
+ }
26
+ catch {
27
+ this.error('No installed modules found. Run `docstar-cli init <domain>` first.');
28
+ }
29
+ if (savedConfig.collections.length === 0) {
30
+ this.log('No collections installed. Run `docstar-cli init <domain>` to install one.');
31
+ return;
32
+ }
33
+ if (!args.collection) {
34
+ for (const collection of savedConfig.collections) {
35
+ this.log(`${collection.name} (${collection.cliName}) — ${collection.domain} — ${collection.modules.length} module${collection.modules.length === 1 ? '' : 's'}`);
36
+ }
37
+ return;
38
+ }
39
+ const collection = savedConfig.collections.find((c) => c.cliName === args.collection);
40
+ if (!collection) {
41
+ this.error(`Collection "${args.collection}" isn't installed. Run \`docstar-cli list\` to see installed collections.`);
42
+ }
43
+ if (!args.module) {
44
+ for (const module of collection.modules) {
45
+ // eslint-disable-next-line no-await-in-loop
46
+ const moduleJson = await loadModule(collection.cliName, module.path);
47
+ const count = moduleJson.endpoints.length;
48
+ this.log(`${module.name} (${module.path}) — ${count} endpoint${count === 1 ? '' : 's'}`);
49
+ }
50
+ return;
51
+ }
52
+ const moduleJson = await loadModule(collection.cliName, args.module);
53
+ if (moduleJson.endpoints.length === 0) {
54
+ this.log(`No endpoints found for module "${args.module}".`);
55
+ return;
56
+ }
57
+ for (const endpoint of moduleJson.endpoints) {
58
+ const { cli } = endpoint;
59
+ const aliases = cli.command.aliases.filter((alias) => alias !== cli.command.name);
60
+ this.log(`${cli.command.name}${aliases.length > 0 ? ` (aliases: ${aliases.join(', ')})` : ''}`);
61
+ if (cli.description)
62
+ this.log(` ${cli.description}`);
63
+ this.log(` ${endpoint.method}`);
64
+ if (cli.parameters.required.length > 0)
65
+ this.log(` required: ${cli.parameters.required.join(', ')}`);
66
+ if (cli.parameters.optional.length > 0)
67
+ this.log(` optional: ${cli.parameters.optional.join(', ')}`);
68
+ this.log('');
69
+ }
70
+ }
71
+ }
@@ -1,8 +1,6 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class World extends Command {
3
- static args: {};
2
+ export default class Mcp extends Command {
4
3
  static description: string;
5
4
  static examples: string[];
6
- static flags: {};
7
5
  run(): Promise<void>;
8
6
  }
@@ -0,0 +1,66 @@
1
+ import { Command } from '@oclif/core';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { z } from 'zod';
6
+ import { executeCliCommand, loadModule } from '../lib/http.js';
7
+ import { configPath } from '../lib/paths.js';
8
+ const sanitize = (value) => value.replaceAll(/[^a-zA-Z0-9_-]/g, '_');
9
+ const buildInputShape = (required, optional) => {
10
+ const shape = {};
11
+ for (const name of required)
12
+ shape[name] = z.string().describe(`Required parameter: ${name}`);
13
+ for (const name of optional)
14
+ shape[name] = z.string().optional().describe(`Optional parameter: ${name}`);
15
+ return shape;
16
+ };
17
+ export default class Mcp extends Command {
18
+ static description = [
19
+ 'Start an MCP (Model Context Protocol) server exposing every installed endpoint (across all installed collections) as a callable tool for AI clients (Claude Desktop, Claude Code, etc.).',
20
+ 'Each endpoint becomes one tool named `<cli-name>__<module>__<command>`, e.g. `msg91__slack__send-message-1`. Run `docstar-cli init <domain>` first so there is at least one installed module to expose.',
21
+ 'This command does not exit on its own — it stays running and communicates over stdio for as long as the connecting AI client keeps the connection open.',
22
+ ].join('\n');
23
+ static examples = [
24
+ '<%= config.bin %> <%= command.id %>',
25
+ '# Then register it in an MCP client config, e.g. .mcp.json:',
26
+ '# { "mcpServers": { "docstar": { "command": "docstar-cli", "args": ["mcp"] } } }',
27
+ ];
28
+ async run() {
29
+ let savedConfig;
30
+ try {
31
+ savedConfig = JSON.parse(await readFile(configPath(), 'utf8'));
32
+ }
33
+ catch {
34
+ this.error('No installed modules found. Run `docstar-cli init <domain>` first.');
35
+ }
36
+ const server = new McpServer({ name: 'docstar-cli', version: '1.0.0' });
37
+ let toolCount = 0;
38
+ for (const collection of savedConfig.collections) {
39
+ for (const module of collection.modules) {
40
+ // eslint-disable-next-line no-await-in-loop
41
+ const moduleJson = await loadModule(collection.cliName, module.path);
42
+ for (const endpoint of moduleJson.endpoints) {
43
+ const { cli } = endpoint;
44
+ const toolName = sanitize(`${collection.cliName}__${module.path}__${cli.command.name}`);
45
+ const description = cli.description || `${cli.command.name} (${collection.cliName} ${module.path})`;
46
+ const inputShape = buildInputShape(cli.parameters.required, cli.parameters.optional);
47
+ server.tool(toolName, description, inputShape, async (args) => {
48
+ try {
49
+ const result = await executeCliCommand(collection.cliName, module.path, cli.command.name, args);
50
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
51
+ }
52
+ catch (error) {
53
+ return { content: [{ type: 'text', text: error.message }], isError: true };
54
+ }
55
+ });
56
+ toolCount++;
57
+ }
58
+ }
59
+ }
60
+ if (toolCount === 0) {
61
+ this.error('No installed endpoints found. Run `docstar-cli init <domain>` and select at least one module first.');
62
+ }
63
+ const transport = new StdioServerTransport();
64
+ await server.connect(transport);
65
+ }
66
+ }
@@ -1,3 +1,2 @@
1
1
  import type { EndpointContract } from './types.js';
2
- export declare const generateCommandFile: (cliRoot: string, modulePathSegment: string, endpoint: EndpointContract) => Promise<string>;
3
- export declare const generateListCommandFile: (cliRoot: string, modulePathSegment: string) => Promise<string>;
2
+ export declare const generateCommandFile: (cliRoot: string, cliName: string, modulePathSegment: string, endpoint: EndpointContract) => Promise<string>;
@@ -3,20 +3,20 @@ import { join } from 'node:path';
3
3
  const flagLine = (name, required) =>
4
4
  // Required params are collected interactively when missing, so the flag itself stays optional to oclif.
5
5
  ` '${name}': Flags.string({description: '${required ? 'Required' : 'Optional'} parameter'}),`;
6
- const commandFileContent = (modulePathSegment, endpoint) => {
6
+ const commandFileContent = (cliName, modulePathSegment, endpoint) => {
7
7
  const { cli } = endpoint;
8
8
  const flagLines = [
9
9
  ...cli.parameters.required.map((name) => flagLine(name, true)),
10
10
  ...cli.parameters.optional.map((name) => flagLine(name, false)),
11
11
  ].join('\n');
12
12
  return `import {Command, Flags} from '@oclif/core'
13
- import {executeCliCommand} from '../../lib/http.js'
14
- import {promptForMissingParams} from '../../lib/prompt.js'
13
+ import {executeCliCommand} from '../../../lib/http.js'
14
+ import {promptForMissingParams} from '../../../lib/prompt.js'
15
15
 
16
16
  const REQUIRED_PARAMS = ${JSON.stringify(cli.parameters.required)}
17
17
 
18
18
  export default class Generated extends Command {
19
- static description = ${JSON.stringify(cli.description || `${cli.name} (${modulePathSegment})`)}
19
+ static description = ${JSON.stringify(cli.description || `${cli.name} (${cliName} ${modulePathSegment})`)}
20
20
  static aliases = ${JSON.stringify(cli.command.aliases.filter((alias) => alias !== cli.name))}
21
21
  static flags = {
22
22
  ${flagLines}
@@ -32,7 +32,7 @@ ${flagLines}
32
32
  Object.assign(params, await promptForMissingParams(REQUIRED_PARAMS, params))
33
33
 
34
34
  try {
35
- const result = await executeCliCommand('${modulePathSegment}', '${cli.command.name}', params)
35
+ const result = await executeCliCommand('${cliName}', '${modulePathSegment}', '${cli.command.name}', params)
36
36
  this.log(JSON.stringify(result, null, 2))
37
37
  } catch (error) {
38
38
  this.error(error.message)
@@ -41,38 +41,10 @@ ${flagLines}
41
41
  }
42
42
  `;
43
43
  };
44
- const listCommandFileContent = (modulePathSegment) => `import {Command} from '@oclif/core'
45
- import {loadModule} from '../../lib/http.js'
46
-
47
- export default class GeneratedList extends Command {
48
- static description = 'List installed APIs for the ${modulePathSegment} module'
49
-
50
- async run() {
51
- const moduleJson = await loadModule('${modulePathSegment}')
52
-
53
- for (const endpoint of moduleJson.endpoints) {
54
- const {cli} = endpoint
55
- this.log(\`\${cli.command.name}\${cli.command.aliases.filter((alias) => alias !== cli.command.name).length > 0 ? \` (aliases: \${cli.command.aliases.filter((alias) => alias !== cli.command.name).join(', ')})\` : ''}\`)
56
- if (cli.description) this.log(\` \${cli.description}\`)
57
- this.log(\` \${endpoint.method}\`)
58
- if (cli.parameters.required.length > 0) this.log(\` required: \${cli.parameters.required.join(', ')}\`)
59
- if (cli.parameters.optional.length > 0) this.log(\` optional: \${cli.parameters.optional.join(', ')}\`)
60
- this.log('')
61
- }
62
- }
63
- }
64
- `;
65
- export const generateCommandFile = async (cliRoot, modulePathSegment, endpoint) => {
66
- const dir = join(cliRoot, 'dist', 'commands', modulePathSegment);
44
+ export const generateCommandFile = async (cliRoot, cliName, modulePathSegment, endpoint) => {
45
+ const dir = join(cliRoot, 'dist', 'commands', cliName, modulePathSegment);
67
46
  await mkdir(dir, { recursive: true });
68
47
  const filePath = join(dir, `${endpoint.cli.command.name}.js`);
69
- await writeFile(filePath, commandFileContent(modulePathSegment, endpoint));
70
- return filePath;
71
- };
72
- export const generateListCommandFile = async (cliRoot, modulePathSegment) => {
73
- const dir = join(cliRoot, 'dist', 'commands', modulePathSegment);
74
- await mkdir(dir, { recursive: true });
75
- const filePath = join(dir, 'list.js');
76
- await writeFile(filePath, listCommandFileContent(modulePathSegment));
48
+ await writeFile(filePath, commandFileContent(cliName, modulePathSegment, endpoint));
77
49
  return filePath;
78
50
  };
@@ -1,8 +1,8 @@
1
1
  import type { EndpointContract, ModuleJson } from './types.js';
2
2
  export declare const stripHtml: (value: string | null | undefined) => string;
3
- export declare const loadModule: (modulePathSegment: string) => Promise<ModuleJson>;
3
+ export declare const loadModule: (cliName: string, modulePathSegment: string) => Promise<ModuleJson>;
4
4
  export declare const findEndpoint: (moduleJson: ModuleJson, commandName: string) => EndpointContract | undefined;
5
- export declare const executeCliCommand: (modulePathSegment: string, commandName: string, params: Record<string, string>) => Promise<{
5
+ export declare const executeCliCommand: (cliName: string, modulePathSegment: string, commandName: string, params: Record<string, string>) => Promise<{
6
6
  status: number;
7
7
  body: unknown;
8
8
  }>;
package/dist/lib/http.js CHANGED
@@ -4,8 +4,8 @@ import { modulePath } from './paths.js';
4
4
  // `<span text-block="true">...</span>`-style markup; unwrap to the raw value.
5
5
  export const stripHtml = (value) => (value ?? '').replaceAll(/<[^>]*>/g, '');
6
6
  const substitutePlaceholders = (template, params) => Object.entries(params).reduce((result, [key, value]) => result.replaceAll(`:${key}`, value).replaceAll(`{${key}}`, value).replaceAll(`{{${key}}}`, value), template);
7
- export const loadModule = async (modulePathSegment) => {
8
- const raw = await readFile(modulePath(modulePathSegment), 'utf8');
7
+ export const loadModule = async (cliName, modulePathSegment) => {
8
+ const raw = await readFile(modulePath(cliName, modulePathSegment), 'utf8');
9
9
  return JSON.parse(raw);
10
10
  };
11
11
  export const findEndpoint = (moduleJson, commandName) => moduleJson.endpoints.find((endpoint) => endpoint.cli.command.name === commandName || endpoint.cli.command.aliases.includes(commandName));
@@ -17,14 +17,47 @@ const buildUrl = (endpoint, params) => {
17
17
  return `${baseUrl}${path}`;
18
18
  };
19
19
  const buildHeaders = (endpoint, params) => {
20
+ // Header names are case-insensitive; the docs editor can end up storing two entries that only
21
+ // differ by case (e.g. "Content-Type" and "content-type"). Sending both as separate header
22
+ // lines has been observed to break body parsing on the receiving server, so dedupe by
23
+ // lowercased name — first entry wins, using its original casing for readability.
24
+ const seenLowerNames = new Set();
20
25
  const headers = {};
21
26
  for (const [rawKey, rawValue] of Object.entries(endpoint.headers || {})) {
22
27
  const key = stripHtml(rawKey);
28
+ const lowerKey = key.toLowerCase();
29
+ if (seenLowerNames.has(lowerKey))
30
+ continue;
31
+ seenLowerNames.add(lowerKey);
23
32
  const value = stripHtml(rawValue?.value ?? rawValue);
24
33
  headers[key] = params[key] ?? value;
25
34
  }
26
35
  return headers;
27
36
  };
37
+ // CLI flag values always arrive as strings, but the JSON body template's existing value for a
38
+ // key tells us its real type (boolean/number/array) — coerce back to that shape instead of
39
+ // clobbering e.g. `getUrl: true` with the literal string "true", or `collectionIds: []` with a
40
+ // plain string.
41
+ const coerceToTemplateType = (existingValue, rawValue) => {
42
+ if (typeof existingValue === 'boolean')
43
+ return rawValue === 'true';
44
+ if (typeof existingValue === 'number') {
45
+ const parsedNumber = Number(rawValue);
46
+ return Number.isNaN(parsedNumber) ? rawValue : parsedNumber;
47
+ }
48
+ if (Array.isArray(existingValue)) {
49
+ try {
50
+ const parsed = JSON.parse(rawValue);
51
+ if (Array.isArray(parsed))
52
+ return parsed;
53
+ }
54
+ catch {
55
+ // not a JSON array literal — fall through to comma-splitting
56
+ }
57
+ return rawValue.split(',').map((item) => item.trim());
58
+ }
59
+ return rawValue;
60
+ };
28
61
  const buildBody = (endpoint, params) => {
29
62
  const rawValue = endpoint.body?.raw?.value;
30
63
  if (endpoint.body?.type !== 'JSON' || !rawValue)
@@ -38,15 +71,15 @@ const buildBody = (endpoint, params) => {
38
71
  }
39
72
  for (const [key, value] of Object.entries(params)) {
40
73
  if (key in parsed)
41
- parsed[key] = value;
74
+ parsed[key] = coerceToTemplateType(parsed[key], value);
42
75
  }
43
76
  return JSON.stringify(parsed);
44
77
  };
45
- export const executeCliCommand = async (modulePathSegment, commandName, params) => {
46
- const moduleJson = await loadModule(modulePathSegment);
78
+ export const executeCliCommand = async (cliName, modulePathSegment, commandName, params) => {
79
+ const moduleJson = await loadModule(cliName, modulePathSegment);
47
80
  const endpoint = findEndpoint(moduleJson, commandName);
48
81
  if (!endpoint) {
49
- throw new Error(`No endpoint found for command "${commandName}" in module "${modulePathSegment}"`);
82
+ throw new Error(`No endpoint found for command "${commandName}" in module "${cliName} ${modulePathSegment}"`);
50
83
  }
51
84
  const url = buildUrl(endpoint, params);
52
85
  const headers = buildHeaders(endpoint, params);
@@ -1,4 +1,5 @@
1
1
  export declare const configDir: () => string;
2
2
  export declare const configPath: () => string;
3
3
  export declare const modulesDir: () => string;
4
- export declare const modulePath: (modulePathSegment: string) => string;
4
+ export declare const collectionModulesDir: (cliName: string) => string;
5
+ export declare const modulePath: (cliName: string, modulePathSegment: string) => string;
package/dist/lib/paths.js CHANGED
@@ -3,4 +3,5 @@ import { join } from 'node:path';
3
3
  export const configDir = () => join(homedir(), '.docstar');
4
4
  export const configPath = () => join(configDir(), 'config.json');
5
5
  export const modulesDir = () => join(configDir(), 'modules');
6
- export const modulePath = (modulePathSegment) => join(modulesDir(), `${modulePathSegment}.json`);
6
+ export const collectionModulesDir = (cliName) => join(modulesDir(), cliName);
7
+ export const modulePath = (cliName, modulePathSegment) => join(collectionModulesDir(cliName), `${modulePathSegment}.json`);
@@ -35,15 +35,26 @@ export type ModuleJson = {
35
35
  export type ModuleSummary = {
36
36
  name: string;
37
37
  path: string;
38
+ endpointCount?: number;
39
+ endpoints?: {
40
+ name: string;
41
+ path: string;
42
+ }[];
38
43
  };
39
- export type SavedConfig = {
40
- baseUrl: string;
44
+ export type CollectionSummary = {
45
+ id: string;
46
+ name: string;
47
+ cliName: string;
48
+ };
49
+ export type CollectionConfig = {
50
+ cliName: string;
51
+ name: string;
41
52
  domain: string;
53
+ baseUrl: string;
42
54
  collectionId: string | null;
43
- collection: {
44
- id: string;
45
- name: string;
46
- } | null;
47
55
  modules: ModuleSummary[];
48
56
  updatedAt: string;
49
57
  };
58
+ export type SavedConfig = {
59
+ collections: CollectionConfig[];
60
+ };