@spekn/cli 1.0.0 → 1.0.2

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 (154) hide show
  1. package/README.md +58 -0
  2. package/dist/main.js +40540 -32176
  3. package/dist/prompts/governance-analysis.prompt.md +109 -0
  4. package/dist/resources/prompts/repo-analysis.prompt.md +28 -136
  5. package/dist/resources/prompts/repo-sync-analysis.prompt.md +31 -68
  6. package/dist/tui/chunk-4WEASLXY.mjs +3444 -0
  7. package/dist/tui/chunk-755CADEG.mjs +3401 -0
  8. package/dist/tui/chunk-BUJQVTY5.mjs +3409 -0
  9. package/dist/tui/chunk-BZKKMGFB.mjs +1959 -0
  10. package/dist/tui/chunk-DJYOBCNM.mjs +3159 -0
  11. package/dist/tui/chunk-GTFTFDY4.mjs +3417 -0
  12. package/dist/tui/chunk-IMEBD2KA.mjs +3444 -0
  13. package/dist/tui/chunk-IX6DR5SW.mjs +3433 -0
  14. package/dist/tui/chunk-JKFOY4IF.mjs +2003 -0
  15. package/dist/tui/chunk-OXXZ3O5L.mjs +3378 -0
  16. package/dist/tui/chunk-SHJNIAAJ.mjs +1697 -0
  17. package/dist/tui/chunk-V4SNDRUS.mjs +1666 -0
  18. package/dist/tui/chunk-VXVHNZST.mjs +1666 -0
  19. package/dist/tui/chunk-WCTSFKTA.mjs +3459 -0
  20. package/dist/tui/chunk-X2XP5ACW.mjs +3443 -0
  21. package/dist/tui/chunk-YUYJ7VBG.mjs +2029 -0
  22. package/dist/tui/chunk-ZM3EI5IA.mjs +3384 -0
  23. package/dist/tui/chunk-ZYOX64HP.mjs +1653 -0
  24. package/dist/tui/index.mjs +6999 -6938
  25. package/dist/tui/prompts/spec-creation-system.prompt.md +47 -0
  26. package/dist/tui/prompts/spec-refinement-system.prompt.md +72 -0
  27. package/dist/tui/use-session-store-63YUGUFA.mjs +8 -0
  28. package/dist/tui/use-session-store-ACO2SMJC.mjs +8 -0
  29. package/dist/tui/use-session-store-BVFDAWOB.mjs +8 -0
  30. package/dist/tui/use-session-store-DJIZ3FQZ.mjs +9 -0
  31. package/dist/tui/use-session-store-EAIQA4UG.mjs +9 -0
  32. package/dist/tui/use-session-store-EFBAXC3G.mjs +8 -0
  33. package/dist/tui/use-session-store-FJOR4KTG.mjs +8 -0
  34. package/dist/tui/use-session-store-IJE5KVOC.mjs +8 -0
  35. package/dist/tui/use-session-store-KGAFXCKI.mjs +8 -0
  36. package/dist/tui/use-session-store-KS4DPNDY.mjs +8 -0
  37. package/dist/tui/use-session-store-MMHJENNL.mjs +8 -0
  38. package/dist/tui/use-session-store-OZ6HC4I2.mjs +9 -0
  39. package/dist/tui/use-session-store-PTMWISNJ.mjs +8 -0
  40. package/dist/tui/use-session-store-VCDECQMW.mjs +8 -0
  41. package/dist/tui/use-session-store-VOK5ML5J.mjs +9 -0
  42. package/package.json +33 -13
  43. package/dist/__tests__/export-cli.test.d.ts +0 -1
  44. package/dist/__tests__/export-cli.test.js +0 -70
  45. package/dist/__tests__/tui-args-policy.test.d.ts +0 -1
  46. package/dist/__tests__/tui-args-policy.test.js +0 -50
  47. package/dist/acp-S2MHZOAD.mjs +0 -23
  48. package/dist/acp-UCCI44JY.mjs +0 -25
  49. package/dist/auth/credentials-store.d.ts +0 -2
  50. package/dist/auth/credentials-store.js +0 -5
  51. package/dist/auth/device-flow.d.ts +0 -36
  52. package/dist/auth/device-flow.js +0 -189
  53. package/dist/auth/jwt.d.ts +0 -1
  54. package/dist/auth/jwt.js +0 -6
  55. package/dist/auth/session.d.ts +0 -67
  56. package/dist/auth/session.js +0 -86
  57. package/dist/auth-login.d.ts +0 -34
  58. package/dist/auth-login.js +0 -202
  59. package/dist/auth-logout.d.ts +0 -25
  60. package/dist/auth-logout.js +0 -115
  61. package/dist/auth-status.d.ts +0 -24
  62. package/dist/auth-status.js +0 -109
  63. package/dist/backlog-generate.d.ts +0 -11
  64. package/dist/backlog-generate.js +0 -308
  65. package/dist/backlog-health.d.ts +0 -11
  66. package/dist/backlog-health.js +0 -287
  67. package/dist/bridge-login.d.ts +0 -40
  68. package/dist/bridge-login.js +0 -277
  69. package/dist/chunk-3PAYRI4G.mjs +0 -2428
  70. package/dist/chunk-M4CS3A25.mjs +0 -2426
  71. package/dist/commands/auth/login.d.ts +0 -30
  72. package/dist/commands/auth/login.js +0 -164
  73. package/dist/commands/auth/logout.d.ts +0 -25
  74. package/dist/commands/auth/logout.js +0 -115
  75. package/dist/commands/auth/status.d.ts +0 -24
  76. package/dist/commands/auth/status.js +0 -109
  77. package/dist/commands/backlog/generate.d.ts +0 -11
  78. package/dist/commands/backlog/generate.js +0 -308
  79. package/dist/commands/backlog/health.d.ts +0 -11
  80. package/dist/commands/backlog/health.js +0 -287
  81. package/dist/commands/bridge/login.d.ts +0 -36
  82. package/dist/commands/bridge/login.js +0 -258
  83. package/dist/commands/export.d.ts +0 -35
  84. package/dist/commands/export.js +0 -485
  85. package/dist/commands/marketplace-export.d.ts +0 -21
  86. package/dist/commands/marketplace-export.js +0 -214
  87. package/dist/commands/project-clean.d.ts +0 -1
  88. package/dist/commands/project-clean.js +0 -126
  89. package/dist/commands/repo/common.d.ts +0 -105
  90. package/dist/commands/repo/common.js +0 -775
  91. package/dist/commands/repo/detach.d.ts +0 -2
  92. package/dist/commands/repo/detach.js +0 -120
  93. package/dist/commands/repo/register.d.ts +0 -21
  94. package/dist/commands/repo/register.js +0 -175
  95. package/dist/commands/repo/sync.d.ts +0 -22
  96. package/dist/commands/repo/sync.js +0 -873
  97. package/dist/commands/skills-import-local.d.ts +0 -16
  98. package/dist/commands/skills-import-local.js +0 -352
  99. package/dist/commands/spec/drift-check.d.ts +0 -3
  100. package/dist/commands/spec/drift-check.js +0 -186
  101. package/dist/commands/spec/frontmatter.d.ts +0 -11
  102. package/dist/commands/spec/frontmatter.js +0 -219
  103. package/dist/commands/spec/lint.d.ts +0 -11
  104. package/dist/commands/spec/lint.js +0 -499
  105. package/dist/commands/spec/parse.d.ts +0 -11
  106. package/dist/commands/spec/parse.js +0 -162
  107. package/dist/export.d.ts +0 -35
  108. package/dist/export.js +0 -485
  109. package/dist/main.d.ts +0 -1
  110. package/dist/marketplace-export.d.ts +0 -21
  111. package/dist/marketplace-export.js +0 -214
  112. package/dist/project-clean.d.ts +0 -1
  113. package/dist/project-clean.js +0 -126
  114. package/dist/project-context.d.ts +0 -99
  115. package/dist/project-context.js +0 -376
  116. package/dist/repo-common.d.ts +0 -101
  117. package/dist/repo-common.js +0 -671
  118. package/dist/repo-detach.d.ts +0 -2
  119. package/dist/repo-detach.js +0 -102
  120. package/dist/repo-ingest.d.ts +0 -29
  121. package/dist/repo-ingest.js +0 -305
  122. package/dist/repo-register.d.ts +0 -21
  123. package/dist/repo-register.js +0 -175
  124. package/dist/repo-sync.d.ts +0 -16
  125. package/dist/repo-sync.js +0 -152
  126. package/dist/resources/prompt-loader.d.ts +0 -1
  127. package/dist/resources/prompt-loader.js +0 -62
  128. package/dist/skills-import-local.d.ts +0 -16
  129. package/dist/skills-import-local.js +0 -352
  130. package/dist/spec-drift-check.d.ts +0 -3
  131. package/dist/spec-drift-check.js +0 -186
  132. package/dist/spec-frontmatter.d.ts +0 -11
  133. package/dist/spec-frontmatter.js +0 -219
  134. package/dist/spec-lint.d.ts +0 -11
  135. package/dist/spec-lint.js +0 -499
  136. package/dist/spec-parse.d.ts +0 -11
  137. package/dist/spec-parse.js +0 -162
  138. package/dist/stubs/dotenv.d.ts +0 -5
  139. package/dist/stubs/dotenv.js +0 -6
  140. package/dist/stubs/typeorm.d.ts +0 -22
  141. package/dist/stubs/typeorm.js +0 -28
  142. package/dist/tui-bundle.d.ts +0 -1
  143. package/dist/tui-bundle.js +0 -5
  144. package/dist/tui-entry.mjs +0 -1407
  145. package/dist/utils/cli-runtime.d.ts +0 -5
  146. package/dist/utils/cli-runtime.js +0 -22
  147. package/dist/utils/help-error.d.ts +0 -7
  148. package/dist/utils/help-error.js +0 -14
  149. package/dist/utils/interaction.d.ts +0 -19
  150. package/dist/utils/interaction.js +0 -93
  151. package/dist/utils/structured-log.d.ts +0 -7
  152. package/dist/utils/structured-log.js +0 -112
  153. package/dist/utils/trpc-url.d.ts +0 -4
  154. package/dist/utils/trpc-url.js +0 -15
package/dist/repo-sync.js DELETED
@@ -1,152 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- /**
4
- * repo-sync CLI command
5
- *
6
- * Syncs metadata for the current git repository with Spekn.
7
- * Looks up the repo by its remote URL and updates name and default branch.
8
- * Optionally runs AI analysis (same as repo register --analyze).
9
- * Must be run from inside a local git clone.
10
- *
11
- * Usage: spekn repo sync --project-id <uuid> [--analyze] [--agent <name>] [--api-url <url>]
12
- */
13
- Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.runRepoSyncCli = runRepoSyncCli;
15
- exports.main = main;
16
- exports.parseArgs = parseArgs;
17
- const repo_common_1 = require("./repo-common");
18
- const structured_log_1 = require("./utils/structured-log");
19
- // ── Help & Args ─────────────────────────────────────────────────────
20
- function printHelp(stderr) {
21
- stderr(`
22
- repo sync - Sync git repository metadata with Spekn
23
-
24
- USAGE:
25
- spekn repo sync --project-id <uuid> [options]
26
-
27
- OPTIONS:
28
- --project-id <uuid> Project ID that owns the repository (optional if .spekn/context is present)
29
- --analyze Run AI analysis after syncing metadata
30
- --agent <name> Force a specific agent (claude, codex, opencode, etc.)
31
- --path <dir> Repository root path (default: current directory)
32
- --dry-run Discover files only, skip AI analysis
33
- --mcp-url <url> MCP HTTP server URL (default: MCP_HTTP_URL or https://app.spekn.com/mcp)
34
- --api-url <url> API base URL (default: SPEKN_API_URL or https://app.spekn.com)
35
- --debug Show detailed debug output (tokens, HTTP exchanges)
36
- --help Show this help message
37
-
38
- ENVIRONMENT:
39
- SPEKN_API_URL API base URL
40
- SPEKN_AUTH_TOKEN Bearer token for authentication
41
- SPEKN_ORGANIZATION_ID Organization ID header
42
- MCP_HTTP_URL MCP HTTP server URL (default: https://app.spekn.com/mcp)
43
- AGENT_ARGS Override agent CLI args (skips ACP registry resolution)
44
-
45
- DESCRIPTION:
46
- Reads the 'origin' remote URL from the current git repository, looks up the
47
- matching registered repository in the project, and updates its name and
48
- default branch to match the local git state.
49
-
50
- With --analyze, also runs AI-powered analysis to discover specs, assess
51
- governance, and create improvement specifications (same as repo register).
52
-
53
- EXAMPLES:
54
- spekn repo sync --project-id 11111111-1111-4111-8111-111111111111
55
- spekn repo sync --project-id 11111111-1111-4111-8111-111111111111 --analyze
56
- spekn repo sync --project-id 11111111-1111-4111-8111-111111111111 --analyze --agent claude
57
- `);
58
- }
59
- function parseArgs(args) {
60
- const opts = (0, repo_common_1.commonDefaults)(false);
61
- for (let i = 0; i < args.length;) {
62
- const consumed = (0, repo_common_1.parseCommonFlag)(args, i, opts);
63
- if (consumed > 0) {
64
- i += consumed;
65
- continue;
66
- }
67
- i++; // skip unknown
68
- }
69
- return (0, repo_common_1.finalizeOptions)(opts);
70
- }
71
- // ── Main ────────────────────────────────────────────────────────────
72
- async function runRepoSyncCli(args, deps = repo_common_1.defaultDeps) {
73
- try {
74
- const options = parseArgs(args);
75
- (0, structured_log_1.appendCliStructuredLog)({
76
- source: "cli.repo.sync",
77
- level: "info",
78
- message: "Starting repo sync",
79
- details: { repoPath: options.repoPath, analyze: options.analyze, apiUrl: options.apiUrl },
80
- });
81
- const { authToken, organizationId, projectId } = await (0, repo_common_1.resolveAuth)(deps, {
82
- projectId: options.projectId,
83
- repoPath: options.repoPath,
84
- });
85
- // ── Phase 1: Sync repository metadata ──────────────────────────
86
- const git = (0, repo_common_1.readGitMetadata)(options.repoPath, deps);
87
- if (!git)
88
- return 1;
89
- deps.stdout(`Syncing repository "${git.name}" (${git.remoteUrl})\n`);
90
- deps.stdout(` Default branch : ${git.defaultBranch}\n`);
91
- deps.stdout(` Project : ${projectId}\n`);
92
- const client = (0, repo_common_1.createApiClient)(options.apiUrl, authToken, organizationId);
93
- // Find matching repository
94
- const repos =
95
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
- await client.gitRepository.list.query({
97
- projectId,
98
- limit: 100,
99
- offset: 0,
100
- });
101
- const match = repos.find((r) => r.repositoryUrl === git.remoteUrl);
102
- if (!match) {
103
- deps.stderr(`Error: No registered repository found for URL "${git.remoteUrl}" in project ${projectId}.\n` +
104
- "Use 'spekn repo register' to register this repository first.\n");
105
- return 1;
106
- }
107
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
- await client.gitRepository.update.mutate({
109
- projectId,
110
- id: match.id,
111
- data: { name: git.name, defaultBranch: git.defaultBranch },
112
- });
113
- deps.stdout(`Repository synced successfully. ID: ${match.id}\n`);
114
- (0, structured_log_1.appendCliStructuredLog)({
115
- source: "cli.repo.sync",
116
- level: "info",
117
- message: "Repository metadata synced",
118
- details: { projectId, repositoryId: match.id },
119
- });
120
- // ── Phase 2: AI analysis (optional) ─────────────────────────────
121
- if (!options.analyze)
122
- return 0;
123
- const exitCode = await (0, repo_common_1.runAnalysisPhase)({ ...options, projectId }, authToken ?? "", deps, organizationId);
124
- (0, structured_log_1.appendCliStructuredLog)({
125
- source: "cli.repo.sync",
126
- level: exitCode === 0 ? "info" : "error",
127
- message: "Repo sync completed",
128
- details: { exitCode, analyzed: options.analyze, projectId },
129
- });
130
- return exitCode;
131
- }
132
- catch (error) {
133
- if (error instanceof repo_common_1.HelpRequestedError) {
134
- printHelp(deps.stderr);
135
- return 0;
136
- }
137
- deps.stderr(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
138
- (0, structured_log_1.appendCliStructuredLog)({
139
- source: "cli.repo.sync",
140
- level: "error",
141
- message: error instanceof Error ? error.message : String(error),
142
- });
143
- return 1;
144
- }
145
- }
146
- async function main() {
147
- const exitCode = await runRepoSyncCli(process.argv.slice(2));
148
- process.exit(exitCode);
149
- }
150
- if (require.main === module) {
151
- void main();
152
- }
@@ -1 +0,0 @@
1
- export declare function loadPromptTemplate(name: string): string;
@@ -1,62 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.loadPromptTemplate = loadPromptTemplate;
37
- const fs = __importStar(require("node:fs"));
38
- const path = __importStar(require("node:path"));
39
- const promptCache = new Map();
40
- function resolvePromptPath(name) {
41
- const candidates = [
42
- path.resolve(__dirname, "resources", "prompts", name),
43
- path.resolve(__dirname, "prompts", name),
44
- path.resolve(__dirname, "..", "resources", "prompts", name),
45
- path.resolve(process.cwd(), "packages", "cli", "src", "resources", "prompts", name),
46
- ];
47
- for (const candidate of candidates) {
48
- if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
49
- return candidate;
50
- }
51
- }
52
- throw new Error(`Prompt resource "${name}" not found. Looked in: ${candidates.join(", ")}`);
53
- }
54
- function loadPromptTemplate(name) {
55
- const cached = promptCache.get(name);
56
- if (cached)
57
- return cached;
58
- const promptPath = resolvePromptPath(name);
59
- const content = fs.readFileSync(promptPath, "utf-8");
60
- promptCache.set(name, content);
61
- return content;
62
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * skills import-local CLI command
3
- *
4
- * Imports SKILL.md files from a local directory into a Spekn project.
5
- *
6
- * Usage: spekn skills import-local <path> --project-id <uuid> [--namespace <ns>] [--api-url <url>]
7
- */
8
- import { CredentialsStore } from "./auth/credentials-store";
9
- interface Deps {
10
- stdout: (content: string) => void;
11
- stderr: (content: string) => void;
12
- credentialsStore: CredentialsStore;
13
- }
14
- export declare function runSkillsImportLocalCli(args: string[], deps?: Deps): Promise<number>;
15
- export declare function main(): Promise<void>;
16
- export {};
@@ -1,352 +0,0 @@
1
- "use strict";
2
- /**
3
- * skills import-local CLI command
4
- *
5
- * Imports SKILL.md files from a local directory into a Spekn project.
6
- *
7
- * Usage: spekn skills import-local <path> --project-id <uuid> [--namespace <ns>] [--api-url <url>]
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.runSkillsImportLocalCli = runSkillsImportLocalCli;
11
- exports.main = main;
12
- const node_fs_1 = require("node:fs");
13
- const node_path_1 = require("node:path");
14
- const client_1 = require("@trpc/client");
15
- const shared_1 = require("@spekn/shared");
16
- const credentials_store_1 = require("./auth/credentials-store");
17
- const project_context_1 = require("./project-context");
18
- const structured_log_1 = require("./utils/structured-log");
19
- const trpc_url_1 = require("./utils/trpc-url");
20
- const help_error_1 = require("./utils/help-error");
21
- const defaultDeps = {
22
- stdout: (content) => process.stdout.write(content),
23
- stderr: (content) => process.stderr.write(content),
24
- credentialsStore: new credentials_store_1.CredentialsStore(),
25
- };
26
- function printHelp(stderr) {
27
- stderr(`
28
- skills import-local - Import SKILL.md files from a local directory
29
-
30
- USAGE:
31
- spekn skills import-local <path> --project-id <uuid> [options]
32
-
33
- ARGUMENTS:
34
- <path> Directory containing SKILL.md files (searched recursively)
35
-
36
- OPTIONS:
37
- --project-id <uuid> Project ID to import skills into (optional if .spekn/context is present)
38
- --namespace <ns> Namespace for imported skills (default: directory name)
39
- --names <a,b,c> Comma-separated skill names to import (default: all)
40
- --api-url <url> API base URL (default: SPEKN_API_URL or https://app.spekn.com)
41
- --help Show this help message
42
-
43
- ENVIRONMENT:
44
- SPEKN_API_URL API base URL
45
- SPEKN_AUTH_TOKEN Bearer token for authentication
46
- SPEKN_ORGANIZATION_ID Organization ID header
47
-
48
- EXAMPLES:
49
- spekn skills import-local ./my-skills --project-id 11111111-1111-4111-8111-111111111111
50
- spekn skills import-local ./plugins/skills --project-id <uuid> --namespace my-org
51
- `);
52
- }
53
- function parseArgs(args) {
54
- let path = "";
55
- let projectId = "";
56
- let namespace = "";
57
- let names = [];
58
- let apiUrl = process.env.SPEKN_API_URL ?? "https://app.spekn.com";
59
- for (let index = 0; index < args.length; index++) {
60
- const arg = args[index];
61
- if (arg === "--help" || arg === "-h") {
62
- throw new help_error_1.HelpRequestedError();
63
- }
64
- if (arg === "--project-id" && args[index + 1]) {
65
- projectId = args[++index];
66
- continue;
67
- }
68
- if (arg?.startsWith("--project-id=")) {
69
- projectId = arg.slice("--project-id=".length);
70
- continue;
71
- }
72
- if (arg === "--namespace" && args[index + 1]) {
73
- namespace = args[++index];
74
- continue;
75
- }
76
- if (arg?.startsWith("--namespace=")) {
77
- namespace = arg.slice("--namespace=".length);
78
- continue;
79
- }
80
- if (arg === "--names" && args[index + 1]) {
81
- names = args[++index].split(",").map((n) => n.trim()).filter(Boolean);
82
- continue;
83
- }
84
- if (arg?.startsWith("--names=")) {
85
- names = arg.slice("--names=".length).split(",").map((n) => n.trim()).filter(Boolean);
86
- continue;
87
- }
88
- if (arg === "--api-url" && args[index + 1]) {
89
- apiUrl = args[++index];
90
- continue;
91
- }
92
- if (arg?.startsWith("--api-url=")) {
93
- apiUrl = arg.slice("--api-url=".length);
94
- continue;
95
- }
96
- // Positional argument: path
97
- if (!arg?.startsWith("--") && !path) {
98
- path = arg;
99
- }
100
- }
101
- if (!path) {
102
- throw new Error("Missing required argument: <path>");
103
- }
104
- const resolvedPath = (0, node_path_1.resolve)(path);
105
- if (!namespace) {
106
- namespace = (0, node_path_1.basename)(resolvedPath);
107
- }
108
- return { path: resolvedPath, projectId, namespace, names, apiUrl };
109
- }
110
- /**
111
- * Recursively find all SKILL.md files under a directory.
112
- */
113
- function findSkillFiles(dir) {
114
- const results = [];
115
- const entries = (0, node_fs_1.readdirSync)(dir);
116
- for (const entry of entries) {
117
- const fullPath = (0, node_path_1.join)(dir, entry);
118
- const stat = (0, node_fs_1.statSync)(fullPath);
119
- if (stat.isDirectory()) {
120
- results.push(...findSkillFiles(fullPath));
121
- }
122
- else if (entry === "SKILL.md") {
123
- results.push(fullPath);
124
- }
125
- }
126
- return results;
127
- }
128
- /**
129
- * Recursively collect all files in a skill directory and return a map
130
- * of relative paths to file contents.
131
- */
132
- function collectSkillFiles(skillDir) {
133
- const files = {};
134
- function walk(dir) {
135
- const entries = (0, node_fs_1.readdirSync)(dir);
136
- for (const entry of entries) {
137
- const fullPath = (0, node_path_1.join)(dir, entry);
138
- const stat = (0, node_fs_1.statSync)(fullPath);
139
- if (stat.isDirectory()) {
140
- walk(fullPath);
141
- }
142
- else {
143
- const relPath = (0, node_path_1.relative)(skillDir, fullPath);
144
- files[relPath] = (0, node_fs_1.readFileSync)(fullPath, "utf-8");
145
- }
146
- }
147
- }
148
- walk(skillDir);
149
- return files;
150
- }
151
- /**
152
- * Parse a SKILL.md file: extract YAML frontmatter and body content.
153
- */
154
- function parseSkillFile(filePath) {
155
- const content = (0, node_fs_1.readFileSync)(filePath, "utf-8");
156
- const fmMatch = /^---\n([\s\S]*?)\n---/.exec(content);
157
- if (!fmMatch?.[1]) {
158
- return { error: `${filePath}: no frontmatter found` };
159
- }
160
- // Simple YAML parsing without js-yaml dependency.
161
- // SKILL.md frontmatter is flat key-value, so we parse it manually.
162
- const yamlText = fmMatch[1];
163
- const frontmatter = {};
164
- let currentKey = "";
165
- let inNestedMap = false;
166
- const nestedMap = {};
167
- for (const line of yamlText.split("\n")) {
168
- // Nested map value (indented)
169
- if (inNestedMap && /^\s{2,}\S/.test(line)) {
170
- const kvMatch = /^\s+(\S+):\s*(.*)$/.exec(line);
171
- if (kvMatch) {
172
- nestedMap[kvMatch[1]] = kvMatch[2].replace(/^["']|["']$/g, "");
173
- }
174
- continue;
175
- }
176
- // Flush nested map if we were in one
177
- if (inNestedMap) {
178
- frontmatter[currentKey] = { ...nestedMap };
179
- for (const k of Object.keys(nestedMap))
180
- delete nestedMap[k];
181
- inNestedMap = false;
182
- }
183
- // Top-level key: value
184
- const topMatch = /^(\S+):\s*(.*)$/.exec(line);
185
- if (topMatch) {
186
- const key = topMatch[1];
187
- const value = topMatch[2].replace(/^["']|["']$/g, "");
188
- if (value === "" || value === undefined) {
189
- // Could be start of a nested map
190
- currentKey = key;
191
- inNestedMap = true;
192
- }
193
- else {
194
- frontmatter[key] = value;
195
- }
196
- }
197
- }
198
- // Flush last nested map
199
- if (inNestedMap) {
200
- frontmatter[currentKey] = { ...nestedMap };
201
- }
202
- // Extract readme (content after frontmatter)
203
- const readme = content.slice(fmMatch[0].length).trim();
204
- return { frontmatter, readme };
205
- }
206
- async function runSkillsImportLocalCli(args, deps = defaultDeps) {
207
- try {
208
- const options = parseArgs(args);
209
- (0, structured_log_1.appendCliStructuredLog)({
210
- source: "cli.skills.import-local",
211
- level: "info",
212
- message: "Starting skills import-local",
213
- details: { path: options.path, namespace: options.namespace, apiUrl: options.apiUrl },
214
- });
215
- // Find SKILL.md files
216
- const skillFiles = findSkillFiles(options.path);
217
- if (skillFiles.length === 0) {
218
- deps.stderr(`No SKILL.md files found under ${options.path}\n`);
219
- return 1;
220
- }
221
- deps.stdout(`Found ${skillFiles.length} SKILL.md file(s) in ${options.path}\n`);
222
- // Parse and validate each file
223
- const skills = [];
224
- const parseErrors = [];
225
- for (const filePath of skillFiles) {
226
- const result = parseSkillFile(filePath);
227
- if ("error" in result) {
228
- parseErrors.push(result.error);
229
- continue;
230
- }
231
- const validation = shared_1.SkillFrontmatterSchema.safeParse(result.frontmatter);
232
- if (!validation.success) {
233
- const issues = validation.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
234
- parseErrors.push(`${filePath}: ${issues.join("; ")}`);
235
- continue;
236
- }
237
- const fm = validation.data;
238
- const meta = fm.metadata;
239
- const skillDir = (0, node_path_1.dirname)(filePath);
240
- const files = collectSkillFiles(skillDir);
241
- skills.push({
242
- name: fm.name,
243
- description: fm.description,
244
- version: meta?.version || "0.0.0",
245
- namespace: options.namespace,
246
- readme: result.readme || undefined,
247
- license: fm.license,
248
- compatibility: fm.compatibility,
249
- author: meta?.author,
250
- tags: [],
251
- metadata: meta ? { ...meta } : undefined,
252
- files,
253
- });
254
- }
255
- if (parseErrors.length > 0) {
256
- deps.stderr(`\nParse errors:\n`);
257
- for (const err of parseErrors) {
258
- deps.stderr(` - ${err}\n`);
259
- }
260
- }
261
- // Filter by --names if provided
262
- if (options.names.length > 0) {
263
- const nameSet = new Set(options.names);
264
- const before = skills.length;
265
- const filtered = skills.filter((s) => nameSet.has(s.name));
266
- skills.length = 0;
267
- skills.push(...filtered);
268
- if (skills.length < before) {
269
- deps.stdout(`Filtered to ${skills.length} of ${before} skill(s) by --names\n`);
270
- }
271
- }
272
- if (skills.length === 0) {
273
- deps.stderr(`No valid skills to import.\n`);
274
- return 1;
275
- }
276
- deps.stdout(`\nImporting ${skills.length} skill(s)...\n`);
277
- // Auth
278
- const storedToken = await deps.credentialsStore.getValidToken();
279
- const authToken = storedToken ?? process.env.SPEKN_AUTH_TOKEN;
280
- const storedCreds = deps.credentialsStore.load();
281
- const context = (0, project_context_1.resolveProjectContext)({
282
- explicitProjectId: options.projectId,
283
- repoPath: process.cwd(),
284
- credentialsOrganizationId: storedCreds?.organizationId,
285
- envOrganizationId: process.env.SPEKN_ORGANIZATION_ID,
286
- });
287
- // tRPC client
288
- const client = (0, client_1.createTRPCProxyClient)({
289
- links: [
290
- (0, client_1.httpBatchLink)({
291
- url: (0, trpc_url_1.normalizeTrpcUrl)(options.apiUrl),
292
- headers: {
293
- "x-organization-id": context.organizationId,
294
- authorization: authToken ? `Bearer ${authToken}` : "",
295
- },
296
- }),
297
- ],
298
- });
299
- const result = await client.skills.importLocal.mutate({
300
- projectId: context.projectId,
301
- skills,
302
- });
303
- // Report
304
- if (result.imported.length > 0) {
305
- deps.stdout(`\nImported: ${result.imported.join(", ")}\n`);
306
- }
307
- if (result.updated.length > 0) {
308
- deps.stdout(`Updated: ${result.updated.join(", ")}\n`);
309
- }
310
- if (result.errors.length > 0) {
311
- deps.stderr(`\nServer errors:\n`);
312
- for (const err of result.errors) {
313
- deps.stderr(` - ${err}\n`);
314
- }
315
- }
316
- const total = result.imported.length + result.updated.length;
317
- deps.stdout(`\nDone. ${total} skill(s) processed.\n`);
318
- (0, structured_log_1.appendCliStructuredLog)({
319
- source: "cli.skills.import-local",
320
- level: result.errors.length > 0 ? "warn" : "info",
321
- message: "Skills import-local completed",
322
- details: {
323
- totalProcessed: total,
324
- imported: result.imported.length,
325
- updated: result.updated.length,
326
- errors: result.errors.length,
327
- },
328
- });
329
- return result.errors.length > 0 ? 1 : 0;
330
- }
331
- catch (error) {
332
- if (error instanceof help_error_1.HelpRequestedError) {
333
- printHelp(deps.stderr);
334
- return 0;
335
- }
336
- const message = error instanceof Error ? error.message : String(error);
337
- deps.stderr(`Error: ${message}\n`);
338
- (0, structured_log_1.appendCliStructuredLog)({
339
- source: "cli.skills.import-local",
340
- level: "error",
341
- message,
342
- });
343
- return 1;
344
- }
345
- }
346
- async function main() {
347
- const exitCode = await runSkillsImportLocalCli(process.argv.slice(2));
348
- process.exit(exitCode);
349
- }
350
- if (require.main === module) {
351
- void main();
352
- }
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- declare function main(argv?: string[]): Promise<number>;
3
- export { main };