@kntic/kntic 0.4.1 → 0.4.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kntic/kntic",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "author": "Thomas Robak <contact@kntic.ai> (https://kntic.ai)",
5
5
  "description": "KNTIC CLI — bootstrap and manage KNTIC projects",
6
6
  "main": "src/index.js",
package/src/cli.js CHANGED
@@ -16,7 +16,8 @@ if (!subcommand || subcommand === "usage") {
16
16
  });
17
17
  } else if (subcommand === "start") {
18
18
  try {
19
- commands.start();
19
+ const startOpts = { screen: args.includes("--screen") };
20
+ commands.start(startOpts);
20
21
  } catch (err) {
21
22
  console.error(`Error: ${err.message}`);
22
23
  process.exit(1);
@@ -29,7 +30,8 @@ if (!subcommand || subcommand === "usage") {
29
30
  process.exit(1);
30
31
  }
31
32
  } else if (subcommand === "update") {
32
- commands.update().catch((err) => {
33
+ const updateOpts = { libOnly: args.includes("--lib-only") };
34
+ commands.update(updateOpts).catch((err) => {
33
35
  console.error(`Error: ${err.message}`);
34
36
  process.exit(1);
35
37
  });
@@ -30,16 +30,19 @@ function getScreenName() {
30
30
  return basename(process.cwd());
31
31
  }
32
32
 
33
- function start() {
33
+ function start(options = {}) {
34
34
  const composeCmd = "docker compose -f kntic.yml --env-file .kntic.env up --build";
35
+ const useScreen = !!options.screen;
35
36
 
36
- if (isScreenAvailable() && !isInsideScreen()) {
37
+ if (useScreen && isScreenAvailable() && !isInsideScreen()) {
37
38
  const screenName = getScreenName();
38
39
  console.log(`Starting KNTIC services in screen session "${screenName}"…`);
39
40
  execSync(`screen -S ${screenName} ${composeCmd}`, { stdio: "inherit" });
40
41
  } else {
41
- if (isInsideScreen()) {
42
+ if (useScreen && isInsideScreen()) {
42
43
  console.log("Already inside a screen session, skipping screen wrapper.");
44
+ } else if (useScreen && !isScreenAvailable()) {
45
+ console.log("screen is not available, starting without screen wrapper.");
43
46
  }
44
47
  console.log("Starting KNTIC services…");
45
48
  execSync(composeCmd, { stdio: "inherit" });
@@ -182,7 +182,35 @@ function extractLibOnly(tarball, destDir) {
182
182
  );
183
183
  }
184
184
 
185
- async function update() {
185
+ /**
186
+ * Extract .kntic/lib/** and .kntic/adrs/** from a tarball into the destination
187
+ * directory. Clears both directories first, then extracts fresh content.
188
+ *
189
+ * @param {string} tarball – path to the .tar.gz file
190
+ * @param {string} destDir – target directory (usually ".")
191
+ */
192
+ function extractUpdate(tarball, destDir) {
193
+ const libDir = path.join(destDir, ".kntic", "lib");
194
+ const adrsDir = path.join(destDir, ".kntic", "adrs");
195
+
196
+ // Ensure target directories exist
197
+ fs.mkdirSync(libDir, { recursive: true });
198
+ fs.mkdirSync(adrsDir, { recursive: true });
199
+
200
+ // Clear existing contents
201
+ clearDirectory(libDir);
202
+ clearDirectory(adrsDir);
203
+
204
+ // Extract both .kntic/lib/ and .kntic/adrs/ from the archive.
205
+ execSync(
206
+ `tar xzf "${tarball}" -C "${destDir}" "./.kntic/lib/" "./.kntic/adrs/"`,
207
+ { stdio: "pipe" }
208
+ );
209
+ }
210
+
211
+ async function update(options = {}) {
212
+ const libOnly = options.libOnly || false;
213
+
186
214
  // Resolve current version from the artifact metadata file
187
215
  const artifactFilename = await fetchText(BOOTSTRAP_ARTIFACT_URL);
188
216
  const version = extractVersion(artifactFilename);
@@ -192,8 +220,13 @@ async function update() {
192
220
  console.log(`Downloading KNTIC bootstrap archive… (v${version})`);
193
221
  await download(BOOTSTRAP_URL, tmpFile);
194
222
 
195
- console.log("Updating .kntic/lib …");
196
- extractLibOnly(tmpFile, ".");
223
+ if (libOnly) {
224
+ console.log("Updating .kntic/lib …");
225
+ extractLibOnly(tmpFile, ".");
226
+ } else {
227
+ console.log("Updating .kntic/lib and .kntic/adrs …");
228
+ extractUpdate(tmpFile, ".");
229
+ }
197
230
 
198
231
  // Update KNTIC_VERSION in .kntic.env
199
232
  updateEnvVersion(version);
@@ -202,11 +235,16 @@ async function update() {
202
235
  fs.unlinkSync(tmpFile);
203
236
 
204
237
  console.log(`@kntic/kntic@${version}`);
205
- console.log("Done. .kntic/lib updated successfully.");
238
+ if (libOnly) {
239
+ console.log("Done. .kntic/lib updated successfully.");
240
+ } else {
241
+ console.log("Done. .kntic/lib and .kntic/adrs updated successfully.");
242
+ }
206
243
  }
207
244
 
208
245
  module.exports = update;
209
246
  module.exports.extractLibOnly = extractLibOnly;
247
+ module.exports.extractUpdate = extractUpdate;
210
248
  module.exports.extractVersion = extractVersion;
211
249
  module.exports.clearDirectory = clearDirectory;
212
250
  module.exports.updateEnvVersion = updateEnvVersion;
@@ -7,7 +7,7 @@ const path = require("path");
7
7
  const os = require("os");
8
8
  const { execSync } = require("child_process");
9
9
 
10
- const { extractLibOnly, extractVersion, clearDirectory, updateEnvVersion } = require("./update");
10
+ const { extractLibOnly, extractUpdate, extractVersion, clearDirectory, updateEnvVersion } = require("./update");
11
11
 
12
12
  /**
13
13
  * Helper — create a tar.gz archive in `tmpDir` containing the given files.
@@ -202,6 +202,121 @@ describe("extractLibOnly", () => {
202
202
  });
203
203
  });
204
204
 
205
+ describe("extractUpdate", () => {
206
+ let tmpDir;
207
+ let destDir;
208
+
209
+ beforeEach(() => {
210
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kntic-test-extractupdate-"));
211
+ destDir = path.join(tmpDir, "dest");
212
+ fs.mkdirSync(destDir, { recursive: true });
213
+ });
214
+
215
+ afterEach(() => {
216
+ fs.rmSync(tmpDir, { recursive: true, force: true });
217
+ });
218
+
219
+ it("extracts both .kntic/lib and .kntic/adrs from the archive", () => {
220
+ const tarball = createTarball(tmpDir, {
221
+ ".kntic/lib/orchestrator.py": "# orchestrator\n",
222
+ ".kntic/lib/skills/validator.py": "# validator\n",
223
+ ".kntic/adrs/ADR-001.md": "# ADR 001\n",
224
+ ".kntic/adrs/ADR-002.md": "# ADR 002\n",
225
+ ".kntic/MEMORY.MD": "# Memory\n",
226
+ "README.md": "# Hello\n",
227
+ });
228
+
229
+ extractUpdate(tarball, destDir);
230
+
231
+ // lib files must be extracted
232
+ assert.equal(
233
+ fs.readFileSync(path.join(destDir, ".kntic", "lib", "orchestrator.py"), "utf8"),
234
+ "# orchestrator\n"
235
+ );
236
+ assert.equal(
237
+ fs.readFileSync(path.join(destDir, ".kntic", "lib", "skills", "validator.py"), "utf8"),
238
+ "# validator\n"
239
+ );
240
+
241
+ // adrs files must be extracted
242
+ assert.equal(
243
+ fs.readFileSync(path.join(destDir, ".kntic", "adrs", "ADR-001.md"), "utf8"),
244
+ "# ADR 001\n"
245
+ );
246
+ assert.equal(
247
+ fs.readFileSync(path.join(destDir, ".kntic", "adrs", "ADR-002.md"), "utf8"),
248
+ "# ADR 002\n"
249
+ );
250
+
251
+ // Files outside lib/adrs must NOT be extracted
252
+ assert.ok(
253
+ !fs.existsSync(path.join(destDir, ".kntic", "MEMORY.MD")),
254
+ "MEMORY.MD must not be extracted"
255
+ );
256
+ assert.ok(
257
+ !fs.existsSync(path.join(destDir, "README.md")),
258
+ "README.md must not be extracted"
259
+ );
260
+ });
261
+
262
+ it("replaces existing .kntic/lib and .kntic/adrs content", () => {
263
+ const libDir = path.join(destDir, ".kntic", "lib");
264
+ const adrsDir = path.join(destDir, ".kntic", "adrs");
265
+ fs.mkdirSync(libDir, { recursive: true });
266
+ fs.mkdirSync(adrsDir, { recursive: true });
267
+ fs.writeFileSync(path.join(libDir, "old_lib.py"), "# old lib\n");
268
+ fs.writeFileSync(path.join(adrsDir, "ADR-OLD.md"), "# old adr\n");
269
+
270
+ const tarball = createTarball(tmpDir, {
271
+ ".kntic/lib/new_lib.py": "# new lib\n",
272
+ ".kntic/adrs/ADR-NEW.md": "# new adr\n",
273
+ });
274
+
275
+ extractUpdate(tarball, destDir);
276
+
277
+ // Old files must be gone
278
+ assert.ok(!fs.existsSync(path.join(libDir, "old_lib.py")), "old lib file must be removed");
279
+ assert.ok(!fs.existsSync(path.join(adrsDir, "ADR-OLD.md")), "old adr file must be removed");
280
+
281
+ // New files must be present
282
+ assert.equal(
283
+ fs.readFileSync(path.join(libDir, "new_lib.py"), "utf8"),
284
+ "# new lib\n"
285
+ );
286
+ assert.equal(
287
+ fs.readFileSync(path.join(adrsDir, "ADR-NEW.md"), "utf8"),
288
+ "# new adr\n"
289
+ );
290
+ });
291
+
292
+ it("preserves files outside .kntic/lib and .kntic/adrs", () => {
293
+ const knticDir = path.join(destDir, ".kntic");
294
+ fs.mkdirSync(knticDir, { recursive: true });
295
+ fs.writeFileSync(path.join(knticDir, "MEMORY.MD"), "# My memory\n");
296
+ fs.writeFileSync(path.join(destDir, "README.md"), "# My readme\n");
297
+
298
+ const tarball = createTarball(tmpDir, {
299
+ ".kntic/lib/orchestrator.py": "# orchestrator\n",
300
+ ".kntic/adrs/ADR-001.md": "# ADR 001\n",
301
+ ".kntic/MEMORY.MD": "# Archive memory\n",
302
+ "README.md": "# Archive readme\n",
303
+ });
304
+
305
+ extractUpdate(tarball, destDir);
306
+
307
+ assert.equal(
308
+ fs.readFileSync(path.join(knticDir, "MEMORY.MD"), "utf8"),
309
+ "# My memory\n",
310
+ "MEMORY.MD must not be overwritten"
311
+ );
312
+ assert.equal(
313
+ fs.readFileSync(path.join(destDir, "README.md"), "utf8"),
314
+ "# My readme\n",
315
+ "README.md must not be overwritten"
316
+ );
317
+ });
318
+ });
319
+
205
320
  describe("updateEnvVersion", () => {
206
321
  let tmpDir;
207
322
 
@@ -7,8 +7,10 @@ function usage() {
7
7
  console.log(" usage List all available sub-commands");
8
8
  console.log(" init Download and extract the KNTIC bootstrap template into the current directory");
9
9
  console.log(" start Build and start KNTIC services via docker compose (uses kntic.yml + .kntic.env)");
10
+ console.log(" --screen Run inside a GNU screen session");
10
11
  console.log(" stop Stop KNTIC services via docker compose");
11
- console.log(" update Download the latest KNTIC bootstrap and update .kntic/lib only");
12
+ console.log(" update Download the latest KNTIC bootstrap and update .kntic/lib and .kntic/adrs");
13
+ console.log(" --lib-only Update only .kntic/lib (skip .kntic/adrs)");
12
14
  console.log("");
13
15
  }
14
16