@codyswann/lisa 4.13.0 → 4.13.2
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/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +7 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +3 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +16 -14
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +7 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +48 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-jam-access/SKILL.md +38 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +47 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-posthog-access/SKILL.md +38 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-sentry-access/SKILL.md +38 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-atlassian/SKILL.md +53 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-notion/SKILL.md +53 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-sonarcloud-access/SKILL.md +49 -7
- package/plugins/lisa/hooks/parity-safety-net.sh +7 -1
- package/plugins/lisa/rules/eager/falsifiable-checks.md +1 -1
- package/plugins/lisa/rules/reference/config-resolution.md +43 -6
- package/plugins/lisa/rules/reference/falsifiable-checks.md +1 -1
- package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +48 -6
- package/plugins/lisa/skills/lisa-jam-access/SKILL.md +38 -2
- package/plugins/lisa/skills/lisa-notion-access/SKILL.md +47 -6
- package/plugins/lisa/skills/lisa-posthog-access/SKILL.md +38 -2
- package/plugins/lisa/skills/lisa-sentry-access/SKILL.md +38 -2
- package/plugins/lisa/skills/lisa-setup-atlassian/SKILL.md +53 -7
- package/plugins/lisa/skills/lisa-setup-notion/SKILL.md +53 -7
- package/plugins/lisa/skills/lisa-sonarcloud-access/SKILL.md +49 -7
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +7 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +48 -6
- package/plugins/lisa-agy/skills/lisa-jam-access/SKILL.md +38 -2
- package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +47 -6
- package/plugins/lisa-agy/skills/lisa-posthog-access/SKILL.md +38 -2
- package/plugins/lisa-agy/skills/lisa-sentry-access/SKILL.md +38 -2
- package/plugins/lisa-agy/skills/lisa-setup-atlassian/SKILL.md +53 -7
- package/plugins/lisa-agy/skills/lisa-setup-notion/SKILL.md +53 -7
- package/plugins/lisa-agy/skills/lisa-sonarcloud-access/SKILL.md +49 -7
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +7 -1
- package/plugins/lisa-copilot/rules/eager/falsifiable-checks.md +1 -1
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +43 -6
- package/plugins/lisa-copilot/rules/reference/falsifiable-checks.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +48 -6
- package/plugins/lisa-copilot/skills/lisa-jam-access/SKILL.md +38 -2
- package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +47 -6
- package/plugins/lisa-copilot/skills/lisa-posthog-access/SKILL.md +38 -2
- package/plugins/lisa-copilot/skills/lisa-sentry-access/SKILL.md +38 -2
- package/plugins/lisa-copilot/skills/lisa-setup-atlassian/SKILL.md +53 -7
- package/plugins/lisa-copilot/skills/lisa-setup-notion/SKILL.md +53 -7
- package/plugins/lisa-copilot/skills/lisa-sonarcloud-access/SKILL.md +49 -7
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +7 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +43 -6
- package/plugins/lisa-cursor/rules/falsifiable-checks-reference.mdc +1 -1
- package/plugins/lisa-cursor/rules/falsifiable-checks.mdc +1 -1
- package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +48 -6
- package/plugins/lisa-cursor/skills/lisa-jam-access/SKILL.md +38 -2
- package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +47 -6
- package/plugins/lisa-cursor/skills/lisa-posthog-access/SKILL.md +38 -2
- package/plugins/lisa-cursor/skills/lisa-sentry-access/SKILL.md +38 -2
- package/plugins/lisa-cursor/skills/lisa-setup-atlassian/SKILL.md +53 -7
- package/plugins/lisa-cursor/skills/lisa-setup-notion/SKILL.md +53 -7
- package/plugins/lisa-cursor/skills/lisa-sonarcloud-access/SKILL.md +49 -7
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +7 -1
- package/plugins/src/base/rules/eager/falsifiable-checks.md +1 -1
- package/plugins/src/base/rules/reference/config-resolution.md +43 -6
- package/plugins/src/base/rules/reference/falsifiable-checks.md +1 -1
- package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +48 -6
- package/plugins/src/base/skills/lisa-jam-access/SKILL.md +38 -2
- package/plugins/src/base/skills/lisa-notion-access/SKILL.md +47 -6
- package/plugins/src/base/skills/lisa-posthog-access/SKILL.md +38 -2
- package/plugins/src/base/skills/lisa-sentry-access/SKILL.md +38 -2
- package/plugins/src/base/skills/lisa-setup-atlassian/SKILL.md +53 -7
- package/plugins/src/base/skills/lisa-setup-notion/SKILL.md +53 -7
- package/plugins/src/base/skills/lisa-sonarcloud-access/SKILL.md +49 -7
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +19 -20
|
@@ -752,7 +752,13 @@ while IFS= read -r rm_stmt; do
|
|
|
752
752
|
# arm A). Unquoted and run-leading invocations get the whole statement back,
|
|
753
753
|
# so this is a no-op for every real delete.
|
|
754
754
|
rm_assignment_stmt="$rm_stmt"
|
|
755
|
-
|
|
755
|
+
rm_walk_status=0
|
|
756
|
+
rm_walk_text="$(rm_scan_scope "$rm_stmt")" || rm_walk_status=$?
|
|
757
|
+
if [ "$rm_walk_status" -ne 0 ]; then
|
|
758
|
+
# The substitution above runs in a subshell. A denial inside it cannot stop
|
|
759
|
+
# this parent hook, so carry the failure out explicitly and deny here.
|
|
760
|
+
scan_failed "$rm_walk_status"
|
|
761
|
+
fi
|
|
756
762
|
while IFS= read -r rm_scope; do
|
|
757
763
|
set -f
|
|
758
764
|
seen_rm=0
|
|
@@ -34,7 +34,7 @@ Each has been observed in real runs; each reported success while asserting nothi
|
|
|
34
34
|
This form is preferred over the alternatives because it is the only one that both survives `sh` and keeps the full output, which is what you need the moment the status says the gate failed. `set -o pipefail` is correct where it exists but is **not POSIX `sh`** — dash has no such option, and this repository runs hooks under `sh`. `${PIPESTATUS[0]}` is **bash-only** (zsh spells it `$pipestatus[1]`, 1-indexed) and is overwritten by the very next command, so it must be read immediately. Never report a gate as green from a status read through `| tail`, `| head`, or `| tee`.
|
|
35
35
|
- **Falsify before reporting.** No clean result is reportable until the check has been shown to fail on a deliberate break. **"Mentally reverting" does not count** — reasoning that the assertion *would* fail is precisely the step that lets a non-functional guard ship, because the author already believes it is load-bearing. Run the break.
|
|
36
36
|
- **Mutation-prove the guard, then read the cardinality.** Neuter the protection **in production code**, run the **whole suite**, count the failures and **read their names**, then revert. **Zero** ⇒ the guard is inert (one pinning a single field shipped with all 50 tests green while the regression walked through). **Many, unrelated** ⇒ over-broad; its next failure will not name the cause. **Exactly one, or several all named for the same regression** ⇒ load-bearing and correctly scoped. A **zero is robust to contention** — load only ever adds failures — so only the non-zero counts need a quiet machine.
|
|
37
|
-
- **The automated mutation gate is language-scoped; the manual probe above is not.** Stryker instruments the
|
|
37
|
+
- **The automated mutation gate is language-scoped; the manual probe above is not.** Stryker instruments the JavaScript and TypeScript family plus Vue, HTML and Svelte — there is **no shell parser**, so a guard whose logic lives in `.sh` yields no mutants under **any** `mutate` list. Adding one does not measure it: it aborts the whole run (`No parser registered for .sh!` — measured, not inferred) and takes every other guard's score with it. So a mutation gate that stays grey or green on a shell-guard change **measured nothing about that guard**, and reading it as coverage is the fifth failure mode above wearing a config file. Shell guards are covered by **driving tests only**: execute the script against a payload table and assert the blocked/allowed verdict, with a control on **both** sides — a refusal case *and* an allowed case. A shell guard nothing executes has no bite evidence at all, whatever the gate printed.
|
|
38
38
|
- **Never scope the probe by filename, and never infer absent coverage from a grep.** Suites split at the line cap, so a protection's tests often sit in a sibling file: a filename-scoped probe reported cardinality 0 on a live security fix that a whole-suite run showed failing 4 tests, every one named for it.
|
|
39
39
|
- **Assert that no copy is wrong, not that some copy is right.** An existential assertion over duplicated content is satisfied by the correct copy and cannot see the broken one — rewriting one suite's assertions to universal negatives took the same mutation from **0 to 18** failures. Enumerate the property (*every* marker uses an em-dash), never the one known-bad spelling.
|
|
40
40
|
- **Reconstruct a red state; never narrate one you did not run.** Evidence describing a failing or dirty state must come from a run you actually observed — an evidence file once claimed "working tree clean" while listing two untracked scripts in the same file. To observe a state that no longer exists, check that commit out in a **throwaway detached-HEAD worktree** and run it there. Writing down what the output would have said is fabrication, however confident the reasoning.
|
|
@@ -871,9 +871,34 @@ read_notion_token() {
|
|
|
871
871
|
# Preferred path: the single secrets chokepoint, which owns the one-store rule
|
|
872
872
|
# and the surface ladder. An agent following this reference must try it before
|
|
873
873
|
# any keychain — a second reader is how one credential ends up in two places.
|
|
874
|
+
#
|
|
875
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
876
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
877
|
+
# wants used. The plugin rungs are the floor: `resolve-secret.mjs` ships
|
|
878
|
+
# beside the skill, so a rung pointing at it is reachable from anywhere the
|
|
879
|
+
# plugin itself is installed. Without one, a consumer repository that vendors
|
|
880
|
+
# none of the leading paths never reaches a resolver at all — and an agent
|
|
881
|
+
# copying a two-rung ladder out of THIS reference reproduces that.
|
|
882
|
+
local candidates=(
|
|
883
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
884
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
885
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
886
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
887
|
+
)
|
|
888
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
889
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
890
|
+
fi
|
|
891
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
892
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
893
|
+
fi
|
|
894
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
895
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
896
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
897
|
+
|
|
874
898
|
local resolver
|
|
875
|
-
|
|
876
|
-
|
|
899
|
+
local tried=()
|
|
900
|
+
for resolver in "${candidates[@]}"; do
|
|
901
|
+
tried+=("$resolver")
|
|
877
902
|
if [ -f "$resolver" ]; then
|
|
878
903
|
local via_lisa
|
|
879
904
|
via_lisa=$(node "$resolver" get NOTION_API_TOKEN 2>/dev/null) \
|
|
@@ -884,16 +909,17 @@ read_notion_token() {
|
|
|
884
909
|
# Legacy fallback: the OS keychain written by the guided setup flow, for
|
|
885
910
|
# projects with no credentials provider. Reached only when the chokepoint is
|
|
886
911
|
# absent or has no entry.
|
|
912
|
+
local from_keychain=""
|
|
887
913
|
case "$(uname -s)" in
|
|
888
|
-
Darwin) security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null ;;
|
|
914
|
+
Darwin) from_keychain=$(security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null) ;;
|
|
889
915
|
Linux) command -v secret-tool >/dev/null && \
|
|
890
|
-
secret-tool lookup service lisa-notion account "$workspace" 2>/dev/null ;;
|
|
916
|
+
from_keychain=$(secret-tool lookup service lisa-notion account "$workspace" 2>/dev/null) ;;
|
|
891
917
|
MINGW*|MSYS*|CYGWIN*)
|
|
892
918
|
# `cmdkey /generic ... /pass:` stores the secret in Windows Credential Manager, but
|
|
893
919
|
# `cmdkey /list` never prints stored passwords (by design). Read the CredentialBlob
|
|
894
920
|
# back via the Win32 CredRead API through PowerShell; pass the target name via an env
|
|
895
921
|
# var to dodge nested quoting, and strip the CRLF powershell.exe appends.
|
|
896
|
-
LISA_CRED_TARGET="lisa-notion-${workspace}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
922
|
+
from_keychain=$(LISA_CRED_TARGET="lisa-notion-${workspace}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
897
923
|
Add-Type -TypeDefinition @"
|
|
898
924
|
using System;
|
|
899
925
|
using System.Runtime.InteropServices;
|
|
@@ -919,8 +945,19 @@ public static class LisaCred {
|
|
|
919
945
|
}
|
|
920
946
|
}
|
|
921
947
|
"@
|
|
922
|
-
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r' ;;
|
|
948
|
+
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r') ;;
|
|
923
949
|
esac
|
|
950
|
+
[ -n "$from_keychain" ] && { echo "$from_keychain"; return; }
|
|
951
|
+
|
|
952
|
+
# Name every path. A bare empty return sends the next reader hunting for a
|
|
953
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
954
|
+
# seconds-long diagnosis. Paths and store coordinates only — never any
|
|
955
|
+
# resolved value, on any path.
|
|
956
|
+
echo "Error: could not resolve NOTION_API_TOKEN through lisa-secrets-access or the legacy keychain." >&2
|
|
957
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
958
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
959
|
+
echo " <OS keychain> service=lisa-notion account=$workspace" >&2
|
|
960
|
+
return 1
|
|
924
961
|
}
|
|
925
962
|
```
|
|
926
963
|
|
|
@@ -170,7 +170,7 @@ Practical consequence: an inert-guard finding is safe to report from a busy mach
|
|
|
170
170
|
|
|
171
171
|
Everything above is a *manual* probe, and it is language-agnostic: neuter a shell guard, run the suite, read the names, and the yardstick works exactly as it does for TypeScript.
|
|
172
172
|
|
|
173
|
-
The **automated** mutation gate is not language-agnostic, and the difference is easy to miss because both end in a green result. Stryker's instrumenter is per-language — the
|
|
173
|
+
The **automated** mutation gate is not language-agnostic, and the difference is easy to miss because both end in a green result. Stryker's instrumenter is per-language — the JavaScript and TypeScript family, plus Vue, HTML and Svelte, which delegate to it. There is **no shell parser**. So:
|
|
174
174
|
|
|
175
175
|
- A guard whose logic lives in `.sh` produces **no mutants under any `mutate` list**. It is outside the gate by construction, not by a selection decision, and widening the patterns cannot bring it in.
|
|
176
176
|
- Adding a `.sh` path to `mutate` does not measure it and does not quietly no-op. Stryker throws out of instrumentation — `Unable to parse …. No parser registered for .sh!` — and the run aborts before a single mutant is tried, so **one such entry destroys the score of every other guard in the list.** Measured, not inferred.
|
|
@@ -56,9 +56,38 @@ read_atlassian_token() {
|
|
|
56
56
|
# and the surface ladder, so anything it can answer must not be read out of an
|
|
57
57
|
# OS keychain here — a second reader is how the same credential ends up living
|
|
58
58
|
# in two places and drifting.
|
|
59
|
+
#
|
|
60
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
61
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
62
|
+
# wants used, and that decision must survive this change untouched. The plugin
|
|
63
|
+
# rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
|
|
64
|
+
# pointing at it is reachable from anywhere the plugin itself is installed.
|
|
65
|
+
# Without one, a consumer repository that vendors none of the leading paths
|
|
66
|
+
# never reaches a resolver at all — the ladder exits without having asked
|
|
67
|
+
# anything, which is what pushed agents into improvising their own credential
|
|
68
|
+
# lookups. This LADDER is identical in every skill that resolves a credential
|
|
69
|
+
# and `credential-resolver-ladder` fails if any copy diverges. Only what
|
|
70
|
+
# happens AFTER the ladder may differ between them.
|
|
71
|
+
local candidates=(
|
|
72
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
73
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
74
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
75
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
76
|
+
)
|
|
77
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
78
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
79
|
+
fi
|
|
80
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
81
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
82
|
+
fi
|
|
83
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
84
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
85
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
86
|
+
|
|
59
87
|
local resolver
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
local tried=()
|
|
89
|
+
for resolver in "${candidates[@]}"; do
|
|
90
|
+
tried+=("$resolver")
|
|
62
91
|
if [ -f "$resolver" ]; then
|
|
63
92
|
local via_lisa
|
|
64
93
|
via_lisa=$(node "$resolver" get ATLASSIAN_API_TOKEN 2>/dev/null) \
|
|
@@ -76,15 +105,17 @@ read_atlassian_token() {
|
|
|
76
105
|
# surface can reach, so a project resting on it has no working tier 1 in cron,
|
|
77
106
|
# CI, or a cloud session. Re-run /lisa:setup:atlassian before that date to
|
|
78
107
|
# store ATLASSIAN_API_TOKEN through the chokepoint instead.
|
|
108
|
+
local from_keychain=""
|
|
79
109
|
case "$(uname -s)" in
|
|
80
|
-
Darwin) security find-generic-password -s lisa-atlassian -a "$email" -w 2>/dev/null ;;
|
|
81
|
-
Linux) command -v secret-tool >/dev/null &&
|
|
110
|
+
Darwin) from_keychain=$(security find-generic-password -s lisa-atlassian -a "$email" -w 2>/dev/null) ;;
|
|
111
|
+
Linux) command -v secret-tool >/dev/null && \
|
|
112
|
+
from_keychain=$(secret-tool lookup service lisa-atlassian account "$email" 2>/dev/null) ;;
|
|
82
113
|
MINGW*|MSYS*|CYGWIN*)
|
|
83
114
|
# `cmdkey /generic ... /pass:` stores the secret in Windows Credential Manager, but
|
|
84
115
|
# `cmdkey /list` never prints stored passwords (by design). Read the CredentialBlob
|
|
85
116
|
# back via the Win32 CredRead API through PowerShell; pass the target name via an env
|
|
86
117
|
# var to dodge nested quoting, and strip the CRLF powershell.exe appends.
|
|
87
|
-
LISA_CRED_TARGET="lisa-atlassian-${email}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
118
|
+
from_keychain=$(LISA_CRED_TARGET="lisa-atlassian-${email}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
88
119
|
Add-Type -TypeDefinition @"
|
|
89
120
|
using System;
|
|
90
121
|
using System.Runtime.InteropServices;
|
|
@@ -110,8 +141,19 @@ public static class LisaCred {
|
|
|
110
141
|
}
|
|
111
142
|
}
|
|
112
143
|
"@
|
|
113
|
-
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r' ;;
|
|
144
|
+
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r') ;;
|
|
114
145
|
esac
|
|
146
|
+
[ -n "$from_keychain" ] && { echo "$from_keychain"; return; }
|
|
147
|
+
|
|
148
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
149
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
150
|
+
# seconds-long diagnosis. Paths and store coordinates only — never any
|
|
151
|
+
# resolved value, on any path.
|
|
152
|
+
echo "Error: could not resolve ATLASSIAN_API_TOKEN through lisa-secrets-access or the legacy keychain." >&2
|
|
153
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
154
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
155
|
+
echo " <OS keychain> service=lisa-atlassian account=$email" >&2
|
|
156
|
+
return 1
|
|
115
157
|
}
|
|
116
158
|
TOKEN=$(read_atlassian_token "$EMAIL")
|
|
117
159
|
if [ -n "$TOKEN" ]; then
|
|
@@ -47,9 +47,38 @@ export PATH="$HOME/.local/bin:$PATH"
|
|
|
47
47
|
# divergence `credential-substrate-precedence` exists to remove.
|
|
48
48
|
read_jam_pat() {
|
|
49
49
|
[ -n "${JAM_PAT:-}" ] && { echo "$JAM_PAT"; return; }
|
|
50
|
+
#
|
|
51
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
52
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
53
|
+
# wants used, and that decision must survive this change untouched. The plugin
|
|
54
|
+
# rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
|
|
55
|
+
# pointing at it is reachable from anywhere the plugin itself is installed.
|
|
56
|
+
# Without one, a consumer repository that vendors none of the leading paths
|
|
57
|
+
# never reaches a resolver at all — the ladder exits without having asked
|
|
58
|
+
# anything, which is what pushed agents into improvising their own credential
|
|
59
|
+
# lookups. This LADDER is identical in every skill that resolves a credential
|
|
60
|
+
# and `credential-resolver-ladder` fails if any copy diverges. Only what
|
|
61
|
+
# happens AFTER the ladder may differ between them.
|
|
62
|
+
local candidates=(
|
|
63
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
64
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
65
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
66
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
67
|
+
)
|
|
68
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
69
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
70
|
+
fi
|
|
71
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
72
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
73
|
+
fi
|
|
74
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
75
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
76
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
77
|
+
|
|
50
78
|
local resolver
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
local tried=()
|
|
80
|
+
for resolver in "${candidates[@]}"; do
|
|
81
|
+
tried+=("$resolver")
|
|
53
82
|
if [ -f "$resolver" ]; then
|
|
54
83
|
local via_lisa
|
|
55
84
|
via_lisa=$(node "$resolver" get JAM_PAT 2>/dev/null) \
|
|
@@ -57,6 +86,13 @@ read_jam_pat() {
|
|
|
57
86
|
break
|
|
58
87
|
fi
|
|
59
88
|
done
|
|
89
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
90
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
91
|
+
# seconds-long diagnosis. Paths and store coordinates only — never any
|
|
92
|
+
# resolved value, on any path.
|
|
93
|
+
echo "Error: could not resolve JAM_PAT through lisa-secrets-access." >&2
|
|
94
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
95
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
60
96
|
return 1
|
|
61
97
|
}
|
|
62
98
|
|
|
@@ -57,9 +57,38 @@ read_notion_token() {
|
|
|
57
57
|
# and the surface ladder, so anything it can answer must not be read out of an
|
|
58
58
|
# OS keychain here — a second reader is how the same credential ends up living
|
|
59
59
|
# in two places and drifting.
|
|
60
|
+
#
|
|
61
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
62
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
63
|
+
# wants used, and that decision must survive this change untouched. The plugin
|
|
64
|
+
# rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
|
|
65
|
+
# pointing at it is reachable from anywhere the plugin itself is installed.
|
|
66
|
+
# Without one, a consumer repository that vendors none of the leading paths
|
|
67
|
+
# never reaches a resolver at all — the ladder exits without having asked
|
|
68
|
+
# anything, which is what pushed agents into improvising their own credential
|
|
69
|
+
# lookups. This LADDER is identical in every skill that resolves a credential
|
|
70
|
+
# and `credential-resolver-ladder` fails if any copy diverges. Only what
|
|
71
|
+
# happens AFTER the ladder may differ between them.
|
|
72
|
+
local candidates=(
|
|
73
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
74
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
75
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
76
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
77
|
+
)
|
|
78
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
79
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
80
|
+
fi
|
|
81
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
82
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
83
|
+
fi
|
|
84
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
85
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
86
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
87
|
+
|
|
60
88
|
local resolver
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
local tried=()
|
|
90
|
+
for resolver in "${candidates[@]}"; do
|
|
91
|
+
tried+=("$resolver")
|
|
63
92
|
if [ -f "$resolver" ]; then
|
|
64
93
|
local via_lisa
|
|
65
94
|
via_lisa=$(node "$resolver" get NOTION_API_TOKEN 2>/dev/null) \
|
|
@@ -77,16 +106,17 @@ read_notion_token() {
|
|
|
77
106
|
# surface can reach, so a project resting on it has no working tier 1 in cron,
|
|
78
107
|
# CI, or a cloud session. Re-run /lisa:setup:notion before that date to store
|
|
79
108
|
# NOTION_API_TOKEN through the chokepoint instead.
|
|
109
|
+
local from_keychain=""
|
|
80
110
|
case "$(uname -s)" in
|
|
81
|
-
Darwin) security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null ;;
|
|
111
|
+
Darwin) from_keychain=$(security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null) ;;
|
|
82
112
|
Linux) command -v secret-tool >/dev/null && \
|
|
83
|
-
secret-tool lookup service lisa-notion account "$workspace" 2>/dev/null ;;
|
|
113
|
+
from_keychain=$(secret-tool lookup service lisa-notion account "$workspace" 2>/dev/null) ;;
|
|
84
114
|
MINGW*|MSYS*|CYGWIN*)
|
|
85
115
|
# `cmdkey /generic ... /pass:` stores the secret in Windows Credential Manager, but
|
|
86
116
|
# `cmdkey /list` never prints stored passwords (by design). Read the CredentialBlob
|
|
87
117
|
# back via the Win32 CredRead API through PowerShell; pass the target name via an env
|
|
88
118
|
# var to dodge nested quoting, and strip the CRLF powershell.exe appends.
|
|
89
|
-
LISA_CRED_TARGET="lisa-notion-${workspace}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
119
|
+
from_keychain=$(LISA_CRED_TARGET="lisa-notion-${workspace}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
90
120
|
Add-Type -TypeDefinition @"
|
|
91
121
|
using System;
|
|
92
122
|
using System.Runtime.InteropServices;
|
|
@@ -112,8 +142,19 @@ public static class LisaCred {
|
|
|
112
142
|
}
|
|
113
143
|
}
|
|
114
144
|
"@
|
|
115
|
-
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r' ;;
|
|
145
|
+
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r') ;;
|
|
116
146
|
esac
|
|
147
|
+
[ -n "$from_keychain" ] && { echo "$from_keychain"; return; }
|
|
148
|
+
|
|
149
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
150
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
151
|
+
# seconds-long diagnosis. Paths and store coordinates only — never any
|
|
152
|
+
# resolved value, on any path.
|
|
153
|
+
echo "Error: could not resolve NOTION_API_TOKEN through lisa-secrets-access or the legacy keychain." >&2
|
|
154
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
155
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
156
|
+
echo " <OS keychain> service=lisa-notion account=$workspace" >&2
|
|
157
|
+
return 1
|
|
117
158
|
}
|
|
118
159
|
TOKEN=$(read_notion_token "$WORKSPACE")
|
|
119
160
|
if [ -n "$TOKEN" ]; then
|
|
@@ -48,9 +48,38 @@ POSTHOG_HOST=${POSTHOG_HOST:-https://app.posthog.com}
|
|
|
48
48
|
# MCP — the exact divergence `credential-substrate-precedence` exists to remove.
|
|
49
49
|
read_posthog_key() {
|
|
50
50
|
[ -n "${POSTHOG_PERSONAL_API_KEY:-}" ] && { echo "$POSTHOG_PERSONAL_API_KEY"; return; }
|
|
51
|
+
#
|
|
52
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
53
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
54
|
+
# wants used, and that decision must survive this change untouched. The plugin
|
|
55
|
+
# rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
|
|
56
|
+
# pointing at it is reachable from anywhere the plugin itself is installed.
|
|
57
|
+
# Without one, a consumer repository that vendors none of the leading paths
|
|
58
|
+
# never reaches a resolver at all — the ladder exits without having asked
|
|
59
|
+
# anything, which is what pushed agents into improvising their own credential
|
|
60
|
+
# lookups. This LADDER is identical in every skill that resolves a credential
|
|
61
|
+
# and `credential-resolver-ladder` fails if any copy diverges. Only what
|
|
62
|
+
# happens AFTER the ladder may differ between them.
|
|
63
|
+
local candidates=(
|
|
64
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
65
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
66
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
67
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
68
|
+
)
|
|
69
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
70
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
71
|
+
fi
|
|
72
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
73
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
74
|
+
fi
|
|
75
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
76
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
77
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
78
|
+
|
|
51
79
|
local resolver
|
|
52
|
-
|
|
53
|
-
|
|
80
|
+
local tried=()
|
|
81
|
+
for resolver in "${candidates[@]}"; do
|
|
82
|
+
tried+=("$resolver")
|
|
54
83
|
if [ -f "$resolver" ]; then
|
|
55
84
|
local via_lisa
|
|
56
85
|
via_lisa=$(node "$resolver" get POSTHOG_PERSONAL_API_KEY 2>/dev/null) \
|
|
@@ -58,6 +87,13 @@ read_posthog_key() {
|
|
|
58
87
|
break
|
|
59
88
|
fi
|
|
60
89
|
done
|
|
90
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
91
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
92
|
+
# seconds-long diagnosis. Paths and store coordinates only — never any
|
|
93
|
+
# resolved value, on any path.
|
|
94
|
+
echo "Error: could not resolve POSTHOG_PERSONAL_API_KEY through lisa-secrets-access." >&2
|
|
95
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
96
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
61
97
|
return 1
|
|
62
98
|
}
|
|
63
99
|
|
|
@@ -47,9 +47,38 @@ interactively and headlessly — which is why it leads. The REST tier uses:
|
|
|
47
47
|
# Mirrors `linear-access`, `atlassian-access`, and `notion-access`.
|
|
48
48
|
read_sentry_token() {
|
|
49
49
|
[ -n "${SENTRY_AUTH_TOKEN:-}" ] && { echo "$SENTRY_AUTH_TOKEN"; return; }
|
|
50
|
+
#
|
|
51
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
52
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
53
|
+
# wants used, and that decision must survive this change untouched. The plugin
|
|
54
|
+
# rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
|
|
55
|
+
# pointing at it is reachable from anywhere the plugin itself is installed.
|
|
56
|
+
# Without one, a consumer repository that vendors none of the leading paths
|
|
57
|
+
# never reaches a resolver at all — the ladder exits without having asked
|
|
58
|
+
# anything, which is what pushed agents into improvising their own credential
|
|
59
|
+
# lookups. This LADDER is identical in every skill that resolves a credential
|
|
60
|
+
# and `credential-resolver-ladder` fails if any copy diverges. Only what
|
|
61
|
+
# happens AFTER the ladder may differ between them.
|
|
62
|
+
local candidates=(
|
|
63
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
64
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
65
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
66
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
67
|
+
)
|
|
68
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
69
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
70
|
+
fi
|
|
71
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
72
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
73
|
+
fi
|
|
74
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
75
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
76
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
77
|
+
|
|
50
78
|
local resolver
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
local tried=()
|
|
80
|
+
for resolver in "${candidates[@]}"; do
|
|
81
|
+
tried+=("$resolver")
|
|
53
82
|
if [ -f "$resolver" ]; then
|
|
54
83
|
local via_lisa
|
|
55
84
|
via_lisa=$(node "$resolver" get SENTRY_AUTH_TOKEN 2>/dev/null) \
|
|
@@ -57,6 +86,13 @@ read_sentry_token() {
|
|
|
57
86
|
break
|
|
58
87
|
fi
|
|
59
88
|
done
|
|
89
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
90
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
91
|
+
# seconds-long diagnosis. Paths and store coordinates only — never any
|
|
92
|
+
# resolved value, on any path.
|
|
93
|
+
echo "Error: could not resolve SENTRY_AUTH_TOKEN through lisa-secrets-access." >&2
|
|
94
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
95
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
60
96
|
return 1
|
|
61
97
|
}
|
|
62
98
|
|
|
@@ -80,10 +80,43 @@ read_token() {
|
|
|
80
80
|
# Preferred path: the single secrets chokepoint. It owns the one-store rule
|
|
81
81
|
# and the surface ladder, so anything it can answer must not be read out of an
|
|
82
82
|
# OS keychain here — a second reader is how the same credential ends up living
|
|
83
|
-
# in two places and drifting. Keep
|
|
83
|
+
# in two places and drifting. Keep the LADDER below identical to
|
|
84
|
+
# `atlassian-access`; the tail may differ, because this skill falls through to a
|
|
85
|
+
# legacy keychain rung and that one does not. Saying "the ladder" rather
|
|
86
|
+
# than "this function" is deliberate — the old wording asked for something
|
|
87
|
+
# unachievable, and an unachievable rule is one that gets quietly dropped.
|
|
88
|
+
#
|
|
89
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
90
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
91
|
+
# wants used, and that decision must survive this change untouched. The plugin
|
|
92
|
+
# rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
|
|
93
|
+
# pointing at it is reachable from anywhere the plugin itself is installed.
|
|
94
|
+
# Without one, a consumer repository that vendors none of the leading paths
|
|
95
|
+
# never reaches a resolver at all — the ladder exits without having asked
|
|
96
|
+
# anything, which is what pushed agents into improvising their own credential
|
|
97
|
+
# lookups. This LADDER is identical in every skill that resolves a credential
|
|
98
|
+
# and `credential-resolver-ladder` fails if any copy diverges. Only what
|
|
99
|
+
# happens AFTER the ladder may differ between them.
|
|
100
|
+
local candidates=(
|
|
101
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
102
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
103
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
104
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
105
|
+
)
|
|
106
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
107
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
108
|
+
fi
|
|
109
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
110
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
111
|
+
fi
|
|
112
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
113
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
114
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
115
|
+
|
|
84
116
|
local resolver
|
|
85
|
-
|
|
86
|
-
|
|
117
|
+
local tried=()
|
|
118
|
+
for resolver in "${candidates[@]}"; do
|
|
119
|
+
tried+=("$resolver")
|
|
87
120
|
if [ -f "$resolver" ]; then
|
|
88
121
|
local via_lisa
|
|
89
122
|
via_lisa=$(node "$resolver" get ATLASSIAN_API_TOKEN 2>/dev/null) \
|
|
@@ -94,15 +127,17 @@ read_token() {
|
|
|
94
127
|
# Legacy fallback: the OS keychain written by the guided flow below, for
|
|
95
128
|
# projects that have not adopted a credentials provider. Reached only when the
|
|
96
129
|
# chokepoint is absent or has no entry.
|
|
130
|
+
local from_keychain=""
|
|
97
131
|
case "$(uname -s)" in
|
|
98
|
-
Darwin) security find-generic-password -s lisa-atlassian -a "$email" -w 2>/dev/null ;;
|
|
99
|
-
Linux) command -v secret-tool >/dev/null &&
|
|
132
|
+
Darwin) from_keychain=$(security find-generic-password -s lisa-atlassian -a "$email" -w 2>/dev/null) ;;
|
|
133
|
+
Linux) command -v secret-tool >/dev/null && \
|
|
134
|
+
from_keychain=$(secret-tool lookup service lisa-atlassian account "$email" 2>/dev/null) ;;
|
|
100
135
|
MINGW*|MSYS*|CYGWIN*)
|
|
101
136
|
# `cmdkey /generic ... /pass:` stores the secret in Windows Credential Manager, but
|
|
102
137
|
# `cmdkey /list` never prints stored passwords (by design). Read the CredentialBlob
|
|
103
138
|
# back via the Win32 CredRead API through PowerShell; pass the target name via an env
|
|
104
139
|
# var to dodge nested quoting, and strip the CRLF powershell.exe appends.
|
|
105
|
-
LISA_CRED_TARGET="lisa-atlassian-${email}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
140
|
+
from_keychain=$(LISA_CRED_TARGET="lisa-atlassian-${email}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
106
141
|
Add-Type -TypeDefinition @"
|
|
107
142
|
using System;
|
|
108
143
|
using System.Runtime.InteropServices;
|
|
@@ -128,8 +163,19 @@ public static class LisaCred {
|
|
|
128
163
|
}
|
|
129
164
|
}
|
|
130
165
|
"@
|
|
131
|
-
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r' ;;
|
|
166
|
+
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r') ;;
|
|
132
167
|
esac
|
|
168
|
+
[ -n "$from_keychain" ] && { echo "$from_keychain"; return; }
|
|
169
|
+
|
|
170
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
171
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
172
|
+
# seconds-long diagnosis. Paths and store coordinates only — never any
|
|
173
|
+
# resolved value, on any path.
|
|
174
|
+
echo "Error: could not resolve ATLASSIAN_API_TOKEN through lisa-secrets-access or the legacy keychain." >&2
|
|
175
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
176
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
177
|
+
echo " <OS keychain> service=lisa-atlassian account=$email" >&2
|
|
178
|
+
return 1
|
|
133
179
|
}
|
|
134
180
|
|
|
135
181
|
EXISTING=$(read_token "$EMAIL")
|
|
@@ -167,10 +167,43 @@ read_notion_token() {
|
|
|
167
167
|
# Preferred path: the single secrets chokepoint. It owns the one-store rule
|
|
168
168
|
# and the surface ladder, so anything it can answer must not be read out of an
|
|
169
169
|
# OS keychain here — a second reader is how the same credential ends up living
|
|
170
|
-
# in two places and drifting. Keep
|
|
170
|
+
# in two places and drifting. Keep the LADDER below identical to
|
|
171
|
+
# `notion-access`; the tail may differ, because this skill falls through to a
|
|
172
|
+
# legacy keychain rung and that one does not. Saying "the ladder" rather
|
|
173
|
+
# than "this function" is deliberate — the old wording asked for something
|
|
174
|
+
# unachievable, and an unachievable rule is one that gets quietly dropped.
|
|
175
|
+
#
|
|
176
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
177
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
178
|
+
# wants used, and that decision must survive this change untouched. The plugin
|
|
179
|
+
# rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
|
|
180
|
+
# pointing at it is reachable from anywhere the plugin itself is installed.
|
|
181
|
+
# Without one, a consumer repository that vendors none of the leading paths
|
|
182
|
+
# never reaches a resolver at all — the ladder exits without having asked
|
|
183
|
+
# anything, which is what pushed agents into improvising their own credential
|
|
184
|
+
# lookups. This LADDER is identical in every skill that resolves a credential
|
|
185
|
+
# and `credential-resolver-ladder` fails if any copy diverges. Only what
|
|
186
|
+
# happens AFTER the ladder may differ between them.
|
|
187
|
+
local candidates=(
|
|
188
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
189
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
190
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
191
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
192
|
+
)
|
|
193
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
194
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
195
|
+
fi
|
|
196
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
197
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
198
|
+
fi
|
|
199
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
200
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
201
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
202
|
+
|
|
171
203
|
local resolver
|
|
172
|
-
|
|
173
|
-
|
|
204
|
+
local tried=()
|
|
205
|
+
for resolver in "${candidates[@]}"; do
|
|
206
|
+
tried+=("$resolver")
|
|
174
207
|
if [ -f "$resolver" ]; then
|
|
175
208
|
local via_lisa
|
|
176
209
|
via_lisa=$(node "$resolver" get NOTION_API_TOKEN 2>/dev/null) \
|
|
@@ -181,15 +214,17 @@ read_notion_token() {
|
|
|
181
214
|
# Legacy fallback: the OS keychain written by the guided flow below, for
|
|
182
215
|
# projects that have not adopted a credentials provider. Reached only when the
|
|
183
216
|
# chokepoint is absent or has no entry.
|
|
217
|
+
local from_keychain=""
|
|
184
218
|
case "$(uname -s)" in
|
|
185
|
-
Darwin) security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null ;;
|
|
186
|
-
Linux) command -v secret-tool >/dev/null &&
|
|
219
|
+
Darwin) from_keychain=$(security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null) ;;
|
|
220
|
+
Linux) command -v secret-tool >/dev/null && \
|
|
221
|
+
from_keychain=$(secret-tool lookup service lisa-notion account "$workspace" 2>/dev/null) ;;
|
|
187
222
|
MINGW*|MSYS*|CYGWIN*)
|
|
188
223
|
# `cmdkey /generic ... /pass:` stores the secret in Windows Credential Manager, but
|
|
189
224
|
# `cmdkey /list` never prints stored passwords (by design). Read the CredentialBlob
|
|
190
225
|
# back via the Win32 CredRead API through PowerShell; pass the target name via an env
|
|
191
226
|
# var to dodge nested quoting, and strip the CRLF powershell.exe appends.
|
|
192
|
-
LISA_CRED_TARGET="lisa-notion-${workspace}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
227
|
+
from_keychain=$(LISA_CRED_TARGET="lisa-notion-${workspace}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
193
228
|
Add-Type -TypeDefinition @"
|
|
194
229
|
using System;
|
|
195
230
|
using System.Runtime.InteropServices;
|
|
@@ -215,8 +250,19 @@ public static class LisaCred {
|
|
|
215
250
|
}
|
|
216
251
|
}
|
|
217
252
|
"@
|
|
218
|
-
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r' ;;
|
|
253
|
+
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r') ;;
|
|
219
254
|
esac
|
|
255
|
+
[ -n "$from_keychain" ] && { echo "$from_keychain"; return; }
|
|
256
|
+
|
|
257
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
258
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
259
|
+
# seconds-long diagnosis. Paths and store coordinates only — never any
|
|
260
|
+
# resolved value, on any path.
|
|
261
|
+
echo "Error: could not resolve NOTION_API_TOKEN through lisa-secrets-access or the legacy keychain." >&2
|
|
262
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
263
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
264
|
+
echo " <OS keychain> service=lisa-notion account=$workspace" >&2
|
|
265
|
+
return 1
|
|
220
266
|
}
|
|
221
267
|
|
|
222
268
|
TOKEN=$(read_notion_token "$WORKSPACE")
|