@stravica-ai/digital-operator-pa 0.1.52 → 0.1.54
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/dist/engineering-pa-cli/src/environment/probes.mjs +22 -33
- package/dist/engineering-pa-cli/src/pa/cli.mjs +43 -51
- package/dist/engineering-pa-cli/src/scheduler/cli.mjs +7 -2
- package/dist/engineering-pa-cli/src/secrets/cli.mjs +18 -16
- package/dist/engineering-pa-cli/src/secrets/commands/get.mjs +6 -1
- package/dist/engineering-pa-cli/src/secrets/commands/set.mjs +3 -3
- package/dist/engineering-pa-cli/src/secrets/commands/status.mjs +17 -0
- package/dist/engineering-pa-cli/src/secrets/vault.mjs +90 -52
- package/dist/engineering-pa-core/docs/commands/onboard.md +69 -40
- package/dist/engineering-pa-core/roles/agentic-workflow-designer/definition.md +53 -6
- package/dist/engineering-pa-core/src/capabilities/registry.mjs +88 -0
- package/dist/engineering-pa-core/src/home/resolve.mjs +48 -0
- package/dist/engineering-pa-core/src/scheduler/install.mjs +73 -25
- package/dist/engineering-pa-core/src/scheduler/list.mjs +12 -2
- package/dist/engineering-pa-core/src/scheduler/remove.mjs +123 -18
- package/dist/engineering-pa-core/src/scheduler/verify.mjs +2 -1
- package/dist/engineering-pa-core/src/vault/require.mjs +13 -3
- package/dist/engineering-pa-core/src/workspace/resolve.mjs +184 -0
- package/dist/engineering-pa-scaffold/src/generator.mjs +74 -7
- package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +17 -0
- package/dist/engineering-pa-workspace/app/nav.json +6 -0
- package/dist/engineering-pa-workspace/app/notes.html +16 -3
- package/dist/engineering-pa-workspace/app/review-annotator.js +1390 -0
- package/dist/engineering-pa-workspace/app/reviews.html +551 -0
- package/dist/engineering-pa-workspace/src/api.mjs +49 -15
- package/dist/engineering-pa-workspace/src/review/api-handler.mjs +836 -0
- package/dist/engineering-pa-workspace/src/review/page-key.mjs +92 -0
- package/dist/engineering-pa-workspace/src/server.mjs +34 -22
- package/dist/engineering-pa-workspace/src/ws.mjs +47 -0
- package/docs/onboarding-solo.md +6 -0
- package/package.json +3 -2
|
@@ -11,6 +11,7 @@ import { basename, join, resolve, dirname } from 'node:path';
|
|
|
11
11
|
import { resolve4 } from 'node:dns/promises';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { readPreferencesSync } from '../../../engineering-pa-core/src/preferences.mjs';
|
|
14
|
+
import { resolveWorkspace as coreResolveWorkspace } from '../../../engineering-pa-core/src/workspace/resolve.mjs';
|
|
14
15
|
|
|
15
16
|
const execFileP = promisify(execFile);
|
|
16
17
|
|
|
@@ -204,45 +205,33 @@ async function withTimeout(promise, ms) {
|
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
/**
|
|
207
|
-
* Workspace port +
|
|
208
|
-
*
|
|
209
|
-
*
|
|
208
|
+
* Workspace port + identity, resolved through the ONE shared resolver
|
|
209
|
+
* in @stravica-ai/engineering-pa-core/src/workspace/resolve.mjs (0.1.54
|
|
210
|
+
* fix, w-2026-09-21-dave-002, F6/F7b). The 0.1.53 probe read a bare TCP
|
|
211
|
+
* connect on the default port and called any accepting listener
|
|
212
|
+
* `running` — which reported Docker on 4200 as our workspace and made
|
|
213
|
+
* every viewer link the PA handed out wrong. The shared resolver reads
|
|
214
|
+
* the persisted `workspacePort`, honours the running-port pidfile, and
|
|
215
|
+
* identifies the incumbent via `/__api/identity` before reporting
|
|
216
|
+
* `running`.
|
|
210
217
|
*/
|
|
211
218
|
export async function probeWorkspace(repoRoot) {
|
|
212
|
-
const
|
|
213
|
-
const net = await import('node:net');
|
|
214
|
-
const running = await new Promise((res) => {
|
|
215
|
-
const socket = new net.Socket();
|
|
216
|
-
let done = false;
|
|
217
|
-
const finish = (value) => {
|
|
218
|
-
if (done) return;
|
|
219
|
-
done = true;
|
|
220
|
-
try { socket.destroy(); } catch { /* ignore */ }
|
|
221
|
-
res(value);
|
|
222
|
-
};
|
|
223
|
-
socket.setTimeout(1000);
|
|
224
|
-
socket.once('connect', () => finish(true));
|
|
225
|
-
socket.once('error', () => finish(false));
|
|
226
|
-
socket.once('timeout', () => finish(false));
|
|
227
|
-
socket.connect(port, '127.0.0.1');
|
|
228
|
-
});
|
|
219
|
+
const resolved = await coreResolveWorkspace({ repoRoot });
|
|
229
220
|
return {
|
|
230
|
-
port,
|
|
231
|
-
|
|
232
|
-
|
|
221
|
+
port: resolved.effectivePort,
|
|
222
|
+
configuredPort: resolved.configuredPort,
|
|
223
|
+
runningPort: resolved.runningPort,
|
|
224
|
+
// Legacy boolean kept for callers that still read `running`. True
|
|
225
|
+
// only when identity matched — a foreign process on the port is
|
|
226
|
+
// never running as far as this probe is concerned.
|
|
227
|
+
running: resolved.state === 'running',
|
|
228
|
+
state: resolved.state,
|
|
229
|
+
identity: resolved.identity,
|
|
230
|
+
detail: resolved.detail,
|
|
231
|
+
url: resolved.url,
|
|
233
232
|
};
|
|
234
233
|
}
|
|
235
234
|
|
|
236
|
-
function readWorkspacePort(repoRoot) {
|
|
237
|
-
try {
|
|
238
|
-
const prefs = readPreferencesSync(repoRoot);
|
|
239
|
-
if (prefs && typeof prefs.workspacePort === 'number') return prefs.workspacePort;
|
|
240
|
-
} catch {
|
|
241
|
-
// fall through
|
|
242
|
-
}
|
|
243
|
-
return 4200;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
235
|
/**
|
|
247
236
|
* Working directory + project root resolution. Search-upward for an
|
|
248
237
|
* `agents/<name>/CLAUDE.md` marker or a `.git` ancestor. Env var override
|
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
} from '../../../engineering-pa-core/src/distill/health.mjs';
|
|
18
18
|
import { vaultState } from '../../../engineering-pa-core/src/vault/require.mjs';
|
|
19
19
|
import { enabledToolsNeedingSecrets } from '../../../engineering-pa-core/src/vault/tool-needs.mjs';
|
|
20
|
+
import {
|
|
21
|
+
resolveWorkspace as coreResolveWorkspace,
|
|
22
|
+
formatIncumbentClause,
|
|
23
|
+
} from '../../../engineering-pa-core/src/workspace/resolve.mjs';
|
|
20
24
|
import { resolveDefaultScope } from '../secrets/vault.mjs';
|
|
21
25
|
import { main as runMigrateInspect } from '../../../engineering-pa-migrate-inspect/src/cli.mjs';
|
|
22
26
|
import { doOnboard } from './onboard.mjs';
|
|
@@ -184,23 +188,32 @@ async function doStatus(opts = {}) {
|
|
|
184
188
|
|
|
185
189
|
// 3. Workspace reachability probe.
|
|
186
190
|
//
|
|
187
|
-
// 0.1.
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
191
|
+
// 0.1.54 (w-2026-09-21-dave-002, A2/A3): resolved through the ONE
|
|
192
|
+
// shared workspace resolver in core/workspace/resolve.mjs so `pa
|
|
193
|
+
// status`, `pa environment` and `pa workspace start`'s auto-probe
|
|
194
|
+
// all report the same fact. The 0.1.53 F1 finding — the "nothing of
|
|
195
|
+
// yours to reclaim" clause landed only on `workspace start`'s
|
|
196
|
+
// hard-fail branch and never on `pa status` — is closed by rendering
|
|
197
|
+
// the clause here whenever the incumbent is not ours.
|
|
192
198
|
out.write('\nWorkspace:\n');
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
const probeUrl = `http://localhost:${effectivePort}/__api/identity`;
|
|
196
|
-
const reachability = await probeWorkspace(probeUrl, opts.fetchImpl);
|
|
199
|
+
const resolved = await coreResolveWorkspace({ repoRoot, fetchImpl: opts.fetchImpl });
|
|
200
|
+
const probeUrl = `${resolved.url}/__api/identity`;
|
|
197
201
|
out.write(` ${probeUrl}\n`);
|
|
198
|
-
if (runningPort !== null && runningPort !==
|
|
199
|
-
out.write(` running port: ${runningPort} (configured: ${
|
|
202
|
+
if (resolved.runningPort !== null && resolved.runningPort !== resolved.configuredPort) {
|
|
203
|
+
out.write(` running port: ${resolved.runningPort} (configured: ${resolved.configuredPort})\n`);
|
|
200
204
|
}
|
|
201
|
-
|
|
202
|
-
|
|
205
|
+
const statusLabel = resolved.state === 'running'
|
|
206
|
+
? 'reachable'
|
|
207
|
+
: resolved.state === 'other-process'
|
|
208
|
+
? 'in use by another process'
|
|
209
|
+
: 'not running';
|
|
210
|
+
out.write(` status: ${statusLabel}`);
|
|
211
|
+
if (resolved.detail) out.write(` (${resolved.detail})`);
|
|
203
212
|
out.write('\n');
|
|
213
|
+
const incumbent = formatIncumbentClause(resolved);
|
|
214
|
+
if (incumbent) {
|
|
215
|
+
out.write(` Note: ${incumbent}.\n`);
|
|
216
|
+
}
|
|
204
217
|
// Workspace-not-running is OK — operators may run pa status before
|
|
205
218
|
// starting the workspace server.
|
|
206
219
|
|
|
@@ -323,48 +336,27 @@ function resolveWorkspacePort(repoRoot, opts = {}) {
|
|
|
323
336
|
* (see packages/workspace/src/api.mjs). Any other body on the port means
|
|
324
337
|
* the port is bound by an unrelated process, not our workspace.
|
|
325
338
|
*/
|
|
326
|
-
|
|
339
|
+
import { probeWorkspaceIdentity, WORKSPACE_IDENTITY_SERVER as CORE_WORKSPACE_IDENTITY_SERVER } from '../../../engineering-pa-core/src/workspace/resolve.mjs';
|
|
340
|
+
const WORKSPACE_IDENTITY_SERVER = CORE_WORKSPACE_IDENTITY_SERVER;
|
|
327
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Legacy signature retained for existing tests. All new callers go via
|
|
344
|
+
* `coreResolveWorkspace` in packages/core/src/workspace/resolve.mjs.
|
|
345
|
+
* The `{status, detail}` shape is preserved for the WSD-laptop probe
|
|
346
|
+
* tests; internally it delegates to the shared identity probe.
|
|
347
|
+
*/
|
|
328
348
|
async function probeWorkspace(url, fetchImpl) {
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
331
|
-
return { status: '
|
|
349
|
+
const probe = await probeWorkspaceIdentity(url, { fetchImpl });
|
|
350
|
+
if (probe.state === 'running') {
|
|
351
|
+
return { status: 'reachable', detail: probe.detail };
|
|
332
352
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// before the probe reports "reachable". WSD-laptop cold install of
|
|
339
|
-
// 0.1.41 (2026-09-16): a docker container bound to :4200 answered
|
|
340
|
-
// /__api/identity with HTTP 404 and `pa status` reported the
|
|
341
|
-
// workspace "reachable (HTTP 404)". Identity-gate the probe so a
|
|
342
|
-
// foreign process on the port surfaces as "in use by another
|
|
343
|
-
// process" instead of a false green.
|
|
344
|
-
if (res.status >= 200 && res.status < 500) {
|
|
345
|
-
let body = null;
|
|
346
|
-
try {
|
|
347
|
-
body = await res.json();
|
|
348
|
-
} catch { /* not JSON — falls through to the identity-mismatch branch */ }
|
|
349
|
-
if (body && body.server === WORKSPACE_IDENTITY_SERVER) {
|
|
350
|
-
return { status: 'reachable', detail: `HTTP ${res.status}` };
|
|
351
|
-
}
|
|
352
|
-
return {
|
|
353
|
-
status: 'in use by another process',
|
|
354
|
-
detail: `HTTP ${res.status}, /__api/identity did not identify as ${WORKSPACE_IDENTITY_SERVER}`,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
return { status: 'error', detail: `HTTP ${res.status}` };
|
|
358
|
-
} catch (e) {
|
|
359
|
-
// ECONNREFUSED / abort / DNS — server is not running.
|
|
360
|
-
const code = e && (e.cause?.code || e.code);
|
|
361
|
-
if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || e?.name === 'AbortError') {
|
|
362
|
-
return { status: 'not running', detail: null };
|
|
363
|
-
}
|
|
364
|
-
return { status: 'error', detail: e?.message || String(e) };
|
|
365
|
-
} finally {
|
|
366
|
-
clearTimeout(timer);
|
|
353
|
+
if (probe.state === 'other-process') {
|
|
354
|
+
return { status: 'in use by another process', detail: probe.detail };
|
|
355
|
+
}
|
|
356
|
+
if (probe.detail === null) {
|
|
357
|
+
return { status: 'not running', detail: null };
|
|
367
358
|
}
|
|
359
|
+
return { status: probe.detail && probe.detail.startsWith('HTTP') ? 'error' : 'error', detail: probe.detail };
|
|
368
360
|
}
|
|
369
361
|
|
|
370
362
|
export { doStatus, probeWorkspace, resolveWorkspacePort };
|
|
@@ -25,8 +25,13 @@ Usage:
|
|
|
25
25
|
Install a schedule onto its declared backend
|
|
26
26
|
scheduler list [--backend <name>] [--agent <name>]
|
|
27
27
|
List installed schedules
|
|
28
|
-
scheduler remove <id
|
|
29
|
-
Remove an installed schedule
|
|
28
|
+
scheduler remove <id|com.<agent>.<id>> [--backend N] [--agent <name>] [--delete-file]
|
|
29
|
+
Remove an installed schedule. --backend is
|
|
30
|
+
optional: when omitted, the id is looked up in
|
|
31
|
+
schedules/<id>.json and the file's own backend
|
|
32
|
+
field wins (OS-native detect is the fallback).
|
|
33
|
+
The label form the list verb prints as a
|
|
34
|
+
secondary line is accepted here directly.
|
|
30
35
|
scheduler verify <schedule.json> Verify a schedule fires correctly
|
|
31
36
|
scheduler -h, --help Show this help
|
|
32
37
|
|
|
@@ -19,7 +19,7 @@ import { runFind } from './commands/find.mjs';
|
|
|
19
19
|
import { runVerify } from './commands/verify.mjs';
|
|
20
20
|
import { resolveScopePaths, VaultError } from './vault.mjs';
|
|
21
21
|
|
|
22
|
-
const HELP = `secrets
|
|
22
|
+
const HELP = `secrets: SOPS+age secrets management
|
|
23
23
|
|
|
24
24
|
Usage:
|
|
25
25
|
secrets init Initialise the vault (generate keys, create empty secrets file).
|
|
@@ -27,7 +27,8 @@ Usage:
|
|
|
27
27
|
--stdin-json: after init, seed KEYs from a JSON object
|
|
28
28
|
read on stdin (values never echoed; refuses a TTY stdin).
|
|
29
29
|
secrets get KEY Retrieve a secret (add --exists for a present/absent check that never prints the value)
|
|
30
|
-
secrets set KEY
|
|
30
|
+
secrets set KEY - Read the value from stdin. Safe form: keeps the value out of shell history and argv (0.1.54, w-2026-09-21-dave-002, D4)
|
|
31
|
+
secrets set KEY VALUE Store or update a secret (the argv form is retained but records the value in shell history and the process listing)
|
|
31
32
|
Since 0.1.20, canonical-first dual-write is
|
|
32
33
|
the DEFAULT for scoped writes when a canonical
|
|
33
34
|
clone for the scope is discoverable on this
|
|
@@ -45,7 +46,7 @@ Usage:
|
|
|
45
46
|
secrets status Check vault health and report key info
|
|
46
47
|
secrets backup Show QR code of master key for backup (opens browser)
|
|
47
48
|
secrets vaults List every vault visible from the workspace
|
|
48
|
-
(scope, layout, key count, path
|
|
49
|
+
(scope, layout, key count, path; no decryption)
|
|
49
50
|
secrets find KEY Report which vault(s) contain KEY by name
|
|
50
51
|
(alias: which; names/paths only, never values;
|
|
51
52
|
exits 1 when not found)
|
|
@@ -53,7 +54,7 @@ Usage:
|
|
|
53
54
|
Byte-level comparison of DECRYPTED values
|
|
54
55
|
between this vault and a reference vault at
|
|
55
56
|
<path>. Reports per-key same/differs/missing.
|
|
56
|
-
--values is required
|
|
57
|
+
--values is required; decrypt-only is never
|
|
57
58
|
a valid pass (see below). Never prints values.
|
|
58
59
|
|
|
59
60
|
Age private key resolution (0.1.51, w-2026-09-18-dave-007). One shared
|
|
@@ -75,13 +76,13 @@ SOPS config: .vault/.sops.yaml (committed to repo)
|
|
|
75
76
|
Workspace resolution (all verbs), in order:
|
|
76
77
|
1. Walk up from the current directory for a vault-bearing workspace
|
|
77
78
|
(.vault/ or vault/*.enc.json). A git repo WITHOUT a vault does not
|
|
78
|
-
stop resolution
|
|
79
|
+
stop resolution; it falls through to the next tiers with a stderr
|
|
79
80
|
notice naming the store actually targeted.
|
|
80
81
|
2. $DAVE_ROOT (or $DAVE_REPO_ROOT / $PA_REPO_ROOT).
|
|
81
82
|
3. "workspaceRoot" in ~/.config/stravica-pa/defaults.json (written by
|
|
82
83
|
you or provisioning; the CLI never creates it).
|
|
83
84
|
4. Error listing what was tried.
|
|
84
|
-
NOTE: this precedence is a change
|
|
85
|
+
NOTE: this precedence is a change; the environment variable used to be
|
|
85
86
|
checked BEFORE the directory walk. Standing inside a workspace now
|
|
86
87
|
always targets THAT workspace, even when $DAVE_ROOT points elsewhere.
|
|
87
88
|
Exception: 'secrets init' inside a vault-less git repo initialises the
|
|
@@ -93,7 +94,7 @@ Scoped vaults:
|
|
|
93
94
|
up to the repo root and checking, in order:
|
|
94
95
|
.vault/scopes/NAME.enc.json (workspace layout)
|
|
95
96
|
vault/NAME.enc.json (canonical layout)
|
|
96
|
-
Both present is an error
|
|
97
|
+
Both present is an error; pass --file to
|
|
97
98
|
disambiguate. Supported on: get, set, list,
|
|
98
99
|
delete, rotate, status, vaults, find.
|
|
99
100
|
--file PATH Bypass scope resolution and operate directly on
|
|
@@ -145,38 +146,38 @@ Default scope (get/set only, when neither --scope nor --file is given):
|
|
|
145
146
|
in order:
|
|
146
147
|
1. $SECRETS_DEFAULT_SCOPE env var.
|
|
147
148
|
2. "defaultScope" in ~/.config/stravica-pa/defaults.json (same file
|
|
148
|
-
as "workspaceRoot" above; written by you or provisioning
|
|
149
|
+
as "workspaceRoot" above; written by you or provisioning; the
|
|
149
150
|
CLI never creates it).
|
|
150
151
|
3. The calling PA's own agents/<name>/identity.json "defaultScope"
|
|
151
152
|
field, found by walking up from the current directory (or, for
|
|
152
153
|
Dave specifically, $DAVE_ROOT/agents/dave/identity.json). A
|
|
153
154
|
malformed identity.json is a stderr warning, not a crash.
|
|
154
|
-
4. Legacy fallback: the single-file .vault/ vault
|
|
155
|
+
4. Legacy fallback: the single-file .vault/ vault; the notice says
|
|
155
156
|
so explicitly ("no scope specified and no default configured").
|
|
156
157
|
Example notices:
|
|
157
158
|
note: writing to 'hq-estate' pot (default from SECRETS_DEFAULT_SCOPE env var)
|
|
158
159
|
note: writing to 'wsd' pot (default from /home/dex/agents/barry/dex/identity.json)
|
|
159
|
-
note: writing to '(legacy)' pot
|
|
160
|
+
note: writing to '(legacy)' pot (no scope specified and no default configured). Use --scope <name> or configure a default.
|
|
160
161
|
|
|
161
162
|
Canonical-first writes (set only):
|
|
162
|
-
Canonical-first dual-write is DEFAULT for scoped writes since 0.1.20
|
|
163
|
+
Canonical-first dual-write is DEFAULT for scoped writes since 0.1.20:
|
|
163
164
|
a plain 'secrets set' on a scoped vault writes canonical FIRST and
|
|
164
165
|
workspace SECOND whenever a canonical clone for the scope is
|
|
165
166
|
discoverable on this host. Discovery order, first hit wins:
|
|
166
167
|
1. Explicit --canonical-clone PATH (always wins, bootstraps a
|
|
167
168
|
missing file).
|
|
168
|
-
2. "canonicalClone" in ~/.config/stravica-pa/defaults.json
|
|
169
|
+
2. "canonicalClone" in ~/.config/stravica-pa/defaults.json, in one of two shapes:
|
|
169
170
|
object (scope → file map):
|
|
170
171
|
{ "canonicalClone": { "seo-ops": "/abs/vault/seo-ops.enc.json" } }
|
|
171
172
|
string (root dir), derives <root>/vault/<scope>.enc.json:
|
|
172
173
|
{ "canonicalClone": "/Users/thefoot/stravica-canonical/stravica-operator-barry" }
|
|
173
174
|
3. Estate convention: ~/stravica-canonical/*/vault/<scope>.enc.json
|
|
174
175
|
(exactly one match; ambiguity or zero matches skips this tier).
|
|
175
|
-
Absence is a NOTICE, never a failure
|
|
176
|
+
Absence is a NOTICE, never a failure; a host with no canonical layout
|
|
176
177
|
keeps writing to workspace only, with a one-line stderr line naming why
|
|
177
178
|
nothing resolved. If any canonical write fails (existing store
|
|
178
179
|
undecryptable, sops encrypt failure), the command aborts with exit 2
|
|
179
|
-
BEFORE touching the workspace copy
|
|
180
|
+
BEFORE touching the workspace copy; the ordering invariant is
|
|
180
181
|
identical to the flag path since it always was.
|
|
181
182
|
|
|
182
183
|
--canonical-clone PATH Force PATH as the canonical target (wins over
|
|
@@ -187,13 +188,13 @@ Canonical-first writes (set only):
|
|
|
187
188
|
Value-compare verification:
|
|
188
189
|
secrets verify --values --against <path>
|
|
189
190
|
Decrypts BOTH the resolved vault and the vault at <path>, then
|
|
190
|
-
compares every key's VALUE byte-for-byte
|
|
191
|
+
compares every key's VALUE byte-for-byte; never decrypt-only. A
|
|
191
192
|
vault clobbered by an always-take-canonical materialisation still
|
|
192
193
|
decrypts cleanly with placeholder/stale values (same recipients);
|
|
193
194
|
only a value comparison against a known-good reference catches that.
|
|
194
195
|
Reports per-key same/differs/missing-in-working/missing-in-reference.
|
|
195
196
|
Exits 2 if any key differs or is missing on either side. Never
|
|
196
|
-
prints a decrypted value
|
|
197
|
+
prints a decrypted value; only the comparison result per key name.
|
|
197
198
|
`;
|
|
198
199
|
|
|
199
200
|
const USAGE = `Usage: secrets {init|get|set|list|delete|rotate|status|backup|vaults|find|verify} [KEY] [VALUE]
|
|
@@ -201,6 +202,7 @@ const USAGE = `Usage: secrets {init|get|set|list|delete|rotate|status|backup|vau
|
|
|
201
202
|
Commands:
|
|
202
203
|
init Initialise the vault (generate keys, create config)
|
|
203
204
|
get KEY Retrieve a secret value
|
|
205
|
+
set KEY - Read the value from stdin (safe form; 0.1.54)
|
|
204
206
|
set KEY VALUE Store or update a secret (canonical-first by default; --no-canonical to opt out)
|
|
205
207
|
list List all secret key names
|
|
206
208
|
delete KEY Remove a secret
|
|
@@ -30,7 +30,12 @@ export async function runGet(args, overrides = {}) {
|
|
|
30
30
|
// resolveCommandPaths prints the interim stderr notice (naming the
|
|
31
31
|
// target vault) whenever --scope/--file wasn't given upstream in
|
|
32
32
|
// cli.mjs. See vault.mjs's default-scope resolution section.
|
|
33
|
-
|
|
33
|
+
//
|
|
34
|
+
// 0.1.54 fix (w-2026-09-21-dave-002, D4): suppress the legacy-pot
|
|
35
|
+
// notice on value-blind reads. `--exists` is scripted-friendly and
|
|
36
|
+
// an interactive operator running `get KEY --exists` doesn't need
|
|
37
|
+
// the routing tip on every call.
|
|
38
|
+
const paths = resolveCommandPaths({ ...overrides, suppressLegacyNotice: exists });
|
|
34
39
|
const key = rest[0];
|
|
35
40
|
|
|
36
41
|
if (!key) {
|
|
@@ -65,7 +65,7 @@ export async function runSet(args, overrides = {}) {
|
|
|
65
65
|
let value = rest[1];
|
|
66
66
|
|
|
67
67
|
if (!key || value == null || value === '') {
|
|
68
|
-
console.error('Usage: secrets set KEY
|
|
68
|
+
console.error('Usage: secrets set KEY - [--canonical-clone PATH | --no-canonical] (reads the value from stdin; the safe form)\n secrets set KEY VALUE [--canonical-clone PATH | --no-canonical] (records the value in shell history and argv)');
|
|
69
69
|
throw new VaultError('usage', 1);
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -82,7 +82,7 @@ export async function runSet(args, overrides = {}) {
|
|
|
82
82
|
// legitimately contains it.
|
|
83
83
|
value = raw.replace(/\r?\n$/, '');
|
|
84
84
|
if (value === '') {
|
|
85
|
-
console.error('Empty value on stdin
|
|
85
|
+
console.error('Empty value on stdin; refusing to store an empty secret.');
|
|
86
86
|
throw new VaultError('empty stdin', 1);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -184,7 +184,7 @@ export async function runSet(args, overrides = {}) {
|
|
|
184
184
|
const tail = resolved.tried && resolved.tried.length > 0
|
|
185
185
|
? ` (tried: ${resolved.tried.join('; ')})`
|
|
186
186
|
: '';
|
|
187
|
-
console.error(`notice: no canonical clone configured for scope '${paths.scope}' on this host
|
|
187
|
+
console.error(`notice: no canonical clone configured for scope '${paths.scope}' on this host; workspace-only write${tail}`);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
sopsConfigExists,
|
|
14
14
|
sopsDecrypt,
|
|
15
15
|
} from '../vault.mjs';
|
|
16
|
+
import { resolveKeyFile as coreResolveKeyFile } from '../../../../engineering-pa-core/src/vault/require.mjs';
|
|
16
17
|
|
|
17
18
|
async function printDepVersions() {
|
|
18
19
|
console.log('');
|
|
@@ -42,6 +43,22 @@ export async function runStatus(overrides = {}) {
|
|
|
42
43
|
console.log('============');
|
|
43
44
|
console.log('');
|
|
44
45
|
|
|
46
|
+
// 0.1.54 (w-2026-09-21-dave-002, A1 diagnostic): print the resolver
|
|
47
|
+
// datapoint on one line so anyone hitting a decrypt-failure has the
|
|
48
|
+
// resolved keyFile + source + mode value-blind. Barry ruling
|
|
49
|
+
// 2026-09-21 (post-QA): the secrets surfaces make the datapoint
|
|
50
|
+
// visible without asking; the F4 root cause is only findable when
|
|
51
|
+
// every failing host reports the same shape.
|
|
52
|
+
try {
|
|
53
|
+
const keyResolved = coreResolveKeyFile({
|
|
54
|
+
repoRoot: paths.root,
|
|
55
|
+
home: paths.home,
|
|
56
|
+
env: {},
|
|
57
|
+
mode: 'auto',
|
|
58
|
+
});
|
|
59
|
+
console.log(`Key resolver: ${keyResolved.keyFile} (source: ${keyResolved.source}, mode: ${keyResolved.mode})`);
|
|
60
|
+
} catch { /* non-fatal; render nothing */ }
|
|
61
|
+
|
|
45
62
|
// Private key
|
|
46
63
|
if (keyFileExists(paths)) {
|
|
47
64
|
console.log(`Private key: ${paths.keyFile} (present)`);
|