@kosha-ai/cli 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Codemancers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # cli
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build cli` to build the library.
8
+
9
+ ## Running unit tests
10
+
11
+ Run `nx test cli` to execute the unit tests via [Jest](https://jestjs.io).
package/bin/kosha.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { run } from '../dist/lib/cli.js';
3
+
4
+ run(process.argv.slice(2))
5
+ .then(({ exitCode }) => {
6
+ // A running server keeps the event loop alive; only exit on a real code.
7
+ if (exitCode !== 0) process.exit(exitCode);
8
+ })
9
+ .catch((error) => {
10
+ process.stderr.write(`${error instanceof Error ? error.message : error}\n`);
11
+ process.exit(1);
12
+ });
@@ -0,0 +1,10 @@
1
+ export { run, USAGE, redactUrl, type RunResult } from './lib/cli.js';
2
+ export { loadConfig, findConfigPath, CONFIG_FILENAMES, ConfigNotFoundError, } from './lib/load-config.js';
3
+ export { loadEnv, ENV_FILENAMES } from './lib/load-env.js';
4
+ export { start, type StartOptions } from './lib/commands/start.js';
5
+ export { migrate, resolvePrismaBin, type MigrateOptions, type MigrateSubcommand, } from './lib/commands/migrate.js';
6
+ export { addIntegrations, listIntegrations, applyConfigEdit, detectPackageManager, installedIntegrationKeys, packageNameFor, type AddIntegrationOptions, type AddIntegrationResult, type IntegrationListEntry, } from './lib/commands/integration.js';
7
+ export { findInstalledIntegrations, type InstalledIntegration, } from './lib/commands/installed.js';
8
+ export { resolveServerPackageDir, resolveSchemaPath, resolveMigrationsDir, } from './lib/paths.js';
9
+ export { runNodeScript, runShellCommand, type RunCommandOptions, } from './lib/spawn.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EACL,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,wBAAwB,EACxB,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,yBAAyB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,aAAa,EACb,eAAe,EACf,KAAK,iBAAiB,GACvB,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { run, USAGE, redactUrl } from './lib/cli.js';
2
+ export { loadConfig, findConfigPath, CONFIG_FILENAMES, ConfigNotFoundError, } from './lib/load-config.js';
3
+ export { loadEnv, ENV_FILENAMES } from './lib/load-env.js';
4
+ export { start } from './lib/commands/start.js';
5
+ export { migrate, resolvePrismaBin, } from './lib/commands/migrate.js';
6
+ export { addIntegrations, listIntegrations, applyConfigEdit, detectPackageManager, installedIntegrationKeys, packageNameFor, } from './lib/commands/integration.js';
7
+ export { findInstalledIntegrations, } from './lib/commands/installed.js';
8
+ export { resolveServerPackageDir, resolveSchemaPath, resolveMigrationsDir, } from './lib/paths.js';
9
+ export { runNodeScript, runShellCommand, } from './lib/spawn.js';
@@ -0,0 +1,8 @@
1
+ export declare const USAGE = "kosha \u2014 the Kosha integration platform CLI\n\nUsage\n kosha start Boot the server from kosha.config.ts\n kosha migrate [deploy|dev|status|reset]\n Run Prisma against the packaged schema\n kosha integration list Show installed vs configured integrations\n kosha integration add <key...> Install integrations and register them\n kosha info Print the resolved config summary\n\nOptions\n --config <path> Path to the config file (default: ./kosha.config.ts)\n --port <number> Override server.port for this run\n --name <name> Migration name, for `migrate dev`\n --skip-install `integration add` edits the config without installing\n -h, --help Show this help\n -v, --version Show the CLI version\n";
2
+ export interface RunResult {
3
+ exitCode: number;
4
+ }
5
+ export declare function run(argv: string[]): Promise<RunResult>;
6
+ /** Never print credentials, even to a local terminal. */
7
+ export declare function redactUrl(url?: string): string;
8
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,KAAK,8zBAiBjB,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAqH5D;AAED,yDAAyD;AACzD,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAU9C"}
@@ -0,0 +1,136 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { addIntegrations, listIntegrations } from './commands/integration.js';
3
+ import { migrate } from './commands/migrate.js';
4
+ import { start } from './commands/start.js';
5
+ export const USAGE = `kosha — the Kosha integration platform CLI
6
+
7
+ Usage
8
+ kosha start Boot the server from kosha.config.ts
9
+ kosha migrate [deploy|dev|status|reset]
10
+ Run Prisma against the packaged schema
11
+ kosha integration list Show installed vs configured integrations
12
+ kosha integration add <key...> Install integrations and register them
13
+ kosha info Print the resolved config summary
14
+
15
+ Options
16
+ --config <path> Path to the config file (default: ./kosha.config.ts)
17
+ --port <number> Override server.port for this run
18
+ --name <name> Migration name, for \`migrate dev\`
19
+ --skip-install \`integration add\` edits the config without installing
20
+ -h, --help Show this help
21
+ -v, --version Show the CLI version
22
+ `;
23
+ export async function run(argv) {
24
+ const { values, positionals } = parseArgs({
25
+ args: argv,
26
+ allowPositionals: true,
27
+ options: {
28
+ config: { type: 'string' },
29
+ port: { type: 'string' },
30
+ name: { type: 'string' },
31
+ 'skip-install': { type: 'boolean' },
32
+ help: { type: 'boolean', short: 'h' },
33
+ version: { type: 'boolean', short: 'v' },
34
+ },
35
+ });
36
+ const [command, ...rest] = positionals;
37
+ if (values.help || !command) {
38
+ process.stdout.write(USAGE);
39
+ return { exitCode: command ? 0 : values.help ? 0 : 1 };
40
+ }
41
+ const common = {
42
+ cwd: process.cwd(),
43
+ config: values.config,
44
+ };
45
+ switch (command) {
46
+ case 'start': {
47
+ await start({
48
+ ...common,
49
+ port: values.port ? Number(values.port) : undefined,
50
+ });
51
+ // startKoshaServer keeps the process alive.
52
+ return { exitCode: 0 };
53
+ }
54
+ case 'migrate': {
55
+ const exitCode = await migrate({
56
+ ...common,
57
+ subcommand: rest[0],
58
+ name: values.name,
59
+ });
60
+ return { exitCode };
61
+ }
62
+ case 'integration': {
63
+ const [subcommand, ...keys] = rest;
64
+ if (subcommand === 'list' || subcommand === undefined) {
65
+ const entries = await listIntegrations(common);
66
+ if (entries.length === 0) {
67
+ process.stdout.write('No integrations installed. Try `kosha integration add github`.\n');
68
+ return { exitCode: 0 };
69
+ }
70
+ for (const entry of entries) {
71
+ const state = !entry.installed
72
+ ? 'configured but NOT installed'
73
+ : !entry.configured
74
+ ? 'installed but NOT in kosha.config.ts'
75
+ : 'ready';
76
+ process.stdout.write(`${entry.key.padEnd(16)} ${state}\n`);
77
+ }
78
+ return {
79
+ exitCode: entries.every((e) => e.installed && e.configured) ? 0 : 1,
80
+ };
81
+ }
82
+ if (subcommand === 'add') {
83
+ const result = await addIntegrations({
84
+ ...common,
85
+ keys,
86
+ skipInstall: values['skip-install'],
87
+ });
88
+ process.stdout.write(result.installed
89
+ ? `Installed ${result.packages.join(', ')}\n`
90
+ : `Registered ${result.packages.join(', ')} (install skipped)\n`);
91
+ if (result.configUpdated) {
92
+ process.stdout.write('Updated kosha.config.ts\n');
93
+ }
94
+ else {
95
+ process.stdout.write(`Could not edit the config automatically. Add this yourself:\n\n${result.manualSnippet}\n\n`);
96
+ }
97
+ return { exitCode: result.configUpdated ? 0 : 1 };
98
+ }
99
+ process.stderr.write(`Unknown integration subcommand "${subcommand}". Expected list or add.\n`);
100
+ return { exitCode: 1 };
101
+ }
102
+ case 'info': {
103
+ const { loadConfig } = await import('./load-config.js');
104
+ const { config, path } = await loadConfig(common);
105
+ process.stdout.write([
106
+ `config ${path}`,
107
+ `database ${redactUrl(config.database?.url)}`,
108
+ `port ${config.server?.port ?? 3333}`,
109
+ `prefix ${config.server?.globalPrefix ?? 'api'}`,
110
+ `integrations ${(config.integrations ?? []).map((i) => i.key).join(', ') || '(none)'}`,
111
+ '',
112
+ ].join('\n'));
113
+ return { exitCode: 0 };
114
+ }
115
+ default: {
116
+ process.stderr.write(`Unknown command "${command}".\n\n${USAGE}`);
117
+ return { exitCode: 1 };
118
+ }
119
+ }
120
+ }
121
+ /** Never print credentials, even to a local terminal. */
122
+ export function redactUrl(url) {
123
+ if (!url)
124
+ return '(not set)';
125
+ try {
126
+ const parsed = new URL(url);
127
+ if (parsed.password)
128
+ parsed.password = '***';
129
+ if (parsed.username)
130
+ parsed.username = '***';
131
+ return parsed.toString();
132
+ }
133
+ catch {
134
+ return '(unparseable)';
135
+ }
136
+ }
@@ -0,0 +1,14 @@
1
+ export interface InstalledIntegration {
2
+ key: string;
3
+ packageName: string;
4
+ }
5
+ /**
6
+ * Finds installed integration packages by reading each dependency's
7
+ * `kosha.kind`.
8
+ *
9
+ * Not a name-prefix match: `@kosha-ai/integration-sdk` shares the prefix but is
10
+ * the authoring toolkit, not an integration, and a third-party package under
11
+ * any name should be able to register itself.
12
+ */
13
+ export declare function findInstalledIntegrations(cwd: string): InstalledIntegration[];
14
+ //# sourceMappingURL=installed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installed.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/installed.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,EAAE,CA0B7E"}
@@ -0,0 +1,46 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { join, resolve } from 'node:path';
4
+ /**
5
+ * Finds installed integration packages by reading each dependency's
6
+ * `kosha.kind`.
7
+ *
8
+ * Not a name-prefix match: `@kosha-ai/integration-sdk` shares the prefix but is
9
+ * the authoring toolkit, not an integration, and a third-party package under
10
+ * any name should be able to register itself.
11
+ */
12
+ export function findInstalledIntegrations(cwd) {
13
+ const appManifestPath = join(resolve(cwd), 'package.json');
14
+ if (!existsSync(appManifestPath))
15
+ return [];
16
+ const appManifest = JSON.parse(readFileSync(appManifestPath, 'utf8'));
17
+ const require = createRequire(join(resolve(cwd), 'noop.js'));
18
+ const found = [];
19
+ for (const packageName of Object.keys({
20
+ ...appManifest.dependencies,
21
+ ...appManifest.devDependencies,
22
+ })) {
23
+ const kosha = readKoshaField(require, packageName);
24
+ if (kosha?.kind !== 'integration')
25
+ continue;
26
+ found.push({
27
+ packageName,
28
+ key: kosha.key ?? keyFromPackageName(packageName),
29
+ });
30
+ }
31
+ return found;
32
+ }
33
+ function readKoshaField(require, packageName) {
34
+ try {
35
+ const manifest = JSON.parse(readFileSync(require.resolve(`${packageName}/package.json`), 'utf8'));
36
+ return manifest.kosha;
37
+ }
38
+ catch {
39
+ // Not installed, or ships no package.json export map — either way, not
40
+ // something we can treat as an integration.
41
+ return undefined;
42
+ }
43
+ }
44
+ function keyFromPackageName(packageName) {
45
+ return packageName.replace(/^@[^/]+\//, '').replace(/^integration-/, '');
46
+ }
@@ -0,0 +1,52 @@
1
+ export interface IntegrationListEntry {
2
+ key: string;
3
+ packageName: string;
4
+ installed: boolean;
5
+ configured: boolean;
6
+ }
7
+ export declare function packageNameFor(key: string): string;
8
+ /**
9
+ * Reports installed vs configured integrations.
10
+ *
11
+ * The two can disagree — a package installed but not added to the config does
12
+ * nothing, and a configured package that was uninstalled breaks boot — so
13
+ * drift is worth surfacing explicitly rather than inferring.
14
+ */
15
+ export declare function listIntegrations(options?: {
16
+ cwd?: string;
17
+ config?: string;
18
+ }): Promise<IntegrationListEntry[]>;
19
+ /** Integration keys the app actually has installed. */
20
+ export declare function installedIntegrationKeys(cwd: string): string[];
21
+ export interface AddIntegrationOptions {
22
+ cwd?: string;
23
+ config?: string;
24
+ keys: string[];
25
+ packageManager?: string;
26
+ skipInstall?: boolean;
27
+ run?: (commandLine: string, options: {
28
+ cwd?: string;
29
+ }) => Promise<number>;
30
+ }
31
+ export interface AddIntegrationResult {
32
+ packages: string[];
33
+ installed: boolean;
34
+ configUpdated: boolean;
35
+ /** Set when the config could not be edited automatically. */
36
+ manualSnippet?: string;
37
+ }
38
+ export declare function addIntegrations(options: AddIntegrationOptions): Promise<AddIntegrationResult>;
39
+ /**
40
+ * Adds the import and the `integrations` entry for each key.
41
+ *
42
+ * Deliberately conservative string editing rather than a TypeScript codemod:
43
+ * the config file is small and hand-written, and when the shape is not what we
44
+ * expect the command prints the snippet instead of mangling the file.
45
+ */
46
+ export declare function applyConfigEdit(source: string, keys: string[]): {
47
+ updated: boolean;
48
+ source: string;
49
+ snippet: string;
50
+ };
51
+ export declare function detectPackageManager(cwd: string): string;
52
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/integration.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAID,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAajC;AAED,uDAAuD;AACvD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9D;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3E;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAuC/B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,GACb;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAyCvD;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKxD"}
@@ -0,0 +1,112 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { findConfigPath, loadConfig } from '../load-config.js';
4
+ import { runShellCommand } from '../spawn.js';
5
+ import { findInstalledIntegrations } from './installed.js';
6
+ const PACKAGE_PREFIX = '@kosha-ai/integration-';
7
+ export function packageNameFor(key) {
8
+ return `${PACKAGE_PREFIX}${key}`;
9
+ }
10
+ /**
11
+ * Reports installed vs configured integrations.
12
+ *
13
+ * The two can disagree — a package installed but not added to the config does
14
+ * nothing, and a configured package that was uninstalled breaks boot — so
15
+ * drift is worth surfacing explicitly rather than inferring.
16
+ */
17
+ export async function listIntegrations(options = {}) {
18
+ const cwd = options.cwd ?? process.cwd();
19
+ const { config } = await loadConfig({ cwd, config: options.config });
20
+ const configured = new Set((config.integrations ?? []).map((i) => i.key));
21
+ const installed = new Set(installedIntegrationKeys(cwd));
22
+ return [...new Set([...configured, ...installed])].sort().map((key) => ({
23
+ key,
24
+ packageName: packageNameFor(key),
25
+ installed: installed.has(key),
26
+ configured: configured.has(key),
27
+ }));
28
+ }
29
+ /** Integration keys the app actually has installed. */
30
+ export function installedIntegrationKeys(cwd) {
31
+ return findInstalledIntegrations(cwd).map((entry) => entry.key);
32
+ }
33
+ export async function addIntegrations(options) {
34
+ const cwd = options.cwd ?? process.cwd();
35
+ const keys = options.keys.filter(Boolean);
36
+ if (keys.length === 0) {
37
+ throw new Error('Specify at least one integration, e.g. `kosha integration add github`.');
38
+ }
39
+ const packages = keys.map(packageNameFor);
40
+ if (!options.skipInstall) {
41
+ const packageManager = options.packageManager ?? detectPackageManager(cwd);
42
+ const run = options.run ?? runShellCommand;
43
+ const code = await run(`${packageManager} add ${packages.join(' ')}`, {
44
+ cwd,
45
+ });
46
+ if (code !== 0) {
47
+ throw new Error(`${packageManager} add ${packages.join(' ')} failed with exit code ${code}.`);
48
+ }
49
+ }
50
+ const configPath = findConfigPath(cwd, options.config);
51
+ const edit = applyConfigEdit(readFileSync(configPath, 'utf8'), keys);
52
+ if (edit.updated) {
53
+ writeFileSync(configPath, edit.source);
54
+ return { packages, installed: !options.skipInstall, configUpdated: true };
55
+ }
56
+ return {
57
+ packages,
58
+ installed: !options.skipInstall,
59
+ configUpdated: false,
60
+ manualSnippet: edit.snippet,
61
+ };
62
+ }
63
+ /**
64
+ * Adds the import and the `integrations` entry for each key.
65
+ *
66
+ * Deliberately conservative string editing rather than a TypeScript codemod:
67
+ * the config file is small and hand-written, and when the shape is not what we
68
+ * expect the command prints the snippet instead of mangling the file.
69
+ */
70
+ export function applyConfigEdit(source, keys) {
71
+ const snippet = [
72
+ ...keys.map((key) => `import ${camelCase(key)} from '${packageNameFor(key)}';`),
73
+ '',
74
+ ` integrations: [${keys.map((key) => `${camelCase(key)}()`).join(', ')}],`,
75
+ ].join('\n');
76
+ const integrationsMatch = source.match(/integrations\s*:\s*\[([\s\S]*?)\]/);
77
+ if (!integrationsMatch) {
78
+ return { updated: false, source, snippet };
79
+ }
80
+ let updated = source;
81
+ for (const key of keys) {
82
+ const identifier = camelCase(key);
83
+ const packageName = packageNameFor(key);
84
+ if (!updated.includes(`from '${packageName}'`)) {
85
+ updated = `import ${identifier} from '${packageName}';\n${updated}`;
86
+ }
87
+ const currentMatch = updated.match(/integrations\s*:\s*\[([\s\S]*?)\]/);
88
+ if (!currentMatch)
89
+ return { updated: false, source, snippet };
90
+ const entries = currentMatch[1];
91
+ if (new RegExp(`\\b${identifier}\\s*\\(`).test(entries))
92
+ continue;
93
+ const existing = entries.trim().replace(/,$/, '');
94
+ const nextEntries = existing
95
+ ? `${existing}, ${identifier}()`
96
+ : `${identifier}()`;
97
+ updated = updated.replace(currentMatch[0], `integrations: [${nextEntries}]`);
98
+ }
99
+ return { updated: updated !== source, source: updated, snippet };
100
+ }
101
+ export function detectPackageManager(cwd) {
102
+ if (existsSync(join(cwd, 'pnpm-lock.yaml')))
103
+ return 'pnpm';
104
+ if (existsSync(join(cwd, 'yarn.lock')))
105
+ return 'yarn';
106
+ if (existsSync(join(cwd, 'bun.lockb')))
107
+ return 'bun';
108
+ return 'npm';
109
+ }
110
+ function camelCase(key) {
111
+ return key.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
112
+ }
@@ -0,0 +1,28 @@
1
+ import { type RunCommandOptions } from '../spawn.js';
2
+ export type MigrateSubcommand = 'deploy' | 'dev' | 'status' | 'reset';
3
+ export interface MigrateOptions {
4
+ cwd?: string;
5
+ config?: string;
6
+ subcommand?: string;
7
+ name?: string;
8
+ /** Injected in tests. */
9
+ run?: (script: string, args: string[], options: RunCommandOptions) => Promise<number>;
10
+ }
11
+ /**
12
+ * Runs Prisma against the schema shipped inside `@kosha-ai/server`.
13
+ *
14
+ * Prisma 7 takes the datasource URL from a config file rather than the schema,
15
+ * so a throwaway config is written per invocation pointing at the packaged
16
+ * schema and the app's own DATABASE_URL. This is why apps never carry a
17
+ * `prisma.config.ts` of their own.
18
+ */
19
+ export declare function migrate(options?: MigrateOptions): Promise<number>;
20
+ /**
21
+ * Locates Prisma's CLI entry script.
22
+ *
23
+ * `prisma` is a dependency of this package rather than of `@kosha-ai/server`:
24
+ * generated apps need migrations but never need to generate a client, since
25
+ * `@kosha-ai/server` ships one.
26
+ */
27
+ export declare function resolvePrismaBin(): string;
28
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/migrate.ts"],"names":[],"mappings":"AAMA,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAItE,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,GAAG,CAAC,EAAE,CACJ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,MAAM,CAAC,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC3E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAuBzC"}
@@ -0,0 +1,83 @@
1
+ import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { loadConfig } from '../load-config.js';
6
+ import { resolveMigrationsDir, resolveSchemaPath } from '../paths.js';
7
+ import { runNodeScript } from '../spawn.js';
8
+ const SUBCOMMANDS = ['deploy', 'dev', 'status', 'reset'];
9
+ /**
10
+ * Runs Prisma against the schema shipped inside `@kosha-ai/server`.
11
+ *
12
+ * Prisma 7 takes the datasource URL from a config file rather than the schema,
13
+ * so a throwaway config is written per invocation pointing at the packaged
14
+ * schema and the app's own DATABASE_URL. This is why apps never carry a
15
+ * `prisma.config.ts` of their own.
16
+ */
17
+ export async function migrate(options = {}) {
18
+ const cwd = options.cwd ?? process.cwd();
19
+ const subcommand = (options.subcommand ?? 'deploy');
20
+ if (!SUBCOMMANDS.includes(subcommand)) {
21
+ throw new Error(`Unknown migrate subcommand "${subcommand}". Expected one of: ${SUBCOMMANDS.join(', ')}`);
22
+ }
23
+ const { config } = await loadConfig({ cwd, config: options.config });
24
+ if (!config.database?.url) {
25
+ throw new Error('kosha.config.ts has no database.url, so there is nothing to migrate against.');
26
+ }
27
+ const configPath = writePrismaConfig({
28
+ schema: resolveSchemaPath(cwd),
29
+ migrations: resolveMigrationsDir(cwd),
30
+ });
31
+ const args = ['migrate', subcommand, '--config', configPath];
32
+ if (subcommand === 'dev' && options.name) {
33
+ args.push('--name', options.name);
34
+ }
35
+ const run = options.run ?? runNodeScript;
36
+ return run(resolvePrismaBin(), args, {
37
+ cwd,
38
+ env: { ...process.env, DATABASE_URL: config.database.url },
39
+ });
40
+ }
41
+ /**
42
+ * Locates Prisma's CLI entry script.
43
+ *
44
+ * `prisma` is a dependency of this package rather than of `@kosha-ai/server`:
45
+ * generated apps need migrations but never need to generate a client, since
46
+ * `@kosha-ai/server` ships one.
47
+ */
48
+ export function resolvePrismaBin() {
49
+ const require = createRequire(import.meta.url);
50
+ let manifestPath;
51
+ try {
52
+ manifestPath = require.resolve('prisma/package.json');
53
+ }
54
+ catch {
55
+ throw new Error('Could not resolve the prisma CLI. Reinstall @kosha-ai/cli so its dependencies are present.');
56
+ }
57
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
58
+ const bin = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin?.['prisma'];
59
+ if (!bin) {
60
+ throw new Error(`The installed prisma package declares no "prisma" bin.`);
61
+ }
62
+ return join(dirname(manifestPath), bin);
63
+ }
64
+ function writePrismaConfig(paths) {
65
+ const dir = mkdtempSync(join(tmpdir(), 'kosha-prisma-'));
66
+ const path = join(dir, 'prisma.config.mjs');
67
+ // A plain object rather than `defineConfig(...)`: the file lives outside the
68
+ // app, where `prisma/config` is not resolvable. `defineConfig` is only a
69
+ // types helper, so the shape is what matters.
70
+ writeFileSync(path, [
71
+ 'export default {',
72
+ ` schema: ${JSON.stringify(paths.schema)},`,
73
+ ' migrations: {',
74
+ ` path: ${JSON.stringify(paths.migrations)},`,
75
+ ' },',
76
+ ' datasource: {',
77
+ ' url: process.env.DATABASE_URL,',
78
+ ' },',
79
+ '};',
80
+ '',
81
+ ].join('\n'));
82
+ return path;
83
+ }
@@ -0,0 +1,13 @@
1
+ export interface StartOptions {
2
+ cwd?: string;
3
+ config?: string;
4
+ port?: number;
5
+ }
6
+ /**
7
+ * Boots the server and the admin UI from the app's config.
8
+ *
9
+ * `@kosha-ai/server` is imported lazily so that `kosha --help` and `kosha info`
10
+ * stay instant and do not need a database or a valid config.
11
+ */
12
+ export declare function start(options?: StartOptions): Promise<void>;
13
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/start.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BrE"}
@@ -0,0 +1,33 @@
1
+ import { dirname } from 'node:path';
2
+ import { loadConfig } from '../load-config.js';
3
+ import { resolveAdminServerEntry } from '../paths.js';
4
+ import { spawnNodeScript } from '../spawn.js';
5
+ /**
6
+ * Boots the server and the admin UI from the app's config.
7
+ *
8
+ * `@kosha-ai/server` is imported lazily so that `kosha --help` and `kosha info`
9
+ * stay instant and do not need a database or a valid config.
10
+ */
11
+ export async function start(options = {}) {
12
+ const cwd = options.cwd ?? process.cwd();
13
+ const { config, path } = await loadConfig({
14
+ cwd: options.cwd,
15
+ config: options.config,
16
+ });
17
+ const { startKoshaServer, resolveConfig } = await import('@kosha-ai/server');
18
+ process.stdout.write(`Kosha config: ${path}\n`);
19
+ const resolved = resolveConfig(options.port
20
+ ? { ...config, server: { ...config.server, port: options.port } }
21
+ : config);
22
+ await startKoshaServer(resolved);
23
+ const adminEntry = resolveAdminServerEntry(cwd);
24
+ const admin = spawnNodeScript(adminEntry, [], {
25
+ cwd: dirname(adminEntry),
26
+ env: { ...process.env, PORT: String(resolved.admin.port) },
27
+ });
28
+ // Ctrl+C in a terminal already reaches this child (same process group);
29
+ // this covers being sent a lone SIGTERM/SIGINT, e.g. under a process manager.
30
+ for (const signal of ['SIGINT', 'SIGTERM']) {
31
+ process.once(signal, () => admin.kill(signal));
32
+ }
33
+ }
@@ -0,0 +1,20 @@
1
+ import type { KoshaConfig } from '@kosha-ai/server';
2
+ export declare const CONFIG_FILENAMES: string[];
3
+ export declare class ConfigNotFoundError extends Error {
4
+ constructor(cwd: string);
5
+ }
6
+ export declare function findConfigPath(cwd: string, explicit?: string): string;
7
+ /**
8
+ * Loads `kosha.config.ts` without the app needing a build step.
9
+ *
10
+ * jiti transpiles on the fly, which is what keeps a generated app thin: it has
11
+ * a config file and a `package.json`, no bundler and no `tsconfig` of its own.
12
+ */
13
+ export declare function loadConfig(options?: {
14
+ cwd?: string;
15
+ config?: string;
16
+ }): Promise<{
17
+ config: KoshaConfig;
18
+ path: string;
19
+ }>;
20
+ //# sourceMappingURL=load-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/lib/load-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,eAAO,MAAM,gBAAgB,UAK5B,CAAC;AAEF,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,GAAG,EAAE,MAAM;CAMxB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAerE;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACZ,GACL,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA2BhD"}
@@ -0,0 +1,56 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { createJiti } from 'jiti';
5
+ import { loadEnv } from './load-env.js';
6
+ export const CONFIG_FILENAMES = [
7
+ 'kosha.config.ts',
8
+ 'kosha.config.mts',
9
+ 'kosha.config.js',
10
+ 'kosha.config.mjs',
11
+ ];
12
+ export class ConfigNotFoundError extends Error {
13
+ constructor(cwd) {
14
+ super(`No Kosha config found in ${cwd}. Expected one of: ${CONFIG_FILENAMES.join(', ')}`);
15
+ this.name = 'ConfigNotFoundError';
16
+ }
17
+ }
18
+ export function findConfigPath(cwd, explicit) {
19
+ if (explicit) {
20
+ const path = resolve(cwd, explicit);
21
+ if (!existsSync(path)) {
22
+ throw new ConfigNotFoundError(path);
23
+ }
24
+ return path;
25
+ }
26
+ for (const filename of CONFIG_FILENAMES) {
27
+ const path = resolve(cwd, filename);
28
+ if (existsSync(path))
29
+ return path;
30
+ }
31
+ throw new ConfigNotFoundError(cwd);
32
+ }
33
+ /**
34
+ * Loads `kosha.config.ts` without the app needing a build step.
35
+ *
36
+ * jiti transpiles on the fly, which is what keeps a generated app thin: it has
37
+ * a config file and a `package.json`, no bundler and no `tsconfig` of its own.
38
+ */
39
+ export async function loadConfig(options = {}) {
40
+ const cwd = options.cwd ?? process.cwd();
41
+ const path = findConfigPath(cwd, options.config);
42
+ // The config reads process.env, so the app's env files come first.
43
+ loadEnv(cwd);
44
+ // interopDefault stays off so that a config file with no default export is
45
+ // an error rather than silently yielding the module namespace as the config.
46
+ const jiti = createJiti(pathToFileURL(`${cwd}/`).href, {
47
+ interopDefault: false,
48
+ fsCache: false,
49
+ });
50
+ const loaded = await jiti.import(pathToFileURL(path).href);
51
+ const config = loaded?.default;
52
+ if (!config || typeof config !== 'object') {
53
+ throw new Error(`${path} must export a Kosha config as its default export (see defineConfig from @kosha-ai/server).`);
54
+ }
55
+ return { config, path };
56
+ }
@@ -0,0 +1,19 @@
1
+ export declare const ENV_FILENAMES: string[];
2
+ export interface LoadEnvOptions {
3
+ /**
4
+ * Injected in tests. Node's loader writes to the real process environment,
5
+ * which Jest's sandboxed `process.env` does not reflect, so the seam exists
6
+ * to assert which files get loaded and in what order.
7
+ */
8
+ load?: (path: string) => void;
9
+ }
10
+ /**
11
+ * Loads the app's env files before the config is evaluated.
12
+ *
13
+ * `kosha.config.ts` reads `process.env`, so this has to happen first. Node's
14
+ * built-in loader — like `--env-file` — never overwrites a variable that is
15
+ * already set, so a shell override or a container's injected secrets beat a
16
+ * checked-in file. `.env.local` is read first for the same reason.
17
+ */
18
+ export declare function loadEnv(cwd: string, options?: LoadEnvOptions): string[];
19
+ //# sourceMappingURL=load-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-env.d.ts","sourceRoot":"","sources":["../../src/lib/load-env.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,UAAyB,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,EAAE,CAa3E"}
@@ -0,0 +1,29 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const ENV_FILENAMES = ['.env.local', '.env'];
4
+ /**
5
+ * Loads the app's env files before the config is evaluated.
6
+ *
7
+ * `kosha.config.ts` reads `process.env`, so this has to happen first. Node's
8
+ * built-in loader — like `--env-file` — never overwrites a variable that is
9
+ * already set, so a shell override or a container's injected secrets beat a
10
+ * checked-in file. `.env.local` is read first for the same reason.
11
+ */
12
+ export function loadEnv(cwd, options = {}) {
13
+ const load = options.load ?? nodeLoadEnvFile;
14
+ const loaded = [];
15
+ for (const filename of ENV_FILENAMES) {
16
+ const path = join(cwd, filename);
17
+ if (!existsSync(path))
18
+ continue;
19
+ load(path);
20
+ loaded.push(path);
21
+ }
22
+ return loaded;
23
+ }
24
+ function nodeLoadEnvFile(path) {
25
+ if (typeof process.loadEnvFile !== 'function') {
26
+ throw new Error('Kosha needs Node 20.12 or newer (process.loadEnvFile is unavailable).');
27
+ }
28
+ process.loadEnvFile(path);
29
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Locates the installed `@kosha-ai/server` package from the app's directory.
3
+ *
4
+ * The Prisma schema and migrations ship inside that package — the app never
5
+ * owns them — so every database command has to resolve it first.
6
+ */
7
+ export declare function resolveServerPackageDir(cwd: string): string;
8
+ export declare function resolveSchemaPath(cwd: string): string;
9
+ export declare function resolveMigrationsDir(cwd: string): string;
10
+ export declare function resolveAdminPackageDir(cwd: string): string;
11
+ /**
12
+ * Finds the standalone `server.js` Next.js ships inside `@kosha-ai/admin`.
13
+ *
14
+ * The published package mirrors this monorepo's `packages/admin/` path
15
+ * inside `.next/standalone` (Next has to trace outside the package for its
16
+ * workspace deps), so the depth isn't fixed — walk for the one `server.js`
17
+ * standalone output produces rather than assume a shape.
18
+ */
19
+ export declare function resolveAdminServerEntry(cwd: string): string;
20
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAU3D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQrD;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAExD;AASD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAU1D;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAc3D"}
@@ -0,0 +1,74 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ /**
5
+ * Locates the installed `@kosha-ai/server` package from the app's directory.
6
+ *
7
+ * The Prisma schema and migrations ship inside that package — the app never
8
+ * owns them — so every database command has to resolve it first.
9
+ */
10
+ export function resolveServerPackageDir(cwd) {
11
+ const require = createRequire(join(resolve(cwd), 'noop.js'));
12
+ try {
13
+ return dirname(require.resolve('@kosha-ai/server/package.json'));
14
+ }
15
+ catch {
16
+ throw new Error(`Could not resolve @kosha-ai/server from ${cwd}. Run your package manager's install first.`);
17
+ }
18
+ }
19
+ export function resolveSchemaPath(cwd) {
20
+ const schema = join(resolveServerPackageDir(cwd), 'prisma', 'schema.prisma');
21
+ if (!existsSync(schema)) {
22
+ throw new Error(`@kosha-ai/server is installed but ships no Prisma schema at ${schema}.`);
23
+ }
24
+ return schema;
25
+ }
26
+ export function resolveMigrationsDir(cwd) {
27
+ return join(resolveServerPackageDir(cwd), 'prisma', 'migrations');
28
+ }
29
+ // Built from parts rather than a literal `require.resolve('@kosha-ai/admin/...')`:
30
+ // Nx's TS project graph treats that literal shape as a static import and
31
+ // wires a project reference to it, which breaks the build since @kosha-ai/admin
32
+ // is a Next app (noEmit) and isn't actually a TS-level dependency of the cli
33
+ // — only a runtime path lookup.
34
+ const ADMIN_PACKAGE_JSON = ['@kosha', 'admin', 'package.json'].join('/');
35
+ export function resolveAdminPackageDir(cwd) {
36
+ const require = createRequire(join(resolve(cwd), 'noop.js'));
37
+ try {
38
+ return dirname(require.resolve(ADMIN_PACKAGE_JSON));
39
+ }
40
+ catch {
41
+ throw new Error(`Could not resolve @kosha-ai/admin from ${cwd}. Run your package manager's install first.`);
42
+ }
43
+ }
44
+ /**
45
+ * Finds the standalone `server.js` Next.js ships inside `@kosha-ai/admin`.
46
+ *
47
+ * The published package mirrors this monorepo's `packages/admin/` path
48
+ * inside `.next/standalone` (Next has to trace outside the package for its
49
+ * workspace deps), so the depth isn't fixed — walk for the one `server.js`
50
+ * standalone output produces rather than assume a shape.
51
+ */
52
+ export function resolveAdminServerEntry(cwd) {
53
+ const standaloneDir = join(resolveAdminPackageDir(cwd), '.next', 'standalone');
54
+ const entry = findServerEntry(standaloneDir);
55
+ if (!entry) {
56
+ throw new Error(`@kosha-ai/admin is installed but ships no standalone server at ${standaloneDir}.`);
57
+ }
58
+ return entry;
59
+ }
60
+ function findServerEntry(dir) {
61
+ if (!existsSync(dir))
62
+ return null;
63
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
64
+ const full = join(dir, entry.name);
65
+ if (entry.isFile() && entry.name === 'server.js')
66
+ return full;
67
+ if (entry.isDirectory() && entry.name !== 'node_modules') {
68
+ const found = findServerEntry(full);
69
+ if (found)
70
+ return found;
71
+ }
72
+ }
73
+ return null;
74
+ }
@@ -0,0 +1,29 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ export interface RunCommandOptions {
3
+ cwd?: string;
4
+ env?: NodeJS.ProcessEnv;
5
+ }
6
+ /**
7
+ * Starts a Node script without waiting for it to exit — for a process meant
8
+ * to keep running alongside the caller, like the admin UI next to the API
9
+ * server. Same shim-avoidance rationale as {@link runNodeScript}.
10
+ */
11
+ export declare function spawnNodeScript(script: string, args: string[], options?: RunCommandOptions): ChildProcess;
12
+ /**
13
+ * Runs a Node script with the current interpreter.
14
+ *
15
+ * Preferred over invoking a package's bin name: Node refuses to spawn the
16
+ * `.cmd` shims that npm installs on Windows unless a shell is involved
17
+ * (spawn EINVAL), and a shell brings its own quoting problems.
18
+ */
19
+ export declare function runNodeScript(script: string, args: string[], options?: RunCommandOptions): Promise<number>;
20
+ /**
21
+ * Runs a package manager command through the platform shell.
22
+ *
23
+ * Package managers only exist as shell shims on Windows, so there is no
24
+ * script to hand to {@link runNodeScript}. The command is passed as a single
25
+ * string — Node deprecates arrays combined with `shell: true` (DEP0190) — and
26
+ * every interpolated value is validated before it gets here.
27
+ */
28
+ export declare function runShellCommand(commandLine: string, options?: RunCommandOptions): Promise<number>;
29
+ //# sourceMappingURL=spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../src/lib/spawn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,MAAM,oBAAoB,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,iBAAsB,GAC9B,YAAY,CAMd;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAWjB"}
@@ -0,0 +1,51 @@
1
+ import { spawn } from 'node:child_process';
2
+ /**
3
+ * Starts a Node script without waiting for it to exit — for a process meant
4
+ * to keep running alongside the caller, like the admin UI next to the API
5
+ * server. Same shim-avoidance rationale as {@link runNodeScript}.
6
+ */
7
+ export function spawnNodeScript(script, args, options = {}) {
8
+ return spawn(process.execPath, [script, ...args], {
9
+ cwd: options.cwd,
10
+ env: options.env ?? process.env,
11
+ stdio: 'inherit',
12
+ });
13
+ }
14
+ /**
15
+ * Runs a Node script with the current interpreter.
16
+ *
17
+ * Preferred over invoking a package's bin name: Node refuses to spawn the
18
+ * `.cmd` shims that npm installs on Windows unless a shell is involved
19
+ * (spawn EINVAL), and a shell brings its own quoting problems.
20
+ */
21
+ export function runNodeScript(script, args, options = {}) {
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn(process.execPath, [script, ...args], {
24
+ cwd: options.cwd,
25
+ env: options.env ?? process.env,
26
+ stdio: 'inherit',
27
+ });
28
+ child.on('error', reject);
29
+ child.on('close', (code) => resolve(code ?? 1));
30
+ });
31
+ }
32
+ /**
33
+ * Runs a package manager command through the platform shell.
34
+ *
35
+ * Package managers only exist as shell shims on Windows, so there is no
36
+ * script to hand to {@link runNodeScript}. The command is passed as a single
37
+ * string — Node deprecates arrays combined with `shell: true` (DEP0190) — and
38
+ * every interpolated value is validated before it gets here.
39
+ */
40
+ export function runShellCommand(commandLine, options = {}) {
41
+ return new Promise((resolve, reject) => {
42
+ const child = spawn(commandLine, {
43
+ cwd: options.cwd,
44
+ env: options.env ?? process.env,
45
+ stdio: 'inherit',
46
+ shell: true,
47
+ });
48
+ child.on('error', reject);
49
+ child.on('close', (code) => resolve(code ?? 1));
50
+ });
51
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@kosha-ai/cli",
3
+ "version": "0.0.1",
4
+ "description": "Kosha CLI: migrate, start, and manage a Kosha app",
5
+ "keywords": [
6
+ "kosha",
7
+ "cli",
8
+ "integrations"
9
+ ],
10
+ "license": "MIT",
11
+ "homepage": "https://github.com/codemancers/kosha#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/codemancers/kosha/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/codemancers/kosha.git",
18
+ "directory": "packages/cli"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ "./package.json": "./package.json",
29
+ ".": {
30
+ "@org/source": "./src/index.ts",
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js",
33
+ "default": "./dist/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "bin",
39
+ "!**/*.tsbuildinfo"
40
+ ],
41
+ "dependencies": {
42
+ "jiti": "^2.7.0",
43
+ "prisma": "^7.10.0",
44
+ "tslib": "^2.3.0",
45
+ "@kosha-ai/server": "0.0.1"
46
+ },
47
+ "kosha": {
48
+ "kind": "cli"
49
+ },
50
+ "bin": {
51
+ "kosha": "./bin/kosha.js"
52
+ },
53
+ "engines": {
54
+ "node": ">=20.12"
55
+ }
56
+ }