@anna-ai/cli 0.1.9 → 0.1.12
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 +64 -3
- package/dist/apps-CDe6Fjq2.js +44 -0
- package/dist/bridge-BEHyfpPI.js +3 -0
- package/dist/{bridge-CBcQUQGU.js → bridge-BQUo6ehX.js} +1 -1
- package/dist/cli.js +55 -8
- package/dist/credentials-BTv2IfUZ.js +122 -0
- package/dist/credentials-CIOYq2Lm.js +3 -0
- package/dist/dashboard.html +8 -4
- package/dist/dev-DoY58pBM.js +411 -0
- package/dist/dev-app-cache-BMfOlTHd.js +93 -0
- package/dist/dev-app-cache-cXvO2XwQ.js +4 -0
- package/dist/{doctor-BmR0POfL.js → doctor-DP2UB10l.js} +2 -2
- package/dist/login-dl1Zfny8.js +102 -0
- package/dist/logout-DablvlFs.js +23 -0
- package/dist/server-NXmiWJjX.js +684 -0
- package/dist/test/index.js +45 -1
- package/dist/whoami-giXOY415.js +43 -0
- package/package.json +4 -3
- package/dist/bridge-BDBECvV1.js +0 -3
- package/dist/dev-D-Tru6gP.js +0 -163
- package/dist/server-gl345fFN.js +0 -261
- /package/dist/{fixture-BGjMtqWA.js → fixture-BEu4LXLG.js} +0 -0
package/README.md
CHANGED
|
@@ -48,17 +48,62 @@ Layered fail-fast checks: JSON Schema → `ui` static → cross-file `tool_id`
|
|
|
48
48
|
linter (with Levenshtein-1 typo detection) → `--strict` host_api ACL grep
|
|
49
49
|
of bundle JS/TS.
|
|
50
50
|
|
|
51
|
-
### `anna-app dev [--manifest …] [--bundle …] [--port 5180] [--matrix-nexus-root <path>]`
|
|
51
|
+
### `anna-app dev [--manifest …] [--bundle …] [--port 5180] [--matrix-nexus-root <path>] [--executa <spec>…]`
|
|
52
52
|
|
|
53
53
|
Boots the local harness:
|
|
54
54
|
|
|
55
55
|
- Spawns the Python `anna-app-bridge` (production dispatcher reused via
|
|
56
56
|
`WindowStoreProtocol`).
|
|
57
57
|
- Serves a mock dashboard at `http://localhost:<port>/`.
|
|
58
|
-
- Auto-discovers `<manifest-dir>/executas/<name
|
|
59
|
-
registers them in the in-process
|
|
58
|
+
- Auto-discovers `<manifest-dir>/executas/<name>/` plugins (Python /
|
|
59
|
+
Node.js / Go / pre-built binary) and registers them in the in-process
|
|
60
|
+
`ExecutaPool`. See "Multi-language executas" below.
|
|
60
61
|
- Hot-reloads the bundle on disk changes (use `--no-watch` to disable).
|
|
61
62
|
|
|
63
|
+
#### Multi-language executas
|
|
64
|
+
|
|
65
|
+
Each subdirectory of `<manifest-dir>/executas/` is launched according to
|
|
66
|
+
the first sentinel that matches:
|
|
67
|
+
|
|
68
|
+
| # | Sentinel | Type | Default launch |
|
|
69
|
+
| - | ----------------------- | -------- | ------------------------------------------------------------- |
|
|
70
|
+
| 0 | `executa.json` | (any) | `command` field, else type-specific default below |
|
|
71
|
+
| 1 | `pyproject.toml` | `python` | `uv run --project <dir> <tool_id>` |
|
|
72
|
+
| 2 | `package.json` | `node` | `node <bin[tool_id] \| bin \| main \| module>` |
|
|
73
|
+
| 3 | `go.mod` (alone) | `go` | requires `executa.json` declaring `type: "go"` |
|
|
74
|
+
| 4 | `bin/<dirname>` exec | `binary` | runs the executable directly |
|
|
75
|
+
|
|
76
|
+
`executa.json` (recommended for clarity, required for Go and pre-built
|
|
77
|
+
binary tools):
|
|
78
|
+
|
|
79
|
+
```jsonc
|
|
80
|
+
{
|
|
81
|
+
"tool_id": "tool-yourhandle-foo-abcd1234",
|
|
82
|
+
"type": "python" | "node" | "go" | "binary",
|
|
83
|
+
"command": ["…"], // optional; full override of type defaults
|
|
84
|
+
"enabled": true // optional; default true. Set false to skip
|
|
85
|
+
// (useful when shipping multiple language
|
|
86
|
+
// flavours of the same tool_id).
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The `--executa <spec>` flag (repeatable) registers an out-of-tree
|
|
91
|
+
executa, or fully overrides auto-discovery for the run. Spec syntax:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Auto-detect from the dir (same rules as in-tree discovery):
|
|
95
|
+
anna-app dev --executa dir=./vendor/external-tool
|
|
96
|
+
|
|
97
|
+
# Force type when there's no executa.json:
|
|
98
|
+
anna-app dev --executa dir=./executas/foo,type=go
|
|
99
|
+
|
|
100
|
+
# Fully explicit:
|
|
101
|
+
anna-app dev --executa dir=./executas/foo,tool_id=tool-h-foo-12345678,command="node plugin.js"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For the full discovery / `executa.json` reference, see
|
|
105
|
+
[`anna-executa-examples/docs/multi-language-anna-apps.md`](https://github.com/openclaw/anna-executa-examples/blob/main/docs/multi-language-anna-apps.md).
|
|
106
|
+
|
|
62
107
|
Two runtime modes (auto-selected):
|
|
63
108
|
|
|
64
109
|
| Mode | When | Command |
|
|
@@ -130,6 +175,22 @@ public npm package [`@anna-ai/app-runtime`](https://www.npmjs.com/package/@anna-
|
|
|
130
175
|
which is declared as a normal dependency. No vendored copy, no sync
|
|
131
176
|
step.
|
|
132
177
|
|
|
178
|
+
## Persistent storage (APS)
|
|
179
|
+
|
|
180
|
+
Anna 1.2+ exposes a per-user JSON-RPC storage surface under the
|
|
181
|
+
`storage/*` namespace. Plugins authored with this CLI can opt in by:
|
|
182
|
+
|
|
183
|
+
1. Declaring `storage.user` (or `.app`/`.tool`) in the manifest's
|
|
184
|
+
`host_capabilities` array.
|
|
185
|
+
2. Negotiating `client_capabilities.storage = {}` during `initialize`.
|
|
186
|
+
3. Asking the user to grant storage in the Anna admin panel.
|
|
187
|
+
|
|
188
|
+
See the protocol & best-practice guide at
|
|
189
|
+
[anna-executa-examples/docs/persistent-storage.md](https://github.com/openclaw/anna-executa-examples/blob/main/docs/persistent-storage.md)
|
|
190
|
+
for wire format, error codes, and a worked OCR-cache example. The
|
|
191
|
+
local `dev` harness mocks APS by default so end-to-end tests do not
|
|
192
|
+
need network access.
|
|
193
|
+
|
|
133
194
|
## Roadmap
|
|
134
195
|
|
|
135
196
|
| Phase | Status | Scope |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getAccount } from "./credentials-BTv2IfUZ.js";
|
|
2
|
+
import { listDevApps } from "./dev-app-cache-BMfOlTHd.js";
|
|
3
|
+
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
4
|
+
|
|
5
|
+
//#region src/commands/apps.ts
|
|
6
|
+
async function runAppsList(opts) {
|
|
7
|
+
const acc = getAccount(opts.account);
|
|
8
|
+
if (!acc) {
|
|
9
|
+
console.error(red("✗ no PAT on disk — run `anna-app login --host <nexus-url>` first."));
|
|
10
|
+
return 2;
|
|
11
|
+
}
|
|
12
|
+
let apps;
|
|
13
|
+
try {
|
|
14
|
+
apps = await listDevApps({
|
|
15
|
+
host: acc.host,
|
|
16
|
+
pat: acc.pat
|
|
17
|
+
});
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.error(red(`✗ ${e.message}`));
|
|
20
|
+
return 2;
|
|
21
|
+
}
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
console.log(JSON.stringify({
|
|
24
|
+
host: acc.host,
|
|
25
|
+
apps
|
|
26
|
+
}, null, 2));
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
console.log(bold(cyan("dev apps installed for")) + " " + cyan(acc.host));
|
|
30
|
+
if (apps.length === 0) {
|
|
31
|
+
console.log(dim(" (none — run `anna-app dev` in a project to register one)"));
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
for (const a of apps) {
|
|
35
|
+
const tag = a.is_dev ? yellow("[dev]") : green("[prod]");
|
|
36
|
+
const enabled = a.is_enabled ? green("✓") : red("✗");
|
|
37
|
+
console.log(` ${tag} ${enabled} ${bold(a.slug)} ${dim(`(app_id=${a.app_id}, v=${a.installed_version})`)}`);
|
|
38
|
+
if (a.name && a.name !== a.slug) console.log(` ${dim(a.name)}`);
|
|
39
|
+
}
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { runAppsList };
|
|
@@ -9,7 +9,7 @@ import { createInterface } from "node:readline";
|
|
|
9
9
|
* `uvx <pkg>@<version>` so end users always run the dispatcher version
|
|
10
10
|
* the CLI was tested against.
|
|
11
11
|
*/
|
|
12
|
-
const PINNED_RUNTIME_VERSION = "0.2.
|
|
12
|
+
const PINNED_RUNTIME_VERSION = "0.2.0a2";
|
|
13
13
|
var PythonBridge = class {
|
|
14
14
|
proc = null;
|
|
15
15
|
nextId = 1;
|
package/dist/cli.js
CHANGED
|
@@ -444,10 +444,23 @@ program.command("validate").description("Run schema + ACL checks on a manifest+b
|
|
|
444
444
|
const code = printResult(result);
|
|
445
445
|
process.exit(code);
|
|
446
446
|
});
|
|
447
|
-
program.command("dev").description("Run a local harness (in-process dispatcher + iframe + SSE relay)").option("--manifest <path>", "manifest.json path", "manifest.json").option("--bundle <dir>", "bundle directory (default: ./bundle)").option("--slug <slug>", "App slug (overrides manifest.slug/name)").option("--view <name>", "View name to open (default: manifest default)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (auto-detected if omitted; can also use $ANNA_NEXUS_ROOT)").option("--port <number>", "HTTP port", "5180").option("--user-id <id>", "Harness user_id", "1").option("--cwd <dir>", "Project root (default: cwd)").option("--no-watch", "Disable bundle file watcher (default: enabled)").action(async (opts) => {
|
|
448
|
-
const { runDev } = await import("./dev-
|
|
447
|
+
program.command("dev").description("Run a local harness (in-process dispatcher + iframe + SSE relay)").option("--manifest <path>", "manifest.json path", "manifest.json").option("--bundle <dir>", "bundle directory (default: ./bundle)").option("--slug <slug>", "App slug (overrides manifest.slug/name)").option("--view <name>", "View name to open (default: manifest default)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (auto-detected if omitted; can also use $ANNA_NEXUS_ROOT)").option("--port <number>", "HTTP port", "5180").option("--user-id <id>", "Harness user_id", "1").option("--cwd <dir>", "Project root (default: cwd)").option("--no-watch", "Disable bundle file watcher (default: enabled)").option("--executa <spec>", "Explicit executa registration; repeatable. Spec: comma-separated key=value (dir=<path>[,tool_id=<id>][,type=python|node|go|binary][,command=\"<argv>\"]). When only `dir=` is given, the executa is auto-detected from executa.json / pyproject.toml / package.json / go.mod. Overrides directory auto-discovery under <manifest-dir>/executas/.", (val, prev) => prev ? [...prev, val] : [val]).option("--no-llm", "Disable LLM bridge (anna.llm/agent return llm_disabled)").option("--mock-llm <fixture>", "Serve canned LLM responses from a JSONL fixture").option("--llm-account <host>", "Saved account host to use (default: current)").option("--llm-app-slug <slug>", "Override the manifest slug used to register / look up the dev AnnaApp (default: manifest.slug)").action(async (opts) => {
|
|
448
|
+
const { runDev, parseExecutaSpec } = await import("./dev-DoY58pBM.js");
|
|
449
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
450
|
+
let executas;
|
|
451
|
+
if (opts.executa && opts.executa.length > 0) {
|
|
452
|
+
executas = [];
|
|
453
|
+
for (const spec of opts.executa) {
|
|
454
|
+
const r = parseExecutaSpec(spec, cwd);
|
|
455
|
+
if (r instanceof Error) {
|
|
456
|
+
console.error(`✗ --executa: ${r.message}`);
|
|
457
|
+
process.exit(2);
|
|
458
|
+
}
|
|
459
|
+
executas.push(r);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
449
462
|
const code = await runDev({
|
|
450
|
-
cwd
|
|
463
|
+
cwd,
|
|
451
464
|
manifestPath: opts.manifest,
|
|
452
465
|
bundleDir: opts.bundle,
|
|
453
466
|
slug: opts.slug,
|
|
@@ -455,13 +468,18 @@ program.command("dev").description("Run a local harness (in-process dispatcher +
|
|
|
455
468
|
matrixNexusRoot: opts.matrixNexusRoot,
|
|
456
469
|
port: Number.parseInt(opts.port, 10),
|
|
457
470
|
userId: Number.parseInt(opts.userId, 10),
|
|
458
|
-
noWatch: opts.watch === false
|
|
471
|
+
noWatch: opts.watch === false,
|
|
472
|
+
executas,
|
|
473
|
+
noLlm: opts.llm === false,
|
|
474
|
+
mockLlm: opts.mockLlm,
|
|
475
|
+
llmAccount: opts.llmAccount,
|
|
476
|
+
llmAppSlug: opts.llmAppSlug
|
|
459
477
|
});
|
|
460
478
|
process.exit(code);
|
|
461
479
|
});
|
|
462
480
|
const fixture = program.command("fixture").description("Inspect / replay harness recordings (Phase 6)");
|
|
463
481
|
fixture.command("verify <file>").description("Schema + invariant checks on a harness JSONL recording").option("--json", "Emit machine-readable JSON", false).action(async (file, opts) => {
|
|
464
|
-
const { runFixtureVerify } = await import("./fixture-
|
|
482
|
+
const { runFixtureVerify } = await import("./fixture-BEu4LXLG.js");
|
|
465
483
|
const code = await runFixtureVerify({
|
|
466
484
|
file,
|
|
467
485
|
json: opts.json
|
|
@@ -469,7 +487,7 @@ fixture.command("verify <file>").description("Schema + invariant checks on a har
|
|
|
469
487
|
process.exit(code);
|
|
470
488
|
});
|
|
471
489
|
fixture.command("summarize <file>").description("Print a human-readable digest of a harness recording").option("--json", "Emit machine-readable JSON", false).action(async (file, opts) => {
|
|
472
|
-
const { runFixtureSummarize } = await import("./fixture-
|
|
490
|
+
const { runFixtureSummarize } = await import("./fixture-BEu4LXLG.js");
|
|
473
491
|
const code = await runFixtureSummarize({
|
|
474
492
|
file,
|
|
475
493
|
json: opts.json
|
|
@@ -477,7 +495,7 @@ fixture.command("summarize <file>").description("Print a human-readable digest o
|
|
|
477
495
|
process.exit(code);
|
|
478
496
|
});
|
|
479
497
|
fixture.command("replay <file>").description("Dry-run replay of a harness recording (Phase 6 MVP)").option("--manifest <path>", "manifest.json path", "manifest.json").action(async (file, opts) => {
|
|
480
|
-
const { runFixtureReplay } = await import("./fixture-
|
|
498
|
+
const { runFixtureReplay } = await import("./fixture-BEu4LXLG.js");
|
|
481
499
|
const code = await runFixtureReplay({
|
|
482
500
|
file,
|
|
483
501
|
manifest: opts.manifest
|
|
@@ -485,10 +503,39 @@ fixture.command("replay <file>").description("Dry-run replay of a harness record
|
|
|
485
503
|
process.exit(code);
|
|
486
504
|
});
|
|
487
505
|
program.command("doctor").description("Check environment for `anna-app dev` (uv, matrix-nexus, dev key)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (optional)").action(async (opts) => {
|
|
488
|
-
const { runDoctor } = await import("./doctor-
|
|
506
|
+
const { runDoctor } = await import("./doctor-DP2UB10l.js");
|
|
489
507
|
const code = await runDoctor({ matrixNexusRoot: opts.matrixNexusRoot });
|
|
490
508
|
process.exit(code);
|
|
491
509
|
});
|
|
510
|
+
program.command("login").description("Device-flow login against a nexus host; saves a PAT to ~/.config/anna/credentials.json").requiredOption("--host <url>", "nexus base URL, e.g. https://nexus.example.com").option("--no-browser", "Do not open a browser window automatically", false).action(async (opts) => {
|
|
511
|
+
const { runLogin } = await import("./login-dl1Zfny8.js");
|
|
512
|
+
const code = await runLogin({
|
|
513
|
+
host: opts.host,
|
|
514
|
+
noBrowser: opts.browser === false
|
|
515
|
+
});
|
|
516
|
+
process.exit(code);
|
|
517
|
+
});
|
|
518
|
+
program.command("logout").description("Remove a saved PAT entry").option("--host <url>", "Account to remove (default: current)").option("--all", "Remove every saved account", false).action(async (opts) => {
|
|
519
|
+
const { runLogout } = await import("./logout-DablvlFs.js");
|
|
520
|
+
const code = await runLogout({
|
|
521
|
+
host: opts.host,
|
|
522
|
+
all: opts.all
|
|
523
|
+
});
|
|
524
|
+
process.exit(code);
|
|
525
|
+
});
|
|
526
|
+
program.command("whoami").description("Show the current account (and any others)").option("--json", "Emit machine-readable JSON", false).action(async (opts) => {
|
|
527
|
+
const { runWhoami } = await import("./whoami-giXOY415.js");
|
|
528
|
+
const code = await runWhoami({ json: opts.json });
|
|
529
|
+
process.exit(code);
|
|
530
|
+
});
|
|
531
|
+
program.command("apps:list").description("List dev apps installed for the current PAT").option("--account <host>", "Saved account host (default: current)").option("--json", "Emit machine-readable JSON", false).action(async (opts) => {
|
|
532
|
+
const { runAppsList } = await import("./apps-CDe6Fjq2.js");
|
|
533
|
+
const code = await runAppsList({
|
|
534
|
+
account: opts.account,
|
|
535
|
+
json: opts.json
|
|
536
|
+
});
|
|
537
|
+
process.exit(code);
|
|
538
|
+
});
|
|
492
539
|
program.parseAsync(process.argv).catch((e) => {
|
|
493
540
|
console.error(e);
|
|
494
541
|
process.exit(2);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
//#region rolldown:runtime
|
|
7
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/credentials.ts
|
|
11
|
+
/** Resolve credentials file path. Honours $XDG_CONFIG_HOME. */
|
|
12
|
+
function credentialsPath() {
|
|
13
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
14
|
+
const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".config");
|
|
15
|
+
return join(base, "anna", "credentials.json");
|
|
16
|
+
}
|
|
17
|
+
/** Normalise a host URL to its canonical key (https://x.example.com). */
|
|
18
|
+
function canonicalHost(input) {
|
|
19
|
+
let url;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(input);
|
|
22
|
+
} catch {
|
|
23
|
+
url = new URL(`https://${input}`);
|
|
24
|
+
}
|
|
25
|
+
const port = url.port ? `:${url.port}` : "";
|
|
26
|
+
return `${url.protocol}//${url.hostname}${port}`;
|
|
27
|
+
}
|
|
28
|
+
/** Read credentials file (returns empty container if missing or unreadable). */
|
|
29
|
+
function readCredentials() {
|
|
30
|
+
const path = credentialsPath();
|
|
31
|
+
if (!existsSync(path)) return {
|
|
32
|
+
version: 1,
|
|
33
|
+
current: null,
|
|
34
|
+
accounts: {}
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
const raw = readFileSync(path, "utf8");
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
return {
|
|
40
|
+
version: 1,
|
|
41
|
+
current: parsed.current ?? null,
|
|
42
|
+
accounts: parsed.accounts ?? {}
|
|
43
|
+
};
|
|
44
|
+
} catch {
|
|
45
|
+
return {
|
|
46
|
+
version: 1,
|
|
47
|
+
current: null,
|
|
48
|
+
accounts: {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Atomically write credentials file (chmod 600). */
|
|
53
|
+
function writeCredentials(data) {
|
|
54
|
+
const path = credentialsPath();
|
|
55
|
+
mkdirSync(dirname(path), {
|
|
56
|
+
recursive: true,
|
|
57
|
+
mode: 448
|
|
58
|
+
});
|
|
59
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
60
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
61
|
+
try {
|
|
62
|
+
__require("node:fs").renameSync(tmp, path);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
try {
|
|
65
|
+
unlinkSync(tmp);
|
|
66
|
+
} catch {}
|
|
67
|
+
throw e;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
chmodSync(path, 384);
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
/** Set or replace the entry for `host`; mark as `current`. */
|
|
74
|
+
function saveAccount(rec) {
|
|
75
|
+
const data = readCredentials();
|
|
76
|
+
const key = canonicalHost(rec.host);
|
|
77
|
+
data.accounts[key] = {
|
|
78
|
+
...rec,
|
|
79
|
+
host: key
|
|
80
|
+
};
|
|
81
|
+
data.current = key;
|
|
82
|
+
writeCredentials(data);
|
|
83
|
+
}
|
|
84
|
+
/** Remove one account; if it was current, switch to any remaining one. */
|
|
85
|
+
function removeAccount(host) {
|
|
86
|
+
const data = readCredentials();
|
|
87
|
+
const key = canonicalHost(host);
|
|
88
|
+
if (!(key in data.accounts)) return false;
|
|
89
|
+
delete data.accounts[key];
|
|
90
|
+
if (data.current === key) {
|
|
91
|
+
const next = Object.keys(data.accounts);
|
|
92
|
+
data.current = next.length > 0 ? next[0] ?? null : null;
|
|
93
|
+
}
|
|
94
|
+
writeCredentials(data);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
/** Look up an account by host (or current account if `host` omitted). */
|
|
98
|
+
function getAccount(host) {
|
|
99
|
+
const data = readCredentials();
|
|
100
|
+
const key = host ? canonicalHost(host) : data.current;
|
|
101
|
+
if (!key) return null;
|
|
102
|
+
return data.accounts[key] ?? null;
|
|
103
|
+
}
|
|
104
|
+
/** Mask a PAT for safe display: ``eyJh…aZQp (90d, scopes=aps:dev)``. */
|
|
105
|
+
function maskPat(pat) {
|
|
106
|
+
if (pat.length <= 12) return "***";
|
|
107
|
+
return `${pat.slice(0, 4)}…${pat.slice(-4)}`;
|
|
108
|
+
}
|
|
109
|
+
/** Permission check — warn callers if file mode is loose. */
|
|
110
|
+
function credentialsAreLooselyPermissioned() {
|
|
111
|
+
const path = credentialsPath();
|
|
112
|
+
if (!existsSync(path)) return false;
|
|
113
|
+
try {
|
|
114
|
+
const st = statSync(path);
|
|
115
|
+
return (st.mode & 63) !== 0;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { canonicalHost, credentialsAreLooselyPermissioned, credentialsPath, getAccount, maskPat, readCredentials, removeAccount, saveAccount, writeCredentials };
|
package/dist/dashboard.html
CHANGED
|
@@ -337,20 +337,24 @@
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
function relayEventToIframe(ev) {
|
|
340
|
-
// ev
|
|
340
|
+
// ev arrives from the WS server as { kind:"event", event:"<name>", payload }
|
|
341
|
+
// (see server.ts: `ws.send({ kind: "event", ...ev })`). The SDK keys its
|
|
342
|
+
// handlers off `event` (e.g. "rpc.stream", "auth.refresh"), so we MUST
|
|
343
|
+
// forward `ev.event` — not `ev.kind`, which is always the literal
|
|
344
|
+
// string "event" and would drop every frame on the floor.
|
|
341
345
|
if (!iframe.contentWindow) return;
|
|
342
346
|
const env = {
|
|
343
347
|
wid: windowUuid,
|
|
344
348
|
kind: "event",
|
|
345
|
-
event: ev.
|
|
349
|
+
event: ev.event,
|
|
346
350
|
payload: ev.payload,
|
|
347
351
|
};
|
|
348
352
|
iframe.contentWindow.postMessage(env, "*");
|
|
349
353
|
logLine(
|
|
350
354
|
"event",
|
|
351
|
-
`← event <span class="pill">${escapeHtml(ev.
|
|
355
|
+
`← event <span class="pill">${escapeHtml(ev.event)}</span> ${escapeHtml(JSON.stringify(ev.payload))}`,
|
|
352
356
|
);
|
|
353
|
-
recPush("event", { event: ev.
|
|
357
|
+
recPush("event", { event: ev.event, payload: ev.payload });
|
|
354
358
|
}
|
|
355
359
|
|
|
356
360
|
// postMessage RPC bridge: iframe → POST /api/session/call → result
|