@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.
- package/README.md +58 -0
- package/dist/main.js +3707 -611
- package/dist/tui/index.mjs +2 -2
- package/package.json +29 -12
- package/dist/__tests__/export-cli.test.d.ts +0 -1
- package/dist/__tests__/export-cli.test.js +0 -70
- package/dist/__tests__/tui-args-policy.test.d.ts +0 -1
- package/dist/__tests__/tui-args-policy.test.js +0 -50
- package/dist/acp-S2MHZOAD.mjs +0 -23
- package/dist/acp-UCCI44JY.mjs +0 -25
- package/dist/auth/credentials-store.d.ts +0 -2
- package/dist/auth/credentials-store.js +0 -5
- package/dist/auth/device-flow.d.ts +0 -36
- package/dist/auth/device-flow.js +0 -189
- package/dist/auth/jwt.d.ts +0 -1
- package/dist/auth/jwt.js +0 -6
- package/dist/auth/session.d.ts +0 -67
- package/dist/auth/session.js +0 -86
- package/dist/auth-login.d.ts +0 -34
- package/dist/auth-login.js +0 -202
- package/dist/auth-logout.d.ts +0 -25
- package/dist/auth-logout.js +0 -115
- package/dist/auth-status.d.ts +0 -24
- package/dist/auth-status.js +0 -109
- package/dist/backlog-generate.d.ts +0 -11
- package/dist/backlog-generate.js +0 -308
- package/dist/backlog-health.d.ts +0 -11
- package/dist/backlog-health.js +0 -287
- package/dist/bridge-login.d.ts +0 -40
- package/dist/bridge-login.js +0 -277
- package/dist/chunk-3PAYRI4G.mjs +0 -2428
- package/dist/chunk-M4CS3A25.mjs +0 -2426
- package/dist/commands/auth/login.d.ts +0 -30
- package/dist/commands/auth/login.js +0 -164
- package/dist/commands/auth/logout.d.ts +0 -25
- package/dist/commands/auth/logout.js +0 -115
- package/dist/commands/auth/status.d.ts +0 -24
- package/dist/commands/auth/status.js +0 -109
- package/dist/commands/backlog/generate.d.ts +0 -11
- package/dist/commands/backlog/generate.js +0 -308
- package/dist/commands/backlog/health.d.ts +0 -11
- package/dist/commands/backlog/health.js +0 -287
- package/dist/commands/bridge/login.d.ts +0 -36
- package/dist/commands/bridge/login.js +0 -258
- package/dist/commands/export.d.ts +0 -35
- package/dist/commands/export.js +0 -485
- package/dist/commands/marketplace-export.d.ts +0 -21
- package/dist/commands/marketplace-export.js +0 -214
- package/dist/commands/project-clean.d.ts +0 -1
- package/dist/commands/project-clean.js +0 -126
- package/dist/commands/repo/common.d.ts +0 -105
- package/dist/commands/repo/common.js +0 -775
- package/dist/commands/repo/detach.d.ts +0 -2
- package/dist/commands/repo/detach.js +0 -120
- package/dist/commands/repo/register.d.ts +0 -21
- package/dist/commands/repo/register.js +0 -175
- package/dist/commands/repo/sync.d.ts +0 -22
- package/dist/commands/repo/sync.js +0 -873
- package/dist/commands/skills-import-local.d.ts +0 -16
- package/dist/commands/skills-import-local.js +0 -352
- package/dist/commands/spec/drift-check.d.ts +0 -3
- package/dist/commands/spec/drift-check.js +0 -186
- package/dist/commands/spec/frontmatter.d.ts +0 -11
- package/dist/commands/spec/frontmatter.js +0 -219
- package/dist/commands/spec/lint.d.ts +0 -11
- package/dist/commands/spec/lint.js +0 -499
- package/dist/commands/spec/parse.d.ts +0 -11
- package/dist/commands/spec/parse.js +0 -162
- package/dist/export.d.ts +0 -35
- package/dist/export.js +0 -485
- package/dist/main.d.ts +0 -1
- package/dist/marketplace-export.d.ts +0 -21
- package/dist/marketplace-export.js +0 -214
- package/dist/project-clean.d.ts +0 -1
- package/dist/project-clean.js +0 -126
- package/dist/project-context.d.ts +0 -99
- package/dist/project-context.js +0 -376
- package/dist/repo-common.d.ts +0 -101
- package/dist/repo-common.js +0 -671
- package/dist/repo-detach.d.ts +0 -2
- package/dist/repo-detach.js +0 -102
- package/dist/repo-ingest.d.ts +0 -29
- package/dist/repo-ingest.js +0 -305
- package/dist/repo-register.d.ts +0 -21
- package/dist/repo-register.js +0 -175
- package/dist/repo-sync.d.ts +0 -16
- package/dist/repo-sync.js +0 -152
- package/dist/resources/prompt-loader.d.ts +0 -1
- package/dist/resources/prompt-loader.js +0 -62
- package/dist/skills-import-local.d.ts +0 -16
- package/dist/skills-import-local.js +0 -352
- package/dist/spec-drift-check.d.ts +0 -3
- package/dist/spec-drift-check.js +0 -186
- package/dist/spec-frontmatter.d.ts +0 -11
- package/dist/spec-frontmatter.js +0 -219
- package/dist/spec-lint.d.ts +0 -11
- package/dist/spec-lint.js +0 -499
- package/dist/spec-parse.d.ts +0 -11
- package/dist/spec-parse.js +0 -162
- package/dist/stubs/dotenv.d.ts +0 -5
- package/dist/stubs/dotenv.js +0 -6
- package/dist/stubs/typeorm.d.ts +0 -22
- package/dist/stubs/typeorm.js +0 -28
- package/dist/tui/app.d.ts +0 -7
- package/dist/tui/app.js +0 -122
- package/dist/tui/args.d.ts +0 -8
- package/dist/tui/args.js +0 -57
- package/dist/tui/capabilities/policy.d.ts +0 -7
- package/dist/tui/capabilities/policy.js +0 -64
- package/dist/tui/components/frame.d.ts +0 -8
- package/dist/tui/components/frame.js +0 -8
- package/dist/tui/components/status-bar.d.ts +0 -8
- package/dist/tui/components/status-bar.js +0 -8
- package/dist/tui/index.d.ts +0 -2
- package/dist/tui/index.js +0 -23
- package/dist/tui/keymap/use-global-keymap.d.ts +0 -19
- package/dist/tui/keymap/use-global-keymap.js +0 -82
- package/dist/tui/navigation/nav-items.d.ts +0 -3
- package/dist/tui/navigation/nav-items.js +0 -18
- package/dist/tui/screens/bridge.d.ts +0 -8
- package/dist/tui/screens/bridge.js +0 -19
- package/dist/tui/screens/decisions.d.ts +0 -5
- package/dist/tui/screens/decisions.js +0 -28
- package/dist/tui/screens/export.d.ts +0 -5
- package/dist/tui/screens/export.js +0 -16
- package/dist/tui/screens/home.d.ts +0 -5
- package/dist/tui/screens/home.js +0 -33
- package/dist/tui/screens/locked.d.ts +0 -5
- package/dist/tui/screens/locked.js +0 -9
- package/dist/tui/screens/specs.d.ts +0 -5
- package/dist/tui/screens/specs.js +0 -31
- package/dist/tui/services/client.d.ts +0 -1
- package/dist/tui/services/client.js +0 -18
- package/dist/tui/services/context-service.d.ts +0 -19
- package/dist/tui/services/context-service.js +0 -246
- package/dist/tui/shared-enums.d.ts +0 -16
- package/dist/tui/shared-enums.js +0 -19
- package/dist/tui/state/use-app-state.d.ts +0 -35
- package/dist/tui/state/use-app-state.js +0 -177
- package/dist/tui/types.d.ts +0 -77
- package/dist/tui/types.js +0 -2
- package/dist/tui-bundle.d.ts +0 -1
- package/dist/tui-bundle.js +0 -5
- package/dist/tui-entry.mjs +0 -1407
- package/dist/utils/cli-runtime.d.ts +0 -5
- package/dist/utils/cli-runtime.js +0 -22
- package/dist/utils/help-error.d.ts +0 -7
- package/dist/utils/help-error.js +0 -14
- package/dist/utils/interaction.d.ts +0 -19
- package/dist/utils/interaction.js +0 -93
- package/dist/utils/structured-log.d.ts +0 -7
- package/dist/utils/structured-log.js +0 -112
- package/dist/utils/trpc-url.d.ts +0 -4
- package/dist/utils/trpc-url.js +0 -15
|
@@ -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,186 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.main = main;
|
|
5
|
-
const shared_1 = require("@spekn/shared");
|
|
6
|
-
const cli_runtime_1 = require("../../utils/cli-runtime");
|
|
7
|
-
async function columnExists(tableName, columnName) {
|
|
8
|
-
const rows = (await shared_1.AppDataSource.query(`
|
|
9
|
-
SELECT 1
|
|
10
|
-
FROM information_schema.columns
|
|
11
|
-
WHERE table_schema = current_schema()
|
|
12
|
-
AND table_name = $1
|
|
13
|
-
AND column_name = $2
|
|
14
|
-
LIMIT 1
|
|
15
|
-
`, [tableName, columnName]));
|
|
16
|
-
return rows.length > 0;
|
|
17
|
-
}
|
|
18
|
-
function parseArgs(args) {
|
|
19
|
-
const options = {};
|
|
20
|
-
for (const arg of args) {
|
|
21
|
-
if (arg.startsWith("--project-id=")) {
|
|
22
|
-
options.projectId = arg.split("=")[1];
|
|
23
|
-
}
|
|
24
|
-
else if (arg.startsWith("--spec-id=")) {
|
|
25
|
-
options.specId = arg.split("=")[1];
|
|
26
|
-
}
|
|
27
|
-
else if (arg === "--json" || arg === "-j") {
|
|
28
|
-
options.json = true;
|
|
29
|
-
}
|
|
30
|
-
else if (arg === "--verbose" || arg === "-v") {
|
|
31
|
-
options.verbose = true;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return options;
|
|
35
|
-
}
|
|
36
|
-
function printHelp() {
|
|
37
|
-
console.log(`
|
|
38
|
-
spec-drift-check - Fail when task/spec bindings drift
|
|
39
|
-
|
|
40
|
-
USAGE:
|
|
41
|
-
npm run spec-drift-check [OPTIONS]
|
|
42
|
-
|
|
43
|
-
OPTIONS:
|
|
44
|
-
--project-id=<id> Filter by project ID
|
|
45
|
-
--spec-id=<id> Filter by specification ID
|
|
46
|
-
-j, --json Output JSON
|
|
47
|
-
-v, --verbose Print individual drift rows
|
|
48
|
-
-h, --help Show this help message
|
|
49
|
-
`);
|
|
50
|
-
}
|
|
51
|
-
async function main(argv) {
|
|
52
|
-
const args = argv ?? process.argv.slice(2);
|
|
53
|
-
if ((0, cli_runtime_1.hasHelpFlag)(args)) {
|
|
54
|
-
printHelp();
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
const options = parseArgs(args);
|
|
58
|
-
const hasDbConfig = Boolean(process.env.DATABASE_URL ||
|
|
59
|
-
process.env.DB_HOST ||
|
|
60
|
-
process.env.DB_NAME ||
|
|
61
|
-
process.env.POSTGRES_HOST);
|
|
62
|
-
if (!hasDbConfig) {
|
|
63
|
-
console.log("spec-drift-check: skipped (no database configuration found in environment).");
|
|
64
|
-
return 0;
|
|
65
|
-
}
|
|
66
|
-
await shared_1.AppDataSource.initialize();
|
|
67
|
-
try {
|
|
68
|
-
const hasTaskSpecVersion = await columnExists("task", "specVersion");
|
|
69
|
-
const hasTaskSpecRef = await columnExists("task", "specRef");
|
|
70
|
-
const hasSpecVersion = await columnExists("specification", "version");
|
|
71
|
-
const hasSpecRef = await columnExists("specification", "specRef");
|
|
72
|
-
const filters = [];
|
|
73
|
-
const params = [];
|
|
74
|
-
if (options.projectId) {
|
|
75
|
-
params.push(options.projectId);
|
|
76
|
-
filters.push(`t."projectId" = $${params.length}`);
|
|
77
|
-
}
|
|
78
|
-
if (options.specId) {
|
|
79
|
-
params.push(options.specId);
|
|
80
|
-
filters.push(`t."specificationId" = $${params.length}`);
|
|
81
|
-
}
|
|
82
|
-
const whereClause = filters.length > 0 ? `WHERE ${filters.join(" AND ")}` : "";
|
|
83
|
-
const taskSpecVersionExpr = hasTaskSpecVersion
|
|
84
|
-
? `t."specVersion"`
|
|
85
|
-
: `NULL::text`;
|
|
86
|
-
const taskSpecRefExpr = hasTaskSpecRef ? `t."specRef"` : `NULL::text`;
|
|
87
|
-
const currentSpecVersionExpr = hasSpecVersion
|
|
88
|
-
? `s."version"`
|
|
89
|
-
: `NULL::text`;
|
|
90
|
-
const currentSpecRefExpr = hasSpecRef ? `s."specRef"` : `NULL::text`;
|
|
91
|
-
const rows = (await shared_1.AppDataSource.query(`
|
|
92
|
-
SELECT
|
|
93
|
-
t."id" AS "taskId",
|
|
94
|
-
t."title" AS "taskTitle",
|
|
95
|
-
t."specificationId" AS "specificationId",
|
|
96
|
-
s."id" AS "specId",
|
|
97
|
-
s."title" AS "specTitle",
|
|
98
|
-
${taskSpecVersionExpr} AS "taskSpecVersion",
|
|
99
|
-
${taskSpecRefExpr} AS "taskSpecRef",
|
|
100
|
-
${currentSpecVersionExpr} AS "currentSpecVersion",
|
|
101
|
-
${currentSpecRefExpr} AS "currentSpecRef"
|
|
102
|
-
FROM "task" t
|
|
103
|
-
LEFT JOIN "specification" s ON s."id" = t."specificationId"
|
|
104
|
-
${whereClause}
|
|
105
|
-
`, params));
|
|
106
|
-
const driftItems = rows
|
|
107
|
-
.map((row) => {
|
|
108
|
-
const driftReasons = [];
|
|
109
|
-
if (!row.specId) {
|
|
110
|
-
driftReasons.push("spec_missing");
|
|
111
|
-
}
|
|
112
|
-
if (!hasTaskSpecVersion || !row.taskSpecVersion) {
|
|
113
|
-
driftReasons.push("task_spec_version_missing");
|
|
114
|
-
}
|
|
115
|
-
if (!hasTaskSpecRef || !row.taskSpecRef) {
|
|
116
|
-
driftReasons.push("task_spec_ref_missing");
|
|
117
|
-
}
|
|
118
|
-
if (!hasSpecRef) {
|
|
119
|
-
driftReasons.push("spec_ref_missing");
|
|
120
|
-
}
|
|
121
|
-
if (row.taskSpecVersion &&
|
|
122
|
-
row.currentSpecVersion &&
|
|
123
|
-
row.taskSpecVersion !== row.currentSpecVersion) {
|
|
124
|
-
driftReasons.push("spec_version_drift");
|
|
125
|
-
}
|
|
126
|
-
if (row.taskSpecRef &&
|
|
127
|
-
row.currentSpecRef &&
|
|
128
|
-
row.taskSpecRef !== row.currentSpecRef) {
|
|
129
|
-
driftReasons.push("spec_ref_drift");
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
taskId: row.taskId,
|
|
133
|
-
taskTitle: row.taskTitle,
|
|
134
|
-
specificationId: row.specificationId,
|
|
135
|
-
specTitle: row.specTitle || "<missing specification>",
|
|
136
|
-
taskSpecVersion: row.taskSpecVersion,
|
|
137
|
-
currentSpecVersion: row.currentSpecVersion,
|
|
138
|
-
taskSpecRef: row.taskSpecRef,
|
|
139
|
-
currentSpecRef: row.currentSpecRef,
|
|
140
|
-
driftReasons,
|
|
141
|
-
};
|
|
142
|
-
})
|
|
143
|
-
.filter((item) => item.driftReasons.length > 0);
|
|
144
|
-
const summary = {
|
|
145
|
-
checkedTasks: rows.length,
|
|
146
|
-
driftedTasks: driftItems.length,
|
|
147
|
-
versionDrift: driftItems.filter((d) => d.driftReasons.includes("spec_version_drift")).length,
|
|
148
|
-
refDrift: driftItems.filter((d) => d.driftReasons.includes("spec_ref_drift")).length,
|
|
149
|
-
missingBindings: driftItems.filter((d) => d.driftReasons.includes("task_spec_ref_missing") ||
|
|
150
|
-
d.driftReasons.includes("task_spec_version_missing")).length,
|
|
151
|
-
missingSpecs: driftItems.filter((d) => d.driftReasons.includes("spec_missing")).length,
|
|
152
|
-
};
|
|
153
|
-
if (options.json) {
|
|
154
|
-
console.log(JSON.stringify({ summary, items: driftItems }, null, 2));
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
console.log("Spec Drift Check");
|
|
158
|
-
console.log(`Checked tasks: ${summary.checkedTasks}`);
|
|
159
|
-
console.log(`Drifted tasks: ${summary.driftedTasks}`);
|
|
160
|
-
console.log(`Version drift: ${summary.versionDrift}`);
|
|
161
|
-
console.log(`Ref drift: ${summary.refDrift}`);
|
|
162
|
-
console.log(`Missing bindings: ${summary.missingBindings}`);
|
|
163
|
-
console.log(`Missing specs: ${summary.missingSpecs}`);
|
|
164
|
-
if (options.verbose && driftItems.length > 0) {
|
|
165
|
-
console.log("\nDrift items:");
|
|
166
|
-
for (const item of driftItems) {
|
|
167
|
-
console.log(`- ${item.taskTitle} (${item.taskId})`);
|
|
168
|
-
console.log(` Spec: ${item.specTitle} (${item.specificationId})`);
|
|
169
|
-
console.log(` Version: ${item.taskSpecVersion ?? "<missing>"} -> ${item.currentSpecVersion ?? "<missing>"}`);
|
|
170
|
-
console.log(` Ref: ${item.taskSpecRef ?? "<missing>"} -> ${item.currentSpecRef ?? "<missing>"}`);
|
|
171
|
-
console.log(` Reasons: ${item.driftReasons.join(", ")}`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (driftItems.length > 0) {
|
|
176
|
-
return 2;
|
|
177
|
-
}
|
|
178
|
-
return 0;
|
|
179
|
-
}
|
|
180
|
-
finally {
|
|
181
|
-
await shared_1.AppDataSource.destroy();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
if (require.main === module) {
|
|
185
|
-
void (0, cli_runtime_1.runCliMain)(main, { errorPrefix: "spec-drift-check failed" });
|
|
186
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* spec-frontmatter CLI Tool
|
|
4
|
-
*
|
|
5
|
-
* Parse and validate YAML frontmatter from a specification file.
|
|
6
|
-
*
|
|
7
|
-
* Usage: spekn spec frontmatter <spec-file-path> [OPTIONS]
|
|
8
|
-
* Example: spekn spec frontmatter specs/SPECIFICATION.md
|
|
9
|
-
*/
|
|
10
|
-
declare function main(argv?: string[]): number;
|
|
11
|
-
export { main };
|