@spekn/cli 1.0.0 → 1.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.
Files changed (154) hide show
  1. package/README.md +58 -0
  2. package/dist/main.js +3707 -611
  3. package/dist/tui/index.mjs +2 -2
  4. package/package.json +29 -12
  5. package/dist/__tests__/export-cli.test.d.ts +0 -1
  6. package/dist/__tests__/export-cli.test.js +0 -70
  7. package/dist/__tests__/tui-args-policy.test.d.ts +0 -1
  8. package/dist/__tests__/tui-args-policy.test.js +0 -50
  9. package/dist/acp-S2MHZOAD.mjs +0 -23
  10. package/dist/acp-UCCI44JY.mjs +0 -25
  11. package/dist/auth/credentials-store.d.ts +0 -2
  12. package/dist/auth/credentials-store.js +0 -5
  13. package/dist/auth/device-flow.d.ts +0 -36
  14. package/dist/auth/device-flow.js +0 -189
  15. package/dist/auth/jwt.d.ts +0 -1
  16. package/dist/auth/jwt.js +0 -6
  17. package/dist/auth/session.d.ts +0 -67
  18. package/dist/auth/session.js +0 -86
  19. package/dist/auth-login.d.ts +0 -34
  20. package/dist/auth-login.js +0 -202
  21. package/dist/auth-logout.d.ts +0 -25
  22. package/dist/auth-logout.js +0 -115
  23. package/dist/auth-status.d.ts +0 -24
  24. package/dist/auth-status.js +0 -109
  25. package/dist/backlog-generate.d.ts +0 -11
  26. package/dist/backlog-generate.js +0 -308
  27. package/dist/backlog-health.d.ts +0 -11
  28. package/dist/backlog-health.js +0 -287
  29. package/dist/bridge-login.d.ts +0 -40
  30. package/dist/bridge-login.js +0 -277
  31. package/dist/chunk-3PAYRI4G.mjs +0 -2428
  32. package/dist/chunk-M4CS3A25.mjs +0 -2426
  33. package/dist/commands/auth/login.d.ts +0 -30
  34. package/dist/commands/auth/login.js +0 -164
  35. package/dist/commands/auth/logout.d.ts +0 -25
  36. package/dist/commands/auth/logout.js +0 -115
  37. package/dist/commands/auth/status.d.ts +0 -24
  38. package/dist/commands/auth/status.js +0 -109
  39. package/dist/commands/backlog/generate.d.ts +0 -11
  40. package/dist/commands/backlog/generate.js +0 -308
  41. package/dist/commands/backlog/health.d.ts +0 -11
  42. package/dist/commands/backlog/health.js +0 -287
  43. package/dist/commands/bridge/login.d.ts +0 -36
  44. package/dist/commands/bridge/login.js +0 -258
  45. package/dist/commands/export.d.ts +0 -35
  46. package/dist/commands/export.js +0 -485
  47. package/dist/commands/marketplace-export.d.ts +0 -21
  48. package/dist/commands/marketplace-export.js +0 -214
  49. package/dist/commands/project-clean.d.ts +0 -1
  50. package/dist/commands/project-clean.js +0 -126
  51. package/dist/commands/repo/common.d.ts +0 -105
  52. package/dist/commands/repo/common.js +0 -775
  53. package/dist/commands/repo/detach.d.ts +0 -2
  54. package/dist/commands/repo/detach.js +0 -120
  55. package/dist/commands/repo/register.d.ts +0 -21
  56. package/dist/commands/repo/register.js +0 -175
  57. package/dist/commands/repo/sync.d.ts +0 -22
  58. package/dist/commands/repo/sync.js +0 -873
  59. package/dist/commands/skills-import-local.d.ts +0 -16
  60. package/dist/commands/skills-import-local.js +0 -352
  61. package/dist/commands/spec/drift-check.d.ts +0 -3
  62. package/dist/commands/spec/drift-check.js +0 -186
  63. package/dist/commands/spec/frontmatter.d.ts +0 -11
  64. package/dist/commands/spec/frontmatter.js +0 -219
  65. package/dist/commands/spec/lint.d.ts +0 -11
  66. package/dist/commands/spec/lint.js +0 -499
  67. package/dist/commands/spec/parse.d.ts +0 -11
  68. package/dist/commands/spec/parse.js +0 -162
  69. package/dist/export.d.ts +0 -35
  70. package/dist/export.js +0 -485
  71. package/dist/main.d.ts +0 -1
  72. package/dist/marketplace-export.d.ts +0 -21
  73. package/dist/marketplace-export.js +0 -214
  74. package/dist/project-clean.d.ts +0 -1
  75. package/dist/project-clean.js +0 -126
  76. package/dist/project-context.d.ts +0 -99
  77. package/dist/project-context.js +0 -376
  78. package/dist/repo-common.d.ts +0 -101
  79. package/dist/repo-common.js +0 -671
  80. package/dist/repo-detach.d.ts +0 -2
  81. package/dist/repo-detach.js +0 -102
  82. package/dist/repo-ingest.d.ts +0 -29
  83. package/dist/repo-ingest.js +0 -305
  84. package/dist/repo-register.d.ts +0 -21
  85. package/dist/repo-register.js +0 -175
  86. package/dist/repo-sync.d.ts +0 -16
  87. package/dist/repo-sync.js +0 -152
  88. package/dist/resources/prompt-loader.d.ts +0 -1
  89. package/dist/resources/prompt-loader.js +0 -62
  90. package/dist/skills-import-local.d.ts +0 -16
  91. package/dist/skills-import-local.js +0 -352
  92. package/dist/spec-drift-check.d.ts +0 -3
  93. package/dist/spec-drift-check.js +0 -186
  94. package/dist/spec-frontmatter.d.ts +0 -11
  95. package/dist/spec-frontmatter.js +0 -219
  96. package/dist/spec-lint.d.ts +0 -11
  97. package/dist/spec-lint.js +0 -499
  98. package/dist/spec-parse.d.ts +0 -11
  99. package/dist/spec-parse.js +0 -162
  100. package/dist/stubs/dotenv.d.ts +0 -5
  101. package/dist/stubs/dotenv.js +0 -6
  102. package/dist/stubs/typeorm.d.ts +0 -22
  103. package/dist/stubs/typeorm.js +0 -28
  104. package/dist/tui/app.d.ts +0 -7
  105. package/dist/tui/app.js +0 -122
  106. package/dist/tui/args.d.ts +0 -8
  107. package/dist/tui/args.js +0 -57
  108. package/dist/tui/capabilities/policy.d.ts +0 -7
  109. package/dist/tui/capabilities/policy.js +0 -64
  110. package/dist/tui/components/frame.d.ts +0 -8
  111. package/dist/tui/components/frame.js +0 -8
  112. package/dist/tui/components/status-bar.d.ts +0 -8
  113. package/dist/tui/components/status-bar.js +0 -8
  114. package/dist/tui/index.d.ts +0 -2
  115. package/dist/tui/index.js +0 -23
  116. package/dist/tui/keymap/use-global-keymap.d.ts +0 -19
  117. package/dist/tui/keymap/use-global-keymap.js +0 -82
  118. package/dist/tui/navigation/nav-items.d.ts +0 -3
  119. package/dist/tui/navigation/nav-items.js +0 -18
  120. package/dist/tui/screens/bridge.d.ts +0 -8
  121. package/dist/tui/screens/bridge.js +0 -19
  122. package/dist/tui/screens/decisions.d.ts +0 -5
  123. package/dist/tui/screens/decisions.js +0 -28
  124. package/dist/tui/screens/export.d.ts +0 -5
  125. package/dist/tui/screens/export.js +0 -16
  126. package/dist/tui/screens/home.d.ts +0 -5
  127. package/dist/tui/screens/home.js +0 -33
  128. package/dist/tui/screens/locked.d.ts +0 -5
  129. package/dist/tui/screens/locked.js +0 -9
  130. package/dist/tui/screens/specs.d.ts +0 -5
  131. package/dist/tui/screens/specs.js +0 -31
  132. package/dist/tui/services/client.d.ts +0 -1
  133. package/dist/tui/services/client.js +0 -18
  134. package/dist/tui/services/context-service.d.ts +0 -19
  135. package/dist/tui/services/context-service.js +0 -246
  136. package/dist/tui/shared-enums.d.ts +0 -16
  137. package/dist/tui/shared-enums.js +0 -19
  138. package/dist/tui/state/use-app-state.d.ts +0 -35
  139. package/dist/tui/state/use-app-state.js +0 -177
  140. package/dist/tui/types.d.ts +0 -77
  141. package/dist/tui/types.js +0 -2
  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 };