@astrale-os/cli 0.8.1-alpha.2 → 0.8.1-alpha.4

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 CHANGED
@@ -110,7 +110,7 @@ Main command groups:
110
110
 
111
111
  | Group | What it covers |
112
112
  |-------|----------------|
113
- | Kernel | `ls`, `get`, `call`, `query`, `describe`, `token` |
113
+ | Kernel | `get`, `call`, `query`, `describe`, `token` |
114
114
  | Context | `status`, `whoami`, `use` |
115
115
  | Management | `admin`, `instance`, `identity`, `auth`, `idp`, `update` |
116
116
  | Agent | `browser` |
package/dist/astrale.js CHANGED
@@ -2576,7 +2576,7 @@ var package_default;
2576
2576
  var init_package = __esm(() => {
2577
2577
  package_default = {
2578
2578
  name: "@astrale-os/cli",
2579
- version: "0.8.1-alpha.2",
2579
+ version: "0.8.1-alpha.4",
2580
2580
  description: "Astrale CLI — connect to existing Astrale kernels",
2581
2581
  keywords: [
2582
2582
  "astrale",
@@ -90137,6 +90137,9 @@ var init_mutation2 = __esm(() => {
90137
90137
 
90138
90138
  // src/graph/mutation.ts
90139
90139
  function prepareMutation(input) {
90140
+ if (isLegacyPatchData(input)) {
90141
+ throw new TypeError("Legacy PatchData { nodes, edges } is not Mutation V3. Author { preconditions, operations } or a canonical astrale.graph.mutation/v3 document.");
90142
+ }
90140
90143
  if (isCanonicalCandidate(input))
90141
90144
  return MutationAST.decode(input);
90142
90145
  return MutationAST.create(input);
@@ -90144,6 +90147,9 @@ function prepareMutation(input) {
90144
90147
  function isCanonicalCandidate(input) {
90145
90148
  return input !== null && typeof input === "object" && !Array.isArray(input) && (Object.hasOwn(input, "format") || Object.hasOwn(input, "version"));
90146
90149
  }
90150
+ function isLegacyPatchData(input) {
90151
+ return input !== null && typeof input === "object" && !Array.isArray(input) && !Object.hasOwn(input, "operations") && (Object.hasOwn(input, "nodes") || Object.hasOwn(input, "edges"));
90152
+ }
90147
90153
  var init_mutation3 = __esm(() => {
90148
90154
  init_mutation2();
90149
90155
  });
@@ -90308,6 +90314,99 @@ var init_binary4 = __esm(() => {
90308
90314
  init_output();
90309
90315
  });
90310
90316
 
90317
+ // src/commands/call-describe.ts
90318
+ function describeCallableFromSchema(path5, schema2) {
90319
+ if (path5.ast.anchor.kind !== "domain")
90320
+ return;
90321
+ const origin = path5.ast.anchor.origin;
90322
+ const last = path5.ast.steps.at(-1);
90323
+ const document = asRecord2(schema2);
90324
+ if (document === undefined || last === undefined)
90325
+ return;
90326
+ if (last.kind === "method") {
90327
+ const owner = path5.ast.steps.at(-2);
90328
+ if (owner?.kind === "projection" && (owner.projection.kind === "class" || owner.projection.kind === "interface")) {
90329
+ const bag = asRecord2(owner.projection.kind === "class" ? document.classes : document.interfaces);
90330
+ const definition3 = asRecord2(bag?.[owner.projection.name]);
90331
+ const method = asRecord2(asRecord2(definition3?.methods)?.[last.name]);
90332
+ if (method !== undefined) {
90333
+ return Object.freeze({
90334
+ path: path5.raw,
90335
+ origin,
90336
+ class: owner.projection.name,
90337
+ method: last.name,
90338
+ dispatch: last.dispatch,
90339
+ ...callableFields(method)
90340
+ });
90341
+ }
90342
+ }
90343
+ const matches3 = findNamedMethods(document, origin, path5.raw, last.name, last.dispatch);
90344
+ if (matches3.length === 1)
90345
+ return matches3[0];
90346
+ if (matches3.length > 1) {
90347
+ return Object.freeze({
90348
+ path: path5.raw,
90349
+ origin,
90350
+ method: last.name,
90351
+ dispatch: last.dispatch,
90352
+ candidates: Object.freeze(matches3)
90353
+ });
90354
+ }
90355
+ return;
90356
+ }
90357
+ if (last.kind === "projection" && last.projection.kind === "function") {
90358
+ const fn2 = asRecord2(asRecord2(document.functions)?.[last.projection.name]);
90359
+ if (fn2 === undefined)
90360
+ return;
90361
+ return Object.freeze({
90362
+ path: path5.raw,
90363
+ origin,
90364
+ function: last.projection.name,
90365
+ ...callableFields(fn2)
90366
+ });
90367
+ }
90368
+ return;
90369
+ }
90370
+ function missingCallableDescription(path5) {
90371
+ return new AstraleError("CALL_DESCRIBE_UNAVAILABLE", `No callable schema is installed for ${path5}.`, "Use a Domain-rooted Path such as /:host.astrale.ai:class.Manager:createInstance. Method Paths are not Function nodes.");
90372
+ }
90373
+ function findNamedMethods(schema2, origin, path5, method, dispatch) {
90374
+ const matches3 = [];
90375
+ for (const bag of [schema2.classes, schema2.interfaces]) {
90376
+ const definitions2 = asRecord2(bag);
90377
+ if (definitions2 === undefined)
90378
+ continue;
90379
+ for (const [className, definition3] of Object.entries(definitions2)) {
90380
+ const methodDef = asRecord2(asRecord2(asRecord2(definition3)?.methods)?.[method]);
90381
+ if (methodDef === undefined)
90382
+ continue;
90383
+ matches3.push(Object.freeze({
90384
+ path: path5,
90385
+ origin,
90386
+ class: className,
90387
+ method,
90388
+ dispatch,
90389
+ ...callableFields(methodDef)
90390
+ }));
90391
+ }
90392
+ }
90393
+ return matches3;
90394
+ }
90395
+ function callableFields(value2) {
90396
+ return {
90397
+ ...typeof value2.description === "string" ? { description: value2.description } : {},
90398
+ ...value2.auth === undefined ? {} : { auth: value2.auth },
90399
+ ...value2.input === undefined ? {} : { input: value2.input },
90400
+ ...value2.output === undefined ? {} : { output: value2.output }
90401
+ };
90402
+ }
90403
+ function asRecord2(input) {
90404
+ return input !== null && typeof input === "object" && !Array.isArray(input) ? input : undefined;
90405
+ }
90406
+ var init_call_describe = __esm(() => {
90407
+ init_errors2();
90408
+ });
90409
+
90311
90410
  // src/commands/call.ts
90312
90411
  var exports_call = {};
90313
90412
  __export(exports_call, {
@@ -90374,28 +90473,20 @@ async function describeOperation(path5, opts) {
90374
90473
  await runKernelCommand({
90375
90474
  opts,
90376
90475
  label: `Schema for ${path5}`,
90377
- fn: async ({ graph }) => await graph.get(Path.parse(path5)),
90378
- format: (node4, fmtOpts) => {
90379
- const input = nodeProperty(node4, "inputSchema");
90380
- const outputSchema = nodeProperty(node4, "outputSchema");
90381
- const schema2 = {};
90382
- if (input)
90383
- schema2.input = tryParseJson(input);
90384
- if (outputSchema)
90385
- schema2.output = tryParseJson(outputSchema);
90386
- output(Object.keys(schema2).length > 0 ? schema2 : node4, fmtOpts);
90387
- }
90476
+ fn: async ({ graph }) => {
90477
+ const parsed = Path.parse(path5);
90478
+ if (parsed.ast.anchor.kind !== "domain") {
90479
+ throw missingCallableDescription(path5);
90480
+ }
90481
+ const domain3 = await graph.getOrThrow(Path.parse(`/:${parsed.ast.anchor.origin}`));
90482
+ const described = describeCallableFromSchema(parsed, nodeProperty(domain3, "schema"));
90483
+ if (described === undefined)
90484
+ throw missingCallableDescription(path5);
90485
+ return described;
90486
+ },
90487
+ format: (schema2, fmtOpts) => output(schema2, fmtOpts)
90388
90488
  });
90389
90489
  }
90390
- function tryParseJson(value2) {
90391
- if (typeof value2 !== "string")
90392
- return value2;
90393
- try {
90394
- return JSON.parse(value2);
90395
- } catch {
90396
- return value2;
90397
- }
90398
- }
90399
90490
  async function parseParams(rawParams, dataFlag) {
90400
90491
  if (dataFlag) {
90401
90492
  if (rawParams.length > 0) {
@@ -90471,6 +90562,7 @@ var init_call2 = __esm(() => {
90471
90562
  init_binary4();
90472
90563
  init_log();
90473
90564
  init_output();
90565
+ init_call_describe();
90474
90566
  PARAM_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
90475
90567
  call_default = {
90476
90568
  name: "call",
@@ -90490,16 +90582,19 @@ Self-reference:
90490
90582
  (e.g. via 'astrale get @self --json'). Resolution authenticates to
90491
90583
  the selected Kernel and never trusts a local registration or JWT sub.
90492
90584
 
90585
+ --describe reads the callable's input/output from the installed Domain
90586
+ schema. Method Paths are not Function nodes.
90587
+
90493
90588
  Examples:
90494
- $ astrale call /:host.astrale.ai:class.KernelInstance:list
90589
+ $ astrale call /:host.astrale.ai:class.Manager:createInstance --describe
90495
90590
  $ astrale call /:blog.acme.com:class.Author:list limit=10
90496
90591
  $ astrale call '@self::deactivate'
90497
- $ astrale call /:shell.astrale.ai:function.search-domains --json
90592
+ $ astrale call /:kernel.astrale.ai:function.journal --data '{"limit":5}' --json
90498
90593
  `,
90499
90594
  arguments: [
90500
90595
  {
90501
90596
  name: "path",
90502
- description: "Operation path (e.g., /:host.astrale.ai:class.KernelInstance:list or /node::method)"
90597
+ description: "Operation path (e.g., /:host.astrale.ai:class.Manager:createInstance or /node::method)"
90503
90598
  },
90504
90599
  { name: "params...", description: "Params as key=value pairs", required: false }
90505
90600
  ],
@@ -90597,7 +90692,7 @@ async function getCommand(target2, opts) {
90597
90692
  try {
90598
90693
  ({ path: path5, meta: meta3 } = await expandSelfInPath(target2, opts));
90599
90694
  } catch (error52) {
90600
- log.error(error52 instanceof Error ? error52.message : "Invalid target");
90695
+ await formatKernelError(error52, isMachine(opts), undefined, opts.debug);
90601
90696
  process.exit(1);
90602
90697
  }
90603
90698
  await runKernelCommand({
@@ -90611,7 +90706,7 @@ var get_default;
90611
90706
  var init_get = __esm(() => {
90612
90707
  init_path5();
90613
90708
  init_connection2();
90614
- init_log();
90709
+ init_errors12();
90615
90710
  init_output();
90616
90711
  get_default = {
90617
90712
  name: "get",
@@ -90757,115 +90852,6 @@ var init_class3 = __esm(() => {
90757
90852
  init_class();
90758
90853
  });
90759
90854
 
90760
- // src/commands/ls.ts
90761
- var exports_ls = {};
90762
- __export(exports_ls, {
90763
- lsCommand: () => lsCommand,
90764
- listProjection: () => listProjection,
90765
- displayName: () => displayName,
90766
- default: () => ls_default
90767
- });
90768
- async function lsCommand(source2, opts) {
90769
- if (opts.edge === undefined) {
90770
- log.error("ls requires --edge <class>; Kernel V2 has no universal child relation");
90771
- process.exit(1);
90772
- }
90773
- let path5;
90774
- let meta3;
90775
- let prepared;
90776
- try {
90777
- ({ path: path5, meta: meta3 } = await expandSelfInPath(source2, opts));
90778
- prepared = prepareQuery({
90779
- sources: [path5],
90780
- edge: opts.edge,
90781
- direction: opts.direction,
90782
- limit: opts.limit,
90783
- cursor: opts.cursor
90784
- });
90785
- } catch (error52) {
90786
- log.error(error52 instanceof Error ? error52.message : "Invalid edge neighborhood");
90787
- process.exit(1);
90788
- }
90789
- await runKernelCommand({
90790
- opts,
90791
- label: `Neighbors of ${path5}`,
90792
- fn: ({ graph }) => withSelfHint(() => graph.query(prepared.ast, { page: prepared.page }), meta3),
90793
- format: (response2, format3) => {
90794
- const graph = completeGraph(response2.result);
90795
- presentList([...graph.nodes], { ...format3, long: opts.long, quiet: opts.quiet, count: opts.count }, listProjection);
90796
- if (response2.page.next && !isMachine(format3) && !opts.quiet && !opts.count) {
90797
- process.stderr.write(` cursor: ${response2.page.next}
90798
- `);
90799
- }
90800
- }
90801
- });
90802
- }
90803
- function completeGraph(result) {
90804
- if (result.kind !== "graph")
90805
- throw new TypeError("ls expected one complete graph Query result");
90806
- return result.graph;
90807
- }
90808
- function listProjection(nodes) {
90809
- return {
90810
- columns: [
90811
- { key: "name", header: "NAME", color: source_default.cyan },
90812
- { key: "class", header: "CLASS", color: source_default.dim },
90813
- { key: "id", header: "ID", color: source_default.dim }
90814
- ],
90815
- rows: nodes.map((node4) => ({
90816
- name: displayName(node4),
90817
- class: ClassPath.name(node4.class),
90818
- id: node4.id
90819
- })),
90820
- paths: nodes.map((node4) => `@${node4.id}`)
90821
- };
90822
- }
90823
- function displayName(node4) {
90824
- const value2 = nodeProperty(node4, "name") ?? nodeProperty(node4, "title") ?? nodeProperty(node4, "slug");
90825
- return typeof value2 === "string" && value2.length > 0 ? value2 : `@${node4.id}`;
90826
- }
90827
- var ls_default;
90828
- var init_ls = __esm(() => {
90829
- init_class3();
90830
- init_source();
90831
- init_connection2();
90832
- init_graph6();
90833
- init_log();
90834
- init_output();
90835
- ls_default = {
90836
- name: "ls",
90837
- description: "List one finite exact edge neighborhood",
90838
- afterHelpText: `
90839
- Behavior:
90840
- Lists Nodes reached from one source through one exact Edge Class. Kernel V2
90841
- has no universal parent/child relation, so --edge is required and recursive
90842
- tree walking is intentionally absent. Direction defaults to outgoing and the
90843
- finite default limit is 100. -q emits one @id per line.
90844
-
90845
- Examples:
90846
- $ astrale ls @note --edge /:notes.example.dev:class.references
90847
- $ astrale ls @note --edge /:notes.example.dev:class.references --direction incoming --limit 25
90848
- `,
90849
- arguments: [{ name: "source", description: "Canonical source Path or @id" }],
90850
- options: [
90851
- { flags: "--edge <class>", description: "Exact Edge Class to traverse" },
90852
- {
90853
- flags: "--direction <direction>",
90854
- description: "Traversal direction (default: outgoing)",
90855
- choices: ["outgoing", "incoming", "incident"]
90856
- },
90857
- { flags: "--limit <n>", description: "Finite selected Node limit (default: 100)" },
90858
- { flags: "--cursor <token>", description: "Resume one live query scope" },
90859
- { flags: "-l, --long", description: "Print complete canonical Nodes" },
90860
- { flags: "-q, --quiet", description: "Print one @id per line" },
90861
- { flags: "--count", description: "Print only the number of returned Nodes" }
90862
- ],
90863
- action: async (source2, opts) => {
90864
- await lsCommand(source2, opts);
90865
- }
90866
- };
90867
- });
90868
-
90869
90855
  // src/commands/describe.ts
90870
90856
  var exports_describe = {};
90871
90857
  __export(exports_describe, {
@@ -91173,24 +91159,37 @@ function printRecord(record10) {
91173
91159
  `);
91174
91160
  }
91175
91161
  function acceptRecord2(input, index) {
91176
- if (!isRecord8(input) || !Number.isSafeInteger(input.sequence) || typeof input.timestamp !== "string" || typeof input.topic !== "string") {
91162
+ if (!isRecord8(input) || !Number.isSafeInteger(input.sequence) || typeof input.topic !== "string") {
91177
91163
  throw new TypeError(`Kernel journal record ${index} is invalid`);
91178
91164
  }
91179
- for (const key of ["principal", "correlationId", "causationId"]) {
91180
- if (input[key] !== undefined && typeof input[key] !== "string") {
91181
- throw new TypeError(`Kernel journal record ${index}.${key} must be text`);
91182
- }
91165
+ const occurredAt = optionalText2(input.occurredAt, index, "occurredAt");
91166
+ const timestamp = optionalText2(input.timestamp, index, "timestamp") ?? occurredAt;
91167
+ if (timestamp === undefined) {
91168
+ throw new TypeError(`Kernel journal record ${index} is missing occurredAt/timestamp`);
91183
91169
  }
91170
+ const correlation = isRecord8(input.correlation) ? input.correlation : undefined;
91171
+ const correlationId = optionalText2(input.correlationId, index, "correlationId") ?? optionalText2(correlation?.invocationId, index, "correlation.invocationId");
91172
+ const principal = optionalText2(input.principal, index, "principal");
91184
91173
  return Object.freeze({
91185
91174
  sequence: input.sequence,
91186
- timestamp: input.timestamp,
91175
+ timestamp,
91187
91176
  topic: input.topic,
91188
91177
  payload: input.payload,
91189
- ...input.principal === undefined ? {} : { principal: input.principal },
91190
- ...input.correlationId === undefined ? {} : { correlationId: input.correlationId },
91191
- ...input.causationId === undefined ? {} : { causationId: input.causationId }
91178
+ ...occurredAt === undefined ? {} : { occurredAt },
91179
+ ...optionalText2(input.committedAt, index, "committedAt") === undefined ? {} : { committedAt: input.committedAt },
91180
+ ...principal === undefined ? {} : { principal },
91181
+ ...correlationId === undefined ? {} : { correlationId },
91182
+ ...optionalText2(input.causationId, index, "causationId") === undefined ? {} : { causationId: input.causationId }
91192
91183
  });
91193
91184
  }
91185
+ function optionalText2(input, index, field) {
91186
+ if (input === undefined)
91187
+ return;
91188
+ if (typeof input !== "string") {
91189
+ throw new TypeError(`Kernel journal record ${index}.${field} must be text`);
91190
+ }
91191
+ return input;
91192
+ }
91194
91193
  function positiveInteger3(flag, raw2) {
91195
91194
  if (!/^\d+$/.test(raw2))
91196
91195
  throw new TypeError(`${flag} must be a positive integer`);
@@ -92742,7 +92741,8 @@ Examples:
92742
92741
  var exports_list = {};
92743
92742
  __export(exports_list, {
92744
92743
  default: () => list_default,
92745
- buildInstanceRows: () => buildInstanceRows
92744
+ buildInstanceRows: () => buildInstanceRows,
92745
+ adminInventoryUnavailable: () => adminInventoryUnavailable
92746
92746
  });
92747
92747
  function buildInstanceRows(managed, bookmarks, show) {
92748
92748
  const rows = [];
@@ -92786,9 +92786,19 @@ function buildInstanceRows(managed, bookmarks, show) {
92786
92786
  }
92787
92787
  return rows;
92788
92788
  }
92789
- var COLUMNS, list_default;
92789
+ function adminInventoryUnavailable(cause) {
92790
+ const code = cause instanceof AstraleError ? cause.code : undefined;
92791
+ if (code !== undefined && ADMIN_INVENTORY_CODES.has(code)) {
92792
+ return new AstraleError("ADMIN_INVENTORY_UNAVAILABLE", "Managed instance listing needs the Admin Domain and an IdP-backed identity. Admin is not deployed in this environment.", "Use `astrale instance list --bookmarked` for local kernel bookmarks. Key-backed identities cannot mint an Admin Domain token.");
92793
+ }
92794
+ if (cause instanceof AstraleError)
92795
+ return cause;
92796
+ return new AstraleError("ADMIN_INVENTORY_UNAVAILABLE", cause instanceof Error ? cause.message : String(cause), "Use `astrale instance list --bookmarked` for local kernel bookmarks.");
92797
+ }
92798
+ var COLUMNS, list_default, ADMIN_INVENTORY_CODES;
92790
92799
  var init_list = __esm(() => {
92791
92800
  init_source();
92801
+ init_errors2();
92792
92802
  init_admin_instance();
92793
92803
  init_admin_instance();
92794
92804
  init_admin_target();
@@ -92823,7 +92833,11 @@ var init_list = __esm(() => {
92823
92833
  }));
92824
92834
  let managed = [];
92825
92835
  if (!opts.bookmarked) {
92826
- managed = await withSpinner("Fetching instances", !isMachine(opts), () => listOwnedInstances(opts));
92836
+ try {
92837
+ managed = await withSpinner("Fetching instances", !isMachine(opts), () => listOwnedInstances(opts));
92838
+ } catch (error52) {
92839
+ throw adminInventoryUnavailable(error52);
92840
+ }
92827
92841
  }
92828
92842
  if (isMachine(opts)) {
92829
92843
  output({
@@ -92848,6 +92862,15 @@ var init_list = __esm(() => {
92848
92862
  }
92849
92863
  }
92850
92864
  };
92865
+ ADMIN_INVENTORY_CODES = new Set([
92866
+ "TOKEN_EXCHANGE_SOURCE_INVALID",
92867
+ "TOKEN_EXCHANGE_SOURCE_EXPIRED",
92868
+ "TOKEN_EXCHANGE_UNSUPPORTED",
92869
+ "TOKEN_EXCHANGE_DISCOVERY_FAILED",
92870
+ "TOKEN_EXCHANGE_PROTOCOL_ERROR",
92871
+ "TOKEN_EXCHANGE_INSECURE",
92872
+ "ADMIN_DOMAIN_ISSUER_MISSING"
92873
+ ]);
92851
92874
  });
92852
92875
 
92853
92876
  // src/commands/instance/bookmark.ts
@@ -93437,7 +93460,7 @@ Behavior:
93437
93460
  Reads the admin catalog — every domain that has been \`publish\`ed
93438
93461
  (origin → published worker URL). Listing only shows what is INSTALLABLE;
93439
93462
  what is actually mounted where lives on each instance's own graph
93440
- (\`astrale ls /\` against that instance).
93463
+ (\`astrale query\` against that instance).
93441
93464
 
93442
93465
  Default output is a NAME/ORIGIN/URL/DEFAULT table on a TTY, JSON when piped
93443
93466
  or with --json/--raw (agent-friendly — full DomainInfo objects). -q prints
@@ -93591,8 +93614,23 @@ __export(exports_install, {
93591
93614
  isIdentityOverride: () => isIdentityOverride,
93592
93615
  installViaAdmin: () => installViaAdmin,
93593
93616
  domainRefFromTarget: () => domainRefFromTarget,
93617
+ directInstallCallInput: () => directInstallCallInput,
93594
93618
  default: () => install_default
93595
93619
  });
93620
+ function directInstallCallInput(url3, token, operation2 = crypto.randomUUID()) {
93621
+ return Object.freeze({
93622
+ operation: operation2,
93623
+ domains: [
93624
+ Object.freeze({
93625
+ source: Object.freeze({
93626
+ kind: "remote",
93627
+ url: url3,
93628
+ ...token === undefined ? {} : { token }
93629
+ })
93630
+ })
93631
+ ]
93632
+ });
93633
+ }
93596
93634
  async function installViaAdmin(target2, opts, dependencies = {}) {
93597
93635
  const admin = { ...defaultAdminInstallDependencies, ...dependencies };
93598
93636
  const interactive = !!process.stdin.isTTY && !(opts.ci || opts.noPrompt || process.env.CI);
@@ -93710,20 +93748,18 @@ async function installDirect(target2, opts) {
93710
93748
  await runKernelCommand({
93711
93749
  opts,
93712
93750
  label: `Installing domain from ${url3}`,
93713
- fn: async ({ session }) => await session.call(createPathCall(Path.project(exports_syscalls.install.ref).raw, {
93714
- domains: [{ url: url3, ...opts.token ? { token: opts.token } : {} }]
93715
- })),
93751
+ fn: async ({ session }) => await session.call(createPathCall(Path.project(exports_syscalls.install.ref).raw, directInstallCallInput(url3, opts.token))),
93716
93752
  format: (result, fmtOpts, isRaw) => {
93717
93753
  if (isRaw) {
93718
93754
  output(result, fmtOpts);
93719
93755
  return;
93720
93756
  }
93721
- const installed = result[0];
93722
- if (!installed)
93723
- throw new Error("Kernel install returned no installed Domain receipt.");
93724
- log.success(`Domain installed: ${installed.origin}@${installed.revision}`);
93725
- if (installed.etag)
93726
- log.dim(` publication: ${installed.etag}`);
93757
+ const installed = result.transitions[0]?.intent;
93758
+ if (installed === undefined) {
93759
+ throw new Error("Kernel install returned no committed Domain transition.");
93760
+ }
93761
+ const revision = installed.target?.schemaRevision ?? result.operation;
93762
+ log.success(`Domain installed: ${installed.origin}@${revision}`);
93727
93763
  if (isIdentityOverride(installed.origin, host) && installed.origin !== consentedOrigin) {
93728
93764
  log.warn(`Installed origin "${installed.origin}" differs from the serving host "${host}" ` + `and was not confirmed before install (the worker's /meta did not declare it). ` + `Every ${installed.origin}/* call on this instance now routes to ${host}.`);
93729
93765
  }
@@ -95759,9 +95795,21 @@ function renderCommanderError(program2, error, argv = process.argv.slice(2)) {
95759
95795
  ].join(`
95760
95796
  `);
95761
95797
  }
95798
+ var RETIRED_COMMANDS = {
95799
+ ls: "astrale query <source> --edge <class>"
95800
+ };
95762
95801
  function renderUnknownCommand(tokens, catalog) {
95763
95802
  const command = tokens.join(" ");
95764
95803
  const first = tokens[0];
95804
+ const retired = first === undefined ? undefined : RETIRED_COMMANDS[first];
95805
+ if (retired !== undefined) {
95806
+ return [
95807
+ `Unknown command: ${source_default.bold(`astrale ${command}`)}`,
95808
+ "",
95809
+ `\`astrale ${first}\` was removed. Use ${source_default.bold(retired)} instead.`
95810
+ ].join(`
95811
+ `);
95812
+ }
95765
95813
  const namespaceMatches = catalog.filter((entry) => entry.path.at(-1) === first);
95766
95814
  if (namespaceMatches.length > 0) {
95767
95815
  return [
@@ -95961,7 +96009,6 @@ async function buildProgram() {
95961
96009
  registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_token(), exports_token))).default));
95962
96010
  registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_get(), exports_get))).default));
95963
96011
  registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_mutate4(), exports_mutate))).default));
95964
- registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_ls(), exports_ls))).default));
95965
96012
  registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_describe(), exports_describe))).default));
95966
96013
  registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_query7(), exports_query))).default));
95967
96014
  registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_logs(), exports_logs))).default));
@@ -96049,7 +96096,7 @@ async function buildProgram() {
96049
96096
  program3.addHelpText("after", `
96050
96097
  Command groups:
96051
96098
  Getting started setup (sign in, pick an instance, equip your workspace)
96052
- Kernel ls, get, mutate, call, query, describe, token
96099
+ Kernel get, mutate, call, query, describe, token
96053
96100
  Management admin, instance, domain, identity, auth, idp, update
96054
96101
  Agent browser (drive the GUI via agent-browser)
96055
96102
  Studio studio (launch the local Domain Studio GUI for a workspace)
@@ -96063,7 +96110,6 @@ Path syntax:
96063
96110
  @nodeId::method Instance method on a node by UID
96064
96111
 
96065
96112
  Examples:
96066
- $ astrale ls @note --edge /:notes.example.dev:class.references
96067
96113
  $ astrale studio
96068
96114
  $ astrale admin status
96069
96115
  $ astrale update --check
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/cli",
3
- "version": "0.8.1-alpha.2",
3
+ "version": "0.8.1-alpha.4",
4
4
  "description": "Astrale CLI — connect to existing Astrale kernels",
5
5
  "keywords": [
6
6
  "astrale",
@@ -0,0 +1,67 @@
1
+ import { Path } from '@astrale-os/sdk/graph/path'
2
+ import { describe, expect, test } from 'bun:test'
3
+
4
+ import { describeCallableFromSchema } from '../call-describe'
5
+
6
+ const schema = {
7
+ classes: {
8
+ Manager: {
9
+ methods: {
10
+ createInstance: {
11
+ auth: 'authorized',
12
+ description: 'Create one child Instance.',
13
+ input: { type: 'object', required: ['operationId', 'slug'] },
14
+ output: { mode: 'value' },
15
+ },
16
+ },
17
+ },
18
+ },
19
+ functions: {
20
+ journal: {
21
+ description: 'Read the authorized Kernel journal.',
22
+ input: { type: 'object' },
23
+ },
24
+ },
25
+ }
26
+
27
+ describe('describeCallableFromSchema', () => {
28
+ test('reads a static Class method from the Domain schema', () => {
29
+ const described = describeCallableFromSchema(
30
+ Path.parse('/:host.astrale.ai:class.Manager:createInstance'),
31
+ schema,
32
+ )
33
+ expect(described).toMatchObject({
34
+ origin: 'host.astrale.ai',
35
+ class: 'Manager',
36
+ method: 'createInstance',
37
+ dispatch: 'static',
38
+ description: 'Create one child Instance.',
39
+ auth: 'authorized',
40
+ })
41
+ expect(described?.input).toMatchObject({ required: ['operationId', 'slug'] })
42
+ })
43
+
44
+ test('finds an instance method when the receiver Path is not a Class projection', () => {
45
+ expect(
46
+ describeCallableFromSchema(
47
+ Path.parse('/:host.astrale.ai:core.manager::createInstance'),
48
+ schema,
49
+ ),
50
+ ).toMatchObject({
51
+ origin: 'host.astrale.ai',
52
+ class: 'Manager',
53
+ method: 'createInstance',
54
+ dispatch: 'instance',
55
+ })
56
+ })
57
+
58
+ test('reads a standalone Function from the Domain schema', () => {
59
+ expect(
60
+ describeCallableFromSchema(Path.parse('/:kernel.astrale.ai:function.journal'), schema),
61
+ ).toMatchObject({
62
+ origin: 'kernel.astrale.ai',
63
+ function: 'journal',
64
+ description: 'Read the authorized Kernel journal.',
65
+ })
66
+ })
67
+ })
@@ -0,0 +1,20 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { directInstallCallInput } from '../domain/install'
4
+
5
+ describe('directInstallCallInput', () => {
6
+ test('sends the current remote install syscall, not a legacy url list', () => {
7
+ expect(directInstallCallInput('https://tasks.example.test', 'secret', 'op-1')).toEqual({
8
+ operation: 'op-1',
9
+ domains: [
10
+ {
11
+ source: {
12
+ kind: 'remote',
13
+ url: 'https://tasks.example.test',
14
+ token: 'secret',
15
+ },
16
+ },
17
+ ],
18
+ })
19
+ })
20
+ })
@@ -0,0 +1,26 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { AstraleError } from '../../errors'
4
+ import { adminInventoryUnavailable } from '../instance/list'
5
+
6
+ describe('adminInventoryUnavailable', () => {
7
+ test('turns key-backed Admin exchange failures into a bookmark-only instruction', () => {
8
+ const error = adminInventoryUnavailable(
9
+ new AstraleError(
10
+ 'TOKEN_EXCHANGE_SOURCE_INVALID',
11
+ 'The source identity credential has no valid expiration.',
12
+ ),
13
+ )
14
+ expect(error.code).toBe('ADMIN_INVENTORY_UNAVAILABLE')
15
+ expect(error.message).toContain('Admin is not deployed')
16
+ expect(error.hint).toContain('instance list --bookmarked')
17
+ })
18
+
19
+ test('turns a missing Admin Domain issuer into the same instruction', () => {
20
+ const error = adminInventoryUnavailable(
21
+ new AstraleError('ADMIN_DOMAIN_ISSUER_MISSING', 'no Domain issuer'),
22
+ )
23
+ expect(error.code).toBe('ADMIN_INVENTORY_UNAVAILABLE')
24
+ expect(error.hint).toContain('Key-backed identities cannot mint an Admin Domain token')
25
+ })
26
+ })