@namzu/cli 8.1.0 → 8.2.0
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/integrations/providers/index.d.ts +1 -1
- package/dist/integrations/providers/index.d.ts.map +1 -1
- package/dist/integrations/providers/index.js +1 -1
- package/dist/integrations/providers/index.js.map +1 -1
- package/dist/integrations/providers/registry.d.ts +16 -0
- package/dist/integrations/providers/registry.d.ts.map +1 -1
- package/dist/integrations/providers/registry.js +19 -0
- package/dist/integrations/providers/registry.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +63 -6
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Picker.d.ts +28 -1
- package/dist/tui/Picker.d.ts.map +1 -1
- package/dist/tui/Picker.js +67 -20
- package/dist/tui/Picker.js.map +1 -1
- package/dist/tui/agent.d.ts +24 -0
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +54 -5
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/credential-entry.d.ts +38 -6
- package/dist/tui/credential-entry.d.ts.map +1 -1
- package/dist/tui/credential-entry.js +51 -12
- package/dist/tui/credential-entry.js.map +1 -1
- package/package.json +3 -3
|
@@ -25,7 +25,28 @@
|
|
|
25
25
|
* package has no component tests and a secret is the worst thing to leave
|
|
26
26
|
* unverifiable.
|
|
27
27
|
*/
|
|
28
|
-
import type
|
|
28
|
+
import { type DetectedProvider, type ProviderRegistryEntry } from '../integrations/providers/index.js';
|
|
29
|
+
/**
|
|
30
|
+
* Which kind of secret was pasted.
|
|
31
|
+
*
|
|
32
|
+
* The distinction is not cosmetic: the two travel differently on the wire —
|
|
33
|
+
* `constructProvider` sends one as `authToken` and the other as `apiKey` — and
|
|
34
|
+
* they expire differently. A subscription token is short-lived and is renewed
|
|
35
|
+
* from refresh metadata that only the discovered credential carries; a hand
|
|
36
|
+
* pasted one has none, so it lapses in hours with nothing to renew it. An
|
|
37
|
+
* operator who is not told that at the moment they paste discovers it as a 401
|
|
38
|
+
* mid-turn, which is the difference between a feature and a trap.
|
|
39
|
+
*/
|
|
40
|
+
export type CredentialKind = 'api-key' | 'subscription-token';
|
|
41
|
+
/**
|
|
42
|
+
* Classify a pasted secret by its own shape.
|
|
43
|
+
*
|
|
44
|
+
* Reuses the classifier the session layer already decides the wire header with
|
|
45
|
+
* (`isAnthropicOAuthToken`), so the screen cannot say one thing while the
|
|
46
|
+
* request says another. Every other provider takes an API key and nothing else,
|
|
47
|
+
* so asking the question of them would invent a distinction they do not have.
|
|
48
|
+
*/
|
|
49
|
+
export declare function classifyCredential(entry: ProviderRegistryEntry, key: string): CredentialKind;
|
|
29
50
|
/**
|
|
30
51
|
* What a key looks like on screen. Never the key.
|
|
31
52
|
*
|
|
@@ -64,18 +85,29 @@ export type Verification = {
|
|
|
64
85
|
/**
|
|
65
86
|
* What namzu tells the operator it did with what they typed.
|
|
66
87
|
*
|
|
67
|
-
* Every branch says
|
|
68
|
-
* held for this session only, and the
|
|
69
|
-
* The
|
|
70
|
-
* typing a credential into an application usually
|
|
88
|
+
* Every branch says four things: that it worked or did not, WHICH KIND of
|
|
89
|
+
* credential it took, that it is held for this session only, and the
|
|
90
|
+
* environment variable that persists it. The third is the part a person is most
|
|
91
|
+
* likely to assume otherwise — typing a credential into an application usually
|
|
92
|
+
* means it was saved.
|
|
93
|
+
*
|
|
94
|
+
* The kind is said out loud because the two behave differently after this
|
|
95
|
+
* screen, and only one of them can be renewed. See {@link CredentialKind}.
|
|
71
96
|
*/
|
|
72
|
-
export declare function describeDisposition(entry: ProviderRegistryEntry, verification: Verification): string;
|
|
97
|
+
export declare function describeDisposition(entry: ProviderRegistryEntry, verification: Verification, kind?: CredentialKind): string;
|
|
73
98
|
/**
|
|
74
99
|
* The detected-provider record a typed key produces.
|
|
75
100
|
*
|
|
76
101
|
* Shaped exactly like a discovered one so every downstream path — session
|
|
77
102
|
* construction, the model picker, `/provider` — treats it identically. The only
|
|
78
103
|
* difference is `source`, so a surface can say where it came from.
|
|
104
|
+
*
|
|
105
|
+
* `oauth` is deliberately NOT set, for a subscription token as much as for an
|
|
106
|
+
* API key. That field means "this credential can be renewed from a refresh
|
|
107
|
+
* token", the session layer gates its refresh on it, and a hand-pasted token
|
|
108
|
+
* supplies no refresh token — so filling it in would claim a renewal path that
|
|
109
|
+
* does not exist and turn a disclosed expiry into a silent 401. The disclosure
|
|
110
|
+
* in `describeDisposition` is the substitute.
|
|
79
111
|
*/
|
|
80
112
|
export declare function sessionCredential(entry: ProviderRegistryEntry, key: string): DetectedProvider;
|
|
81
113
|
//# sourceMappingURL=credential-entry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-entry.d.ts","sourceRoot":"","sources":["../../src/tui/credential-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"credential-entry.d.ts","sourceRoot":"","sources":["../../src/tui/credential-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAE1B,MAAM,oCAAoC,CAAA;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,oBAAoB,CAAA;AAE7D;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,CAG5F;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAmBxF;AAED,qDAAqD;AACrD,MAAM,MAAM,YAAY,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE;AAC/B,oEAAoE;GAClE;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,qBAAqB,EAC5B,YAAY,EAAE,YAAY,EAC1B,IAAI,GAAE,cAA0B,GAC9B,MAAM,CAmCR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAQ7F"}
|
|
@@ -25,6 +25,20 @@
|
|
|
25
25
|
* package has no component tests and a secret is the worst thing to leave
|
|
26
26
|
* unverifiable.
|
|
27
27
|
*/
|
|
28
|
+
import { isAnthropicOAuthToken, } from '../integrations/providers/index.js';
|
|
29
|
+
/**
|
|
30
|
+
* Classify a pasted secret by its own shape.
|
|
31
|
+
*
|
|
32
|
+
* Reuses the classifier the session layer already decides the wire header with
|
|
33
|
+
* (`isAnthropicOAuthToken`), so the screen cannot say one thing while the
|
|
34
|
+
* request says another. Every other provider takes an API key and nothing else,
|
|
35
|
+
* so asking the question of them would invent a distinction they do not have.
|
|
36
|
+
*/
|
|
37
|
+
export function classifyCredential(entry, key) {
|
|
38
|
+
if (entry.id !== 'anthropic')
|
|
39
|
+
return 'api-key';
|
|
40
|
+
return isAnthropicOAuthToken(key.trim()) ? 'subscription-token' : 'api-key';
|
|
41
|
+
}
|
|
28
42
|
/**
|
|
29
43
|
* What a key looks like on screen. Never the key.
|
|
30
44
|
*
|
|
@@ -56,11 +70,15 @@ export function keyLooksUsable(key) {
|
|
|
56
70
|
if (/\s/.test(trimmed)) {
|
|
57
71
|
return { ok: false, reason: 'That contains a space — check for a truncated or wrapped paste.' };
|
|
58
72
|
}
|
|
59
|
-
|
|
73
|
+
// Assignment-SHAPED, not "contains an equals sign". The old test rejected any
|
|
74
|
+
// `=` anywhere, which is fine for an API key and wrong for a subscription
|
|
75
|
+
// token: a JWT-shaped one is base64 and may carry `=` padding, so the screen
|
|
76
|
+
// refused a valid credential and blamed the operator's paste for it. This
|
|
77
|
+
// still catches the case the check exists for — `ANTHROPIC_API_KEY=sk-…`
|
|
78
|
+
// copied out of a shell profile — because that has a variable name in front.
|
|
79
|
+
if (trimmed.startsWith('$') || /^[A-Za-z_][A-Za-z0-9_]*=/.test(trimmed)) {
|
|
60
80
|
return {
|
|
61
81
|
ok: false,
|
|
62
|
-
// The classic: pasting `ANTHROPIC_API_KEY=sk-…` or `$MY_KEY` from a
|
|
63
|
-
// shell profile rather than the value.
|
|
64
82
|
reason: 'That looks like a shell assignment or variable. Paste the key itself.',
|
|
65
83
|
};
|
|
66
84
|
}
|
|
@@ -69,21 +87,26 @@ export function keyLooksUsable(key) {
|
|
|
69
87
|
/**
|
|
70
88
|
* What namzu tells the operator it did with what they typed.
|
|
71
89
|
*
|
|
72
|
-
* Every branch says
|
|
73
|
-
* held for this session only, and the
|
|
74
|
-
* The
|
|
75
|
-
* typing a credential into an application usually
|
|
90
|
+
* Every branch says four things: that it worked or did not, WHICH KIND of
|
|
91
|
+
* credential it took, that it is held for this session only, and the
|
|
92
|
+
* environment variable that persists it. The third is the part a person is most
|
|
93
|
+
* likely to assume otherwise — typing a credential into an application usually
|
|
94
|
+
* means it was saved.
|
|
95
|
+
*
|
|
96
|
+
* The kind is said out loud because the two behave differently after this
|
|
97
|
+
* screen, and only one of them can be renewed. See {@link CredentialKind}.
|
|
76
98
|
*/
|
|
77
|
-
export function describeDisposition(entry, verification) {
|
|
99
|
+
export function describeDisposition(entry, verification, kind = 'api-key') {
|
|
100
|
+
const noun = kind === 'subscription-token' ? 'subscription token' : 'API key';
|
|
78
101
|
const persist = entry.envVars[0];
|
|
79
102
|
const durable = persist
|
|
80
103
|
? `To keep it, set ${persist} in your shell and restart.`
|
|
81
104
|
: 'To keep it, configure the credential in your environment and restart.';
|
|
82
105
|
if (verification.kind === 'rejected') {
|
|
83
|
-
return `${entry.label} rejected that
|
|
106
|
+
return `${entry.label} rejected that ${noun}: ${verification.reason}\nNothing was stored.`;
|
|
84
107
|
}
|
|
85
108
|
const checked = verification.kind === 'verified'
|
|
86
|
-
? `${entry.label} accepted the
|
|
109
|
+
? `${entry.label} accepted the ${noun}.`
|
|
87
110
|
: // Said plainly rather than implied. Claiming a key is good would be
|
|
88
111
|
// inventing a verification that did not happen — and this now covers
|
|
89
112
|
// two ways of not happening: a driver that declares no credential
|
|
@@ -91,8 +114,17 @@ export function describeDisposition(entry, verification) {
|
|
|
91
114
|
// wording asserted the first ("offers no way to check it"), which
|
|
92
115
|
// would be false for a probe that timed out. Neither is a bad key,
|
|
93
116
|
// and neither is a checked one.
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
`${noun.replace(/^./, (c) => c.toUpperCase())} accepted, but NOT checked — ${entry.label} could not confirm it just now, so the first message will be the test.`;
|
|
118
|
+
// Disclosed HERE, at the paste, and not left to be discovered as a 401 in the
|
|
119
|
+
// middle of a turn. A discovered subscription token arrives with a refresh
|
|
120
|
+
// token and an expiry, and the session layer renews it between turns; a
|
|
121
|
+
// pasted one carries neither, so there is nothing to renew it with. Saying
|
|
122
|
+
// "it lasts hours" is the honest form of a limitation we cannot remove
|
|
123
|
+
// without an authorization flow namzu does not have.
|
|
124
|
+
const expiry = kind === 'subscription-token'
|
|
125
|
+
? '\nA pasted subscription token has no refresh data with it, so it expires in a few hours and namzu cannot renew it — expect to enter it again.'
|
|
126
|
+
: '';
|
|
127
|
+
return `${checked}\nHeld in memory for this session only — it is not written anywhere. ${durable}${expiry}`;
|
|
96
128
|
}
|
|
97
129
|
/**
|
|
98
130
|
* The detected-provider record a typed key produces.
|
|
@@ -100,6 +132,13 @@ export function describeDisposition(entry, verification) {
|
|
|
100
132
|
* Shaped exactly like a discovered one so every downstream path — session
|
|
101
133
|
* construction, the model picker, `/provider` — treats it identically. The only
|
|
102
134
|
* difference is `source`, so a surface can say where it came from.
|
|
135
|
+
*
|
|
136
|
+
* `oauth` is deliberately NOT set, for a subscription token as much as for an
|
|
137
|
+
* API key. That field means "this credential can be renewed from a refresh
|
|
138
|
+
* token", the session layer gates its refresh on it, and a hand-pasted token
|
|
139
|
+
* supplies no refresh token — so filling it in would claim a renewal path that
|
|
140
|
+
* does not exist and turn a disclosed expiry into a silent 401. The disclosure
|
|
141
|
+
* in `describeDisposition` is the substitute.
|
|
103
142
|
*/
|
|
104
143
|
export function sessionCredential(entry, key) {
|
|
105
144
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-entry.js","sourceRoot":"","sources":["../../src/tui/credential-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;
|
|
1
|
+
{"version":3,"file":"credential-entry.js","sourceRoot":"","sources":["../../src/tui/credential-entry.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@namzu/cli",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.0",
|
|
4
4
|
"description": "Operator CLI for the Namzu agent platform — namzu doctor + future commands. Dual-purpose: standalone bin (`namzu doctor`) and library (`import { runDoctor } from '@namzu/cli'`).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"namzu",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"ink": "^7.0.3",
|
|
45
45
|
"react": "^19.2.6",
|
|
46
46
|
"yaml": "^2.9.0",
|
|
47
|
-
"@namzu/
|
|
47
|
+
"@namzu/anthropic": "3.2.0",
|
|
48
48
|
"@namzu/openai": "1.2.0",
|
|
49
49
|
"@namzu/sdk": "^21.1.0",
|
|
50
50
|
"@namzu/openrouter": "2.1.0",
|
|
51
|
-
"@namzu/
|
|
51
|
+
"@namzu/ollama": "2.1.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@biomejs/biome": "^1.9.4",
|