@skrr-ai/cli 0.1.56 → 0.1.57
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/base-command.d.ts +11 -0
- package/dist/base-command.js +80 -6
- package/dist/commands/agents/chat.js +5 -3
- package/dist/commands/agents/create.js +7 -0
- package/dist/commands/agents/update.js +7 -0
- package/dist/commands/login.js +14 -0
- package/dist/commands/tasks/move.js +11 -0
- package/dist/lib/agent-model-check.d.ts +19 -0
- package/dist/lib/agent-model-check.js +68 -0
- package/dist/lib/agentic-stream.d.ts +12 -0
- package/dist/lib/agentic-stream.js +67 -4
- package/dist/lib/auth-core-init.d.ts +0 -9
- package/dist/lib/auth-core-init.js +9 -0
- package/dist/lib/auth-storage.d.ts +15 -0
- package/dist/lib/auth-storage.js +33 -7
- package/dist/lib/commitments.js +3 -0
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.js +4 -2
- package/dist/lib/daemonBroker.d.ts +18 -0
- package/dist/lib/daemonBroker.js +53 -15
- package/dist/lib/daemonHandoff.d.ts +1 -1
- package/dist/lib/daemonHandoff.js +21 -1
- package/dist/lib/internal-id.d.ts +28 -0
- package/dist/lib/internal-id.js +41 -0
- package/dist/lib/workflow-state-hint.d.ts +10 -0
- package/dist/lib/workflow-state-hint.js +27 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +226 -226
- package/oclif.manifest.json +25408 -25408
- package/package.json +1 -1
package/dist/lib/config.d.ts
CHANGED
|
@@ -190,6 +190,10 @@ export declare function isEnvAuthOverride(): boolean;
|
|
|
190
190
|
* resolver and every existing install read it.
|
|
191
191
|
*/
|
|
192
192
|
export declare function configRoot(env?: NodeJS.ProcessEnv): string;
|
|
193
|
+
/** The root this machine uses when nothing relocates it (`~/.skrr`). */
|
|
194
|
+
export declare function defaultConfigRoot(): string;
|
|
195
|
+
/** Is this process on the primary root, however it was reached? */
|
|
196
|
+
export declare function isDefaultConfigRoot(env?: NodeJS.ProcessEnv): boolean;
|
|
193
197
|
/**
|
|
194
198
|
* The machine cliId THIS config root mints when it has none stored
|
|
195
199
|
* (OSK-12179). The primary root keeps the historical (host, user) id; any other
|
package/dist/lib/config.js
CHANGED
|
@@ -39,6 +39,8 @@ exports.describeDeployment = describeDeployment;
|
|
|
39
39
|
exports.canonicalizeKnownBaseURL = canonicalizeKnownBaseURL;
|
|
40
40
|
exports.isEnvAuthOverride = isEnvAuthOverride;
|
|
41
41
|
exports.configRoot = configRoot;
|
|
42
|
+
exports.defaultConfigRoot = defaultConfigRoot;
|
|
43
|
+
exports.isDefaultConfigRoot = isDefaultConfigRoot;
|
|
42
44
|
exports.machineCliId = machineCliId;
|
|
43
45
|
exports.priorConfigDir = priorConfigDir;
|
|
44
46
|
exports.loadConfig = loadConfig;
|
|
@@ -276,8 +278,8 @@ function defaultConfigRoot() {
|
|
|
276
278
|
return (0, auth_core_1.resolveConfigRoot)({});
|
|
277
279
|
}
|
|
278
280
|
/** Is this process on the primary root, however it was reached? */
|
|
279
|
-
function isDefaultConfigRoot() {
|
|
280
|
-
return path.resolve(configRoot()) === path.resolve(defaultConfigRoot());
|
|
281
|
+
function isDefaultConfigRoot(env = process.env) {
|
|
282
|
+
return path.resolve(configRoot(env)) === path.resolve(defaultConfigRoot());
|
|
281
283
|
}
|
|
282
284
|
/**
|
|
283
285
|
* The machine cliId THIS config root mints when it has none stored
|
|
@@ -342,6 +342,24 @@ export declare function maybeRetireStoredCliAuthFile(opts: {
|
|
|
342
342
|
descriptorPath?: string;
|
|
343
343
|
descriptorServerUrl?: string;
|
|
344
344
|
}): Promise<void>;
|
|
345
|
+
/**
|
|
346
|
+
* Should this process broker BEFORE it reads the stored credential?
|
|
347
|
+
* (OSK-12194)
|
|
348
|
+
*
|
|
349
|
+
* True only on a brokered Dedicated guest: the guest hand-off descriptor is
|
|
350
|
+
* live and offers the access-token mode and NOT the durable mint (its secret
|
|
351
|
+
* can only redeem, so any `cli-auth.json` here predates broker mode), and that
|
|
352
|
+
* file carries a refresh family for the descriptor's own server — the same
|
|
353
|
+
* provenance guard the retirement applies.
|
|
354
|
+
*
|
|
355
|
+
* Without this, the retirement could never run: it runs after a successful
|
|
356
|
+
* redeem, the auto-broker runs only when the resolver found no token, and the
|
|
357
|
+
* resolver adopts a file that carries one — so a LIVE pre-broker family was
|
|
358
|
+
* used and kept forever, which is exactly the case the cleanup exists for.
|
|
359
|
+
* A laptop has no guest descriptor and is untouched: there `cli-auth.json` is
|
|
360
|
+
* a legitimate login.
|
|
361
|
+
*/
|
|
362
|
+
export declare function shouldBrokerBeforeStoredCredential(descriptorPath?: string): boolean;
|
|
345
363
|
/**
|
|
346
364
|
* Convenience wrapper: run the broker call and, on a durable-mint success,
|
|
347
365
|
* persist the returned bundle through the canonical Sky CLI auth backend
|
package/dist/lib/daemonBroker.js
CHANGED
|
@@ -86,6 +86,7 @@ exports.redeemBrokeredHandoffToken = redeemBrokeredHandoffToken;
|
|
|
86
86
|
exports.__resetBrokeredHandoffForTest = __resetBrokeredHandoffForTest;
|
|
87
87
|
exports.findBrokeredHandoffDescriptor = findBrokeredHandoffDescriptor;
|
|
88
88
|
exports.maybeRetireStoredCliAuthFile = maybeRetireStoredCliAuthFile;
|
|
89
|
+
exports.shouldBrokerBeforeStoredCredential = shouldBrokerBeforeStoredCredential;
|
|
89
90
|
exports.attemptDaemonBrokerLoginAndPersist = attemptDaemonBrokerLoginAndPersist;
|
|
90
91
|
exports.maybeAutoBroker = maybeAutoBroker;
|
|
91
92
|
const fs = __importStar(require("node:fs"));
|
|
@@ -837,32 +838,69 @@ function findBrokeredHandoffDescriptor(profile = 'default') {
|
|
|
837
838
|
async function maybeRetireStoredCliAuthFile(opts) {
|
|
838
839
|
if (opts.descriptorPath !== exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR)
|
|
839
840
|
return;
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
stored = (0, auth_storage_1.readFromFile)();
|
|
843
|
-
}
|
|
844
|
-
catch {
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
if (!stored?.refreshToken)
|
|
848
|
-
return;
|
|
849
|
-
const storedOrigin = stored.serverOrigin ? (0, auth_storage_1.normalizeServerOrigin)(stored.serverOrigin) : null;
|
|
850
|
-
const descriptorOrigin = opts.descriptorServerUrl
|
|
851
|
-
? (0, auth_storage_1.normalizeServerOrigin)(opts.descriptorServerUrl)
|
|
852
|
-
: null;
|
|
853
|
-
if (!storedOrigin || !descriptorOrigin || storedOrigin !== descriptorOrigin)
|
|
841
|
+
const stale = retirableCliAuthFamily(opts.descriptorServerUrl);
|
|
842
|
+
if (!stale)
|
|
854
843
|
return;
|
|
855
844
|
// The revoke is authenticated by the refresh token itself — presenting it
|
|
856
845
|
// is the proof of ownership. `revokeDaemonRefreshSession` swallows its own
|
|
857
846
|
// network/HTTP failures (best-effort by contract); the delete still runs.
|
|
858
847
|
try {
|
|
859
|
-
await (0, auth_core_1.revokeDaemonRefreshSession)(
|
|
848
|
+
await (0, auth_core_1.revokeDaemonRefreshSession)(stale.origin, stale.refreshToken);
|
|
860
849
|
}
|
|
861
850
|
catch {
|
|
862
851
|
/* best-effort — delete proceeds regardless */
|
|
863
852
|
}
|
|
864
853
|
(0, auth_storage_1.deleteFileBackend)();
|
|
865
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Guards 2 and 3 of the retirement above: the stored file's refresh family,
|
|
857
|
+
* when its origin matches the descriptor's. Read through
|
|
858
|
+
* `readRefreshFamilyFromFile`, which accepts a file whose access token is
|
|
859
|
+
* empty — the realistic pre-broker shape once that token lapsed, and the one
|
|
860
|
+
* `readFromFile` reports as no credential at all (OSK-12194).
|
|
861
|
+
*/
|
|
862
|
+
function retirableCliAuthFamily(descriptorServerUrl) {
|
|
863
|
+
let stored;
|
|
864
|
+
try {
|
|
865
|
+
stored = (0, auth_storage_1.readRefreshFamilyFromFile)();
|
|
866
|
+
}
|
|
867
|
+
catch {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
if (!stored?.refreshToken)
|
|
871
|
+
return null;
|
|
872
|
+
const storedOrigin = stored.serverOrigin ? (0, auth_storage_1.normalizeServerOrigin)(stored.serverOrigin) : null;
|
|
873
|
+
const descriptorOrigin = descriptorServerUrl ? (0, auth_storage_1.normalizeServerOrigin)(descriptorServerUrl) : null;
|
|
874
|
+
if (!storedOrigin || !descriptorOrigin || storedOrigin !== descriptorOrigin)
|
|
875
|
+
return null;
|
|
876
|
+
return { origin: descriptorOrigin, refreshToken: stored.refreshToken };
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Should this process broker BEFORE it reads the stored credential?
|
|
880
|
+
* (OSK-12194)
|
|
881
|
+
*
|
|
882
|
+
* True only on a brokered Dedicated guest: the guest hand-off descriptor is
|
|
883
|
+
* live and offers the access-token mode and NOT the durable mint (its secret
|
|
884
|
+
* can only redeem, so any `cli-auth.json` here predates broker mode), and that
|
|
885
|
+
* file carries a refresh family for the descriptor's own server — the same
|
|
886
|
+
* provenance guard the retirement applies.
|
|
887
|
+
*
|
|
888
|
+
* Without this, the retirement could never run: it runs after a successful
|
|
889
|
+
* redeem, the auto-broker runs only when the resolver found no token, and the
|
|
890
|
+
* resolver adopts a file that carries one — so a LIVE pre-broker family was
|
|
891
|
+
* used and kept forever, which is exactly the case the cleanup exists for.
|
|
892
|
+
* A laptop has no guest descriptor and is untouched: there `cli-auth.json` is
|
|
893
|
+
* a legitimate login.
|
|
894
|
+
*/
|
|
895
|
+
function shouldBrokerBeforeStoredCredential(descriptorPath = exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR) {
|
|
896
|
+
const bootstrap = readBootstrap(descriptorPath);
|
|
897
|
+
if (!bootstrap)
|
|
898
|
+
return false;
|
|
899
|
+
const modes = (0, cli_handoff_wire_1.advertisedHandoffModes)(bootstrap);
|
|
900
|
+
if (!modes.includes('access_token') || modes.includes('refresh_family'))
|
|
901
|
+
return false;
|
|
902
|
+
return retirableCliAuthFamily(bootstrap.serverUrl) !== null;
|
|
903
|
+
}
|
|
866
904
|
/** Bound the confirm so a slow server cannot stall a login that already worked. */
|
|
867
905
|
const CONFIRM_TIMEOUT_MS = 10_000;
|
|
868
906
|
/**
|
|
@@ -40,7 +40,7 @@ export interface DaemonHandoffOutcome {
|
|
|
40
40
|
* The two absent cases are separated because they need different work — one
|
|
41
41
|
* needs a binary fetched, the other only needs the service registered.
|
|
42
42
|
*/
|
|
43
|
-
reason?: 'opted-out' | 'brokered' | 'no-binary' | 'legacy-binary' | 'unsupported-daemon' | 'no-service';
|
|
43
|
+
reason?: 'opted-out' | 'brokered' | 'no-binary' | 'legacy-binary' | 'unsupported-daemon' | 'no-service' | 'foreign-root' | 'mint-refused';
|
|
44
44
|
}
|
|
45
45
|
export declare function handOffToLocalDaemon(env?: NodeJS.ProcessEnv, opts?: {
|
|
46
46
|
loginFlow?: string;
|
|
@@ -196,6 +196,24 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
196
196
|
detail: 'the local daemon brokered this login, so it already has a working credential',
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
|
+
// A relocated config root (`SKRR_CONFIG_DIR`) is a second sign-in on this
|
|
200
|
+
// machine, not the machine's sign-in. The installed daemon service carries no
|
|
201
|
+
// config-root override, so it reads the DEFAULT root — its credential, its
|
|
202
|
+
// deployment, its account belong there. Offering this root's login to it would
|
|
203
|
+
// re-point the machine at whatever this root signed in as, and when the server
|
|
204
|
+
// refused the offer the CLI used to report a healthy, signed-in daemon as
|
|
205
|
+
// "not signed in" and send the user to re-login it (OSK-12189).
|
|
206
|
+
//
|
|
207
|
+
// Decided on the root alone, before anything is spawned or minted: nothing
|
|
208
|
+
// about this machine's daemon should change because a second root signed in.
|
|
209
|
+
if (!(0, config_1.isDefaultConfigRoot)(env)) {
|
|
210
|
+
return {
|
|
211
|
+
status: 'skipped',
|
|
212
|
+
reason: 'foreign-root',
|
|
213
|
+
detail: `this login is in the config root ${(0, config_1.configRoot)(env)}; the local daemon ` +
|
|
214
|
+
`belongs to ${(0, config_1.defaultConfigRoot)()} and keeps its own sign-in`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
199
217
|
const binary = (0, exec_runtime_binary_1.findRuntimeBinary)();
|
|
200
218
|
if (!binary) {
|
|
201
219
|
return { status: 'skipped', reason: 'no-binary', detail: 'no local daemon installed' };
|
|
@@ -264,7 +282,9 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
264
282
|
}));
|
|
265
283
|
}
|
|
266
284
|
catch (err) {
|
|
267
|
-
|
|
285
|
+
// Refused BEFORE the daemon was touched: it still holds exactly the
|
|
286
|
+
// credential it had, so this is "not handed this login", never "signed out".
|
|
287
|
+
return { status: 'failed', reason: 'mint-refused', detail: err.message };
|
|
268
288
|
}
|
|
269
289
|
const payload = JSON.stringify({
|
|
270
290
|
refreshToken: bundle.refreshToken,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The internal Mongo `_id` handed back where a command wants the `id`.
|
|
3
|
+
*
|
|
4
|
+
* `--json` prints every row's `_id` FIRST, so it is the value a person or a
|
|
5
|
+
* script copies. Spaces and Tasks are addressed by `id` (a UUID) everywhere in
|
|
6
|
+
* the API, and no resolver accepts both forms, so `spaces show <_id>`,
|
|
7
|
+
* `tasks show <_id>` and `tasks create --space <_id>` answered with not-found
|
|
8
|
+
* or a raw HTTP 404 — true, and no help: nothing said which field to use
|
|
9
|
+
* (OSK-12205).
|
|
10
|
+
*
|
|
11
|
+
* Only the refusal lives here. Finding the `id` that goes with an `_id` needs
|
|
12
|
+
* a listing, so the caller does that (it holds the API) and passes the answer
|
|
13
|
+
* in when it has one.
|
|
14
|
+
*/
|
|
15
|
+
/** A 24-hex Mongo ObjectId, the shape of `_id`. */
|
|
16
|
+
export declare function looksLikeInternalObjectId(value: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The sentence for an `_id` passed where the `id` belongs.
|
|
19
|
+
*
|
|
20
|
+
* `canonicalId` is the row's `id` when the caller could find it — then the
|
|
21
|
+
* remedy is the exact value to use. Without it the remedy names the field.
|
|
22
|
+
*/
|
|
23
|
+
export declare function describeInternalIdMisuse(opts: {
|
|
24
|
+
value: string;
|
|
25
|
+
noun: 'space' | 'task';
|
|
26
|
+
canonicalId?: string | null;
|
|
27
|
+
bin: string;
|
|
28
|
+
}): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The internal Mongo `_id` handed back where a command wants the `id`.
|
|
4
|
+
*
|
|
5
|
+
* `--json` prints every row's `_id` FIRST, so it is the value a person or a
|
|
6
|
+
* script copies. Spaces and Tasks are addressed by `id` (a UUID) everywhere in
|
|
7
|
+
* the API, and no resolver accepts both forms, so `spaces show <_id>`,
|
|
8
|
+
* `tasks show <_id>` and `tasks create --space <_id>` answered with not-found
|
|
9
|
+
* or a raw HTTP 404 — true, and no help: nothing said which field to use
|
|
10
|
+
* (OSK-12205).
|
|
11
|
+
*
|
|
12
|
+
* Only the refusal lives here. Finding the `id` that goes with an `_id` needs
|
|
13
|
+
* a listing, so the caller does that (it holds the API) and passes the answer
|
|
14
|
+
* in when it has one.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.looksLikeInternalObjectId = looksLikeInternalObjectId;
|
|
18
|
+
exports.describeInternalIdMisuse = describeInternalIdMisuse;
|
|
19
|
+
const OBJECT_ID_RE = /^[0-9a-fA-F]{24}$/;
|
|
20
|
+
/** A 24-hex Mongo ObjectId, the shape of `_id`. */
|
|
21
|
+
function looksLikeInternalObjectId(value) {
|
|
22
|
+
return OBJECT_ID_RE.test(value.trim());
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The sentence for an `_id` passed where the `id` belongs.
|
|
26
|
+
*
|
|
27
|
+
* `canonicalId` is the row's `id` when the caller could find it — then the
|
|
28
|
+
* remedy is the exact value to use. Without it the remedy names the field.
|
|
29
|
+
*/
|
|
30
|
+
function describeInternalIdMisuse(opts) {
|
|
31
|
+
const { value, noun, canonicalId, bin } = opts;
|
|
32
|
+
const head = `"${value.trim()}" is the internal _id of a ${noun}; commands take its \`id\``;
|
|
33
|
+
if (canonicalId) {
|
|
34
|
+
return `${head}. Use \`id\` ${canonicalId}.`;
|
|
35
|
+
}
|
|
36
|
+
const alternatives = noun === 'task'
|
|
37
|
+
? 'the `id` field (a UUID) or the `identifier` (OSK-123) from the same --json output'
|
|
38
|
+
: 'the `id` field (a UUID) or the `identifier` (OSK-S-12) from the same --json output';
|
|
39
|
+
return (`${head}.\n` +
|
|
40
|
+
` → use ${alternatives}; \`${bin} ${noun === 'task' ? 'tasks' : 'spaces'} list --json\` prints both.`);
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The remedy for `tasks move --status <unknown>` (OSK-12205).
|
|
3
|
+
*
|
|
4
|
+
* Workflow states are Space-local, so the CLI cannot declare them as flag
|
|
5
|
+
* options and cannot list them ahead of the refusal. The server now names the
|
|
6
|
+
* accepted states in its message (and in `details.validStates`); this adds the
|
|
7
|
+
* command that lists them with their types, and quotes the states itself when
|
|
8
|
+
* a server sent them only in `details`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function describeUnknownWorkflowState(body: Record<string, unknown> | undefined, bin: string): string | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeUnknownWorkflowState = describeUnknownWorkflowState;
|
|
4
|
+
/**
|
|
5
|
+
* The remedy for `tasks move --status <unknown>` (OSK-12205).
|
|
6
|
+
*
|
|
7
|
+
* Workflow states are Space-local, so the CLI cannot declare them as flag
|
|
8
|
+
* options and cannot list them ahead of the refusal. The server now names the
|
|
9
|
+
* accepted states in its message (and in `details.validStates`); this adds the
|
|
10
|
+
* command that lists them with their types, and quotes the states itself when
|
|
11
|
+
* a server sent them only in `details`.
|
|
12
|
+
*/
|
|
13
|
+
function describeUnknownWorkflowState(body, bin) {
|
|
14
|
+
if (!body || body.code !== 'WORKFLOW_STATE_UNKNOWN')
|
|
15
|
+
return null;
|
|
16
|
+
const details = (body.details ?? {});
|
|
17
|
+
const valid = Array.isArray(details.validStates)
|
|
18
|
+
? details.validStates.filter((v) => typeof v === 'string')
|
|
19
|
+
: [];
|
|
20
|
+
let message = typeof body.error === 'string' && body.error ? body.error : 'Unknown workflow state';
|
|
21
|
+
if (valid.length > 0 && !message.includes(valid[0])) {
|
|
22
|
+
message += `. This Space accepts: ${valid.join(', ')}.`;
|
|
23
|
+
}
|
|
24
|
+
return (`${message}\n` +
|
|
25
|
+
` → \`${bin} spaces states list <space-id>\` lists this Space's states with their types; ` +
|
|
26
|
+
'`--status` takes a state id or its display name.');
|
|
27
|
+
}
|