Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ officecli query <file> <selector> # CSS-like query
officecli validate <file> # Validate against OpenXML schema
```

**Encrypted files:** add `--password <pwd>` (or set `OFFICECLI_PASSWORD`) to any command (Agile/Office-2013+ only; reads decrypt, edits re-save still-encrypted). Missing → `password_required`.

### view modes

| Mode | Description | Useful flags |
Expand Down
10 changes: 5 additions & 5 deletions src/officecli/CommandBuilder.Add.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private static Command BuildAddCommand(Option<bool> jsonOption)
if (position?.Before != null) req.Args["before"] = position.Before;
}, json) is {} rc) return rc != 0 ? rc : (hadWarnings ? 2 : 0);

using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true, password: result.GetValue(PasswordOption));
var oldCount = (handler as OfficeCli.Handlers.PowerPointHandler)?.GetSlideCount() ?? 0;
var resultPath = handler.CopyFrom(from, parentPath, position);
var message = $"Copied to {resultPath}";
Expand Down Expand Up @@ -186,7 +186,7 @@ private static Command BuildAddCommand(Option<bool> jsonOption)
// CONSISTENCY(schema-prop-validation): same approach mirrored
// in ResidentServer.ExecuteAdd.
var tracking = new OfficeCli.Core.TrackingPropertyDictionary(properties);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true, password: result.GetValue(PasswordOption));
var oldCount = (handler as OfficeCli.Handlers.PowerPointHandler)?.GetSlideCount() ?? 0;
var resultPath = handler.Add(parentPath, type!, position, tracking);
var unsupported = tracking.UnusedKeys.ToList();
Expand Down Expand Up @@ -336,7 +336,7 @@ private static Command BuildRemoveCommand(Option<bool> jsonOption)
if (parsedProps != null) req.Props = parsedProps;
}, json) is {} rc) return rc;

using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true, password: result.GetValue(PasswordOption));
var oldCount = (handler as OfficeCli.Handlers.PowerPointHandler)?.GetSlideCount() ?? 0;
string? warning;
if (!string.IsNullOrEmpty(shift))
Expand Down Expand Up @@ -426,7 +426,7 @@ private static Command BuildMoveCommand(Option<bool> jsonOption)
if (moveProps.Count > 0) req.Props = moveProps;
}, json) is {} rc) return rc;

using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true, password: result.GetValue(PasswordOption));
var resultPath = handler.Move(path, to, position, moveProps.Count > 0 ? moveProps : null);
var message = $"Moved to {resultPath}";
if (json) Console.WriteLine(OutputFormatter.WrapEnvelopeText(message));
Expand Down Expand Up @@ -463,7 +463,7 @@ private static Command BuildSwapCommand(Option<bool> jsonOption)
req.Args["to"] = path2;
}, json) is {} rc) return rc;

using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true, password: result.GetValue(PasswordOption));
var (p1, p2) = handler switch
{
OfficeCli.Handlers.PowerPointHandler ppt => ppt.Swap(path1, path2),
Expand Down
2 changes: 1 addition & 1 deletion src/officecli/CommandBuilder.Batch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ private static Command BuildBatchCommand(Option<bool> jsonOption)
// re-serialize that dominates large replays. Save-once is the
// documented intent of this path; per-op Save was redundant given
// the Dispose-time flush.
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true, password: result.GetValue(PasswordOption));
if (handler is OfficeCli.Handlers.WordHandler batchWh) batchWh.DeferSave = true;
// Protection gate against the just-opened in-memory DOM (one check
// for the whole batch; no second file open).
Expand Down
2 changes: 1 addition & 1 deletion src/officecli/CommandBuilder.Check.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ private static Command BuildValidateCommand(Option<bool> jsonOption)
req.Json = json;
}, json) is {} rc) return rc;

using var handler = DocumentHandlerFactory.Open(file.FullName);
using var handler = DocumentHandlerFactory.Open(file.FullName, password: result.GetValue(PasswordOption));
var errors = handler.Validate();
if (json)
{
Expand Down
2 changes: 1 addition & 1 deletion src/officecli/CommandBuilder.Dump.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ private static Command BuildDumpCommand(Option<bool> jsonOption)
// raw OOXML SDK exception out of programmatic callers (tests,
// resident batch) — SafeRun catches it at the CLI surface but
// any in-process consumer sees the unwrapped form.
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: false);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: false, password: result.GetValue(PasswordOption));
if (ext == ".docx")
{
var word = (WordHandler)handler;
Expand Down
10 changes: 5 additions & 5 deletions src/officecli/CommandBuilder.GetQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private static Command BuildGetCommand(Option<bool> jsonOption)
// for the currently-selected element paths and resolve them to nodes.
if (string.Equals(path, "selected", StringComparison.OrdinalIgnoreCase))
{
return GetSelectedAction(file.FullName, depth, json);
return GetSelectedAction(file.FullName, depth, json, result.GetValue(PasswordOption));
}

if (TryResident(file.FullName, req =>
Expand All @@ -54,7 +54,7 @@ private static Command BuildGetCommand(Option<bool> jsonOption)
if (!string.IsNullOrEmpty(savePath)) req.Args["save"] = savePath;
}, json) is {} rc) return rc;

using var handler = DocumentHandlerFactory.Open(file.FullName);
using var handler = DocumentHandlerFactory.Open(file.FullName, password: result.GetValue(PasswordOption));
var node = handler.Get(path, depth);

// CONSISTENCY(get-not-found-exit): some handler Get paths surface
Expand Down Expand Up @@ -109,7 +109,7 @@ private static Command BuildGetCommand(Option<bool> jsonOption)
return getCommand;
}

private static int GetSelectedAction(string filePath, int depth, bool json)
private static int GetSelectedAction(string filePath, int depth, bool json, string? password = null)
{
var paths = WatchNotifier.QuerySelection(filePath);
if (paths == null)
Expand All @@ -127,7 +127,7 @@ private static int GetSelectedAction(string filePath, int depth, bool json)
var nodes = new List<OfficeCli.Core.DocumentNode>();
if (paths.Length > 0)
{
using var handler = DocumentHandlerFactory.Open(filePath);
using var handler = DocumentHandlerFactory.Open(filePath, password: password);
foreach (var p in paths)
{
try
Expand Down Expand Up @@ -194,7 +194,7 @@ private static Command BuildQueryCommand(Option<bool> jsonOption)

var format = json ? OutputFormat.Json : OutputFormat.Text;

using var handler = DocumentHandlerFactory.Open(file.FullName);
using var handler = DocumentHandlerFactory.Open(file.FullName, password: result.GetValue(PasswordOption));
// CONSISTENCY(cell-selector-alias): the Excel cell selector accepts short
// aliases (bold -> font.bold, size -> font.size, ...). FilterSelector
// applies the same normalization, runs the boolean and/or engine, and
Expand Down
6 changes: 3 additions & 3 deletions src/officecli/CommandBuilder.Raw.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private static Command BuildRawCommand(Option<bool> jsonOption)

var rawCols = rawColsStr != null ? new HashSet<string>(rawColsStr.Split(',').Select(c => c.Trim().ToUpperInvariant())) : null;

using var handler = DocumentHandlerFactory.Open(file.FullName);
using var handler = DocumentHandlerFactory.Open(file.FullName, password: result.GetValue(PasswordOption));
var xml = handler.Raw(partPath, startRow, endRow, rawCols);
if (json) Console.WriteLine(OutputFormatter.WrapEnvelopeText(xml));
else Console.WriteLine(xml);
Expand Down Expand Up @@ -90,7 +90,7 @@ private static Command BuildRawSetCommand(Option<bool> jsonOption)
if (xml != null) req.Args["xml"] = xml;
}, json) is {} rc) return rc;

using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true, password: result.GetValue(PasswordOption));
var errorsBefore = handler.Validate().Select(e => e.Description).ToHashSet();
handler.RawSet(partPath, xpath, action, xml);
var warnings = ReportNewErrorsAsWarnings(handler, errorsBefore);
Expand Down Expand Up @@ -132,7 +132,7 @@ private static Command BuildAddPartCommand(Option<bool> jsonOption)
req.Args["type"] = type;
}, json) is {} rc) return rc;

using var handler = DocumentHandlerFactory.Open(file, editable: true);
using var handler = DocumentHandlerFactory.Open(file, editable: true, password: result.GetValue(PasswordOption));
var errorsBefore = handler.Validate().Select(e => e.Description).ToHashSet();
var (relId, partPath) = handler.AddPart(parent, type);
var warnings = ReportNewErrorsAsWarnings(handler, errorsBefore);
Expand Down
2 changes: 1 addition & 1 deletion src/officecli/CommandBuilder.Set.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ private static Command BuildSetCommand(Option<bool> jsonOption)
// stay in sync.
var properties = ParsePropsArray(props);

using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true);
using var handler = DocumentHandlerFactory.Open(file.FullName, editable: true, password: result.GetValue(PasswordOption));

var unsupported = handler.Set(path, properties);

Expand Down
2 changes: 1 addition & 1 deletion src/officecli/CommandBuilder.View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private static Command BuildViewCommand(Option<bool> jsonOption)
var format = json ? OutputFormat.Json : OutputFormat.Text;
var cols = colsStr != null ? new HashSet<string>(colsStr.Split(',').Select(c => c.Trim().ToUpperInvariant())) : null;

using var handler = DocumentHandlerFactory.Open(file.FullName);
using var handler = DocumentHandlerFactory.Open(file.FullName, password: result.GetValue(PasswordOption));

if (mode.ToLowerInvariant() is "html" or "h")
{
Expand Down
11 changes: 11 additions & 0 deletions src/officecli/CommandBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ namespace OfficeCli;

static partial class CommandBuilder
{
/// <summary>
/// Password for an encrypted (Agile) OOXML document. Declared once as a
/// recursive option so every subcommand that opens a document can read it
/// (via <c>result.GetValue(PasswordOption)</c>) and pass it to
/// <see cref="OfficeCli.Handlers.DocumentHandlerFactory.Open"/> without each
/// builder having to thread it through its signature.
/// </summary>
internal static readonly Option<string?> PasswordOption =
new("--password") { Description = "Password for an encrypted (Agile) OOXML document (or set OFFICECLI_PASSWORD)", Recursive = true };

public static RootCommand BuildRootCommand()
{
var jsonOption = new Option<bool>("--json") { Description = "Output as JSON (AI-friendly)" };
Expand All @@ -23,6 +33,7 @@ public static RootCommand BuildRootCommand()
See the Commands section below for the full list of subcommands.
""");
rootCommand.Add(jsonOption);
rootCommand.Add(PasswordOption);

// ==================== open command (start resident) ====================
var openFileArg = new Argument<FileInfo>("file") { Description = "Office document path (required even with open/close mode)" };
Expand Down
Loading