@blkc-code/cli 0.1.0 → 0.1.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 (40) hide show
  1. package/bin/blkc.js +24 -0
  2. package/dist/cli.js +24 -0
  3. package/dist/commands/block.d.ts +12 -1
  4. package/dist/commands/block.d.ts.map +1 -0
  5. package/dist/commands/block.js +180 -268
  6. package/dist/commands/block.js.map +1 -1
  7. package/dist/commands/circuit.d.ts +12 -1
  8. package/dist/commands/circuit.d.ts.map +1 -0
  9. package/dist/commands/circuit.js +146 -152
  10. package/dist/commands/circuit.js.map +1 -1
  11. package/dist/commands/deploy.d.ts +14 -0
  12. package/dist/commands/deploy.d.ts.map +1 -0
  13. package/dist/commands/deploy.js +332 -0
  14. package/dist/commands/deploy.js.map +1 -0
  15. package/dist/commands/kit.d.ts +12 -1
  16. package/dist/commands/kit.d.ts.map +1 -0
  17. package/dist/commands/kit.js +137 -160
  18. package/dist/commands/kit.js.map +1 -1
  19. package/dist/commands/logbook.d.ts +12 -1
  20. package/dist/commands/logbook.d.ts.map +1 -0
  21. package/dist/commands/logbook.js +150 -136
  22. package/dist/commands/logbook.js.map +1 -1
  23. package/dist/index.d.ts +19 -2
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +48 -28
  26. package/dist/index.js.map +1 -1
  27. package/package.json +58 -26
  28. package/src/__tests__/smoke.test.ts +8 -0
  29. package/src/commands/block.ts +219 -0
  30. package/src/commands/circuit.ts +182 -0
  31. package/src/commands/deploy.ts +373 -0
  32. package/src/commands/kit.ts +173 -0
  33. package/src/commands/logbook.ts +194 -0
  34. package/src/index.ts +70 -0
  35. package/dist/utils/filesystem.d.ts +0 -5
  36. package/dist/utils/filesystem.js +0 -21
  37. package/dist/utils/filesystem.js.map +0 -1
  38. package/dist/utils/github.d.ts +0 -23
  39. package/dist/utils/github.js +0 -66
  40. package/dist/utils/github.js.map +0 -1
@@ -0,0 +1,194 @@
1
+ /**
2
+ * BLKC CLI - Logbook Commands
3
+ *
4
+ * Commands for working with BLKC Logbook.
5
+ *
6
+ * @package @blkc-code/cli
7
+ * @license Apache-2.0
8
+ * @author Black Code Enterprises LLC
9
+ */
10
+
11
+ import { Command } from "commander";
12
+ import chalk from "chalk";
13
+ import ora from "ora";
14
+ import { stat } from "node:fs/promises";
15
+ import path from "node:path";
16
+ import { Logbook, verifyLogbook, readLogbook, LogbookVerificationResult } from "@blkc-code/logbook";
17
+
18
+ // ============================================================================
19
+ // Command: logbook verify
20
+ // ============================================================================
21
+
22
+ function setupVerifyCommand(program: Command): void {
23
+ program
24
+ .command("verify <file-path>")
25
+ .description("Verify Logbook chain integrity")
26
+ .option("--algorithm <algo>", "Hash algorithm", "sha256")
27
+ .action(async (filePath: string, options: { algorithm: string }) => {
28
+ const spinner = ora("Verifying Logbook...").start();
29
+
30
+ try {
31
+ const absolutePath = path.resolve(process.cwd(), filePath);
32
+
33
+ // Check if file exists
34
+ try {
35
+ await stat(absolutePath);
36
+ } catch {
37
+ spinner.fail(`File not found: ${absolutePath}`);
38
+ process.exit(1);
39
+ }
40
+
41
+ const result: LogbookVerificationResult = await verifyLogbook(absolutePath, options.algorithm);
42
+
43
+ if (result.valid) {
44
+ spinner.succeed("Logbook verification passed");
45
+ console.log(chalk.green("\n✓ Chain is valid"));
46
+ console.log(chalk.gray(` Entries: ${result.entries.length}`));
47
+ console.log(chalk.gray(` Last hash: ${result.lastHash?.substring(0, 16)}...`));
48
+ } else {
49
+ spinner.fail("Logbook verification failed");
50
+ console.error(chalk.red("\n✗ Chain is invalid"));
51
+ console.error(chalk.red(` Issues: ${result.issues.length}`));
52
+
53
+ for (const issue of result.issues) {
54
+ console.error(chalk.red(`\n Entry ${issue.index}:`));
55
+ console.error(chalk.red(` ${issue.message}`));
56
+ }
57
+
58
+ process.exit(1);
59
+ }
60
+ } catch (error) {
61
+ spinner.fail(`Failed to verify: ${error}`);
62
+ process.exit(1);
63
+ }
64
+ });
65
+ }
66
+
67
+ // ============================================================================
68
+ // Command: logbook check
69
+ // ============================================================================
70
+
71
+ function setupCheckCommand(program: Command): void {
72
+ program
73
+ .command("check <file-path>")
74
+ .description("Check Logbook for issues (alias for verify)")
75
+ .action(async (filePath: string) => {
76
+ // Delegate to verify
77
+ const verifyCmd = program.commands.find((c) => c.name() === "verify");
78
+ if (verifyCmd) {
79
+ await (verifyCmd.action as unknown as (filePath: string, options: Record<string, unknown>) => Promise<void>)(filePath, {});
80
+ }
81
+ });
82
+ }
83
+
84
+ // ============================================================================
85
+ // Command: logbook info
86
+ // ============================================================================
87
+
88
+ function setupInfoCommand(program: Command): void {
89
+ program
90
+ .command("info <file-path>")
91
+ .description("Show Logbook information")
92
+ .action(async (filePath: string) => {
93
+ const spinner = ora("Loading Logbook info...").start();
94
+
95
+ try {
96
+ const absolutePath = path.resolve(process.cwd(), filePath);
97
+
98
+ // Check if file exists
99
+ try {
100
+ await stat(absolutePath);
101
+ } catch {
102
+ spinner.fail(`File not found: ${absolutePath}`);
103
+ process.exit(1);
104
+ }
105
+
106
+ const logbook = new Logbook({ filePath: absolutePath });
107
+ const info = await logbook.info();
108
+
109
+ spinner.succeed(`Logbook: ${info.filePath}`);
110
+
111
+ console.log(chalk.bold("\nLogbook Information"));
112
+ console.log(chalk.gray("─".repeat(40)));
113
+ console.log(chalk.gray(`File: ${info.filePath}`));
114
+ console.log(chalk.gray(`Entry count: ${info.entryCount}`));
115
+
116
+ if (info.firstEntry) {
117
+ console.log(chalk.gray(`\nFirst Entry:`));
118
+ console.log(chalk.gray(` Timestamp: ${info.firstEntry.timestamp}`));
119
+ console.log(chalk.gray(` Actor: ${info.firstEntry.actor.name}`));
120
+ console.log(chalk.gray(` Event: ${info.firstEntry.event}`));
121
+ }
122
+
123
+ if (info.lastEntry) {
124
+ console.log(chalk.gray(`\nLast Entry:`));
125
+ console.log(chalk.gray(` Timestamp: ${info.lastEntry.timestamp}`));
126
+ console.log(chalk.gray(` Actor: ${info.lastEntry.actor.name}`));
127
+ console.log(chalk.gray(` Event: ${info.lastEntry.event}`));
128
+ console.log(chalk.gray(` Hash: ${info.lastHash?.substring(0, 16)}...`));
129
+ }
130
+ } catch (error) {
131
+ spinner.fail(`Failed to show info: ${error}`);
132
+ process.exit(1);
133
+ }
134
+ });
135
+ }
136
+
137
+ // ============================================================================
138
+ // Command: logbook tail
139
+ // ============================================================================
140
+
141
+ function setupTailCommand(program: Command): void {
142
+ program
143
+ .command("tail <file-path>")
144
+ .description("Show the last N entries from Logbook")
145
+ .option("--lines <n>", "Number of lines to show", "10")
146
+ .action(async (filePath: string, options: { lines: string }) => {
147
+ const spinner = ora("Reading Logbook...").start();
148
+
149
+ try {
150
+ const absolutePath = path.resolve(process.cwd(), filePath);
151
+ const lines = parseInt(options.lines, 10) || 10;
152
+
153
+ const entries = await readLogbook(absolutePath);
154
+ const tailEntries = entries.slice(-lines);
155
+
156
+ spinner.succeed(`Showing last ${tailEntries.length} entries`);
157
+
158
+ for (const entry of tailEntries) {
159
+ console.log(chalk.gray(`\n[${entry.timestamp}] ${entry.event}`));
160
+ console.log(chalk.gray(` Actor: ${entry.actor.name}`));
161
+ if (entry.subject) {
162
+ console.log(chalk.gray(` Subject: ${entry.subject.type}:${entry.subject.id}`));
163
+ }
164
+ if (entry.hash) {
165
+ console.log(chalk.gray(` Hash: ${entry.hash.substring(0, 16)}...`));
166
+ }
167
+ }
168
+
169
+ if (tailEntries.length === 0) {
170
+ console.log(chalk.gray("\nNo entries found"));
171
+ }
172
+ } catch (error) {
173
+ spinner.fail(`Failed to tail: ${error}`);
174
+ process.exit(1);
175
+ }
176
+ });
177
+ }
178
+
179
+ // ============================================================================
180
+ // Register Commands
181
+ // ============================================================================
182
+
183
+ /** Register all logbook commands with the program */
184
+ export function registerLogbookCommands(program: Command): void {
185
+ const logbookProgram = program
186
+ .command("logbook")
187
+ .description("Logbook commands")
188
+ .alias("l");
189
+
190
+ setupVerifyCommand(logbookProgram);
191
+ setupCheckCommand(logbookProgram);
192
+ setupInfoCommand(logbookProgram);
193
+ setupTailCommand(logbookProgram);
194
+ }
package/src/index.ts ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * BLKC CLI - Developer tools for BLKC
3
+ *
4
+ * This package provides command-line tools for working with BLKC Blocks, Kits, Circuits, and Logbook.
5
+ *
6
+ * @package @blkc-code/cli
7
+ * @version 0.1.0
8
+ * @license Apache-2.0
9
+ * @author Black Code Enterprises LLC
10
+ */
11
+
12
+ import { Command } from "commander";
13
+ import { registerBlockCommands } from "./commands/block.js";
14
+ import { registerKitCommands } from "./commands/kit.js";
15
+ import { registerCircuitCommands } from "./commands/circuit.js";
16
+ import { registerLogbookCommands } from "./commands/logbook.js";
17
+ import { registerDeployCommands } from "./commands/deploy.js";
18
+
19
+ /**
20
+ * The main BLKC CLI program.
21
+ */
22
+ export const program = new Command();
23
+
24
+ // Configure the CLI
25
+ program
26
+ .name("blkc")
27
+ .description("BLKC developer CLI - Tools for working with Blocks, Kits, Circuits, and Logbook")
28
+ .version("0.1.0");
29
+
30
+ // Show help after errors
31
+ program.showHelpAfterError();
32
+
33
+ // Configure output to prevent duplicate errors
34
+ program.configureOutput({
35
+ outputError: (str) => {
36
+ // Only print the error if it's not already been handled
37
+ if (str && !str.includes("error:")) {
38
+ console.error(str.trim());
39
+ }
40
+ },
41
+ });
42
+
43
+ // Register all command groups
44
+ registerBlockCommands(program);
45
+ registerKitCommands(program);
46
+ registerCircuitCommands(program);
47
+ registerLogbookCommands(program);
48
+ registerDeployCommands(program);
49
+
50
+ // Add a global --help flag
51
+ program.option("--debug", "Enable debug mode");
52
+
53
+ /**
54
+ * Run the CLI.
55
+ */
56
+ export async function run(): Promise<void> {
57
+ try {
58
+ await program.parseAsync(process.argv);
59
+ } catch (error) {
60
+ if (error instanceof Error) {
61
+ console.error(`Error: ${error.message}`);
62
+ } else {
63
+ console.error("An unknown error occurred");
64
+ }
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ // Export for bin usage
70
+ export default program;
@@ -1,5 +0,0 @@
1
- export declare const workspacePath: (...segments: string[]) => string;
2
- export declare const ensureDirectory: (relativePath: string) => Promise<void>;
3
- export declare const readJsonFile: <T>(relativePath: string) => Promise<T>;
4
- export declare const writeJsonFile: (relativePath: string, payload: unknown) => Promise<void>;
5
- export declare const exists: (relativePath: string) => Promise<boolean>;
@@ -1,21 +0,0 @@
1
- import { readFile, writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { ensureDir, pathExists } from "fs-extra";
4
- export const workspacePath = (...segments) => path.resolve(process.cwd(), ...segments);
5
- export const ensureDirectory = async (relativePath) => {
6
- await ensureDir(workspacePath(relativePath));
7
- };
8
- export const readJsonFile = async (relativePath) => {
9
- const filePath = workspacePath(relativePath);
10
- const contents = await readFile(filePath, { encoding: "utf8" });
11
- // Some manifests were written by PowerShell with a UTF-8 BOM, which JSON.parse rejects.
12
- return JSON.parse(contents.replace(/^\uFEFF/, ""));
13
- };
14
- export const writeJsonFile = async (relativePath, payload) => {
15
- const filePath = workspacePath(relativePath);
16
- await ensureDir(path.dirname(filePath));
17
- const formatted = `${JSON.stringify(payload, null, 2)}\n`;
18
- await writeFile(filePath, formatted, { encoding: "utf8" });
19
- };
20
- export const exists = async (relativePath) => pathExists(workspacePath(relativePath));
21
- //# sourceMappingURL=filesystem.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"filesystem.js","sourceRoot":"","sources":["../../src/utils/filesystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAG,QAAkB,EAAE,EAAE,CACrD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,QAAQ,CAAC,CAAC;AAE3C,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,YAAoB,EAAE,EAAE;IAC5D,MAAM,SAAS,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC;AAC/C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAK,YAAoB,EAAc,EAAE;IACxE,MAAM,QAAQ,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAChE,wFAAwF;IACxF,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAM,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,YAAoB,EAAE,OAAgB,EAAE,EAAE;IAC5E,MAAM,QAAQ,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC;IAC7C,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IAC1D,MAAM,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAC7D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG,KAAK,EAAE,YAAoB,EAAE,EAAE,CACnD,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC"}
@@ -1,23 +0,0 @@
1
- export type CreateRepoOptions = {
2
- org?: string;
3
- name: string;
4
- description?: string;
5
- private?: boolean;
6
- token?: string;
7
- };
8
- export declare const ensureGithubRepo: (opts: CreateRepoOptions) => Promise<{
9
- created: boolean;
10
- message: string;
11
- repo?: undefined;
12
- details?: undefined;
13
- } | {
14
- created: boolean;
15
- message: string;
16
- repo: any;
17
- details?: undefined;
18
- } | {
19
- created: boolean;
20
- message: any;
21
- details: any;
22
- repo?: undefined;
23
- }>;
@@ -1,66 +0,0 @@
1
- import https from "node:https";
2
- import { URL } from "node:url";
3
- const requestJson = (url, token, body) => new Promise((resolve, reject) => {
4
- try {
5
- const u = new URL(url);
6
- const data = JSON.stringify(body);
7
- const headers = {
8
- Authorization: token ? `token ${token}` : "",
9
- "Content-Type": "application/json",
10
- Accept: "application/vnd.github+json",
11
- "User-Agent": "blkc-cli",
12
- "Content-Length": Buffer.byteLength(data).toString(),
13
- };
14
- const opts = {
15
- method: "POST",
16
- hostname: u.hostname,
17
- path: u.pathname + (u.search ?? ""),
18
- headers,
19
- };
20
- const req = https.request(opts, (res) => {
21
- let raw = "";
22
- res.setEncoding("utf8");
23
- res.on("data", (chunk) => (raw += chunk));
24
- res.on("end", () => {
25
- try {
26
- const parsed = raw ? JSON.parse(raw) : {};
27
- resolve({ statusCode: res.statusCode, body: parsed });
28
- }
29
- catch (err) {
30
- reject(err);
31
- }
32
- });
33
- });
34
- req.on("error", (err) => reject(err));
35
- req.write(data);
36
- req.end();
37
- }
38
- catch (error) {
39
- reject(error);
40
- }
41
- });
42
- export const ensureGithubRepo = async (opts) => {
43
- if (!opts.token) {
44
- return { created: false, message: "No GITHUB_TOKEN provided; skipping GitHub repo creation." };
45
- }
46
- const url = opts.org
47
- ? `https://api.github.com/orgs/${opts.org}/repos`
48
- : `https://api.github.com/user/repos`;
49
- const body = {
50
- name: opts.name,
51
- description: opts.description ?? "",
52
- private: !!opts.private,
53
- auto_init: true,
54
- };
55
- try {
56
- const res = await requestJson(url, opts.token, body);
57
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
58
- return { created: true, message: "Repository created", repo: res.body.html_url };
59
- }
60
- return { created: false, message: res.body?.message || "Failed to create repo", details: res.body };
61
- }
62
- catch (error) {
63
- return { created: false, message: error.message };
64
- }
65
- };
66
- //# sourceMappingURL=github.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/utils/github.ts"],"names":[],"mappings":"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"}