@aiwg/cli 2026.7.20 → 2026.7.21
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/README.md +4 -4
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +7 -0
- package/dist/src/artifacts/citation-parser.js +96 -35
- package/dist/src/artifacts/index-builder.js +54 -17
- package/dist/src/artifacts/state-transfer.js +27 -0
- package/dist/src/artifacts/stats.js +8 -0
- package/dist/src/cli/cli-extension-loader.js +73 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/sessions.js +966 -0
- package/dist/src/cli/handlers/skill-lint.js +49 -45
- package/dist/src/cli/handlers/use.js +143 -60
- package/dist/src/cli/handlers/utilities.js +22 -8
- package/dist/src/cli/skill-usage.js +146 -24
- package/dist/src/extensions/commands/definitions.js +29 -0
- package/dist/src/extensions/manifest.js +29 -0
- package/dist/src/sessions/adapters/claude.js +357 -0
- package/dist/src/sessions/adapters/codex.js +521 -0
- package/dist/src/sessions/adapters/copilot.js +226 -0
- package/dist/src/sessions/adapters/cursor.js +372 -0
- package/dist/src/sessions/adapters/factory.js +345 -0
- package/dist/src/sessions/adapters/generic.js +225 -0
- package/dist/src/sessions/adapters/hermes.js +341 -0
- package/dist/src/sessions/adapters/openclaw.js +381 -0
- package/dist/src/sessions/adapters/opencode.js +454 -0
- package/dist/src/sessions/adapters/openhuman.js +315 -0
- package/dist/src/sessions/adapters/warp.js +160 -0
- package/dist/src/sessions/adapters/windsurf.js +212 -0
- package/dist/src/sessions/candidates.js +210 -0
- package/dist/src/sessions/contracts.js +310 -0
- package/dist/src/sessions/discovery.js +51 -0
- package/dist/src/sessions/fixtures.js +12 -0
- package/dist/src/sessions/importer.js +315 -0
- package/dist/src/sessions/index.js +25 -0
- package/dist/src/sessions/knowledge-shard.js +61 -0
- package/dist/src/sessions/optional-backends.js +238 -0
- package/dist/src/sessions/policy.js +192 -0
- package/dist/src/sessions/ports.js +2 -0
- package/dist/src/sessions/promotion.js +367 -0
- package/dist/src/sessions/readers.js +176 -0
- package/dist/src/sessions/repository.js +1551 -0
- package/dist/src/skills/adapters/agent-skills.js +59 -0
- package/dist/src/skills/adapters/local.js +19 -1
- package/dist/src/skills/agent-skills.js +249 -0
- package/dist/src/skills/cli.js +463 -7
- package/dist/src/skills/deployer.js +554 -0
- package/dist/src/skills/doctor.js +105 -0
- package/dist/src/skills/exporter.js +382 -0
- package/dist/src/skills/importer.js +921 -0
- package/dist/src/skills/registry.js +19 -0
- package/dist/src/skills/validator.js +323 -0
- package/package.json +2 -2
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, stat, writeFile, appendFile, readdir } from 'fs/promises';
|
|
2
|
-
import { existsSync } from 'fs';
|
|
2
|
+
import { createReadStream, existsSync } from 'fs';
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
|
+
import { createInterface } from 'readline';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import os from 'os';
|
|
6
7
|
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
7
8
|
import { PROJECT_AIWG_LOCATION_FILE, projectAiwgPath } from '../config/project-artifacts.js';
|
|
8
9
|
const DEFAULT_MAX_BYTES = 1_048_576;
|
|
9
10
|
const DEFAULT_REPORT_LIMIT = 20;
|
|
11
|
+
const MAX_TRANSCRIPT_LINE_BYTES = 1_048_576;
|
|
12
|
+
const MAX_TRANSCRIPT_BYTES = 256 * 1_048_576;
|
|
13
|
+
const MAX_TRANSCRIPT_RECORDS = 1_000_000;
|
|
10
14
|
export async function maybeAppendSkillUsage(input) {
|
|
11
15
|
const env = input.env ?? process.env;
|
|
12
16
|
const resolved = await resolveSkillUsageSettings(input.cwd, env);
|
|
@@ -43,15 +47,38 @@ export async function ingestSkillUsageTranscript(options) {
|
|
|
43
47
|
const contextCwd = options.projectRoot ?? options.cwd;
|
|
44
48
|
const resolved = await resolveSkillUsageSettings(contextCwd, env);
|
|
45
49
|
if (!resolved.enabled || resolved.scopes.length === 0) {
|
|
46
|
-
return {
|
|
50
|
+
return {
|
|
51
|
+
events: [], appended: 0, skipped: 0, paths: [],
|
|
52
|
+
receipt: {
|
|
53
|
+
source_generation: '', provider: normalizeProvider(options.provider),
|
|
54
|
+
records_read: 0, events_extracted: 0, events_appended: 0,
|
|
55
|
+
duplicates_skipped: 0, malformed_skipped: 0,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const provider = normalizeProvider(options.provider);
|
|
60
|
+
if (provider !== 'claude-code') {
|
|
61
|
+
throw new Error(`unsupported skill-usage transcript provider: ${provider}`);
|
|
47
62
|
}
|
|
48
|
-
const
|
|
49
|
-
const
|
|
63
|
+
const sourceStat = await stat(options.transcriptPath);
|
|
64
|
+
const sourceGeneration = digest([
|
|
65
|
+
provider, String(sourceStat.size), String(sourceStat.mtimeMs),
|
|
66
|
+
].join('\0'));
|
|
50
67
|
const version = await readAiwgVersion(options.frameworkRoot);
|
|
51
68
|
const context = await resolvePathContext(contextCwd);
|
|
52
69
|
const extracted = [];
|
|
53
70
|
let skipped = 0;
|
|
54
|
-
|
|
71
|
+
let recordsRead = 0;
|
|
72
|
+
let bytesRead = 0;
|
|
73
|
+
for await (const line of streamTranscriptLines(options.transcriptPath)) {
|
|
74
|
+
recordsRead += 1;
|
|
75
|
+
bytesRead += Buffer.byteLength(line) + 1;
|
|
76
|
+
if (recordsRead > MAX_TRANSCRIPT_RECORDS || bytesRead > MAX_TRANSCRIPT_BYTES) {
|
|
77
|
+
throw new Error('skill-usage transcript exceeds bounded ingestion limits');
|
|
78
|
+
}
|
|
79
|
+
if (Buffer.byteLength(line) > MAX_TRANSCRIPT_LINE_BYTES) {
|
|
80
|
+
throw new Error('skill-usage transcript record exceeds bounded line limit');
|
|
81
|
+
}
|
|
55
82
|
let parsed;
|
|
56
83
|
try {
|
|
57
84
|
parsed = JSON.parse(line);
|
|
@@ -65,22 +92,50 @@ export async function ingestSkillUsageTranscript(options) {
|
|
|
65
92
|
skipped += 1;
|
|
66
93
|
}
|
|
67
94
|
else {
|
|
68
|
-
extracted.push(...events
|
|
95
|
+
extracted.push(...events.map(event => ({
|
|
96
|
+
...event,
|
|
97
|
+
occurredAt: sourceOccurrenceTime(parsed),
|
|
98
|
+
nativeEventId: sourceNativeEventId(parsed),
|
|
99
|
+
position: recordsRead,
|
|
100
|
+
contentDigest: digest(line),
|
|
101
|
+
})));
|
|
69
102
|
}
|
|
70
103
|
}
|
|
71
104
|
const persisted = [];
|
|
72
105
|
const paths = resolved.scopes
|
|
73
106
|
.map(scope => resolveUsagePath(scope, context.projectRoot, env))
|
|
74
107
|
.filter((p) => Boolean(p));
|
|
108
|
+
const knownEventIdsByPath = new Map();
|
|
109
|
+
for (const filePath of paths) {
|
|
110
|
+
const knownEventIds = new Set();
|
|
111
|
+
for (const event of await readJsonlEvents(filePath)) {
|
|
112
|
+
if (event.event_id)
|
|
113
|
+
knownEventIds.add(event.event_id);
|
|
114
|
+
}
|
|
115
|
+
knownEventIdsByPath.set(filePath, knownEventIds);
|
|
116
|
+
}
|
|
117
|
+
let duplicatesSkipped = 0;
|
|
75
118
|
for (const scope of resolved.scopes) {
|
|
76
119
|
const filePath = resolveUsagePath(scope, context.projectRoot, env);
|
|
77
120
|
if (!filePath)
|
|
78
121
|
continue;
|
|
122
|
+
const knownEventIds = knownEventIdsByPath.get(filePath) ?? new Set();
|
|
79
123
|
for (const item of extracted) {
|
|
124
|
+
const eventId = digest([
|
|
125
|
+
sourceGeneration,
|
|
126
|
+
item.nativeEventId ?? String(item.position),
|
|
127
|
+
item.contentDigest,
|
|
128
|
+
item.artifact.kind,
|
|
129
|
+
item.artifact.id,
|
|
130
|
+
].join('\0'));
|
|
131
|
+
if (knownEventIds.has(eventId)) {
|
|
132
|
+
duplicatesSkipped += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
80
135
|
const event = buildUsageEvent({
|
|
81
136
|
env,
|
|
82
137
|
source: 'transcript',
|
|
83
|
-
provider
|
|
138
|
+
provider,
|
|
84
139
|
artifact: item.artifact,
|
|
85
140
|
action: item.action,
|
|
86
141
|
outcome: 'unknown',
|
|
@@ -88,8 +143,13 @@ export async function ingestSkillUsageTranscript(options) {
|
|
|
88
143
|
cwd: context.projectRoot ?? contextCwd,
|
|
89
144
|
context,
|
|
90
145
|
scope,
|
|
146
|
+
occurredAt: item.occurredAt,
|
|
147
|
+
eventId,
|
|
148
|
+
sourceGeneration,
|
|
149
|
+
nativeEventId: item.nativeEventId,
|
|
91
150
|
});
|
|
92
151
|
persisted.push(event);
|
|
152
|
+
knownEventIds.add(eventId);
|
|
93
153
|
if (!options.dryRun) {
|
|
94
154
|
await appendBoundedJsonl(filePath, event, resolved.maxBytes);
|
|
95
155
|
}
|
|
@@ -100,6 +160,15 @@ export async function ingestSkillUsageTranscript(options) {
|
|
|
100
160
|
appended: options.dryRun ? 0 : persisted.length,
|
|
101
161
|
skipped,
|
|
102
162
|
paths,
|
|
163
|
+
receipt: {
|
|
164
|
+
source_generation: sourceGeneration,
|
|
165
|
+
provider,
|
|
166
|
+
records_read: recordsRead,
|
|
167
|
+
events_extracted: extracted.length,
|
|
168
|
+
events_appended: options.dryRun ? 0 : persisted.length,
|
|
169
|
+
duplicates_skipped: duplicatesSkipped,
|
|
170
|
+
malformed_skipped: skipped,
|
|
171
|
+
},
|
|
103
172
|
};
|
|
104
173
|
}
|
|
105
174
|
export async function readSkillUsageReport(options) {
|
|
@@ -131,6 +200,10 @@ export async function readSkillUsageReport(options) {
|
|
|
131
200
|
limit: 5,
|
|
132
201
|
}),
|
|
133
202
|
paths,
|
|
203
|
+
window: {
|
|
204
|
+
retained_segments: paths.reduce((count, filePath) => count + retainedUsagePaths(filePath).filter(existsSync).length, 0),
|
|
205
|
+
truncated_before: paths.some(filePath => existsSync(`${filePath}.1`)),
|
|
206
|
+
},
|
|
134
207
|
};
|
|
135
208
|
}
|
|
136
209
|
export async function printSkillUsageReport(options) {
|
|
@@ -205,10 +278,17 @@ function classifyCliUsage(command, args) {
|
|
|
205
278
|
return { kind: 'command', id: command, action: 'invoke' };
|
|
206
279
|
}
|
|
207
280
|
function buildUsageEvent(input) {
|
|
281
|
+
const observedTimestamp = new Date().toISOString();
|
|
208
282
|
const event = {
|
|
209
|
-
schema_version: 1,
|
|
283
|
+
schema_version: input.eventId ? 2 : 1,
|
|
210
284
|
event_type: 'aiwg.skill_usage',
|
|
211
|
-
timestamp:
|
|
285
|
+
timestamp: input.occurredAt ?? observedTimestamp,
|
|
286
|
+
...(input.eventId ? {
|
|
287
|
+
observed_timestamp: observedTimestamp,
|
|
288
|
+
event_id: input.eventId,
|
|
289
|
+
source_generation: input.sourceGeneration,
|
|
290
|
+
native_event_id: input.nativeEventId,
|
|
291
|
+
} : {}),
|
|
212
292
|
invocation_id: input.env.AIWG_INVOCATION_ID,
|
|
213
293
|
source: input.source,
|
|
214
294
|
provider: input.provider,
|
|
@@ -294,6 +374,25 @@ function normalizeProvider(value) {
|
|
|
294
374
|
return 'claude-code';
|
|
295
375
|
return normalized || 'unknown';
|
|
296
376
|
}
|
|
377
|
+
function sourceOccurrenceTime(value) {
|
|
378
|
+
if (!isRecord(value))
|
|
379
|
+
return undefined;
|
|
380
|
+
for (const key of ['timestamp', 'created_at', 'createdAt', 'time']) {
|
|
381
|
+
const candidate = value[key];
|
|
382
|
+
if (typeof candidate === 'string' && !Number.isNaN(Date.parse(candidate))) {
|
|
383
|
+
return new Date(candidate).toISOString();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
function sourceNativeEventId(value) {
|
|
389
|
+
if (!isRecord(value))
|
|
390
|
+
return undefined;
|
|
391
|
+
return firstString(value, ['uuid', 'event_id', 'eventId', 'id', 'message_id']);
|
|
392
|
+
}
|
|
393
|
+
function digest(value) {
|
|
394
|
+
return createHash('sha256').update(value).digest('hex');
|
|
395
|
+
}
|
|
297
396
|
function isArtifactKind(value) {
|
|
298
397
|
return value === 'skill' ||
|
|
299
398
|
value === 'agent' ||
|
|
@@ -393,15 +492,20 @@ function resolveUsagePath(scope, projectRoot, env) {
|
|
|
393
492
|
}
|
|
394
493
|
async function appendBoundedJsonl(filePath, event, maxBytes) {
|
|
395
494
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
495
|
+
const line = JSON.stringify(event) + '\n';
|
|
496
|
+
const lineBytes = Buffer.byteLength(line);
|
|
497
|
+
if (maxBytes > 0 && lineBytes > maxBytes)
|
|
498
|
+
event.oversized_record = true;
|
|
499
|
+
const finalLine = JSON.stringify(event) + '\n';
|
|
500
|
+
await rotateIfNeeded(filePath, maxBytes, Buffer.byteLength(finalLine));
|
|
501
|
+
await appendFile(filePath, finalLine, 'utf8');
|
|
502
|
+
}
|
|
503
|
+
async function rotateIfNeeded(filePath, maxBytes, appendBytes) {
|
|
400
504
|
if (maxBytes <= 0)
|
|
401
505
|
return;
|
|
402
506
|
try {
|
|
403
507
|
const current = await stat(filePath);
|
|
404
|
-
if (current.size <= maxBytes)
|
|
508
|
+
if (current.size === 0 || current.size + appendBytes <= maxBytes)
|
|
405
509
|
return;
|
|
406
510
|
const rotated = `${filePath}.1`;
|
|
407
511
|
if (existsSync(rotated)) {
|
|
@@ -415,18 +519,36 @@ async function rotateIfNeeded(filePath, maxBytes) {
|
|
|
415
519
|
}
|
|
416
520
|
}
|
|
417
521
|
async function readJsonlEvents(filePath) {
|
|
522
|
+
const events = [];
|
|
523
|
+
for (const retainedPath of retainedUsagePaths(filePath)) {
|
|
524
|
+
try {
|
|
525
|
+
for await (const line of streamTranscriptLines(retainedPath)) {
|
|
526
|
+
if (line.trim())
|
|
527
|
+
events.push(JSON.parse(line));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
if (error.code !== 'ENOENT')
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return events;
|
|
536
|
+
}
|
|
537
|
+
function retainedUsagePaths(filePath) {
|
|
538
|
+
return [`${filePath}.1`, filePath];
|
|
539
|
+
}
|
|
540
|
+
async function* streamTranscriptLines(filePath) {
|
|
541
|
+
const input = createReadStream(filePath, { encoding: 'utf8' });
|
|
542
|
+
const lines = createInterface({ input, crlfDelay: Infinity });
|
|
418
543
|
try {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
.filter(Boolean)
|
|
424
|
-
.map(line => JSON.parse(line));
|
|
544
|
+
for await (const line of lines) {
|
|
545
|
+
if (line.trim())
|
|
546
|
+
yield line;
|
|
547
|
+
}
|
|
425
548
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
throw error;
|
|
549
|
+
finally {
|
|
550
|
+
lines.close();
|
|
551
|
+
input.destroy();
|
|
430
552
|
}
|
|
431
553
|
}
|
|
432
554
|
function summarizeEvents(events) {
|
|
@@ -907,6 +907,34 @@ export const sessionCommand = {
|
|
|
907
907
|
},
|
|
908
908
|
},
|
|
909
909
|
};
|
|
910
|
+
// Session Catalog Command (#1903)
|
|
911
|
+
export const sessionsCommand = {
|
|
912
|
+
id: 'sessions',
|
|
913
|
+
type: 'skill',
|
|
914
|
+
name: 'Sessions',
|
|
915
|
+
description: 'Manage the normalized session catalog with versioned JSON, deterministic pagination, previews, and health checks',
|
|
916
|
+
version: '1.0.0',
|
|
917
|
+
capabilities: ['cli', 'session-catalog', 'session-import', 'session-lifecycle', 'doctor'],
|
|
918
|
+
keywords: ['sessions', 'catalog', 'import', 'source', 'tag', 'reindex', 'delete'],
|
|
919
|
+
category: 'project',
|
|
920
|
+
platforms: {
|
|
921
|
+
claude: 'full',
|
|
922
|
+
generic: 'full',
|
|
923
|
+
},
|
|
924
|
+
deployment: {
|
|
925
|
+
pathTemplate: '.{platform}/commands/{id}.md',
|
|
926
|
+
core: true,
|
|
927
|
+
},
|
|
928
|
+
metadata: {
|
|
929
|
+
type: 'skill',
|
|
930
|
+
triggerPhrases: ['list sessions', 'import sessions', 'session catalog', 'sessions doctor'],
|
|
931
|
+
commandHint: {
|
|
932
|
+
template: 'utility',
|
|
933
|
+
argumentHint: '<sources|import|list|show|tag|relocate|reindex|delete|doctor> [--json] [--dry-run]',
|
|
934
|
+
allowedTools: ['Bash'],
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
};
|
|
910
938
|
// Sandbox Management Commands (#917)
|
|
911
939
|
export const sandboxCommand = {
|
|
912
940
|
id: 'sandbox',
|
|
@@ -3458,6 +3486,7 @@ export const commandDefinitions = [
|
|
|
3458
3486
|
feedbackCommand,
|
|
3459
3487
|
// Session (#884)
|
|
3460
3488
|
sessionCommand,
|
|
3489
|
+
sessionsCommand,
|
|
3461
3490
|
];
|
|
3462
3491
|
// ============================================
|
|
3463
3492
|
// Helper Functions
|
|
@@ -39,6 +39,7 @@ export const ProjectLocalTypeSchema = z.enum([
|
|
|
39
39
|
* underscore + hyphen, optional trailing slash.
|
|
40
40
|
*/
|
|
41
41
|
const safeRelativePath = z.string().regex(/^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*\/?$/, 'must be a relative path (alphanumeric + _-, no leading slash, no ..)');
|
|
42
|
+
const safeModuleFile = z.string().regex(/^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*\.mjs$/, 'must be a relative .mjs path (alphanumeric + _-, no leading slash, no ..)');
|
|
42
43
|
// Single-char ids are allowed; multi-char ids must end with alphanumeric
|
|
43
44
|
// (no trailing hyphen). This pattern: `[a-z0-9]([a-z0-9-]*[a-z0-9])?`
|
|
44
45
|
const bundleNamePattern = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -129,6 +130,31 @@ export const ProviderConfigSchema = z.object({
|
|
|
129
130
|
aliases: z.array(z.string().min(1).max(64)).max(20).optional(),
|
|
130
131
|
capabilities: ProviderCapabilityOverridesSchema.optional(),
|
|
131
132
|
}).strict();
|
|
133
|
+
export const CliCommandsSchema = z.object({
|
|
134
|
+
namespace: z.string()
|
|
135
|
+
.min(1)
|
|
136
|
+
.max(64)
|
|
137
|
+
.regex(bundleNamePattern, 'kebab-case alphanumeric, no leading/trailing hyphen'),
|
|
138
|
+
description: z.string().min(1).max(512),
|
|
139
|
+
entry: safeRelativePath.optional(),
|
|
140
|
+
subcommands: z.record(z.string()
|
|
141
|
+
.min(1)
|
|
142
|
+
.max(64)
|
|
143
|
+
.regex(bundleNamePattern, 'kebab-case alphanumeric, no leading/trailing hyphen'), z.object({
|
|
144
|
+
file: safeModuleFile,
|
|
145
|
+
description: z.string().min(1).max(512),
|
|
146
|
+
hook_event: z.enum([
|
|
147
|
+
'Stop',
|
|
148
|
+
'SessionStart',
|
|
149
|
+
'SessionEnd',
|
|
150
|
+
'PreToolUse',
|
|
151
|
+
'PostToolUse',
|
|
152
|
+
'FeatureComplete',
|
|
153
|
+
]).optional(),
|
|
154
|
+
}).strict()).refine((commands) => Object.keys(commands).length > 0, {
|
|
155
|
+
message: 'at least one CLI subcommand is required',
|
|
156
|
+
}),
|
|
157
|
+
}).strict();
|
|
132
158
|
// ============================================
|
|
133
159
|
// Top-level BundleManifestSchema
|
|
134
160
|
// ============================================
|
|
@@ -162,6 +188,9 @@ export const BundleManifestSchema = z.object({
|
|
|
162
188
|
// Optional patterns shared with existing extension validation
|
|
163
189
|
deprecation: DeprecationSchema.optional(),
|
|
164
190
|
memory: MemoryFootprintSchema.optional(),
|
|
191
|
+
// Expandable CLI namespace contributed by an addon-shaped bundle.
|
|
192
|
+
// The same block is accepted by bundled addon manifests.
|
|
193
|
+
cli_commands: CliCommandsSchema.optional(),
|
|
165
194
|
})
|
|
166
195
|
.strict()
|
|
167
196
|
.refine((m) => {
|