@aiwg/cli 2026.10.6 → 2026.10.8
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/bin/aiwg.mjs +4 -2
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/cost-report.js +7 -1
- package/dist/src/cli/handlers/decision.js +36 -10
- package/dist/src/cli/handlers/effect.js +4 -84
- package/dist/src/cli/handlers/guided-setup.js +1 -1
- package/dist/src/cli/handlers/init.js +1 -1
- package/dist/src/cli/handlers/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +33 -6
- package/dist/src/cli/handlers/marketplace.js +15 -4
- package/dist/src/cli/handlers/setup.js +24 -20
- package/dist/src/cli/handlers/use.js +57 -15
- package/dist/src/cli/prompt-utils.js +73 -0
- package/dist/src/config/gitignore.js +4 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/cost/fleet-report.js +20 -22
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +19 -6
- package/dist/src/decision/driver.js +2 -2
- package/dist/src/decision/jev-credentials.js +18 -5
- package/dist/src/decision/ledger.js +230 -34
- package/dist/src/decision/qualification/gate-evidence.js +6 -4
- package/dist/src/decision/qualification/load-staged.js +47 -10
- package/dist/src/decision/qualification/runner.js +7 -3
- package/dist/src/discovery/interview.js +10 -5
- package/dist/src/discovery/plan-apply.js +26 -30
- package/dist/src/discovery/plan-validation.js +2 -2
- package/dist/src/effects/host.js +91 -0
- package/dist/src/extensions/commands/definitions.js +2 -2
- package/dist/src/extensions/project-local-gitignore.js +2 -0
- package/dist/src/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- package/dist/src/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +122 -109
- package/dist/src/mcp/credentials.d.mts +4 -0
- package/dist/src/mcp/credentials.mjs +44 -3
- package/dist/src/mcp/grok-build-config.mjs +4 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- package/dist/src/mcp/omp-config.mjs +2 -0
- package/dist/src/mcp/registry.js +19 -15
- package/dist/src/mcp/registry.mjs +2 -18
- package/dist/src/mcp/tools/agentic-sandbox.mjs +46 -28
- package/dist/src/mcp/tools/worker-invoke.mjs +13 -2
- package/dist/src/memory/project-registry.js +2 -1
- package/dist/src/mission-protocol/worker-status.js +91 -1
- package/dist/src/models/grok-build-models.js +20 -0
- package/dist/src/models/worker-installation.d.mts +1 -1
- package/dist/src/models/worker-installation.mjs +65 -4
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- package/dist/src/providers/provider-definitions.js +2 -2
- package/dist/src/serve/shared-host-scheduler.js +4 -1
- package/dist/src/serve/worker-invocation.js +10 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +9 -9
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +4 -12
- package/dist/src/storage/backends/fortemi.js +107 -49
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/notifier.mjs +2 -4
- package/package.json +1 -1
- package/tools/agents/providers/base.mjs +5 -1
- package/tools/agents/providers/claude.mjs +5 -2
- package/tools/agents/providers/codex.mjs +4 -1
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +5 -2
- package/tools/agents/providers/hermes.mjs +5 -2
- package/tools/agents/providers/omp.mjs +3 -1
- package/tools/agents/providers/rule-delivery.mjs +29 -12
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +6 -3
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- package/tools/ralph-external/process-monitor.mjs +42 -4
- package/tools/ralph-external/session-launcher.mjs +125 -3
|
@@ -4,7 +4,9 @@ import { JevDecisionAdapter, compileJevQuestion } from '../adapters/jev.js';
|
|
|
4
4
|
import { DecisionAdmissionRegistry } from '../admission.js';
|
|
5
5
|
import { CanonicalJsonByteEstimator } from '../context-plan.js';
|
|
6
6
|
import { assertContextLiveSource } from '../context-live-qualification.js';
|
|
7
|
+
import { tokenCost } from '../pricing.js';
|
|
7
8
|
import { admissionControllerLoadDriver, decisionLoadArrivals, compareDecisionLoadBounds, decisionLoadManifestDigest, decisionLoadPolicy, runDecisionLoadHarness, } from './load.js';
|
|
9
|
+
export const DECISION_STAGED_LOAD_RESULT_VERSIONS = ['decision-load-result/v1', 'decision-load-result/v2'];
|
|
8
10
|
const finite = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
9
11
|
const positive = (value) => finite(value) && value > 0;
|
|
10
12
|
const exactCommit = (value) => /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value);
|
|
@@ -289,7 +291,9 @@ export async function runDecisionStagedLoad(options) {
|
|
|
289
291
|
onObservation: (arrival, attempt, admission, observation) => {
|
|
290
292
|
const receipt = inFlight.get(`${arrival.ordinal}:${attempt}`);
|
|
291
293
|
Object.assign(receipt, { settledAtMs: clock.now(), admission: structuredClone(admission), status: observation.status, reason: observation.reason,
|
|
292
|
-
usage: observation.usage, ...(observation.httpStatus === undefined ? {} : { httpStatus: observation.httpStatus })
|
|
294
|
+
usage: observation.usage, ...(observation.httpStatus === undefined ? {} : { httpStatus: observation.httpStatus }),
|
|
295
|
+
...(observation.actualModel ? { servedModel: observation.actualModel } : {}),
|
|
296
|
+
...(observation.requestId ? { requestId: observation.requestId } : {}) });
|
|
293
297
|
inFlight.delete(`${arrival.ordinal}:${attempt}`);
|
|
294
298
|
guard.observe(arrival.providerId, observation, receipt.tokens, receipt.costUsd);
|
|
295
299
|
},
|
|
@@ -335,7 +339,7 @@ export async function runDecisionStagedLoad(options) {
|
|
|
335
339
|
host: { hostname: hostname(), cpuClass: `${cpus().length} cores; ${cpus()[0]?.model ?? 'unknown'}`, memoryGiB: totalmem() / 2 ** 30,
|
|
336
340
|
networkPath: options.networkPath, node: process.version, platform: process.platform, arch: process.arch },
|
|
337
341
|
pricing: options.pricing, stages, receipts, samples, profileHistory: registry.profileHistory('load-harness'), breakerTransitions: transitions,
|
|
338
|
-
|
|
342
|
+
reservedUsage: guard.usage, stop: guard.stop, startedAtMs: start, elapsedMs: samples.at(-1)?.atMs ?? clock.now() - start });
|
|
339
343
|
}
|
|
340
344
|
/** Token-bucket debit (burst plus refill), matching the admission controller's rate semantics. */
|
|
341
345
|
function bucketDebit(receipts, capacity, periodMs, amount) {
|
|
@@ -400,9 +404,9 @@ export function compareDecisionStagedLoad(record) {
|
|
|
400
404
|
}
|
|
401
405
|
}
|
|
402
406
|
}
|
|
403
|
-
atMost('budget.maxUsd.stopAtFraction', m.budget.maxUsd * m.budget.stopAtFraction, record.
|
|
404
|
-
atMost('budget.maxCalls', m.budget.maxCalls, record.
|
|
405
|
-
atMost('budget.maxTokens', m.budget.maxTokens, record.
|
|
407
|
+
atMost('budget.maxUsd.stopAtFraction', m.budget.maxUsd * m.budget.stopAtFraction, record.reservedUsage.usd);
|
|
408
|
+
atMost('budget.maxCalls', m.budget.maxCalls, record.reservedUsage.calls);
|
|
409
|
+
atMost('budget.maxTokens', m.budget.maxTokens, record.reservedUsage.tokens);
|
|
406
410
|
atMost('budget.maxWallClockSeconds', m.budget.maxWallClockSeconds, record.elapsedMs / 1000);
|
|
407
411
|
atMost('maximumSampleGapMs', m.bounds.maximumSampleGapMs, maximum(record.samples.slice(1).map((s, i) => s.atMs - record.samples[i].atMs)));
|
|
408
412
|
const endingAt = record.startedAtMs + record.elapsedMs;
|
|
@@ -440,10 +444,29 @@ export function compareDecisionStagedLoad(record) {
|
|
|
440
444
|
atMost('stop.maximumCrossPrincipalQuotaViolations', 0, violations);
|
|
441
445
|
return out;
|
|
442
446
|
}
|
|
447
|
+
function reportedUsage(receipts, pricing) {
|
|
448
|
+
const sum = (values) => values.reduce((total, value) => total === null || value === null ? null : total + value, 0);
|
|
449
|
+
const inputs = receipts.map(r => r.usage.inputTokens);
|
|
450
|
+
const outputs = receipts.map(r => r.usage.outputTokens);
|
|
451
|
+
const totals = receipts.map(r => r.usage.inputTokens === null || r.usage.outputTokens === null ? null : r.usage.inputTokens + r.usage.outputTokens);
|
|
452
|
+
const inputTokens = sum(inputs);
|
|
453
|
+
const outputTokens = sum(outputs);
|
|
454
|
+
// The request is pinned to the priced model; an explicitly different served model has no applicable rate.
|
|
455
|
+
const costs = receipts.map(r => r.usage.inputTokens === null || r.usage.outputTokens === null
|
|
456
|
+
|| (r.servedModel !== undefined && r.servedModel !== pricing.jev_model) ? null
|
|
457
|
+
: tokenCost(r.usage, { inputPerMillionUsd: pricing.jev_input_per_mtok, outputPerMillionUsd: pricing.jev_output_per_mtok }));
|
|
458
|
+
const knownSum = (values) => values.length > 0 && values.every(v => v === null)
|
|
459
|
+
? null : sum(values.filter(v => v !== null));
|
|
460
|
+
return { calls: receipts.length, inputTokens, outputTokens, tokens: inputTokens === null || outputTokens === null ? null : inputTokens + outputTokens,
|
|
461
|
+
usd: sum(costs), incompleteCalls: costs.filter(cost => cost === null).length,
|
|
462
|
+
knownSubtotal: { inputTokens: knownSum(inputs), outputTokens: knownSum(outputs), tokens: knownSum(totals), usd: knownSum(costs) } };
|
|
463
|
+
}
|
|
443
464
|
export function decisionStagedLoadResultRecord(input) {
|
|
444
465
|
const manifest = readDecisionLoadManifest(input.manifestBytes);
|
|
445
466
|
validateDecisionLoadManifestV3(manifest);
|
|
446
|
-
|
|
467
|
+
validateDecisionLoadPricing(input.pricing, manifest.model);
|
|
468
|
+
const payload = { ...input, schemaVersion: 'decision-load-result/v2', kind: 'load-manifest-qualified',
|
|
469
|
+
reportedUsage: reportedUsage(input.receipts, input.pricing),
|
|
447
470
|
manifest, manifestDigest: decisionLoadManifestDigest(input.manifestBytes) };
|
|
448
471
|
for (const key of ['digest', 'comparisons', 'passed', 'queueWaitDistribution', 'retries'])
|
|
449
472
|
delete payload[key];
|
|
@@ -461,7 +484,7 @@ export function decisionStagedLoadResultRecord(input) {
|
|
|
461
484
|
export function verifyDecisionStagedLoadRecord(value, sourceCommit) {
|
|
462
485
|
try {
|
|
463
486
|
const r = value;
|
|
464
|
-
if (!r || r.schemaVersion
|
|
487
|
+
if (!r || !DECISION_STAGED_LOAD_RESULT_VERSIONS.includes(r.schemaVersion) || r.kind !== 'load-manifest-qualified' || r.mode !== 'staged-provider'
|
|
465
488
|
|| r.execution !== 'live' || r.dirty !== false || !exactCommit(r.sourceCommit) || (sourceCommit !== undefined && sourceCommit !== r.sourceCommit)
|
|
466
489
|
|| r.stop !== null || !r.passed || !finite(r.startedAtMs) || !r.host.networkPath || !r.host.cpuClass || !positive(r.host.memoryGiB))
|
|
467
490
|
return false;
|
|
@@ -512,8 +535,17 @@ export function verifyDecisionStagedLoadRecord(value, sourceCommit) {
|
|
|
512
535
|
|| !positive(receipt.tokens) || !finite(receipt.costUsd) || !finite(receipt.admittedAtMs) || receipt.settledAtMs < receipt.admittedAtMs
|
|
513
536
|
|| receipt.admission.decision !== 'admit' || receipt.costUsd !== price(receipt.tokens, r.pricing)))
|
|
514
537
|
return false;
|
|
515
|
-
|
|
516
|
-
|
|
538
|
+
const reservedUsage = r.schemaVersion === 'decision-load-result/v1' ? r.usage : r.reservedUsage;
|
|
539
|
+
if (!reservedUsage || ![reservedUsage.calls, reservedUsage.tokens, reservedUsage.usd].every(finite)
|
|
540
|
+
|| reservedUsage.calls !== r.receipts.length || reservedUsage.tokens !== r.receipts.reduce((sum, receipt) => sum + receipt.tokens, 0)
|
|
541
|
+
|| Math.abs(reservedUsage.usd - r.receipts.reduce((sum, receipt) => sum + receipt.costUsd, 0)) > 1e-9)
|
|
542
|
+
return false;
|
|
543
|
+
if (r.schemaVersion === 'decision-load-result/v2' && r.receipts.some(receipt => !receipt.usage
|
|
544
|
+
|| ![receipt.usage.inputTokens, receipt.usage.outputTokens].every(v => v === null || (Number.isSafeInteger(v) && v >= 0))
|
|
545
|
+
|| (receipt.usage.inputTokens ?? 0) + (receipt.usage.outputTokens ?? 0) > receipt.tokens
|
|
546
|
+
|| (receipt.usage.costUsd !== null && (!finite(receipt.usage.costUsd) || receipt.usage.costUsd > receipt.costUsd + 1e-12))
|
|
547
|
+
|| (receipt.servedModel !== undefined && (typeof receipt.servedModel !== 'string' || !receipt.servedModel))
|
|
548
|
+
|| (receipt.requestId !== undefined && (typeof receipt.requestId !== 'string' || !receipt.requestId))))
|
|
517
549
|
return false;
|
|
518
550
|
if (!r.samples.every((sample, index) => finite(sample.atMs) && finite(sample.residentMemoryMiB) && finite(sample.heapUsedMiB)
|
|
519
551
|
&& finite(sample.cpuPercentOneCore) && finite(sample.active) && finite(sample.queued) && (index === 0 ? sample.atMs === 0 : sample.atMs >= r.samples[index - 1].atMs
|
|
@@ -529,7 +561,12 @@ export function verifyDecisionStagedLoadRecord(value, sourceCommit) {
|
|
|
529
561
|
|| change?.kind !== 'change' || change.profileVersion !== manifest.profileVersion || change.digest !== profileDigest(manifest.workspace, manifest.providers)
|
|
530
562
|
|| rollback?.kind !== 'rollback' || rollback.profileVersion !== initial.profileVersion || rollback.digest !== initial.digest)
|
|
531
563
|
return false;
|
|
532
|
-
const rebuilt = decisionStagedLoadResultRecord(r);
|
|
564
|
+
const rebuilt = decisionStagedLoadResultRecord({ ...r, reservedUsage });
|
|
565
|
+
if (r.schemaVersion === 'decision-load-result/v1') {
|
|
566
|
+
const { reservedUsage: _, reportedUsage: __, digest: ___, ...legacy } = rebuilt;
|
|
567
|
+
const legacyBody = { ...legacy, schemaVersion: r.schemaVersion };
|
|
568
|
+
return rebuilt.passed && canonicalJson({ ...legacyBody, digest: decisionLoadManifestDigest(canonicalJson(legacyBody)) }) === canonicalJson(r);
|
|
569
|
+
}
|
|
533
570
|
return rebuilt.passed && canonicalJson(rebuilt) === canonicalJson(r);
|
|
534
571
|
}
|
|
535
572
|
catch {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, resolve, sep } from 'node:path';
|
|
4
|
+
import { DECISION_STAGED_LOAD_RESULT_VERSIONS } from './load.js';
|
|
4
5
|
import { evaluateQualification } from './gates.js';
|
|
5
6
|
import { deriveGateEvidence, DERIVED_GATE_EVIDENCE, GATE_ARTIFACT_FLAGS, GATE_ARTIFACT_SCHEMAS, IMMUTABLE_SPLIT_VERSIONS, validateGateArtifact, } from './gate-evidence.js';
|
|
6
7
|
import { scanQualificationPrivacy } from './privacy.js';
|
|
@@ -233,7 +234,7 @@ async function attachGateArtifacts(plan, evidence, maxBytes) {
|
|
|
233
234
|
const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
234
235
|
await writeFile(temporary, bytes, { flag: 'wx', mode: 0o600 });
|
|
235
236
|
await rename(temporary, target);
|
|
236
|
-
refs[flag] = { artifact: relative, digest: digest(bytes), schemaVersion:
|
|
237
|
+
refs[flag] = { artifact: relative, digest: digest(bytes), schemaVersion: parsed.schemaVersion };
|
|
237
238
|
context.accepted[flag] = parsed;
|
|
238
239
|
}
|
|
239
240
|
return refs;
|
|
@@ -269,8 +270,11 @@ export async function verifyQualificationGateArtifacts(manifest, artifactRoot, m
|
|
|
269
270
|
const parsed = parseJson(bytes);
|
|
270
271
|
const versionOk = flag === 'immutable-splits'
|
|
271
272
|
? IMMUTABLE_SPLIT_VERSIONS.includes(ref.schemaVersion)
|
|
272
|
-
:
|
|
273
|
-
|
|
273
|
+
: flag === 'load-manifest-qualified'
|
|
274
|
+
? DECISION_STAGED_LOAD_RESULT_VERSIONS.includes(ref.schemaVersion)
|
|
275
|
+
: ref.schemaVersion === GATE_ARTIFACT_SCHEMAS[flag];
|
|
276
|
+
if (!versionOk || (flag === 'load-manifest-qualified' && parsed?.schemaVersion !== ref.schemaVersion)
|
|
277
|
+
|| !validateGateArtifact(flag, parsed, context)) {
|
|
274
278
|
results.push({ flag, verified: false, reason: 'invalid-artifact' });
|
|
275
279
|
continue;
|
|
276
280
|
}
|
|
@@ -48,11 +48,14 @@ export function credentialReference(input, paths = { isAbsolute }) {
|
|
|
48
48
|
else if (input.startsWith('env:') && /^[A-Za-z_][A-Za-z0-9_]*$/.test(input.slice(4))) {
|
|
49
49
|
reference = { kind: 'credential-ref', source: 'env', name: input.slice(4) };
|
|
50
50
|
}
|
|
51
|
+
else if (input.startsWith('store:') && /^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(input.slice(6))) {
|
|
52
|
+
reference = { kind: 'credential-ref', source: 'store', name: input.slice(6) };
|
|
53
|
+
}
|
|
51
54
|
else if (input.startsWith('file:') && paths.isAbsolute(input.slice(5))) {
|
|
52
55
|
reference = { kind: 'credential-ref', source: 'file', name: input.slice(5) };
|
|
53
56
|
}
|
|
54
57
|
else
|
|
55
|
-
throw new Error('--credential requires <provider>=
|
|
58
|
+
throw new Error('--credential requires <provider>=stdin|file:PATH|store:NAME|env:NAME (file paths must be absolute).');
|
|
56
59
|
assertPlanRedacted(reference);
|
|
57
60
|
return reference;
|
|
58
61
|
}
|
|
@@ -106,9 +109,10 @@ export function interviewQuestions(context, answers = {}) {
|
|
|
106
109
|
const configPath = (targetRoot === project.root ? project.providerFiles.find(f => f.provider === provider && f.kind === 'config')?.path : undefined)
|
|
107
110
|
?? (resolveProviderPathValue(getProviderDefinition(provider)?.paths.configFile ?? null, targetRoot, paths) || targetRoot);
|
|
108
111
|
add('locations', 'config-dir', "Where this provider's config file lives (informational; A3 writes nothing).", configPath, [configPath], '--target <dir>', 'path', project.root, provider);
|
|
109
|
-
add('locations', 'credential-source', "Where should A4 read this provider's credential from?", null, [{ kind: 'credential-ref', source: '
|
|
112
|
+
add('locations', 'credential-source', "Where should A4 read this provider's credential from?", null, [{ kind: 'credential-ref', source: 'stdin-at-apply', name: 'stdin' },
|
|
110
113
|
{ kind: 'credential-ref', source: 'file', name: paths.join(project.root, '.credentials', provider) },
|
|
111
|
-
{ kind: 'credential-ref', source: '
|
|
114
|
+
{ kind: 'credential-ref', source: 'store', name: provider },
|
|
115
|
+
{ kind: 'credential-ref', source: 'env', name: credentialEnvName(provider) }], '--credential <provider>=stdin|file:PATH|store:NAME|env:NAME', 'credential', project.root, provider, true);
|
|
112
116
|
}
|
|
113
117
|
for (const project of projects) {
|
|
114
118
|
const rootChoice = answers[questionId('targets', 'root', project.root)]?.value;
|
|
@@ -127,8 +131,9 @@ export function interviewQuestions(context, answers = {}) {
|
|
|
127
131
|
const jevStatus = context.jevStatus?.[targetWorkspace];
|
|
128
132
|
add('capabilities', 'jev', `Jev status: ${jevStatus ? jevStatus.enabled ? 'enabled' : 'off' : 'unknown'}; backend ${jevStatus?.backend.status ?? 'not-probed'}. Decisions are sent to an external service; a Jev account/API access and usage charges apply. Enable decision offload?`, false, ['enable', 'decline'], '--jev enable|decline|defer', 'choice', project.root, undefined, true);
|
|
129
133
|
if (jev === 'enable') {
|
|
130
|
-
add('capabilities', 'credential-source', 'Where should A4 read the Jev API input from? Enter a reference only; setup/defer works without an API input.', null, [{ kind: 'credential-ref', source: '
|
|
131
|
-
{ kind: 'credential-ref', source: '
|
|
134
|
+
add('capabilities', 'credential-source', 'Where should A4 read the Jev API input from? Enter a reference only; setup/defer works without an API input.', null, [{ kind: 'credential-ref', source: 'stdin-at-apply', name: 'stdin' },
|
|
135
|
+
{ kind: 'credential-ref', source: 'file', name: paths.join(project.root, '.credentials', 'jev') },
|
|
136
|
+
{ kind: 'credential-ref', source: 'store', name: 'jev' }, { kind: 'credential-ref', source: 'env', name: 'JEV_API_KEY' }], '--credential jev=stdin|file:PATH|store:NAME|env:NAME', 'credential', project.root, 'jev', true);
|
|
132
137
|
questions[questions.length - 1].questionId = questionId('locations', 'credential-source', project.root, 'jev');
|
|
133
138
|
add('capabilities', 'jev-options', 'Optional Jev settings as JSON: region, endpoint, verify. Verification sends a billable request at apply.', null, [null], '--region <r> --endpoint <url> --verify', 'policy', project.root, 'jev', true);
|
|
134
139
|
}
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Credential values are resolved only at apply time and are never stored on
|
|
13
13
|
* the plan, in receipts, or in rendered output. Only the reference
|
|
14
|
-
* (`env:NAME`, `file:PATH`, `stdin`) is ever rendered or persisted.
|
|
14
|
+
* (`env:NAME`, `file:PATH`, `store:NAME`, `stdin`) is ever rendered or persisted.
|
|
15
15
|
*/
|
|
16
16
|
import { relative, resolve, sep, isAbsolute, join } from 'node:path';
|
|
17
|
+
import { resolveSensitiveInput } from '../auth/sensitive-input.js';
|
|
17
18
|
import { revalidateSetupPlan, remainingPlanActions } from './plan-store.js';
|
|
18
19
|
import { questionId } from './interview.js';
|
|
19
20
|
/** Apply order for plan actions. Dependencies flow config -> verify -> deploy -> register. */
|
|
@@ -393,37 +394,32 @@ function formatFactValue(value) {
|
|
|
393
394
|
}
|
|
394
395
|
/** Resolve a credential reference to its value. The value must stay in memory only. */
|
|
395
396
|
export async function resolveCredential(reference, provider, io) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
397
|
+
try {
|
|
398
|
+
const terminal = reference.source === 'stdin-at-apply' && io.promptCredential !== undefined;
|
|
399
|
+
const value = await resolveSensitiveInput({
|
|
400
|
+
prefix: 'credential',
|
|
401
|
+
...(reference.source === 'env' ? { env: reference.name } : {}),
|
|
402
|
+
...(reference.source === 'file' ? { file: reference.name } : {}),
|
|
403
|
+
...(reference.source === 'store' ? { store: reference.name } : {}),
|
|
404
|
+
...(reference.source === 'stdin-at-apply' ? terminal ? { interactive: true } : { stdin: true } : {}),
|
|
405
|
+
}, {
|
|
406
|
+
readEnv: io.readEnv,
|
|
407
|
+
readFile: io.readCredentialFile,
|
|
408
|
+
...(io.readCredentialStore ? { readStore: async (name) => (await io.readCredentialStore(name)) ?? null } : {}),
|
|
409
|
+
// An absent reader explicitly refuses headless stdin; never access the
|
|
410
|
+
// process stream behind an injected apply boundary.
|
|
411
|
+
readStdin: io.readStdinCredential ?? (async () => ''),
|
|
412
|
+
isTTY: terminal,
|
|
413
|
+
...(io.promptCredential ? { prompt: async () => (await io.promptCredential(`Enter ${provider} API input (kept out of the setup plan): `)) ?? '' } : {}),
|
|
414
|
+
});
|
|
403
415
|
return { ok: true, value };
|
|
404
416
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return { ok: true, value };
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
return { ok: false, message: `Credential-file ${reference.name} for ${provider} is unreadable; fix the path or re-answer locations.credential-source.` };
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (io.readStdinCredential) {
|
|
417
|
-
const value = (await io.readStdinCredential()).trim();
|
|
418
|
-
return value ? { ok: true, value } : { ok: false, message: `No stdin-at-apply input for ${provider}; retry with env:/file:.` };
|
|
419
|
-
}
|
|
420
|
-
if (!io.promptCredential) {
|
|
421
|
-
return { ok: false, message: `Stdin-at-apply input for ${provider} needs an interactive terminal; re-run interactively or re-answer with env:/file:.` };
|
|
417
|
+
catch {
|
|
418
|
+
// Source failures can contain arbitrary external text. Receipts persist
|
|
419
|
+
// only the locator and supported alternatives, never the raw failure.
|
|
420
|
+
const source = reference.source === 'stdin-at-apply' ? 'Stdin-at-apply' : `Credential-source ${reference.source}:${reference.name}`;
|
|
421
|
+
return { ok: false, message: `${source} for ${provider} is unavailable; use --credential ${provider}=stdin|file:PATH|store:NAME|env:NAME (stdin prompts at a terminal).` };
|
|
422
422
|
}
|
|
423
|
-
const value = await io.promptCredential(`Enter ${provider} API input (kept out of the setup plan): `);
|
|
424
|
-
if (!value)
|
|
425
|
-
return { ok: false, message: `No stdin-at-apply input given for ${provider}; the action stays eligible for a later run.` };
|
|
426
|
-
return { ok: true, value };
|
|
427
423
|
}
|
|
428
424
|
function receiptFor(plan, action, status, message, now) {
|
|
429
425
|
const next = { ...plan, updatedAt: now, receipts: plan.receipts.map(receipt => ({ ...receipt, actions: { ...receipt.actions } })) };
|
|
@@ -449,7 +445,7 @@ async function executePlanAction(plan, action, io, advisoryNote) {
|
|
|
449
445
|
if (!remove) {
|
|
450
446
|
reference = credentialReferenceFor(plan, action.workspace, 'jev');
|
|
451
447
|
if (!reference) {
|
|
452
|
-
return { status: 'skipped', message: 'Jev setup deferred: choose --credential jev=
|
|
448
|
+
return { status: 'skipped', message: 'Jev setup deferred: choose --credential jev=stdin|file:PATH|store:NAME|env:NAME.' };
|
|
453
449
|
}
|
|
454
450
|
const resolved = await resolveCredential(reference, 'jev', io);
|
|
455
451
|
if (!resolved.ok) {
|
|
@@ -7,7 +7,7 @@ const timestamp = dateTime({ offset: true });
|
|
|
7
7
|
const observation = z.enum(['yes', 'no', 'unknown']);
|
|
8
8
|
export const planIdSchema = text.max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/);
|
|
9
9
|
const referenceSchema = z.strictObject({
|
|
10
|
-
kind: z.literal('credential-ref'), source: z.enum(['env', 'file', 'stdin-at-apply']), name: text,
|
|
10
|
+
kind: z.literal('credential-ref'), source: z.enum(['env', 'file', 'store', 'stdin-at-apply']), name: text,
|
|
11
11
|
});
|
|
12
12
|
const valueSchema = z.lazy(() => z.union([
|
|
13
13
|
z.string(), z.number().finite(), z.boolean(), z.null(), z.array(valueSchema), z.record(z.string(), valueSchema),
|
|
@@ -57,7 +57,7 @@ const planSchema = z.strictObject({
|
|
|
57
57
|
inventory: inventorySchema,
|
|
58
58
|
});
|
|
59
59
|
const sensitiveKey = /(?:password|passwd|token|secret|api[_-]?key|credential|authorization|cookie|private[_-]?key|connection[_-]?string)/i;
|
|
60
|
-
const secretError = () => new Error('Setup plan contains secret-looking material; use a credential-ref (env, file, or stdin-at-apply).');
|
|
60
|
+
const secretError = () => new Error('Setup plan contains secret-looking material; use a credential-ref (env, file, store, or stdin-at-apply).');
|
|
61
61
|
/** Scan the exact JSON snapshot, including keys and free-form evidence/messages. Never echo rejected values. */
|
|
62
62
|
export function assertPlanRedacted(input) {
|
|
63
63
|
let nodes = 0;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Shared host configuration for the effect ledger and its signing consumers. */
|
|
2
|
+
import { basename, resolve } from 'node:path';
|
|
3
|
+
import { createSecretStore } from '../auth/credential-store.js';
|
|
4
|
+
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
5
|
+
import { DEFAULT_LEDGER_KEY_SERVICE, LEDGER_TEST_KEY_ENV, credentialStoreKeyProvider, environmentTestKeyProvider } from './keys.js';
|
|
6
|
+
import { EffectLedgerError, usageError as usage } from './errors.js';
|
|
7
|
+
import { openEffectLedger } from './ledger.js';
|
|
8
|
+
const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,255}$/;
|
|
9
|
+
function defaultProject(projectDir) {
|
|
10
|
+
const cleaned = basename(resolve(projectDir)).replace(/[^A-Za-z0-9._:/@-]/g, '-').replace(/^[^A-Za-z0-9]+/, '');
|
|
11
|
+
return cleaned && IDENTIFIER.test(cleaned) ? cleaned : 'project';
|
|
12
|
+
}
|
|
13
|
+
async function hostConfig(projectDir) {
|
|
14
|
+
let config;
|
|
15
|
+
try {
|
|
16
|
+
config = await readAiwgConfig(projectDir);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new EffectLedgerError('usage', 'aiwg.config could not be read', 'config-unreadable');
|
|
20
|
+
}
|
|
21
|
+
const effects = config?.effects;
|
|
22
|
+
if (effects === undefined || effects === null)
|
|
23
|
+
return {};
|
|
24
|
+
if (typeof effects !== 'object' || Array.isArray(effects))
|
|
25
|
+
throw usage('aiwg.config effects must be an object', 'config-invalid');
|
|
26
|
+
return effects;
|
|
27
|
+
}
|
|
28
|
+
function testContext(env) {
|
|
29
|
+
return env.VITEST === 'true' || env.NODE_ENV === 'test' || env.CI === 'true' || env.CI === '1';
|
|
30
|
+
}
|
|
31
|
+
export function keyStore(session, role) {
|
|
32
|
+
const { deps, config, account } = session;
|
|
33
|
+
if (deps.keyStore)
|
|
34
|
+
return deps.keyStore(role, account);
|
|
35
|
+
if (session.env[LEDGER_TEST_KEY_ENV] && testContext(session.env))
|
|
36
|
+
return null;
|
|
37
|
+
const provider = config.keyProvider ?? {};
|
|
38
|
+
try {
|
|
39
|
+
if (provider.type === 'file') {
|
|
40
|
+
if (!provider.path)
|
|
41
|
+
throw new Error('missing path');
|
|
42
|
+
const pathname = resolve(session.projectDir, provider.path);
|
|
43
|
+
return createSecretStore({ useFile: true, allowFile: true, pathname: role === 'next' ? `${pathname}.next` : pathname, service: DEFAULT_LEDGER_KEY_SERVICE, account });
|
|
44
|
+
}
|
|
45
|
+
if (provider.type !== undefined && provider.type !== 'credential-store')
|
|
46
|
+
throw new Error('unknown provider');
|
|
47
|
+
return createSecretStore({ service: provider.service ?? DEFAULT_LEDGER_KEY_SERVICE, account: role === 'next' ? `${account}.next` : account });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new EffectLedgerError('key-unavailable', 'Effect ledger key provider is not configured correctly on this host', 'key-provider-invalid');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function keyProvider(session) {
|
|
54
|
+
const store = keyStore(session, 'current');
|
|
55
|
+
if (!store)
|
|
56
|
+
return environmentTestKeyProvider(session.env);
|
|
57
|
+
return credentialStoreKeyProvider({ account: session.account, store });
|
|
58
|
+
}
|
|
59
|
+
export function describeKeyProvider(session) {
|
|
60
|
+
if (session.deps.keyStore)
|
|
61
|
+
return { type: 'injected', account: session.account };
|
|
62
|
+
if (session.env[LEDGER_TEST_KEY_ENV] && testContext(session.env))
|
|
63
|
+
return { type: 'environment-test-key' };
|
|
64
|
+
const provider = session.config.keyProvider ?? {};
|
|
65
|
+
if (provider.type === 'file')
|
|
66
|
+
return { type: 'file', account: session.account };
|
|
67
|
+
return { type: 'credential-store', service: provider.service ?? DEFAULT_LEDGER_KEY_SERVICE, account: session.account };
|
|
68
|
+
}
|
|
69
|
+
export function openLedger(session, registry) {
|
|
70
|
+
const writer = session.config.writer ?? 'cli';
|
|
71
|
+
return openEffectLedger({
|
|
72
|
+
projectDir: session.projectDir, scope: session.scope, writer, keyProvider: keyProvider(session),
|
|
73
|
+
...(session.deps.clock ? { clock: session.deps.clock } : {}),
|
|
74
|
+
...(session.deps.sink ? { sink: session.deps.sink } : {}),
|
|
75
|
+
...(registry ? { verifiers: registry } : {}),
|
|
76
|
+
...(session.deps.lockTimeoutMs ? { lockTimeoutMs: session.deps.lockTimeoutMs } : {}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Resolve the same host scope and key provider for CLI and qualification evidence. */
|
|
80
|
+
export async function effectSession(projectDir, subsystem = 'delivery', deps = {}) {
|
|
81
|
+
const config = await hostConfig(projectDir);
|
|
82
|
+
const tenant = config.tenant ?? 'local';
|
|
83
|
+
const project = config.project ?? defaultProject(projectDir);
|
|
84
|
+
if (!IDENTIFIER.test(tenant) || !IDENTIFIER.test(project))
|
|
85
|
+
throw usage('aiwg.config effects tenant and project must be identifiers', 'config-invalid');
|
|
86
|
+
const scope = { tenant, project, subsystem };
|
|
87
|
+
const env = deps.env ?? process.env;
|
|
88
|
+
const account = config.keyProvider?.account ?? `ledger/${tenant}/${project}/${subsystem}`;
|
|
89
|
+
return { projectDir, scope, config, account, deps, env };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -687,7 +687,7 @@ export const initCommand = {
|
|
|
687
687
|
triggerPhrases: ['init project', 'setup project config', 'configure providers'],
|
|
688
688
|
commandHint: {
|
|
689
689
|
template: 'utility',
|
|
690
|
-
argumentHint: '[--scope project|user] [--intent code|docs|ops|mixed] [--credential <provider>=
|
|
690
|
+
argumentHint: '[--scope project|user] [--intent code|docs|ops|mixed] [--credential <provider>=stdin|file:PATH|store:NAME|env:NAME] [--jev enable|decline|defer]',
|
|
691
691
|
allowedTools: ['Read', 'Write'],
|
|
692
692
|
},
|
|
693
693
|
},
|
|
@@ -719,7 +719,7 @@ export const setupCommand = {
|
|
|
719
719
|
],
|
|
720
720
|
commandHint: {
|
|
721
721
|
template: 'utility',
|
|
722
|
-
argumentHint: 'project [--yes] [--dry-run] [--review] [--apply] [--plan <id>] [--target <dir>] [--scope project|user] [--workspace <root>] [--monorepo root|package] [--package <root>] [--scan-roots <dir>] [--intent code|docs|ops|mixed] [--credential <provider>=
|
|
722
|
+
argumentHint: 'project [--yes] [--dry-run] [--review] [--apply] [--plan <id>] [--target <dir>] [--scope project|user] [--workspace <root>] [--monorepo root|package] [--package <root>] [--scan-roots <dir>] [--intent code|docs|ops|mixed] [--credential <provider>=stdin|file:PATH|store:NAME|env:NAME] [--jev enable|decline|defer]',
|
|
723
723
|
allowedTools: ['Read', 'Write', 'Bash'],
|
|
724
724
|
},
|
|
725
725
|
},
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { readFile, writeFile, access } from 'fs/promises';
|
|
22
22
|
import { join } from 'path';
|
|
23
23
|
import { execFile } from 'child_process';
|
|
24
|
+
import { helperEnvironment } from '../process/cli-command.mjs';
|
|
24
25
|
import { promisify } from 'util';
|
|
25
26
|
const execFileP = promisify(execFile);
|
|
26
27
|
/**
|
|
@@ -166,6 +167,7 @@ export async function checkBundleManifestIgnored(projectDir, manifestRelPath) {
|
|
|
166
167
|
// 1 if not ignored, 128 on error. We want a tristate.
|
|
167
168
|
await execFileP('git', ['check-ignore', '-q', manifestRelPath], {
|
|
168
169
|
cwd: projectDir,
|
|
170
|
+
env: helperEnvironment(),
|
|
169
171
|
});
|
|
170
172
|
// exit 0 → ignored
|
|
171
173
|
return true;
|
package/dist/src/issues/cli.js
CHANGED
|
@@ -12,7 +12,8 @@ import path from 'node:path';
|
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
13
|
import { getFrameworkRoot } from '../channel/manager.mjs';
|
|
14
14
|
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
15
|
-
import {
|
|
15
|
+
import { sensitiveInputOptions } from '../auth/sensitive-input.js';
|
|
16
|
+
import { LocalIssueProviderCore, buildExternalIssueSnapshotFromLocal, buildLocalIssueConflictReport, createLiveIssueClient, resolveLiveIssueToken, localIssueRoot, parseCommentIdMappings, parseExternalIssueSnapshot, } from './index.js';
|
|
16
17
|
export async function main(args, cwd = process.cwd()) {
|
|
17
18
|
const parsed = parseArgs(args);
|
|
18
19
|
const subcommand = parsed.positional[0];
|
|
@@ -218,7 +219,7 @@ async function handleImport(issues, args) {
|
|
|
218
219
|
if (from !== "gitea" && from !== "github")
|
|
219
220
|
throw new Error("Usage: aiwg issue import --from gitea|github (--snapshot-file path | --live --repo owner/name --external-id N)");
|
|
220
221
|
const snapshot = args.flags.has("live")
|
|
221
|
-
? await
|
|
222
|
+
? await (await liveClient(args, from)).importIssue(requiredStringFlag(args, "external-id"))
|
|
222
223
|
: parseExternalIssueSnapshot(JSON.parse(await readRequiredFile(args, "snapshot-file")));
|
|
223
224
|
if (from !== snapshot.provider)
|
|
224
225
|
throw new Error(`snapshot provider ${snapshot.provider} does not match --from ${from}`);
|
|
@@ -249,7 +250,7 @@ async function handleExport(issues, rest, args) {
|
|
|
249
250
|
await writeOptionalOutput(args, snapshot);
|
|
250
251
|
return;
|
|
251
252
|
}
|
|
252
|
-
const client =
|
|
253
|
+
const client = await liveClient(args, provider);
|
|
253
254
|
if (snapshot.external_id && !args.flags.has("force")) {
|
|
254
255
|
const remote = await client.fetchIssue(snapshot.external_id);
|
|
255
256
|
const report = buildLocalIssueConflictReport(issue, remote);
|
|
@@ -314,6 +315,11 @@ async function handleIndex(issues, rest, args) {
|
|
|
314
315
|
const index = await issues.rebuildIssueIndex({ force: args.flags.has('force') });
|
|
315
316
|
printJsonOrText(args, index, `Local issue index ready (${index.issues.length} issues)`);
|
|
316
317
|
}
|
|
318
|
+
async function liveClient(args, provider) {
|
|
319
|
+
const repo = requiredStringFlag(args, 'repo');
|
|
320
|
+
const token = await resolveLiveIssueToken(provider, sensitiveInputOptions(args.raw, 'token'));
|
|
321
|
+
return createLiveIssueClient({ provider, repo, apiUrl: stringFlag(args, 'api-url'), token });
|
|
322
|
+
}
|
|
317
323
|
function requiredStringFlag(args, flag) {
|
|
318
324
|
const value = stringFlag(args, flag);
|
|
319
325
|
if (!value)
|
|
@@ -381,7 +387,7 @@ function parseArgs(args) {
|
|
|
381
387
|
flags.set(rawKey, true);
|
|
382
388
|
}
|
|
383
389
|
}
|
|
384
|
-
return { positional, flags };
|
|
390
|
+
return { raw: args, positional, flags };
|
|
385
391
|
}
|
|
386
392
|
function stringFlag(args, name) {
|
|
387
393
|
const value = args.flags.get(name);
|
|
@@ -402,10 +408,15 @@ function printUsage() {
|
|
|
402
408
|
aiwg issue show <KEY> [--comments last:10|all]
|
|
403
409
|
aiwg issue import --from gitea|github --snapshot-file path [--json]
|
|
404
410
|
aiwg issue export <KEY> --to gitea|github [--out path]
|
|
411
|
+
aiwg issue import --from gitea|github --live --repo owner/name --external-id N [token source]
|
|
412
|
+
aiwg issue export <KEY> --to gitea|github --live --repo owner/name [token source]
|
|
405
413
|
aiwg issue sync conflicts <KEY> --snapshot-file path [--out path]
|
|
406
414
|
aiwg issue sync map-comments <KEY> --map-file path [--json]
|
|
407
415
|
aiwg issue comment <KEY> --body "..." [--author name]
|
|
408
416
|
aiwg issue close <KEY> [--reason "..."]
|
|
409
|
-
aiwg issue index rebuild [--force]
|
|
417
|
+
aiwg issue index rebuild [--force]
|
|
418
|
+
|
|
419
|
+
Live token sources: --token-interactive, --token-stdin, --token-file <path>,
|
|
420
|
+
--token-store <name>, --token-env <NAME> (see docs/cli/sensitive-values.md).`);
|
|
410
421
|
}
|
|
411
422
|
//# sourceMappingURL=cli.js.map
|
package/dist/src/issues/live.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveSensitiveInput } from '../auth/sensitive-input.js';
|
|
2
|
+
import { isInteractive } from '../cli/env.js';
|
|
1
3
|
export class LiveIssueClient {
|
|
2
4
|
options;
|
|
3
5
|
baseUrl;
|
|
@@ -63,24 +65,51 @@ export class LiveIssueClient {
|
|
|
63
65
|
headers['Content-Type'] = 'application/json';
|
|
64
66
|
if (this.options.token)
|
|
65
67
|
headers.Authorization = `Bearer ${this.options.token}`;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
try {
|
|
69
|
+
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
70
|
+
method,
|
|
71
|
+
headers,
|
|
72
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const text = await response.text().catch(() => '');
|
|
76
|
+
throw new Error(`${this.options.provider} issue API ${method} ${path} failed: ${response.status}${text ? ` ${redactSecrets(text, this.options.token)}` : ''}`);
|
|
77
|
+
}
|
|
78
|
+
const result = await response.json();
|
|
79
|
+
// A server can reflect request credentials in issue fields or comments.
|
|
80
|
+
// Scrub them before they reach local storage or CLI output.
|
|
81
|
+
if (!this.options.token)
|
|
82
|
+
return result;
|
|
83
|
+
const encodedToken = JSON.stringify(this.options.token).slice(1, -1);
|
|
84
|
+
return JSON.parse(JSON.stringify(result).split(encodedToken).join('[REDACTED]'));
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new Error(redactSecrets(error instanceof Error ? error.message : 'Live issue request failed', this.options.token));
|
|
74
88
|
}
|
|
75
|
-
return response.json();
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
91
|
export function createLiveIssueClient(options) {
|
|
79
92
|
return new LiveIssueClient({
|
|
80
93
|
...options,
|
|
81
|
-
token: resolveToken(options.provider, options.tokenEnv ?? process.env),
|
|
94
|
+
token: options.token ?? resolveToken(options.provider, options.tokenEnv ?? process.env),
|
|
82
95
|
});
|
|
83
96
|
}
|
|
97
|
+
/** CLI source selection; the synchronous client factory remains available to callers. */
|
|
98
|
+
export async function resolveLiveIssueToken(provider, source = {}, dependencies = {}) {
|
|
99
|
+
const environment = dependencies.environment ?? process.env;
|
|
100
|
+
const isTTY = dependencies.isTTY ?? isInteractive();
|
|
101
|
+
const selected = { ...source, prefix: 'token' };
|
|
102
|
+
const hasSource = selected.interactive || selected.stdin || selected.file !== undefined || selected.store !== undefined || selected.env !== undefined;
|
|
103
|
+
if (!hasSource) {
|
|
104
|
+
// Established behavior: use the provider variable when set, otherwise make
|
|
105
|
+
// unauthenticated requests (public repositories). Prompt only on request.
|
|
106
|
+
const names = provider === 'gitea' ? ['AIWG_GITEA_TOKEN', 'GITEA_TOKEN'] : ['AIWG_GITHUB_TOKEN', 'GITHUB_TOKEN'];
|
|
107
|
+
selected.env = names.find(name => environment[name] !== undefined);
|
|
108
|
+
if (selected.env === undefined)
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return resolveSensitiveInput(selected, { ...dependencies, environment, isTTY });
|
|
112
|
+
}
|
|
84
113
|
export function resolveToken(provider, env) {
|
|
85
114
|
if (provider === 'gitea')
|
|
86
115
|
return env.AIWG_GITEA_TOKEN ?? env.GITEA_TOKEN;
|
|
@@ -148,7 +177,10 @@ function requireString(value, field) {
|
|
|
148
177
|
return value;
|
|
149
178
|
throw new Error(`live issue response missing ${field}`);
|
|
150
179
|
}
|
|
151
|
-
function redactSecrets(value) {
|
|
152
|
-
|
|
180
|
+
function redactSecrets(value, token) {
|
|
181
|
+
const redacted = token ? value.split(token).join('[REDACTED]') : value;
|
|
182
|
+
return redacted
|
|
183
|
+
.replace(/Bearer\s+[^\s"',}]+/gi, 'Bearer [REDACTED]')
|
|
184
|
+
.replace(/(token|authorization|password|secret)["':=\s]+[^\s"',}]+/gi, '$1=[REDACTED]');
|
|
153
185
|
}
|
|
154
186
|
//# sourceMappingURL=live.js.map
|
|
@@ -9,6 +9,7 @@ import { access, chmod, mkdir, mkdtemp, readFile, rename, rm, writeFile, } from
|
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
10
|
import os from 'node:os';
|
|
11
11
|
import path from 'node:path';
|
|
12
|
+
import { resolveSensitiveInput } from '../auth/sensitive-input.js';
|
|
12
13
|
import { setPackageEntry } from '../packages/package-registry.js';
|
|
13
14
|
import { discoverInstallablePackage } from '../packages/package-discovery.js';
|
|
14
15
|
import { parseTrustRoot, sha256 as artifactSha256, } from '../security/artifact-trust.js';
|
|
@@ -236,7 +237,13 @@ export async function verifyIndexedPackage(options) {
|
|
|
236
237
|
});
|
|
237
238
|
return { verification, receipt, entry };
|
|
238
239
|
}
|
|
239
|
-
export async function publishLocalPackage(options) {
|
|
240
|
+
export async function publishLocalPackage(options, sensitiveDependencies = {}) {
|
|
241
|
+
const privateKey = await resolveSensitiveInput({
|
|
242
|
+
...options.keyInput,
|
|
243
|
+
prefix: 'key',
|
|
244
|
+
file: options.keyInput?.file ?? options.privateKeyPath,
|
|
245
|
+
multiline: true,
|
|
246
|
+
}, sensitiveDependencies);
|
|
240
247
|
const sourceDir = path.resolve(options.sourceDir);
|
|
241
248
|
const discovered = await discoverInstallablePackage(sourceDir, options.packageSelector);
|
|
242
249
|
const unsigned = await createProvenanceEnvelope({
|
|
@@ -248,12 +255,17 @@ export async function publishLocalPackage(options) {
|
|
|
248
255
|
publisher: options.publisher,
|
|
249
256
|
sequence: options.sequence,
|
|
250
257
|
});
|
|
251
|
-
const privateKey = await readFile(path.resolve(options.privateKeyPath), 'utf8');
|
|
252
258
|
const publicKey = options.publicKeyPath ? await readFile(path.resolve(options.publicKeyPath), 'utf8') : undefined;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
259
|
+
let envelope;
|
|
260
|
+
try {
|
|
261
|
+
envelope = signProvenanceEnvelope(unsigned, privateKey, {
|
|
262
|
+
keyId: options.keyId,
|
|
263
|
+
publicKeyPem: publicKey,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
throw new Error('Marketplace signing failed; check the Ed25519 private key and public-key options.');
|
|
268
|
+
}
|
|
257
269
|
const lock = createPackageLock(envelope, envelope.publication.publishedAt);
|
|
258
270
|
const fortemi = await buildFortemiEnvelopeShard(envelope);
|
|
259
271
|
const receipt = createOperationReceipt({
|