@ikenga/cli 0.2.0 → 0.3.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 (3) hide show
  1. package/README.md +40 -2
  2. package/dist/index.js +308 -39
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # @ikenga/cli
2
2
 
3
- Command-line tool for managing Ikenga pkgs.
3
+ [![Version](https://img.shields.io/badge/version-v0.3.0-blue.svg)](https://github.com/Royalti-io/ikenga-cli/releases)
4
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
5
+
6
+ > `ikenga` — the disk-side package manager for the Ikenga workspace. Install, update, and
7
+ > hot-mount pkgs.
8
+
9
+ ## What it is
10
+
11
+ `ikenga` manages packages on disk: what's installed, what's available in the registry, and
12
+ the dev loop for authoring your own. It's one of two Ikenga CLIs — the **disk-side** one.
13
+ (The other, [`iyke`](https://github.com/Royalti-io/iyke-cli), drives a *running* shell.
14
+ They share no code.)
4
15
 
5
16
  ## Install
6
17
 
@@ -26,11 +37,38 @@ ikenga update <pkg> # update one
26
37
  ikenga update --all # update everything outdated
27
38
  ikenga remove com.ikenga.hello # by manifest id, or...
28
39
  ikenga remove @ikenga/pkg-hello # ...by npm name
40
+ ikenga dev ./my-pkg # hot-mount into running shell
41
+ ```
42
+
43
+ `list / add / update / remove` mutate the shell's pkgs directory (overridable with `IKENGA_APP_DATA_DIR`); the shell registers them on next boot.
44
+
45
+ ### `ikenga dev <path>` — hot-mount for development
46
+
47
+ Different shape: `dev` talks to a **running** shell over its localhost iyke bridge instead of touching disk. Registers the pkg with `source.kind = "dev"` (auto-trusted, regardless of id namespace) and spawns a manifest watcher in the kernel so edits to `manifest.json` or any `restart_when_changed` glob trip an in-place reload — no shell restart.
48
+
49
+ Requires the shell to be running. The CLI discovers its port + bearer token from `control.json` in the shell's local data dir.
50
+
51
+ ```bash
52
+ ikenga dev /home/me/code/my-pkg
53
+ # → mounted as com.example.my-pkg v0.1.0
54
+ # → Routes: /pkg/com.example.my-pkg/
55
+ # Edit manifest.json or watched src/ files; reload fires automatically.
56
+ # Ctrl-C to unregister.
29
57
  ```
30
58
 
31
- Installs land in the shell's pkgs directory (overridable with `IKENGA_APP_DATA_DIR`). The shell registers them on next boot. The CLI does not currently talk to a running shell over IPC; that's a planned enhancement.
59
+ Iframe code changes flow through your dev server's HMR (Vite, Next, ); sidecar / MCP source edits respawn via the supervisor watcher; manifest edits trigger a full pkg reload that emits a `pkg-reloaded` event the shell's iframe + webview hosts listen for. See [`docs/pkg-patterns/07-dev-mode.md`](https://github.com/Royalti-io/ikenga/blob/main/docs/pkg-patterns/07-dev-mode.md) for the kernel semantics.
32
60
 
33
61
  ## Versioning
34
62
 
63
+ `v0.3.0` — adds `ikenga dev <path>` for hot-mounting pkgs into a running shell via the iyke localhost bridge. Watcher-driven manifest reload + clean `Ctrl-C` unregister. Requires the corresponding shell-side dev-mode kernel (lands in shell `v0.0.5+`).
35
64
  `v0.2.0` — JS-source npm distribution; requires Bun on `$PATH`.
36
65
  `v0.1.x` — bun-compiled standalone binaries (deprecated; available on the GitHub Releases page until the next archive sweep).
66
+
67
+ ## Links
68
+
69
+ - [`iyke-cli`](https://github.com/Royalti-io/iyke-cli) — the runtime controller (the *other* CLI)
70
+ - [`ikenga`](https://github.com/Royalti-io/ikenga) — the desktop shell
71
+
72
+ ## License
73
+
74
+ Apache-2.0 — see [`LICENSE`](LICENSE).
package/dist/index.js CHANGED
@@ -1,21 +1,17 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
  var __defProp = Object.defineProperty;
4
- var __returnValue = (v) => v;
5
- function __exportSetter(name, newValue) {
6
- this[name] = __returnValue.bind(null, newValue);
7
- }
8
4
  var __export = (target, all) => {
9
5
  for (var name in all)
10
6
  __defProp(target, name, {
11
7
  get: all[name],
12
8
  enumerable: true,
13
9
  configurable: true,
14
- set: __exportSetter.bind(all, name)
10
+ set: (newValue) => all[name] = () => newValue
15
11
  });
16
12
  };
17
13
 
18
- // node_modules/zod/v3/external.js
14
+ // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
19
15
  var exports_external = {};
20
16
  __export(exports_external, {
21
17
  void: () => voidType,
@@ -127,7 +123,7 @@ __export(exports_external, {
127
123
  BRAND: () => BRAND
128
124
  });
129
125
 
130
- // node_modules/zod/v3/helpers/util.js
126
+ // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
131
127
  var util;
132
128
  (function(util2) {
133
129
  util2.assertEqual = (_) => {};
@@ -258,7 +254,7 @@ var getParsedType = (data) => {
258
254
  }
259
255
  };
260
256
 
261
- // node_modules/zod/v3/ZodError.js
257
+ // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js
262
258
  var ZodIssueCode = util.arrayToEnum([
263
259
  "invalid_type",
264
260
  "invalid_literal",
@@ -377,7 +373,7 @@ ZodError.create = (issues) => {
377
373
  return error;
378
374
  };
379
375
 
380
- // node_modules/zod/v3/locales/en.js
376
+ // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js
381
377
  var errorMap = (issue, _ctx) => {
382
378
  let message;
383
379
  switch (issue.code) {
@@ -480,7 +476,7 @@ var errorMap = (issue, _ctx) => {
480
476
  };
481
477
  var en_default = errorMap;
482
478
 
483
- // node_modules/zod/v3/errors.js
479
+ // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js
484
480
  var overrideErrorMap = en_default;
485
481
  function setErrorMap(map) {
486
482
  overrideErrorMap = map;
@@ -488,7 +484,7 @@ function setErrorMap(map) {
488
484
  function getErrorMap() {
489
485
  return overrideErrorMap;
490
486
  }
491
- // node_modules/zod/v3/helpers/parseUtil.js
487
+ // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
492
488
  var makeIssue = (params) => {
493
489
  const { data, path, errorMaps, issueData } = params;
494
490
  const fullPath = [...path, ...issueData.path || []];
@@ -594,14 +590,14 @@ var isAborted = (x) => x.status === "aborted";
594
590
  var isDirty = (x) => x.status === "dirty";
595
591
  var isValid = (x) => x.status === "valid";
596
592
  var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
597
- // node_modules/zod/v3/helpers/errorUtil.js
593
+ // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
598
594
  var errorUtil;
599
595
  (function(errorUtil2) {
600
596
  errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
601
597
  errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
602
598
  })(errorUtil || (errorUtil = {}));
603
599
 
604
- // node_modules/zod/v3/types.js
600
+ // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
605
601
  class ParseInputLazyPath {
606
602
  constructor(parent, value, path, key) {
607
603
  this._cachedPath = [];
@@ -3988,7 +3984,7 @@ var coerce = {
3988
3984
  date: (arg) => ZodDate.create({ ...arg, coerce: true })
3989
3985
  };
3990
3986
  var NEVER = INVALID;
3991
- // node_modules/@ikenga/contract/dist/engine.js
3987
+ // ../node_modules/.pnpm/@ikenga+contract@0.5.0/node_modules/@ikenga/contract/dist/engine.js
3992
3988
  var AgentCapabilitiesSchema = exports_external.object({
3993
3989
  streaming: exports_external.boolean(),
3994
3990
  toolUse: exports_external.boolean(),
@@ -4019,7 +4015,7 @@ var EngineProvidesSchema = exports_external.object({
4019
4015
  })
4020
4016
  });
4021
4017
 
4022
- // node_modules/@ikenga/contract/dist/manifest.js
4018
+ // ../node_modules/.pnpm/@ikenga+contract@0.5.0/node_modules/@ikenga/contract/dist/manifest.js
4023
4019
  var AuthorSchema = exports_external.object({
4024
4020
  name: exports_external.string(),
4025
4021
  key: exports_external.string().optional()
@@ -4134,7 +4130,7 @@ var ManifestSchema = exports_external.object({
4134
4130
  engine: EngineProvidesSchema.optional()
4135
4131
  });
4136
4132
 
4137
- // node_modules/@ikenga/contract/dist/registry.js
4133
+ // ../node_modules/.pnpm/@ikenga+contract@0.5.0/node_modules/@ikenga/contract/dist/registry.js
4138
4134
  var REGISTRY_SCHEMA_VERSION = 1;
4139
4135
  var PkgDepSchema = exports_external.object({
4140
4136
  name: exports_external.string(),
@@ -4174,7 +4170,7 @@ function pkgDetailPath(npmName) {
4174
4170
  return `pkgs/${pkgShortName(npmName)}.json`;
4175
4171
  }
4176
4172
 
4177
- // node_modules/@noble/ed25519/index.js
4173
+ // ../node_modules/.pnpm/@noble+ed25519@2.3.0/node_modules/@noble/ed25519/index.js
4178
4174
  /*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
4179
4175
  var ed25519_CURVE = {
4180
4176
  p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
@@ -4595,7 +4591,7 @@ var wNAF = (n) => {
4595
4591
  return { p, f };
4596
4592
  };
4597
4593
 
4598
- // node_modules/@noble/hashes/esm/utils.js
4594
+ // ../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/utils.js
4599
4595
  /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
4600
4596
  function isBytes2(a) {
4601
4597
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
@@ -4676,7 +4672,7 @@ function createOptHasher(hashCons) {
4676
4672
  return hashC;
4677
4673
  }
4678
4674
 
4679
- // node_modules/@noble/hashes/esm/_blake.js
4675
+ // ../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/_blake.js
4680
4676
  var BSIGMA = /* @__PURE__ */ Uint8Array.from([
4681
4677
  0,
4682
4678
  1,
@@ -4936,7 +4932,7 @@ var BSIGMA = /* @__PURE__ */ Uint8Array.from([
4936
4932
  9
4937
4933
  ]);
4938
4934
 
4939
- // node_modules/@noble/hashes/esm/_md.js
4935
+ // ../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/_md.js
4940
4936
  function setBigUint64(view, byteOffset, value, isLE2) {
4941
4937
  if (typeof view.setBigUint64 === "function")
4942
4938
  return view.setBigUint64(byteOffset, value, isLE2);
@@ -5058,7 +5054,7 @@ var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
5058
5054
  327033209
5059
5055
  ]);
5060
5056
 
5061
- // node_modules/@noble/hashes/esm/_u64.js
5057
+ // ../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/_u64.js
5062
5058
  var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
5063
5059
  var _32n = /* @__PURE__ */ BigInt(32);
5064
5060
  function fromBig(n, le = false) {
@@ -5095,7 +5091,7 @@ var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0
5095
5091
  var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
5096
5092
  var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
5097
5093
 
5098
- // node_modules/@noble/hashes/esm/blake2.js
5094
+ // ../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/blake2.js
5099
5095
  var B2B_IV = /* @__PURE__ */ Uint32Array.from([
5100
5096
  4089235720,
5101
5097
  1779033703,
@@ -5378,10 +5374,10 @@ class BLAKE2b extends BLAKE2 {
5378
5374
  }
5379
5375
  var blake2b = /* @__PURE__ */ createOptHasher((opts) => new BLAKE2b(opts));
5380
5376
 
5381
- // node_modules/@noble/hashes/esm/blake2b.js
5377
+ // ../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/blake2b.js
5382
5378
  var blake2b2 = blake2b;
5383
5379
 
5384
- // node_modules/@noble/hashes/esm/sha2.js
5380
+ // ../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/sha2.js
5385
5381
  var K512 = /* @__PURE__ */ (() => split([
5386
5382
  "0x428a2f98d728ae22",
5387
5383
  "0x7137449123ef65cd",
@@ -5580,10 +5576,10 @@ class SHA512 extends HashMD {
5580
5576
  }
5581
5577
  var sha512 = /* @__PURE__ */ createHasher(() => new SHA512);
5582
5578
 
5583
- // node_modules/@noble/hashes/esm/sha512.js
5579
+ // ../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/sha512.js
5584
5580
  var sha5122 = sha512;
5585
5581
 
5586
- // node_modules/@ikenga/registry-client/dist/minisign.js
5582
+ // ../node_modules/.pnpm/@ikenga+registry-client@0.1.0/node_modules/@ikenga/registry-client/dist/minisign.js
5587
5583
  etc.sha512Sync = (...m) => sha5122(etc.concatBytes(...m));
5588
5584
  var ALG_ED25519_LEGACY = new Uint8Array([69, 100]);
5589
5585
  var ALG_ED25519_PREHASHED = new Uint8Array([69, 68]);
@@ -5673,7 +5669,7 @@ function hex(bytes) {
5673
5669
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
5674
5670
  }
5675
5671
 
5676
- // node_modules/@ikenga/registry-client/dist/fetch.js
5672
+ // ../node_modules/.pnpm/@ikenga+registry-client@0.1.0/node_modules/@ikenga/registry-client/dist/fetch.js
5677
5673
  async function fetchIndex(opts) {
5678
5674
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
5679
5675
  if (!fetchImpl) {
@@ -5719,7 +5715,7 @@ async function fetchPkgDetail(opts) {
5719
5715
  const json = await res.json();
5720
5716
  return PkgDetailSchema.parse(json);
5721
5717
  }
5722
- // node_modules/@ikenga/registry-client/dist/resolve.js
5718
+ // ../node_modules/.pnpm/@ikenga+registry-client@0.1.0/node_modules/@ikenga/registry-client/dist/resolve.js
5723
5719
  async function resolveInstallPlan(opts) {
5724
5720
  const plan = [];
5725
5721
  const seen = new Map;
@@ -5827,7 +5823,7 @@ import {
5827
5823
  } from "fs";
5828
5824
  import { join as join2 } from "path";
5829
5825
 
5830
- // node_modules/tar/dist/esm/index.min.js
5826
+ // ../node_modules/.pnpm/tar@7.5.15/node_modules/tar/dist/esm/index.min.js
5831
5827
  import Vr from "events";
5832
5828
  import I2 from "fs";
5833
5829
  import { EventEmitter as Li } from "events";
@@ -9208,20 +9204,277 @@ function parseSpec(spec) {
9208
9204
  return { name: spec.slice(0, idx), version: spec.slice(idx + 1) || undefined };
9209
9205
  }
9210
9206
 
9211
- // src/lib/installed.ts
9212
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
9207
+ // src/commands/dev.ts
9208
+ import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
9209
+ import { resolve } from "path";
9210
+
9211
+ // src/lib/iyke-bridge.ts
9212
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync, unlinkSync } from "fs";
9213
+ import { homedir as homedir2, platform as platform2 } from "os";
9213
9214
  import { join as join3 } from "path";
9215
+ var APP_IDENTIFIER = "app.ikenga";
9216
+ var STALE_THRESHOLD_SECS = 5 * 60;
9217
+ function appLocalDataDir() {
9218
+ const override = process.env.IKENGA_APP_LOCAL_DATA_DIR;
9219
+ if (override)
9220
+ return override;
9221
+ const home = homedir2();
9222
+ switch (platform2()) {
9223
+ case "darwin":
9224
+ return join3(home, "Library", "Application Support", APP_IDENTIFIER);
9225
+ case "win32": {
9226
+ const local = process.env.LOCALAPPDATA;
9227
+ if (local)
9228
+ return join3(local, APP_IDENTIFIER);
9229
+ return join3(home, "AppData", "Local", APP_IDENTIFIER);
9230
+ }
9231
+ default: {
9232
+ const xdg = process.env.XDG_DATA_HOME;
9233
+ if (xdg)
9234
+ return join3(xdg, APP_IDENTIFIER);
9235
+ return join3(home, ".local", "share", APP_IDENTIFIER);
9236
+ }
9237
+ }
9238
+ }
9239
+ function controlPath() {
9240
+ return join3(appLocalDataDir(), "control.json");
9241
+ }
9242
+ function isPidAlive(pid) {
9243
+ if (platform2() === "win32")
9244
+ return true;
9245
+ try {
9246
+ process.kill(pid, 0);
9247
+ return true;
9248
+ } catch (e) {
9249
+ const err2 = e;
9250
+ return err2.code === "EPERM";
9251
+ }
9252
+ }
9253
+ function loadControl() {
9254
+ const path = controlPath();
9255
+ if (!existsSync2(path)) {
9256
+ return { kind: "missing" };
9257
+ }
9258
+ const raw = readFileSync2(path, "utf8");
9259
+ const cf = JSON.parse(raw);
9260
+ if (cf.schema_version !== 1) {
9261
+ throw new Error(`unsupported control.json schema_version: ${cf.schema_version} (CLI built for v1)`);
9262
+ }
9263
+ if (isPidAlive(cf.pid)) {
9264
+ return { kind: "ok", cf };
9265
+ }
9266
+ const nowMs = Date.now();
9267
+ const ageMs = Math.max(0, nowMs - cf.started_at_unix_ms);
9268
+ const ageSecs = Math.floor(ageMs / 1000);
9269
+ if (ageSecs >= STALE_THRESHOLD_SECS) {
9270
+ try {
9271
+ unlinkSync(path);
9272
+ } catch {}
9273
+ return { kind: "stale_removed" };
9274
+ }
9275
+ return { kind: "stale_young", age_secs: ageSecs };
9276
+ }
9277
+
9278
+ class IykeClient {
9279
+ base;
9280
+ token;
9281
+ constructor(cf) {
9282
+ this.base = `http://127.0.0.1:${cf.port}`;
9283
+ this.token = cf.token;
9284
+ }
9285
+ async post(path, body) {
9286
+ const res = await fetch(`${this.base}${path}`, {
9287
+ method: "POST",
9288
+ headers: {
9289
+ "Content-Type": "application/json",
9290
+ Authorization: `Bearer ${this.token}`
9291
+ },
9292
+ body: JSON.stringify(body)
9293
+ });
9294
+ if (!res.ok) {
9295
+ const text2 = await res.text().catch(() => "");
9296
+ throw new Error(`POST ${path} failed: ${res.status} ${text2}`);
9297
+ }
9298
+ const text = await res.text();
9299
+ return text ? JSON.parse(text) : {};
9300
+ }
9301
+ async get(path) {
9302
+ const res = await fetch(`${this.base}${path}`, {
9303
+ method: "GET",
9304
+ headers: { Authorization: `Bearer ${this.token}` }
9305
+ });
9306
+ if (!res.ok) {
9307
+ const text = await res.text().catch(() => "");
9308
+ throw new Error(`GET ${path} failed: ${res.status} ${text}`);
9309
+ }
9310
+ return await res.json();
9311
+ }
9312
+ }
9313
+ function connectOrThrow() {
9314
+ const outcome = loadControl();
9315
+ switch (outcome.kind) {
9316
+ case "ok":
9317
+ return new IykeClient(outcome.cf);
9318
+ case "missing":
9319
+ throw new Error(`Ikenga shell is not running.
9320
+ Expected control.json at ${controlPath()}
9321
+ Start the shell, then re-run this command.`);
9322
+ case "stale_removed":
9323
+ throw new Error(`Ikenga shell control file was stale (crashed shell) \u2014 cleaned up.
9324
+ Start the shell and re-run this command.`);
9325
+ case "stale_young":
9326
+ throw new Error(`Ikenga shell control file is ${outcome.age_secs}s old but the PID is dead.
9327
+ Likely a launch race. Retry in a moment or remove ${controlPath()} manually.`);
9328
+ }
9329
+ }
9330
+
9331
+ // src/commands/dev.ts
9332
+ async function devCommand(rawPath) {
9333
+ const path = resolve(rawPath);
9334
+ if (!existsSync3(path)) {
9335
+ process.stderr.write(`error: ${path} does not exist
9336
+ `);
9337
+ return 1;
9338
+ }
9339
+ if (!statSync2(path).isDirectory()) {
9340
+ process.stderr.write(`error: ${path} is not a directory
9341
+ `);
9342
+ return 1;
9343
+ }
9344
+ const manifestPath = `${path}/manifest.json`;
9345
+ if (!existsSync3(manifestPath)) {
9346
+ process.stderr.write(`error: no manifest.json at ${manifestPath}
9347
+ `);
9348
+ return 1;
9349
+ }
9350
+ let manifest;
9351
+ try {
9352
+ manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
9353
+ } catch (err2) {
9354
+ process.stderr.write(`error: failed to parse manifest.json: ${err2.message}
9355
+ `);
9356
+ return 1;
9357
+ }
9358
+ if (!manifest.id || !manifest.name || !manifest.version || !manifest.ikenga_api) {
9359
+ process.stderr.write(`error: manifest missing required fields (id, name, version, ikenga_api)
9360
+ `);
9361
+ return 1;
9362
+ }
9363
+ let client;
9364
+ try {
9365
+ client = connectOrThrow();
9366
+ } catch (err2) {
9367
+ process.stderr.write(`error: ${err2.message}
9368
+ `);
9369
+ return 1;
9370
+ }
9371
+ process.stdout.write(`\u2192 registering ${manifest.id}@${manifest.version} from ${path}
9372
+ `);
9373
+ let registered;
9374
+ try {
9375
+ registered = await client.post("/iyke/pkg/dev/register", {
9376
+ install_path: path
9377
+ });
9378
+ } catch (err2) {
9379
+ process.stderr.write(`error: ${err2.message}
9380
+ `);
9381
+ return 1;
9382
+ }
9383
+ process.stdout.write(`
9384
+ \u2713 mounted as ${registered.installed.id} v${registered.installed.version}
9385
+ `);
9386
+ printPkgSurface(manifest);
9387
+ process.stdout.write(`
9388
+ Watching manifest.json + restart_when_changed globs.
9389
+ `);
9390
+ process.stdout.write(` Edit the manifest to trigger a reload (250ms debounce).
9391
+ `);
9392
+ process.stdout.write(` Sidecar / MCP code changes restart via the supervisor watcher.
9393
+ `);
9394
+ process.stdout.write(` Iframe code changes flow through your dev server's HMR.
9395
+ `);
9396
+ process.stdout.write(`
9397
+ Ctrl-C to unregister and exit.
9398
+ `);
9399
+ const pkgId = registered.installed.id;
9400
+ return new Promise((resolveExit) => {
9401
+ let unregistering = false;
9402
+ const cleanup = async () => {
9403
+ if (unregistering)
9404
+ return;
9405
+ unregistering = true;
9406
+ process.stdout.write(`
9407
+ \u2192 unregistering ${pkgId}
9408
+ `);
9409
+ try {
9410
+ await client.post("/iyke/pkg/dev/unregister", { pkg_id: pkgId });
9411
+ process.stdout.write(`\u2713 done
9412
+ `);
9413
+ resolveExit(0);
9414
+ } catch (err2) {
9415
+ process.stderr.write(`error: ${err2.message}
9416
+ `);
9417
+ resolveExit(1);
9418
+ }
9419
+ };
9420
+ process.on("SIGINT", () => {
9421
+ cleanup();
9422
+ });
9423
+ process.on("SIGTERM", () => {
9424
+ cleanup();
9425
+ });
9426
+ setInterval(() => {}, 60000);
9427
+ });
9428
+ }
9429
+ function printPkgSurface(m2) {
9430
+ if (m2.ui?.routes?.length) {
9431
+ process.stdout.write(`
9432
+ Routes:
9433
+ `);
9434
+ for (const r of m2.ui.routes) {
9435
+ process.stdout.write(` /pkg/${m2.id}${r.path} (${r.kind}: ${r.source})
9436
+ `);
9437
+ }
9438
+ }
9439
+ if (m2.mcp?.length) {
9440
+ process.stdout.write(`
9441
+ MCP servers:
9442
+ `);
9443
+ for (const s3 of m2.mcp) {
9444
+ process.stdout.write(` ${s3.name}
9445
+ `);
9446
+ }
9447
+ }
9448
+ if (m2.sidecars?.length) {
9449
+ process.stdout.write(`
9450
+ Sidecars:
9451
+ `);
9452
+ for (const s3 of m2.sidecars) {
9453
+ process.stdout.write(` ${s3.name}
9454
+ `);
9455
+ }
9456
+ }
9457
+ if (m2.engine?.agentId) {
9458
+ process.stdout.write(`
9459
+ Engine: ${m2.engine.agentId}
9460
+ `);
9461
+ }
9462
+ }
9463
+
9464
+ // src/lib/installed.ts
9465
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
9466
+ import { join as join4 } from "path";
9214
9467
  function listInstalled() {
9215
9468
  const root = pkgsDir();
9216
- if (!existsSync2(root))
9469
+ if (!existsSync4(root))
9217
9470
  return [];
9218
9471
  const out = [];
9219
9472
  for (const name of readdirSync(root)) {
9220
9473
  if (name.startsWith("."))
9221
9474
  continue;
9222
- const dir = join3(root, name);
9475
+ const dir = join4(root, name);
9223
9476
  try {
9224
- if (!statSync(dir).isDirectory())
9477
+ if (!statSync3(dir).isDirectory())
9225
9478
  continue;
9226
9479
  } catch {
9227
9480
  continue;
@@ -9247,11 +9500,11 @@ function findInstalled(pkgId) {
9247
9500
  return listInstalled().find((p2) => p2.id === pkgId) ?? null;
9248
9501
  }
9249
9502
  function readManifest(dir) {
9250
- const path = join3(dir, "manifest.json");
9251
- if (!existsSync2(path))
9503
+ const path = join4(dir, "manifest.json");
9504
+ if (!existsSync4(path))
9252
9505
  return null;
9253
9506
  try {
9254
- const json = JSON.parse(readFileSync2(path, "utf8"));
9507
+ const json = JSON.parse(readFileSync4(path, "utf8"));
9255
9508
  if (typeof json?.id !== "string" || typeof json?.version !== "string") {
9256
9509
  return null;
9257
9510
  }
@@ -9442,7 +9695,7 @@ function npmNameToPkgId2(npmName) {
9442
9695
  }
9443
9696
 
9444
9697
  // src/index.ts
9445
- var SUBCOMMANDS = ["list", "add", "update", "remove"];
9698
+ var SUBCOMMANDS = ["list", "add", "update", "remove", "dev"];
9446
9699
  function usage() {
9447
9700
  return `ikenga \u2014 pkg manager for the Ikenga shell
9448
9701
 
@@ -9451,6 +9704,7 @@ Usage:
9451
9704
  ikenga add <pkg>[@<version>] [--dry-run]
9452
9705
  ikenga update [<pkg> | --all] [--dry-run]
9453
9706
  ikenga remove <pkg>
9707
+ ikenga dev <path>
9454
9708
 
9455
9709
  Examples:
9456
9710
  ikenga list # what's installed locally
@@ -9460,9 +9714,15 @@ Examples:
9460
9714
  ikenga update --all # update everything outdated
9461
9715
  ikenga remove com.ikenga.hello # by manifest id, or...
9462
9716
  ikenga remove @ikenga/pkg-hello # ...by npm name
9717
+ ikenga dev ./my-pkg # hot-mount into running shell
9463
9718
 
9464
9719
  Installs land in the shell's pkgs directory (overridable with
9465
9720
  IKENGA_APP_DATA_DIR). The shell registers them on next boot.
9721
+
9722
+ \`ikenga dev <path>\` is different \u2014 it talks to a running shell over its
9723
+ localhost iyke bridge, registers the pkg with hot-reload semantics
9724
+ (manifest edits trigger an in-place reload, no shell restart), and
9725
+ unregisters cleanly on Ctrl-C. Requires the shell to be running.
9466
9726
  `;
9467
9727
  }
9468
9728
  async function main() {
@@ -9473,7 +9733,7 @@ async function main() {
9473
9733
  return 0;
9474
9734
  }
9475
9735
  if (sub === "--version" || sub === "-V") {
9476
- process.stdout.write(`ikenga ${"0.2.0"}
9736
+ process.stdout.write(`ikenga ${"0.3.1"}
9477
9737
  `);
9478
9738
  return 0;
9479
9739
  }
@@ -9514,6 +9774,15 @@ async function main() {
9514
9774
  }
9515
9775
  return removeCommand(spec);
9516
9776
  }
9777
+ case "dev": {
9778
+ const path = rest.find((a) => !a.startsWith("-"));
9779
+ if (!path) {
9780
+ process.stderr.write(`usage: ikenga dev <path>
9781
+ `);
9782
+ return 1;
9783
+ }
9784
+ return devCommand(path);
9785
+ }
9517
9786
  }
9518
9787
  }
9519
9788
  var code = await main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikenga/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Command-line tool for installing, updating, and managing Ikenga pkgs.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {