@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.
- package/README.md +40 -2
- package/dist/index.js +308 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# @ikenga/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/Royalti-io/ikenga-cli/releases)
|
|
4
|
+
[](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
|
-
|
|
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:
|
|
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/
|
|
9212
|
-
import { existsSync as
|
|
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 (!
|
|
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 =
|
|
9475
|
+
const dir = join4(root, name);
|
|
9223
9476
|
try {
|
|
9224
|
-
if (!
|
|
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 =
|
|
9251
|
-
if (!
|
|
9503
|
+
const path = join4(dir, "manifest.json");
|
|
9504
|
+
if (!existsSync4(path))
|
|
9252
9505
|
return null;
|
|
9253
9506
|
try {
|
|
9254
|
-
const json = JSON.parse(
|
|
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.
|
|
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();
|