@stravica-ai/digital-operator-pa 0.1.27 → 0.1.29

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 (46) hide show
  1. package/bin/library +12 -0
  2. package/bin/library-tunnel +12 -0
  3. package/dist/engineering-pa-cli/bin/estate-sync +0 -0
  4. package/dist/engineering-pa-cli/bin/launch +0 -0
  5. package/dist/engineering-pa-cli/bin/pa +0 -0
  6. package/dist/engineering-pa-cli/bin/pa-coordinator-hook +0 -0
  7. package/dist/engineering-pa-cli/bin/research +0 -0
  8. package/dist/engineering-pa-cli/bin/scheduler +0 -0
  9. package/dist/engineering-pa-cli/bin/secrets +0 -0
  10. package/dist/engineering-pa-cli/bin/smoke +0 -0
  11. package/dist/engineering-pa-cli/bin/sync +0 -0
  12. package/dist/engineering-pa-cli/scripts/migrate-doc-paths.mjs +0 -0
  13. package/dist/engineering-pa-core/bin/desk +0 -0
  14. package/dist/engineering-pa-core/bin/distill +0 -0
  15. package/dist/engineering-pa-core/bin/memory-sync +0 -0
  16. package/dist/engineering-pa-core/bin/slice +0 -0
  17. package/dist/engineering-pa-library-cli/README.md +64 -0
  18. package/dist/engineering-pa-library-cli/bin/library +12 -0
  19. package/dist/engineering-pa-library-cli/package.json +21 -0
  20. package/dist/engineering-pa-library-cli/src/ack.mjs +64 -0
  21. package/dist/engineering-pa-library-cli/src/cli.mjs +142 -0
  22. package/dist/engineering-pa-library-cli/src/config.mjs +315 -0
  23. package/dist/engineering-pa-library-cli/src/envelope.mjs +142 -0
  24. package/dist/engineering-pa-library-cli/src/http.mjs +156 -0
  25. package/dist/engineering-pa-library-cli/src/pull-clones.mjs +74 -0
  26. package/dist/engineering-pa-library-cli/src/verbs/flush.mjs +136 -0
  27. package/dist/engineering-pa-library-cli/src/verbs/get.mjs +45 -0
  28. package/dist/engineering-pa-library-cli/src/verbs/put.mjs +69 -0
  29. package/dist/engineering-pa-library-cli/src/verbs/search.mjs +98 -0
  30. package/dist/engineering-pa-library-cli/src/verbs/status.mjs +124 -0
  31. package/dist/engineering-pa-library-tunnel/README.md +84 -0
  32. package/dist/engineering-pa-library-tunnel/bin/library-tunnel +12 -0
  33. package/dist/engineering-pa-library-tunnel/package.json +18 -0
  34. package/dist/engineering-pa-library-tunnel/src/check-verb.mjs +50 -0
  35. package/dist/engineering-pa-library-tunnel/src/check.mjs +102 -0
  36. package/dist/engineering-pa-library-tunnel/src/cli.mjs +155 -0
  37. package/dist/engineering-pa-library-tunnel/src/config.mjs +169 -0
  38. package/dist/engineering-pa-library-tunnel/src/install.mjs +106 -0
  39. package/dist/engineering-pa-library-tunnel/src/plist.mjs +119 -0
  40. package/dist/engineering-pa-library-tunnel/src/status.mjs +93 -0
  41. package/dist/engineering-pa-library-tunnel/src/uninstall.mjs +48 -0
  42. package/dist/engineering-pa-migrate-inspect/bin/migrate-inspect +0 -0
  43. package/dist/engineering-pa-scaffold/bin/pa-init +0 -0
  44. package/dist/engineering-pa-workspace/src/ws.mjs +0 -0
  45. package/index.mjs +4 -0
  46. package/package.json +10 -6
package/bin/library ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ // library - REMOTE-mode client CLI for the remote-library service.
3
+ // Bundled entry: delegates to the copied @stravica-ai/engineering-pa-library-cli sources.
4
+ import('../dist/engineering-pa-library-cli/src/cli.mjs').then((m) => {
5
+ return m.main(process.argv.slice(2));
6
+ }).then(
7
+ (code) => process.exit(code ?? 0),
8
+ (err) => {
9
+ process.stderr.write(`[library] ${err?.message ?? err}\n`);
10
+ process.exit(typeof err?.exitCode === 'number' ? err.exitCode : 2);
11
+ },
12
+ );
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ // library-tunnel - durable SSH tunnel manager for the remote-library ingest.
3
+ // Bundled entry: delegates to the copied @stravica-ai/engineering-pa-library-tunnel sources.
4
+ import('../dist/engineering-pa-library-tunnel/src/cli.mjs').then((m) => {
5
+ return m.main(process.argv.slice(2));
6
+ }).then(
7
+ (code) => process.exit(code ?? 0),
8
+ (err) => {
9
+ process.stderr.write(`[library-tunnel] ${err?.message ?? err}\n`);
10
+ process.exit(typeof err?.exitCode === 'number' ? err.exitCode : 2);
11
+ },
12
+ );
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,64 @@
1
+ # library-cli
2
+
3
+ REMOTE-mode client for the `@stravica-ai/remote-library` service (spec §3.3).
4
+ Ships the `library` bin with five verbs: `put`, `flush`, `get`, `search`, `status`.
5
+
6
+ Only active when `operator/config/preferences.json` has `library.mode: "remote"`
7
+ plus `library.remoteEndpoint` and `library.remoteKey`. SIMPLE-mode installs (or
8
+ unset `library.mode`) refuse every verb with a clean exit-1 message; the CLI is
9
+ deliberately not a shim for the pre-remote library sweep.
10
+
11
+ ## Verbs
12
+
13
+ - `library put <path> [--kind <kind>] [--tier auto|<tier>]`
14
+ Streams a single event to `POST /events`. `--tier auto` sends the spec §2.3
15
+ sentinel; the server's routing engine resolves the actual tier.
16
+ - `library flush`
17
+ Sweeps `<repoRoot>/agents/<paName>/staging/*` and streams one event per file.
18
+ Archives to `staging/_archived/` on `accepted` or `deduped` (both count as
19
+ ack per spec §4.2). Failures leave the file in place; next flush retries
20
+ (idempotent via `clientEventId`).
21
+ - `library get <path>`
22
+ Reads from the local pulled clone. `<path>` is resolved under
23
+ `LIBRARY_PULL_CLONES_ROOT` (env; else `library.pullClonesRoot` in
24
+ preferences; else `~/stravica-library-clones/`).
25
+ - `library search <query> [--files-only]`
26
+ Filesystem-native substring search over the pull-clones root.
27
+ - `library status [--json]`
28
+ GETs `/status` from the service, plus `git rev-list HEAD..origin/main --count`
29
+ per tier clone.
30
+
31
+ ## Configuration
32
+
33
+ - `operator/config/preferences.json` (base) and `preferences.local.json`
34
+ (device-local overlay; shallow-merged on top):
35
+
36
+ ```json
37
+ {
38
+ "library": {
39
+ "mode": "remote",
40
+ "remoteEndpoint": "https://ops-01.internal",
41
+ "remoteKey": "sk_barry_dave_deadbeef:secret"
42
+ }
43
+ }
44
+ ```
45
+
46
+ - `LIBRARY_PULL_CLONES_ROOT` env — overrides the pull-clones root at runtime.
47
+
48
+ Ingest authenticates by subkey alone (spec §3.4, main ruling 2026-08-20 option b);
49
+ no install-key header is sent. The former `REMOTE_LIBRARY_INSTALL_KEY` env
50
+ interim was removed in the same pass.
51
+
52
+ ## Exit codes
53
+
54
+ `0` ok · `1` usage / configuration error · `2` operational failure · `3` transport/IO failure.
55
+
56
+ ## Testing
57
+
58
+ ```sh
59
+ pnpm --dir operator/extensions/tools/library-cli test
60
+ ```
61
+
62
+ All tests are `node:test` and hit a locally-bound mock ingest server (no
63
+ network, no Mongo). Integration against the real docker-compose stack lands
64
+ with item 16.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ // library — REMOTE-mode client CLI for the remote-library service.
3
+ // See src/cli.mjs for the routing table and exit-code contract.
4
+
5
+ import { main } from '../src/cli.mjs';
6
+
7
+ main(process.argv.slice(2))
8
+ .then((code) => process.exit(code ?? 0))
9
+ .catch((err) => {
10
+ process.stderr.write(`[library] ${err?.message ?? err}\n`);
11
+ process.exit(typeof err?.exitCode === 'number' ? err.exitCode : 2);
12
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@stravica-ai/engineering-pa-library-cli",
3
+ "version": "0.1.1",
4
+ "description": "Local `library` CLI for the remote-library REMOTE-mode write path (spec §3.3): put | flush | get | search | status. SIMPLE mode installs refuse every verb with a clean not-applicable message.",
5
+ "type": "module",
6
+ "main": "./src/cli.mjs",
7
+ "exports": {
8
+ ".": "./src/cli.mjs"
9
+ },
10
+ "private": true,
11
+ "license": "MIT",
12
+ "engines": {
13
+ "node": ">=20"
14
+ },
15
+ "scripts": {
16
+ "test": "node --test test/*.test.mjs"
17
+ },
18
+ "dependencies": {
19
+ "@stravica-ai/remote-library": "^0.1.1"
20
+ }
21
+ }
@@ -0,0 +1,64 @@
1
+ // ack.mjs — parse the remote-library ingest `POST /events` response
2
+ // (single-event path) into a discriminated ack.
3
+ //
4
+ // Wire contract (SOURCE OF TRUTH: remote-library/src/ingest/server.ts
5
+ // as of 2026-08-20 rehearsal fix, RB-5). Locked on BOTH sides by
6
+ // library-cli/test/ingest-contract.test.mjs (this side) and the
7
+ // wire-shape assertions in remote-library/src/test/routing.test.ts
8
+ // (server side).
9
+ //
10
+ // Status 202 body {eventId: <ulid>, receivedAt: <iso8601>}
11
+ // -> newly-accepted event (worker will pick up on next tick).
12
+ // Status 200 body {eventId: <ulid>, deduped: true}
13
+ // -> clientEventId was already on record; return the eventId of
14
+ // the ORIGINAL insert so the producer sees the same anchor
15
+ // (spec §4.2). A retrying producer MUST see success and
16
+ // archive the source file, or the staging directory grows
17
+ // unboundedly across retries.
18
+ // Status 4xx / 5xx with {error: <code>, detail?: <string>, errors?}
19
+ // -> rejected. The `error` code is the taxonomy from
20
+ // ingest/server.ts::statusCodeForRejection (e.g.
21
+ // `no-matching-rule`, `subkey-not-permitted`, `schema-invalid`).
22
+ // Callers surface it verbatim.
23
+
24
+ /**
25
+ * @typedef {{kind: 'accepted', eventId: string}
26
+ * | {kind: 'deduped', eventId: string}
27
+ * | {kind: 'rejected', reason: string}} IngestAck
28
+ */
29
+
30
+ /**
31
+ * @param {{httpStatus: number, body: unknown}} res
32
+ * @returns {IngestAck}
33
+ */
34
+ export function parseIngestAck(res) {
35
+ const { httpStatus, body } = res;
36
+ const isObj = body !== null && typeof body === 'object';
37
+
38
+ if (httpStatus === 202 && isObj) {
39
+ const eventId = /** @type {any} */ (body).eventId;
40
+ if (typeof eventId === 'string' && eventId.length > 0) {
41
+ return { kind: 'accepted', eventId };
42
+ }
43
+ return { kind: 'rejected', reason: `http 202 without eventId: ${JSON.stringify(body)}` };
44
+ }
45
+
46
+ if (httpStatus === 200 && isObj) {
47
+ const b = /** @type {any} */ (body);
48
+ if (b.deduped === true && typeof b.eventId === 'string' && b.eventId.length > 0) {
49
+ return { kind: 'deduped', eventId: b.eventId };
50
+ }
51
+ return { kind: 'rejected', reason: `http 200 without deduped ack shape: ${JSON.stringify(body)}` };
52
+ }
53
+
54
+ // Any other status is a rejection. Prefer the server's error code +
55
+ // detail; fall back to a JSON-encoded body so no signal is lost.
56
+ if (isObj) {
57
+ const b = /** @type {any} */ (body);
58
+ if (typeof b.error === 'string') {
59
+ const detail = typeof b.detail === 'string' ? `: ${b.detail}` : '';
60
+ return { kind: 'rejected', reason: `http ${httpStatus} ${b.error}${detail}` };
61
+ }
62
+ }
63
+ return { kind: 'rejected', reason: `http ${httpStatus}: ${JSON.stringify(body)}` };
64
+ }
@@ -0,0 +1,142 @@
1
+ // cli.mjs — argv dispatch for `library`.
2
+ //
3
+ // Exit code contract (tooling standard §6):
4
+ // 0 ok | 1 usage error | 2 operational failure | 3 transport/IO failure
5
+ //
6
+ // Modes:
7
+ // - SIMPLE mode installs (or unset library.mode) print a clean
8
+ // "not applicable in simple mode" message and exit 1. Spec §3.3
9
+ // is explicit that the verbs are REMOTE-mode only.
10
+ // - REMOTE mode with misconfig refuses to boot (spec §5.1); the
11
+ // error surface is the resolveLibraryConfig throw path.
12
+
13
+ import { parseArgs } from 'node:util';
14
+
15
+ import { loadLibraryConfig, LibraryConfigError } from './config.mjs';
16
+ import { runPut } from './verbs/put.mjs';
17
+ import { runFlush } from './verbs/flush.mjs';
18
+ import { runGet } from './verbs/get.mjs';
19
+ import { runSearch } from './verbs/search.mjs';
20
+ import { runStatus } from './verbs/status.mjs';
21
+
22
+ const USAGE = `library — REMOTE-mode client for the remote-library service.
23
+
24
+ Usage:
25
+ library put <path> [--kind <kind>] [--tier auto|<tier>]
26
+ library flush
27
+ library get <path>
28
+ library search <query> [--files-only]
29
+ library status [--json]
30
+ library -h | --help
31
+
32
+ Reads config from operator/config/preferences.json (+ preferences.local.json overlay).
33
+ Verbs are only active in REMOTE mode. SIMPLE mode exits 1 with a clean message.
34
+ `;
35
+
36
+ /**
37
+ * @param {string[]} argv process.argv.slice(2)
38
+ * @param {object} [seams] test seams (fetchImpl, git, log, err, repoRoot)
39
+ * @returns {Promise<number>} exit code
40
+ */
41
+ export async function main(argv, seams = {}) {
42
+ const log = seams.log ?? ((...m) => process.stdout.write(m.join(' ') + '\n'));
43
+ const err = seams.err ?? ((...m) => process.stderr.write(m.join(' ') + '\n'));
44
+
45
+ if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
46
+ log(USAGE);
47
+ return 0;
48
+ }
49
+
50
+ const verb = argv[0];
51
+ const rest = argv.slice(1);
52
+
53
+ let config;
54
+ try {
55
+ config = loadLibraryConfig({ repoRoot: seams.repoRoot });
56
+ } catch (e) {
57
+ if (e instanceof LibraryConfigError) {
58
+ err(`[library] ${e.message}`);
59
+ return e.exitCode ?? 1;
60
+ }
61
+ throw e;
62
+ }
63
+
64
+ if (config.mode === 'simple') {
65
+ err(
66
+ `[library] not applicable in simple mode (library.mode="simple"). ` +
67
+ `The library CLI is a REMOTE-mode client only (spec §3.3). ` +
68
+ `Set library.mode="remote" in operator/config/preferences.json (with ` +
69
+ `library.remoteEndpoint + library.remoteKey) to enable.`,
70
+ );
71
+ return 1;
72
+ }
73
+
74
+ const ctx = {
75
+ config,
76
+ fetchImpl: seams.fetchImpl,
77
+ log,
78
+ err,
79
+ git: seams.git,
80
+ };
81
+
82
+ try {
83
+ switch (verb) {
84
+ case 'put': return await runPut({ ...ctx, args: parsePut(rest) });
85
+ case 'flush': return await runFlush({ ...ctx, args: parseFlush(rest) });
86
+ case 'get': return await runGet({ ...ctx, args: parseGet(rest) });
87
+ case 'search': return await runSearch({ ...ctx, args: parseSearch(rest) });
88
+ case 'status': return await runStatus({ ...ctx, args: parseStatus(rest) });
89
+ default:
90
+ err(`[library] unknown verb: ${verb}`);
91
+ log(USAGE);
92
+ return 1;
93
+ }
94
+ } catch (e) {
95
+ err(`[library ${verb}] ${e.message ?? e}`);
96
+ return typeof e.exitCode === 'number' ? e.exitCode : 2;
97
+ }
98
+ }
99
+
100
+ function parsePut(argv) {
101
+ const { values, positionals } = parseArgs({
102
+ args: argv,
103
+ strict: true,
104
+ allowPositionals: true,
105
+ options: {
106
+ kind: { type: 'string' },
107
+ tier: { type: 'string', default: 'auto' },
108
+ },
109
+ });
110
+ return { path: positionals[0], kind: values.kind, tier: values.tier };
111
+ }
112
+ function parseFlush(argv) {
113
+ parseArgs({ args: argv, strict: true, allowPositionals: true, options: {} });
114
+ return {};
115
+ }
116
+ function parseGet(argv) {
117
+ const { positionals } = parseArgs({
118
+ args: argv,
119
+ strict: true,
120
+ allowPositionals: true,
121
+ options: {},
122
+ });
123
+ return { path: positionals[0] };
124
+ }
125
+ function parseSearch(argv) {
126
+ const { values, positionals } = parseArgs({
127
+ args: argv,
128
+ strict: true,
129
+ allowPositionals: true,
130
+ options: { 'files-only': { type: 'boolean', default: false } },
131
+ });
132
+ return { query: positionals[0], filesOnly: values['files-only'] };
133
+ }
134
+ function parseStatus(argv) {
135
+ const { values } = parseArgs({
136
+ args: argv,
137
+ strict: true,
138
+ allowPositionals: false,
139
+ options: { json: { type: 'boolean', default: false } },
140
+ });
141
+ return { json: values.json };
142
+ }